From 4c8b2540b38b6b197ff730d9559c9a23472ff986 Mon Sep 17 00:00:00 2001 From: Vivek JM Date: Sat, 15 Aug 2026 21:17:44 +0530 Subject: [PATCH 1/2] fix: forward TypeSpec npm registry during install --- ...orward-typespec-npm-registry-2026-08-15.md | 7 ++++++ packages/compiler/src/install/install.ts | 8 ++++-- .../src/package-manger/npm-registry.ts | 18 +++++++++++++ .../test/package-manager/npm-registry.test.ts | 25 ++++++++++++++++++- 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 .chronus/changes/forward-typespec-npm-registry-2026-08-15.md diff --git a/.chronus/changes/forward-typespec-npm-registry-2026-08-15.md b/.chronus/changes/forward-typespec-npm-registry-2026-08-15.md new file mode 100644 index 00000000000..2152f7a454b --- /dev/null +++ b/.chronus/changes/forward-typespec-npm-registry-2026-08-15.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Forward `TYPESPEC_NPM_REGISTRY` to the npm process launched by `tsp install`. diff --git a/packages/compiler/src/install/install.ts b/packages/compiler/src/install/install.ts index 9460879ee66..ea2ee69e069 100644 --- a/packages/compiler/src/install/install.ts +++ b/packages/compiler/src/install/install.ts @@ -7,7 +7,11 @@ import { createDiagnosticCollector } from "../core/diagnostics.js"; import { getDirectoryPath, joinPaths } from "../core/path-utils.js"; import { NoTarget, type Diagnostic, type Tracer } from "../core/types.js"; import { downloadAndExtractPackage } from "../package-manger/npm-package-download.js"; -import { fetchPackageManifest, type NpmManifest } from "../package-manger/npm-registry.js"; +import { + fetchPackageManifest, + getNpmRegistryEnvironment, + type NpmManifest, +} from "../package-manger/npm-registry.js"; import { mkTempDir } from "../utils/fs-utils.js"; import type { SupportedPackageManager } from "./config.js"; import { getPackageManagerConfig, type PackageManagerConfig } from "./config.js"; @@ -218,7 +222,7 @@ async function runPackageManager( stdio, cwd: directory, env: { - ...process.env, + ...getNpmRegistryEnvironment(), TYPESPEC_CLI_PASSTHROUGH: "1", }, }); diff --git a/packages/compiler/src/package-manger/npm-registry.ts b/packages/compiler/src/package-manger/npm-registry.ts index 4672eafe669..714b354e867 100644 --- a/packages/compiler/src/package-manger/npm-registry.ts +++ b/packages/compiler/src/package-manger/npm-registry.ts @@ -93,6 +93,24 @@ export function getNpmRegistry(): string { return (process.env["TYPESPEC_NPM_REGISTRY"] ?? defaultRegistry).replace(/\/$/, ""); } +/** Returns the environment used to run npm with the TypeSpec registry override applied. */ +export function getNpmRegistryEnvironment(): Record { + const environment = { ...process.env }; + if (process.env["TYPESPEC_NPM_REGISTRY"] === undefined) { + return environment; + } + + // Environment variable names are case-insensitive on Windows. Remove any + // existing spelling so Node does not choose between duplicate keys. + for (const name of Object.keys(environment)) { + if (name.toLowerCase() === "npm_config_registry") { + delete environment[name]; + } + } + environment["npm_config_registry"] = getNpmRegistry(); + return environment; +} + export async function fetchPackageManifest( packageName: string, version: string, diff --git a/packages/compiler/test/package-manager/npm-registry.test.ts b/packages/compiler/test/package-manager/npm-registry.test.ts index 02092ef5b98..be91732be8d 100644 --- a/packages/compiler/test/package-manager/npm-registry.test.ts +++ b/packages/compiler/test/package-manager/npm-registry.test.ts @@ -1,7 +1,10 @@ import * as http from "http"; import type { AddressInfo } from "net"; import { afterEach, beforeEach, expect, it } from "vitest"; -import { fetchPackageManifest } from "../../src/package-manger/npm-registry.js"; +import { + fetchPackageManifest, + getNpmRegistryEnvironment, +} from "../../src/package-manger/npm-registry.js"; let server: http.Server; let registryUrl: string; @@ -34,6 +37,7 @@ beforeEach(async () => { afterEach(async () => { delete process.env["TYPESPEC_NPM_REGISTRY"]; + delete process.env["NPM_CONFIG_REGISTRY"]; await new Promise((resolve) => server.close(() => resolve())); }); @@ -50,3 +54,22 @@ it("strips trailing slash from TYPESPEC_NPM_REGISTRY", async () => { expect(manifest.name).toBe("test-pkg"); expect(lastRequestUrl).toBe("/test-pkg/1.0.0"); }); + +it("forwards TYPESPEC_NPM_REGISTRY to npm", () => { + process.env["TYPESPEC_NPM_REGISTRY"] = `${registryUrl}/`; + process.env["NPM_CONFIG_REGISTRY"] = "https://old-registry.example.com"; + + const environment = getNpmRegistryEnvironment(); + + expect(environment["npm_config_registry"]).toBe(registryUrl); + expect(environment).not.toHaveProperty("NPM_CONFIG_REGISTRY"); +}); + +it("preserves the npm registry environment when no TypeSpec override is set", () => { + delete process.env["TYPESPEC_NPM_REGISTRY"]; + process.env["NPM_CONFIG_REGISTRY"] = "https://configured-registry.example.com"; + + const environment = getNpmRegistryEnvironment(); + + expect(environment["NPM_CONFIG_REGISTRY"]).toBe("https://configured-registry.example.com"); +}); From bd08169c1bdfb5827698529e7b955a36d2401bb8 Mon Sep 17 00:00:00 2001 From: Vivek JM Date: Sun, 16 Aug 2026 12:20:01 +0530 Subject: [PATCH 2/2] fix: ignore empty registry overrides --- packages/compiler/src/package-manger/npm-registry.ts | 7 ++++--- .../test/package-manager/npm-registry.test.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/package-manger/npm-registry.ts b/packages/compiler/src/package-manger/npm-registry.ts index 714b354e867..b12444aad62 100644 --- a/packages/compiler/src/package-manger/npm-registry.ts +++ b/packages/compiler/src/package-manger/npm-registry.ts @@ -90,13 +90,14 @@ const defaultRegistry = `https://registry.npmjs.org`; * otherwise falls back to the default npm registry. */ export function getNpmRegistry(): string { - return (process.env["TYPESPEC_NPM_REGISTRY"] ?? defaultRegistry).replace(/\/$/, ""); + const registry = process.env["TYPESPEC_NPM_REGISTRY"]?.trim(); + return (registry || defaultRegistry).replace(/\/$/, ""); } /** Returns the environment used to run npm with the TypeSpec registry override applied. */ -export function getNpmRegistryEnvironment(): Record { +export function getNpmRegistryEnvironment(): NodeJS.ProcessEnv { const environment = { ...process.env }; - if (process.env["TYPESPEC_NPM_REGISTRY"] === undefined) { + if (!process.env["TYPESPEC_NPM_REGISTRY"]?.trim()) { return environment; } diff --git a/packages/compiler/test/package-manager/npm-registry.test.ts b/packages/compiler/test/package-manager/npm-registry.test.ts index be91732be8d..f9c0edfaef9 100644 --- a/packages/compiler/test/package-manager/npm-registry.test.ts +++ b/packages/compiler/test/package-manager/npm-registry.test.ts @@ -3,6 +3,7 @@ import type { AddressInfo } from "net"; import { afterEach, beforeEach, expect, it } from "vitest"; import { fetchPackageManifest, + getNpmRegistry, getNpmRegistryEnvironment, } from "../../src/package-manger/npm-registry.js"; @@ -73,3 +74,14 @@ it("preserves the npm registry environment when no TypeSpec override is set", () expect(environment["NPM_CONFIG_REGISTRY"]).toBe("https://configured-registry.example.com"); }); + +it.each(["", " "])("does not forward an empty TypeSpec registry override", (registry) => { + process.env["TYPESPEC_NPM_REGISTRY"] = registry; + process.env["NPM_CONFIG_REGISTRY"] = "https://configured-registry.example.com"; + + const environment = getNpmRegistryEnvironment(); + + expect(getNpmRegistry()).toBe("https://registry.npmjs.org"); + expect(environment["NPM_CONFIG_REGISTRY"]).toBe("https://configured-registry.example.com"); + expect(environment).not.toHaveProperty("npm_config_registry"); +});