779ca4872b
- Removed Prettier configuration files (.prettierignore and .prettierrc) and migrated formatting to Biome. - Updated Biome configuration from version 2.3.11 to 2.3.12 and changed arrowParentheses rule from 'always' to 'asNeeded'. - Pinned @biomejs/biome dependency to exact version 2.3.12 in package.json and bun.lock. - Applied consistent arrow function formatting across 489 files by removing unnecessary parentheses around single parameters. - Removed blank lines after comment blocks and reorganized imports for consistency across the codebase.
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
/**
|
|
* Feature bracket parser for plugin specifiers.
|
|
*
|
|
* Supports syntax like:
|
|
* - "my-plugin" -> base features (null)
|
|
* - "my-plugin[search,web]" -> specific features
|
|
* - "my-plugin[*]" -> all features
|
|
* - "my-plugin[]" -> no optional features
|
|
* - "@scope/plugin@1.2.3[feat]" -> scoped with version and features
|
|
*/
|
|
|
|
export interface ParsedPluginSpec {
|
|
/** Package name (may include version specifier like @1.0.0) */
|
|
packageName: string;
|
|
/**
|
|
* Feature selection:
|
|
* - null: use defaults (base features on first install, preserve on reinstall)
|
|
* - "*": all features
|
|
* - string[]: specific features (empty array = no optional features)
|
|
*/
|
|
features: string[] | null | "*";
|
|
}
|
|
|
|
/**
|
|
* Parse plugin specifier with feature bracket syntax.
|
|
*
|
|
* @example
|
|
* parsePluginSpec("my-plugin") // { packageName: "my-plugin", features: null }
|
|
* parsePluginSpec("my-plugin[search,web]") // { packageName: "my-plugin", features: ["search", "web"] }
|
|
* parsePluginSpec("my-plugin[*]") // { packageName: "my-plugin", features: "*" }
|
|
* parsePluginSpec("my-plugin[]") // { packageName: "my-plugin", features: [] }
|
|
* parsePluginSpec("@scope/pkg@1.2.3[feat]") // { packageName: "@scope/pkg@1.2.3", features: ["feat"] }
|
|
*/
|
|
export function parsePluginSpec(spec: string): ParsedPluginSpec {
|
|
// Find the last bracket pair (to handle version specifiers like @1.0.0)
|
|
const bracketStart = spec.lastIndexOf("[");
|
|
const bracketEnd = spec.lastIndexOf("]");
|
|
|
|
// No brackets or malformed -> base features
|
|
if (bracketStart === -1 || bracketEnd === -1 || bracketEnd < bracketStart) {
|
|
return { packageName: spec, features: null };
|
|
}
|
|
|
|
const packageName = spec.slice(0, bracketStart);
|
|
const featureStr = spec.slice(bracketStart + 1, bracketEnd).trim();
|
|
|
|
// All features
|
|
if (featureStr === "*") {
|
|
return { packageName, features: "*" };
|
|
}
|
|
|
|
// No optional features
|
|
if (featureStr === "") {
|
|
return { packageName, features: [] };
|
|
}
|
|
|
|
// Specific features (comma-separated)
|
|
const features = featureStr
|
|
.split(",")
|
|
.map(f => f.trim())
|
|
.filter(Boolean);
|
|
|
|
return { packageName, features };
|
|
}
|
|
|
|
/**
|
|
* Format a parsed plugin spec back to string form.
|
|
*
|
|
* @example
|
|
* formatPluginSpec({ packageName: "pkg", features: null }) // "pkg"
|
|
* formatPluginSpec({ packageName: "pkg", features: "*" }) // "pkg[*]"
|
|
* formatPluginSpec({ packageName: "pkg", features: [] }) // "pkg[]"
|
|
* formatPluginSpec({ packageName: "pkg", features: ["a", "b"] }) // "pkg[a,b]"
|
|
*/
|
|
export function formatPluginSpec(spec: ParsedPluginSpec): string {
|
|
if (spec.features === null) {
|
|
return spec.packageName;
|
|
}
|
|
if (spec.features === "*") {
|
|
return `${spec.packageName}[*]`;
|
|
}
|
|
if (spec.features.length === 0) {
|
|
return `${spec.packageName}[]`;
|
|
}
|
|
return `${spec.packageName}[${spec.features.join(",")}]`;
|
|
}
|
|
|
|
/**
|
|
* Extract the base package name without version specifier.
|
|
* Used for path lookups after npm install.
|
|
*
|
|
* @example
|
|
* extractPackageName("lodash@4.17.21") // "lodash"
|
|
* extractPackageName("@scope/pkg@1.0.0") // "@scope/pkg"
|
|
* extractPackageName("@scope/pkg") // "@scope/pkg"
|
|
*/
|
|
export function extractPackageName(specifier: string): string {
|
|
// Handle scoped packages: @scope/name@version -> @scope/name
|
|
if (specifier.startsWith("@")) {
|
|
const match = specifier.match(/^(@[^/]+\/[^@]+)/);
|
|
return match ? match[1] : specifier;
|
|
}
|
|
// Unscoped: name@version -> name
|
|
return specifier.replace(/@[^@]+$/, "");
|
|
}
|