From 8c836e1ebf9635ec6dbc28e53dee3e743753bfbe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 13:15:27 +0000 Subject: [PATCH 1/2] Initial plan From 272d79612746ac734e1bbb356b1b726c62437e35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 13:29:47 +0000 Subject: [PATCH 2/2] Fix package.json version autocomplete for custom npm registries When npm view pack@latest fails for packages in custom registries (e.g., GitLab), fall back to npm view pack using dist-tags.latest to get the latest version. Also implement the ignoreError parameter in runNpmCommand and use it in npmListInstalledVersion so npm ls non-zero exit codes (e.g. from peer dependency issues) don't hide the installed version. Agent-Logs-Url: https://github.com/microsoft/vscode/sessions/23876571-d34b-4127-95dc-5b9ea680adf5 Co-authored-by: aeschli <6461412+aeschli@users.noreply.github.com> --- .../src/features/packageJSONContribution.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/extensions/npm/src/features/packageJSONContribution.ts b/extensions/npm/src/features/packageJSONContribution.ts index 157823ae03df7..465e73beba107 100644 --- a/extensions/npm/src/features/packageJSONContribution.ts +++ b/extensions/npm/src/features/packageJSONContribution.ts @@ -300,7 +300,7 @@ export class PackageJSONContribution implements IJSONContribution { * Sets up cwd from resource, applies corepack env vars, and handles win32 shell quoting. * Pass ignoreError=true to return stdout even when the command exits with a non-zero code. */ - private async runNpmCommand(npmCommandPath: string, args: string[], resource: Uri | undefined): Promise { + private async runNpmCommand(npmCommandPath: string, args: string[], resource: Uri | undefined, ignoreError?: boolean): Promise { const cp = await import('child_process'); return new Promise((resolve, _reject) => { const cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined; @@ -317,7 +317,7 @@ export class PackageJSONContribution implements IJSONContribution { commandPath = `"${npmCommandPath}"`; } cp.execFile(commandPath, args, options, (error, stdout) => { - resolve(error ? undefined : stdout); + resolve(error && !ignoreError ? undefined : stdout); }); }); } @@ -325,7 +325,6 @@ export class PackageJSONContribution implements IJSONContribution { private async npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise { // Request @latest to avoid fetching publish timestamps for all versions in the time field. const args = ['view', '--json', '--', `${pack}@latest`, 'description', 'homepage', 'version', 'time']; - const stdout = await this.runNpmCommand(npmCommandPath, args, resource); if (stdout) { try { @@ -333,7 +332,7 @@ export class PackageJSONContribution implements IJSONContribution { const version = content['version']; return { description: content['description'], - version: content['version'], + version, time: version ? content['time']?.[version] : undefined, homepage: content['homepage'] }; @@ -341,6 +340,27 @@ export class PackageJSONContribution implements IJSONContribution { // ignore } } + // Fall back for custom registries that may not support @latest dist-tag resolution. + // Do not request the time field here, as it would contain timestamps for all versions. + return this.npmViewFallback(npmCommandPath, pack, resource); + } + + private async npmViewFallback(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise { + const args = ['view', '--json', '--', pack, 'description', 'dist-tags.latest', 'homepage']; + const stdout = await this.runNpmCommand(npmCommandPath, args, resource); + if (stdout) { + try { + const content = JSON.parse(stdout); + return { + description: content['description'], + version: content['dist-tags.latest'], + time: undefined, + homepage: content['homepage'] + }; + } catch (e) { + // ignore + } + } return undefined; } @@ -368,7 +388,9 @@ export class PackageJSONContribution implements IJSONContribution { private async npmListInstalledVersion(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise { const args = ['ls', '--json', '--depth=0', '--', pack]; - const stdout = await this.runNpmCommand(npmCommandPath, args, resource); + // npm ls can exit with a non-zero code (e.g. due to peer dependency issues) even when + // the package is installed and the output JSON is valid, so use ignoreError=true. + const stdout = await this.runNpmCommand(npmCommandPath, args, resource, true); if (stdout) { try { const content = JSON.parse(stdout);