mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-19 08:01:19 +00:00
fix(coding-agent): centralize package source normalization and local path parsing fixes #1304
This commit is contained in:
parent
8792ee2a66
commit
6b0f1fefdb
5 changed files with 178 additions and 121 deletions
|
|
@ -49,6 +49,8 @@ export interface PackageManager {
|
|||
sources: string[],
|
||||
options?: { local?: boolean; temporary?: boolean },
|
||||
): Promise<ResolvedPaths>;
|
||||
addSourceToSettings(source: string, options?: { local?: boolean }): boolean;
|
||||
removeSourceFromSettings(source: string, options?: { local?: boolean }): boolean;
|
||||
setProgressCallback(callback: ProgressCallback | undefined): void;
|
||||
getInstalledPath(source: string, scope: "user" | "project"): string | undefined;
|
||||
}
|
||||
|
|
@ -604,6 +606,43 @@ export class DefaultPackageManager implements PackageManager {
|
|||
this.progressCallback = callback;
|
||||
}
|
||||
|
||||
addSourceToSettings(source: string, options?: { local?: boolean }): boolean {
|
||||
const scope: SourceScope = options?.local ? "project" : "user";
|
||||
const currentSettings =
|
||||
scope === "project" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();
|
||||
const currentPackages = currentSettings.packages ?? [];
|
||||
const normalizedSource = this.normalizePackageSourceForSettings(source, scope);
|
||||
const exists = currentPackages.some((existing) => this.packageSourcesMatch(existing, source, scope));
|
||||
if (exists) {
|
||||
return false;
|
||||
}
|
||||
const nextPackages = [...currentPackages, normalizedSource];
|
||||
if (scope === "project") {
|
||||
this.settingsManager.setProjectPackages(nextPackages);
|
||||
} else {
|
||||
this.settingsManager.setPackages(nextPackages);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
removeSourceFromSettings(source: string, options?: { local?: boolean }): boolean {
|
||||
const scope: SourceScope = options?.local ? "project" : "user";
|
||||
const currentSettings =
|
||||
scope === "project" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();
|
||||
const currentPackages = currentSettings.packages ?? [];
|
||||
const nextPackages = currentPackages.filter((existing) => !this.packageSourcesMatch(existing, source, scope));
|
||||
const changed = nextPackages.length !== currentPackages.length;
|
||||
if (!changed) {
|
||||
return false;
|
||||
}
|
||||
if (scope === "project") {
|
||||
this.settingsManager.setProjectPackages(nextPackages);
|
||||
} else {
|
||||
this.settingsManager.setPackages(nextPackages);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getInstalledPath(source: string, scope: "user" | "project"): string | undefined {
|
||||
const parsed = this.parseSource(source);
|
||||
if (parsed.type === "npm") {
|
||||
|
|
@ -880,6 +919,50 @@ export class DefaultPackageManager implements PackageManager {
|
|||
}
|
||||
}
|
||||
|
||||
private getPackageSourceString(pkg: PackageSource): string {
|
||||
return typeof pkg === "string" ? pkg : pkg.source;
|
||||
}
|
||||
|
||||
private getSourceMatchKeyForInput(source: string): string {
|
||||
const parsed = this.parseSource(source);
|
||||
if (parsed.type === "npm") {
|
||||
return `npm:${parsed.name}`;
|
||||
}
|
||||
if (parsed.type === "git") {
|
||||
return `git:${parsed.host}/${parsed.path}`;
|
||||
}
|
||||
return `local:${this.resolvePath(parsed.path)}`;
|
||||
}
|
||||
|
||||
private getSourceMatchKeyForSettings(source: string, scope: SourceScope): string {
|
||||
const parsed = this.parseSource(source);
|
||||
if (parsed.type === "npm") {
|
||||
return `npm:${parsed.name}`;
|
||||
}
|
||||
if (parsed.type === "git") {
|
||||
return `git:${parsed.host}/${parsed.path}`;
|
||||
}
|
||||
const baseDir = this.getBaseDirForScope(scope);
|
||||
return `local:${this.resolvePathFromBase(parsed.path, baseDir)}`;
|
||||
}
|
||||
|
||||
private packageSourcesMatch(existing: PackageSource, inputSource: string, scope: SourceScope): boolean {
|
||||
const left = this.getSourceMatchKeyForSettings(this.getPackageSourceString(existing), scope);
|
||||
const right = this.getSourceMatchKeyForInput(inputSource);
|
||||
return left === right;
|
||||
}
|
||||
|
||||
private normalizePackageSourceForSettings(source: string, scope: SourceScope): string {
|
||||
const parsed = this.parseSource(source);
|
||||
if (parsed.type !== "local") {
|
||||
return source;
|
||||
}
|
||||
const baseDir = this.getBaseDirForScope(scope);
|
||||
const resolved = this.resolvePath(parsed.path);
|
||||
const rel = relative(baseDir, resolved);
|
||||
return rel || ".";
|
||||
}
|
||||
|
||||
private parseSource(source: string): ParsedSource {
|
||||
if (source.startsWith("npm:")) {
|
||||
const spec = source.slice("npm:".length).trim();
|
||||
|
|
@ -892,6 +975,19 @@ export class DefaultPackageManager implements PackageManager {
|
|||
};
|
||||
}
|
||||
|
||||
const trimmed = source.trim();
|
||||
const isWindowsAbsolutePath = /^[A-Za-z]:[\\/]|^\\\\/.test(trimmed);
|
||||
const isLocalPathLike =
|
||||
trimmed.startsWith("./") ||
|
||||
trimmed.startsWith("../") ||
|
||||
trimmed.startsWith("/") ||
|
||||
trimmed === "~" ||
|
||||
trimmed.startsWith("~/") ||
|
||||
isWindowsAbsolutePath;
|
||||
if (isLocalPathLike) {
|
||||
return { type: "local", path: source };
|
||||
}
|
||||
|
||||
// Try parsing as git URL
|
||||
const gitParsed = parseGitUrl(source);
|
||||
if (gitParsed) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue