@verdaccio/e2e-cli 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,7 +42,8 @@ function createBunAdapter(binPath, _version) {
42
42
  return ["--cwd", folder];
43
43
  },
44
44
  exec(options, ...args) {
45
- if (args[0] === "info") {
45
+ const cmd = args[0];
46
+ if (cmd === "info") {
46
47
  const pkgName = args[1];
47
48
  const cleaned = args.slice(2).filter((a) => a !== "--registry").filter((a) => !a.startsWith("http"));
48
49
  return require_process.exec(options, bin, [
@@ -51,6 +52,7 @@ function createBunAdapter(binPath, _version) {
51
52
  ...cleaned
52
53
  ]);
53
54
  }
55
+ if (cmd === "publish") return require_process.exec(options, bin, args.filter((a) => a !== "--json"));
54
56
  return require_process.exec(options, bin, args);
55
57
  },
56
58
  async prepareProject(packageName, version, registryUrl, port, token, dependencies = {}, devDependencies = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"bun.cjs","names":[],"sources":["../../../src/adapters/bun.ts"],"sourcesContent":["import { SpawnOptions, execSync } from 'child_process';\nimport buildDebug from 'debug';\n\nimport { ExecOutput, PackageManagerAdapter } from '../types';\nimport { exec } from '../utils/process';\nimport { prepareGenericEmptyProject } from '../utils/project';\n\nconst debug = buildDebug('verdaccio:e2e-cli:bun');\n\nconst BUN_SUPPORTED_COMMANDS = new Set(['publish', 'install', 'info', 'audit']);\n\nfunction detectVersion(bin: string): string {\n try {\n return execSync(`${bin} --version`, { encoding: 'utf8', timeout: 5000 }).trim();\n } catch {\n return 'unknown';\n }\n}\n\nfunction resolveBunBin(binPath?: string): string {\n if (binPath) return binPath;\n return 'bun';\n}\n\nexport function createBunAdapter(binPath?: string, _version?: string): PackageManagerAdapter {\n const bin = resolveBunBin(binPath);\n const resolved = detectVersion(bin);\n debug('creating bun adapter with bin: %s (%s)', bin, resolved);\n\n const adapter: PackageManagerAdapter = {\n name: `bun@${resolved}`,\n type: 'bun',\n bin,\n supports: BUN_SUPPORTED_COMMANDS,\n\n registryArg(url: string): string[] {\n return ['--registry', url];\n },\n\n prefixArg(folder: string): string[] {\n return ['--cwd', folder];\n },\n\n exec(options: SpawnOptions, ...args: string[]): Promise<ExecOutput> {\n const cmd = args[0];\n\n if (cmd === 'info') {\n // bun info <pkg> reads registry from .npmrc, strip --registry <url>\n const pkgName = args[1];\n const filteredArgs = args.slice(2).filter((a) => a !== '--registry');\n // also remove the URL that follows --registry\n const cleaned = filteredArgs.filter((a) => !a.startsWith('http'));\n return exec(options, bin, ['info', pkgName, ...cleaned]);\n }\n\n return exec(options, bin, args);\n },\n\n async prepareProject(\n packageName: string,\n version: string,\n registryUrl: string,\n port: number,\n token: string,\n dependencies: Record<string, string> = {},\n devDependencies: Record<string, string> = {}\n ): Promise<{ tempFolder: string }> {\n return prepareGenericEmptyProject(\n packageName,\n version,\n port,\n token,\n registryUrl,\n dependencies,\n devDependencies\n );\n },\n };\n\n return adapter;\n}\n"],"mappings":";;;;;;;AAOA,IAAM,WAAA,GAAQ,MAAA,QAAA,CAAW,uBAAuB;AAEhD,IAAM,yCAAyB,IAAI,IAAI;CAAC;CAAW;CAAW;CAAQ;AAAO,CAAC;AAE9E,SAAS,cAAc,KAAqB;CAC1C,IAAI;EACF,QAAA,GAAO,cAAA,SAAA,CAAS,GAAG,IAAI,aAAa;GAAE,UAAU;GAAQ,SAAS;EAAK,CAAC,CAAC,CAAC,KAAK;CAChF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,SAA0B;CAC/C,IAAI,SAAS,OAAO;CACpB,OAAO;AACT;AAEA,SAAgB,iBAAiB,SAAkB,UAA0C;CAC3F,MAAM,MAAM,cAAc,OAAO;CACjC,MAAM,WAAW,cAAc,GAAG;CAClC,QAAM,0CAA0C,KAAK,QAAQ;CAoD7D,OAAO;EAjDL,MAAM,OAAO;EACb,MAAM;EACN;EACA,UAAU;EAEV,YAAY,KAAuB;GACjC,OAAO,CAAC,cAAc,GAAG;EAC3B;EAEA,UAAU,QAA0B;GAClC,OAAO,CAAC,SAAS,MAAM;EACzB;EAEA,KAAK,SAAuB,GAAG,MAAqC;GAGlE,IAFY,KAAK,OAEL,QAAQ;IAElB,MAAM,UAAU,KAAK;IAGrB,MAAM,UAFe,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,YAEvC,CAAA,CAAa,QAAQ,MAAM,CAAC,EAAE,WAAW,MAAM,CAAC;IAChE,OAAO,gBAAA,KAAK,SAAS,KAAK;KAAC;KAAQ;KAAS,GAAG;IAAO,CAAC;GACzD;GAEA,OAAO,gBAAA,KAAK,SAAS,KAAK,IAAI;EAChC;EAEA,MAAM,eACJ,aACA,SACA,aACA,MACA,OACA,eAAuC,CAAC,GACxC,kBAA0C,CAAC,GACV;GACjC,OAAO,gBAAA,2BACL,aACA,SACA,MACA,OACA,aACA,cACA,eACF;EACF;CAGK;AACT"}
1
+ {"version":3,"file":"bun.cjs","names":[],"sources":["../../../src/adapters/bun.ts"],"sourcesContent":["import { SpawnOptions, execSync } from 'child_process';\nimport buildDebug from 'debug';\n\nimport { ExecOutput, PackageManagerAdapter } from '../types';\nimport { exec } from '../utils/process';\nimport { prepareGenericEmptyProject } from '../utils/project';\n\nconst debug = buildDebug('verdaccio:e2e-cli:bun');\n\nconst BUN_SUPPORTED_COMMANDS = new Set(['publish', 'install', 'info', 'audit']);\n\nfunction detectVersion(bin: string): string {\n try {\n return execSync(`${bin} --version`, { encoding: 'utf8', timeout: 5000 }).trim();\n } catch {\n return 'unknown';\n }\n}\n\nfunction resolveBunBin(binPath?: string): string {\n if (binPath) return binPath;\n return 'bun';\n}\n\nexport function createBunAdapter(binPath?: string, _version?: string): PackageManagerAdapter {\n const bin = resolveBunBin(binPath);\n const resolved = detectVersion(bin);\n debug('creating bun adapter with bin: %s (%s)', bin, resolved);\n\n const adapter: PackageManagerAdapter = {\n name: `bun@${resolved}`,\n type: 'bun',\n bin,\n supports: BUN_SUPPORTED_COMMANDS,\n\n registryArg(url: string): string[] {\n return ['--registry', url];\n },\n\n prefixArg(folder: string): string[] {\n return ['--cwd', folder];\n },\n\n exec(options: SpawnOptions, ...args: string[]): Promise<ExecOutput> {\n const cmd = args[0];\n\n if (cmd === 'info') {\n // bun info <pkg> reads registry from .npmrc, strip --registry <url>\n const pkgName = args[1];\n const filteredArgs = args.slice(2).filter((a) => a !== '--registry');\n // also remove the URL that follows --registry\n const cleaned = filteredArgs.filter((a) => !a.startsWith('http'));\n return exec(options, bin, ['info', pkgName, ...cleaned]);\n }\n\n if (cmd === 'publish') {\n // bun 1.4.4 rejects `publish --json`; the publish test only checks the exit code for bun\n return exec(\n options,\n bin,\n args.filter((a) => a !== '--json')\n );\n }\n\n return exec(options, bin, args);\n },\n\n async prepareProject(\n packageName: string,\n version: string,\n registryUrl: string,\n port: number,\n token: string,\n dependencies: Record<string, string> = {},\n devDependencies: Record<string, string> = {}\n ): Promise<{ tempFolder: string }> {\n return prepareGenericEmptyProject(\n packageName,\n version,\n port,\n token,\n registryUrl,\n dependencies,\n devDependencies\n );\n },\n };\n\n return adapter;\n}\n"],"mappings":";;;;;;;AAOA,IAAM,WAAA,GAAQ,MAAA,QAAA,CAAW,uBAAuB;AAEhD,IAAM,yCAAyB,IAAI,IAAI;CAAC;CAAW;CAAW;CAAQ;AAAO,CAAC;AAE9E,SAAS,cAAc,KAAqB;CAC1C,IAAI;EACF,QAAA,GAAO,cAAA,SAAA,CAAS,GAAG,IAAI,aAAa;GAAE,UAAU;GAAQ,SAAS;EAAK,CAAC,CAAC,CAAC,KAAK;CAChF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,SAA0B;CAC/C,IAAI,SAAS,OAAO;CACpB,OAAO;AACT;AAEA,SAAgB,iBAAiB,SAAkB,UAA0C;CAC3F,MAAM,MAAM,cAAc,OAAO;CACjC,MAAM,WAAW,cAAc,GAAG;CAClC,QAAM,0CAA0C,KAAK,QAAQ;CA6D7D,OAAO;EA1DL,MAAM,OAAO;EACb,MAAM;EACN;EACA,UAAU;EAEV,YAAY,KAAuB;GACjC,OAAO,CAAC,cAAc,GAAG;EAC3B;EAEA,UAAU,QAA0B;GAClC,OAAO,CAAC,SAAS,MAAM;EACzB;EAEA,KAAK,SAAuB,GAAG,MAAqC;GAClE,MAAM,MAAM,KAAK;GAEjB,IAAI,QAAQ,QAAQ;IAElB,MAAM,UAAU,KAAK;IAGrB,MAAM,UAFe,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,YAEvC,CAAA,CAAa,QAAQ,MAAM,CAAC,EAAE,WAAW,MAAM,CAAC;IAChE,OAAO,gBAAA,KAAK,SAAS,KAAK;KAAC;KAAQ;KAAS,GAAG;IAAO,CAAC;GACzD;GAEA,IAAI,QAAQ,WAEV,OAAO,gBAAA,KACL,SACA,KACA,KAAK,QAAQ,MAAM,MAAM,QAAQ,CACnC;GAGF,OAAO,gBAAA,KAAK,SAAS,KAAK,IAAI;EAChC;EAEA,MAAM,eACJ,aACA,SACA,aACA,MACA,OACA,eAAuC,CAAC,GACxC,kBAA0C,CAAC,GACV;GACjC,OAAO,gBAAA,2BACL,aACA,SACA,MACA,OACA,aACA,cACA,eACF;EACF;CAGK;AACT"}
@@ -9,6 +9,7 @@ const require_upm = require("./adapters/upm.cjs");
9
9
  const require_yarn_modern = require("./adapters/yarn-modern.cjs");
10
10
  require("./adapters/index.cjs");
11
11
  const require_runner = require("./runner.cjs");
12
+ const require_http_client = require("./utils/http-client.cjs");
12
13
  const require_index$1 = require("./scenarios/index.cjs");
13
14
  const require_registry_client = require("./utils/registry-client.cjs");
14
15
  const require_index$2 = require("./tests/index.cjs");
@@ -165,6 +166,7 @@ async function main(argv = process.argv) {
165
166
  printHelp();
166
167
  process.exit(1);
167
168
  }
169
+ options.registry = require_http_client.stripTrailingSlashes(options.registry);
168
170
  const { createTempFolder } = await Promise.resolve().then(() => require("./utils/project.cjs"));
169
171
  const runDir = await createTempFolder("e2e-run");
170
172
  process.chdir(runDir);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nimport { runAll } from './runner';\nimport { allTests } from './tests';\nimport { CliOptions, PackageManagerAdapter } from './types';\nimport { createUser, pingRegistry } from './utils/registry-client';\n\nconst debug = buildDebug('verdaccio:e2e-cli');\n\nfunction parsePmSpec(filter: string): { name: string; version?: string; binPath?: string } {\n // --pm yarn-modern@3, --pm npm@10, --pm npm=/path/to/bin\n const eqIdx = filter.indexOf('=');\n if (eqIdx !== -1) {\n return { name: filter.slice(0, eqIdx).toLowerCase(), binPath: filter.slice(eqIdx + 1) };\n }\n const atIdx = filter.indexOf('@');\n if (atIdx !== -1) {\n return { name: filter.slice(0, atIdx).toLowerCase(), version: filter.slice(atIdx + 1) };\n }\n return { name: filter.toLowerCase() };\n}\n\nfunction parseAdapters(pmFilters?: string[]): PackageManagerAdapter[] {\n if (!pmFilters || pmFilters.length === 0) {\n return [createNpmAdapter()];\n }\n\n const adapters: PackageManagerAdapter[] = [];\n\n for (const filter of pmFilters) {\n const { name, version, binPath } = parsePmSpec(filter);\n\n if (name === 'npm') {\n adapters.push(createNpmAdapter(binPath, version));\n } else if (name === 'pnpm') {\n adapters.push(createPnpmAdapter(binPath, version));\n } else if (name === 'yarn-classic' || name === 'yarn1') {\n throw new Error(\n 'yarn classic (v1) is no longer supported. Use yarn-modern (Yarn 3+) instead.'\n );\n } else if (name === 'yarn-modern' || name === 'yarn') {\n adapters.push(createYarnModernAdapter(binPath, version));\n } else if (name === 'bun') {\n adapters.push(createBunAdapter(binPath, version));\n } else if (name === 'deno') {\n adapters.push(createDenoAdapter(binPath, version));\n } else if (name === 'upm') {\n adapters.push(createUpmAdapter(binPath, version));\n } else if (name === 'nub') {\n adapters.push(createNubAdapter(binPath, version));\n } else {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno, upm, nub`\n );\n }\n }\n\n return adapters;\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n const options: CliOptions = {\n registry: '',\n pm: [],\n test: [],\n skipTest: [],\n concurrency: 1,\n timeout: 50000,\n verbose: false,\n };\n\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i];\n\n if (arg === '--registry' || arg === '-r') {\n options.registry = argv[++i];\n } else if (arg === '--pm') {\n options.pm!.push(argv[++i]);\n } else if (arg === '--test' || arg === '-t') {\n options.test!.push(argv[++i]);\n } else if (arg === '--concurrency' || arg === '-c') {\n options.concurrency = parseInt(argv[++i], 10);\n } else if (arg === '--timeout') {\n options.timeout = parseInt(argv[++i], 10);\n } else if (arg === '--token') {\n options.token = argv[++i];\n } else if (arg === '--uplink-port') {\n options.uplinkPort = parseInt(argv[++i], 10);\n } else if (arg === '--print-config') {\n options.printConfig = true;\n } else if (arg === '--server') {\n options.server = argv[++i];\n } else if (arg.startsWith('--server=')) {\n options.server = arg.split('=')[1];\n } else if (arg === '--skip-test') {\n options.skipTest!.push(argv[++i]);\n } else if (arg.startsWith('--skip-test=')) {\n options.skipTest!.push(arg.split('=')[1]);\n } else if (arg === '--verbose' || arg === '-v') {\n options.verbose = true;\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n } else if (arg.startsWith('--registry=')) {\n options.registry = arg.split('=')[1];\n } else if (arg.startsWith('--pm=')) {\n options.pm!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--test=')) {\n options.test!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--token=')) {\n options.token = arg.split('=')[1];\n } else if (arg.startsWith('--timeout=')) {\n options.timeout = parseInt(arg.split('=')[1], 10);\n } else if (arg.startsWith('--uplink-port=')) {\n options.uplinkPort = parseInt(arg.split('=')[1], 10);\n } else {\n console.error(`Unknown argument: ${arg}`);\n printHelp();\n process.exit(1);\n }\n }\n\n return options;\n}\n\nfunction printHelp(): void {\n console.log(`\n @verdaccio/e2e-cli - Run Verdaccio e2e tests against any running registry\n\n Usage:\n verdaccio-e2e --registry <url> [options]\n\n Required:\n -r, --registry <url> Verdaccio registry URL (e.g. http://localhost:4873)\n\n Options:\n --pm <name[@version]> Package manager to test (can be repeated)\n Supported: npm (10-12), pnpm (10+),\n yarn-modern (or yarn, Yarn 3+), bun, deno,\n upm (install/ci only), nub (install/ci only)\n Examples: --pm npm@10 --pm pnpm@11\n --pm yarn-modern@4\n --pm yarn-modern@3\n --pm bun\n --pm npm --pm pnpm (uses system version)\n Auto-installs the requested yarn version if needed\n Default: npm\n\n -t, --test <name> Filter tests by name (can be repeated)\n Available: publish, install, ci, audit, info, deprecate,\n dist-tags, ping, search, unpublish\n Scenarios: scenario:install-multiple-deps,\n scenario:minimum-release-age (pnpm 11.1+),\n scenario:tarballs (npm adapter),\n scenario:metadata (npm adapter),\n scenario:search (npm adapter; the\n uplink sub-tests also need\n --uplink-port / E2E_UPLINK_PORT),\n scenario:uplink-failure (npm adapter,\n needs --uplink-port / E2E_UPLINK_PORT\n and a registry started with the config\n from --print-config)\n Default: all supported by the PM\n\n --skip-test <name> Skip a test by name (can be repeated). Unlike\n --test, the rest of the battery still runs and\n the skipped tests are reported as SKIP\n\n --token <token> Auth token (skips user creation)\n --timeout <ms> Per-test timeout (default: 50000)\n --uplink-port <port> Port for the mock uplink used by\n scenario:uplink-failure (default: $E2E_UPLINK_PORT)\n --print-config Print the recommended registry config for the full\n battery (max_body_size, mock uplink) and exit\n --server <name> Registry server the printed config targets:\n verdaccio (default) or pnpr. pnpr's config has no\n mock uplink (its registry namespaces can't claim\n the unscoped dynamic e2e-uplink-* names), so run\n it without --uplink-port and the uplink tests skip\n -v, --verbose Enable debug output\n -h, --help Show this help\n `);\n}\n\nexport async function main(argv: string[] = process.argv): Promise<void> {\n const options = parseArgs(argv);\n\n if (options.printConfig) {\n const { buildE2EConfig, buildPnprE2EConfig, DEFAULT_UPLINK_PORT } =\n await import('./utils/e2e-config');\n const server = options.server ?? 'verdaccio';\n if (server === 'pnpr') {\n process.stdout.write(buildPnprE2EConfig());\n } else if (server === 'verdaccio') {\n process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));\n } else {\n console.error(`Unknown --server: \"${server}\". Supported: verdaccio, pnpr`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Expose the mock uplink port to scenarios (scenario:uplink-failure gates on it).\n if (options.uplinkPort) {\n process.env.E2E_UPLINK_PORT = String(options.uplinkPort);\n }\n\n if (!options.registry) {\n console.error('Error: --registry is required\\n');\n printHelp();\n process.exit(1);\n }\n\n // Run from a temp dir to isolate from the host project's packageManager, .yarnrc.yml, etc.\n const { createTempFolder } = await import('./utils/project');\n const runDir = await createTempFolder('e2e-run');\n process.chdir(runDir);\n\n if (options.verbose) {\n const { setVerbose } = await import('./utils/process');\n setVerbose(true);\n }\n\n // Ensure registry is reachable\n console.log(`Checking registry at ${options.registry}...`);\n const alive = await pingRegistry(options.registry);\n if (!alive) {\n console.error(`Error: Registry at ${options.registry} is not reachable`);\n process.exit(1);\n }\n console.log(`Registry is alive.`);\n\n // Get auth token\n let token = options.token;\n if (!token) {\n console.log('Creating test user...');\n const auth = await createUser(options.registry);\n token = auth.token;\n console.log(`User \"${auth.user}\" created.`);\n }\n\n // Build adapter list\n const adapters = parseAdapters(options.pm);\n console.log(`Adapters: ${adapters.map((a) => a.name).join(', ')}`);\n\n // Filter tests\n const tests =\n options.test && options.test.length > 0\n ? allTests.filter((t) => options.test!.includes(t.name))\n : allTests;\n\n console.log(`Tests: ${tests.map((t) => t.name).join(', ')}`);\n\n if (options.skipTest && options.skipTest.length > 0) {\n console.log(`Skipping tests: ${options.skipTest.join(', ')}`);\n }\n\n // Run\n const { exitCode } = await runAll(adapters, tests, options.registry, token, {\n timeout: options.timeout,\n concurrency: options.concurrency,\n testFilter: options.test,\n skipTests: options.skipTest,\n });\n\n process.exit(exitCode);\n}\n\n// Re-export for programmatic usage\nexport { allTests } from './tests';\nexport { allScenarios } from './scenarios';\nexport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nimport { runAll } from './runner';\nimport { allTests } from './tests';\nimport { CliOptions, PackageManagerAdapter } from './types';\nimport { stripTrailingSlashes } from './utils/http-client';\nimport { createUser, pingRegistry } from './utils/registry-client';\n\nconst debug = buildDebug('verdaccio:e2e-cli');\n\nfunction parsePmSpec(filter: string): { name: string; version?: string; binPath?: string } {\n // --pm yarn-modern@3, --pm npm@10, --pm npm=/path/to/bin\n const eqIdx = filter.indexOf('=');\n if (eqIdx !== -1) {\n return { name: filter.slice(0, eqIdx).toLowerCase(), binPath: filter.slice(eqIdx + 1) };\n }\n const atIdx = filter.indexOf('@');\n if (atIdx !== -1) {\n return { name: filter.slice(0, atIdx).toLowerCase(), version: filter.slice(atIdx + 1) };\n }\n return { name: filter.toLowerCase() };\n}\n\nfunction parseAdapters(pmFilters?: string[]): PackageManagerAdapter[] {\n if (!pmFilters || pmFilters.length === 0) {\n return [createNpmAdapter()];\n }\n\n const adapters: PackageManagerAdapter[] = [];\n\n for (const filter of pmFilters) {\n const { name, version, binPath } = parsePmSpec(filter);\n\n if (name === 'npm') {\n adapters.push(createNpmAdapter(binPath, version));\n } else if (name === 'pnpm') {\n adapters.push(createPnpmAdapter(binPath, version));\n } else if (name === 'yarn-classic' || name === 'yarn1') {\n throw new Error(\n 'yarn classic (v1) is no longer supported. Use yarn-modern (Yarn 3+) instead.'\n );\n } else if (name === 'yarn-modern' || name === 'yarn') {\n adapters.push(createYarnModernAdapter(binPath, version));\n } else if (name === 'bun') {\n adapters.push(createBunAdapter(binPath, version));\n } else if (name === 'deno') {\n adapters.push(createDenoAdapter(binPath, version));\n } else if (name === 'upm') {\n adapters.push(createUpmAdapter(binPath, version));\n } else if (name === 'nub') {\n adapters.push(createNubAdapter(binPath, version));\n } else {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno, upm, nub`\n );\n }\n }\n\n return adapters;\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n const options: CliOptions = {\n registry: '',\n pm: [],\n test: [],\n skipTest: [],\n concurrency: 1,\n timeout: 50000,\n verbose: false,\n };\n\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i];\n\n if (arg === '--registry' || arg === '-r') {\n options.registry = argv[++i];\n } else if (arg === '--pm') {\n options.pm!.push(argv[++i]);\n } else if (arg === '--test' || arg === '-t') {\n options.test!.push(argv[++i]);\n } else if (arg === '--concurrency' || arg === '-c') {\n options.concurrency = parseInt(argv[++i], 10);\n } else if (arg === '--timeout') {\n options.timeout = parseInt(argv[++i], 10);\n } else if (arg === '--token') {\n options.token = argv[++i];\n } else if (arg === '--uplink-port') {\n options.uplinkPort = parseInt(argv[++i], 10);\n } else if (arg === '--print-config') {\n options.printConfig = true;\n } else if (arg === '--server') {\n options.server = argv[++i];\n } else if (arg.startsWith('--server=')) {\n options.server = arg.split('=')[1];\n } else if (arg === '--skip-test') {\n options.skipTest!.push(argv[++i]);\n } else if (arg.startsWith('--skip-test=')) {\n options.skipTest!.push(arg.split('=')[1]);\n } else if (arg === '--verbose' || arg === '-v') {\n options.verbose = true;\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n } else if (arg.startsWith('--registry=')) {\n options.registry = arg.split('=')[1];\n } else if (arg.startsWith('--pm=')) {\n options.pm!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--test=')) {\n options.test!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--token=')) {\n options.token = arg.split('=')[1];\n } else if (arg.startsWith('--timeout=')) {\n options.timeout = parseInt(arg.split('=')[1], 10);\n } else if (arg.startsWith('--uplink-port=')) {\n options.uplinkPort = parseInt(arg.split('=')[1], 10);\n } else {\n console.error(`Unknown argument: ${arg}`);\n printHelp();\n process.exit(1);\n }\n }\n\n return options;\n}\n\nfunction printHelp(): void {\n console.log(`\n @verdaccio/e2e-cli - Run Verdaccio e2e tests against any running registry\n\n Usage:\n verdaccio-e2e --registry <url> [options]\n\n Required:\n -r, --registry <url> Verdaccio registry URL (e.g. http://localhost:4873)\n\n Options:\n --pm <name[@version]> Package manager to test (can be repeated)\n Supported: npm (10-12), pnpm (10+),\n yarn-modern (or yarn, Yarn 3+), bun, deno,\n upm (install/ci only), nub (install/ci only)\n Examples: --pm npm@10 --pm pnpm@11\n --pm yarn-modern@4\n --pm yarn-modern@3\n --pm bun\n --pm npm --pm pnpm (uses system version)\n Auto-installs the requested yarn version if needed\n Default: npm\n\n -t, --test <name> Filter tests by name (can be repeated)\n Available: publish, install, ci, audit, info, deprecate,\n dist-tags, ping, search, unpublish\n Scenarios: scenario:install-multiple-deps,\n scenario:minimum-release-age (pnpm 11.1+),\n scenario:tarballs (npm adapter),\n scenario:metadata (npm adapter),\n scenario:search (npm adapter; the\n uplink sub-tests also need\n --uplink-port / E2E_UPLINK_PORT),\n scenario:uplink-failure (npm adapter,\n needs --uplink-port / E2E_UPLINK_PORT\n and a registry started with the config\n from --print-config)\n Default: all supported by the PM\n\n --skip-test <name> Skip a test by name (can be repeated). Unlike\n --test, the rest of the battery still runs and\n the skipped tests are reported as SKIP\n\n --token <token> Auth token (skips user creation)\n --timeout <ms> Per-test timeout (default: 50000)\n --uplink-port <port> Port for the mock uplink used by\n scenario:uplink-failure (default: $E2E_UPLINK_PORT)\n --print-config Print the recommended registry config for the full\n battery (max_body_size, mock uplink) and exit\n --server <name> Registry server the printed config targets:\n verdaccio (default) or pnpr. pnpr's config has no\n mock uplink (its registry namespaces can't claim\n the unscoped dynamic e2e-uplink-* names), so run\n it without --uplink-port and the uplink tests skip\n -v, --verbose Enable debug output\n -h, --help Show this help\n `);\n}\n\nexport async function main(argv: string[] = process.argv): Promise<void> {\n const options = parseArgs(argv);\n\n if (options.printConfig) {\n const { buildE2EConfig, buildPnprE2EConfig, DEFAULT_UPLINK_PORT } =\n await import('./utils/e2e-config');\n const server = options.server ?? 'verdaccio';\n if (server === 'pnpr') {\n process.stdout.write(buildPnprE2EConfig());\n } else if (server === 'verdaccio') {\n process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));\n } else {\n console.error(`Unknown --server: \"${server}\". Supported: verdaccio, pnpr`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Expose the mock uplink port to scenarios (scenario:uplink-failure gates on it).\n if (options.uplinkPort) {\n process.env.E2E_UPLINK_PORT = String(options.uplinkPort);\n }\n\n if (!options.registry) {\n console.error('Error: --registry is required\\n');\n printHelp();\n process.exit(1);\n }\n // URLs are built as `${registry}/-/...`; a trailing slash (pnpr's `/npm/`) would double it\n options.registry = stripTrailingSlashes(options.registry);\n\n // Run from a temp dir to isolate from the host project's packageManager, .yarnrc.yml, etc.\n const { createTempFolder } = await import('./utils/project');\n const runDir = await createTempFolder('e2e-run');\n process.chdir(runDir);\n\n if (options.verbose) {\n const { setVerbose } = await import('./utils/process');\n setVerbose(true);\n }\n\n // Ensure registry is reachable\n console.log(`Checking registry at ${options.registry}...`);\n const alive = await pingRegistry(options.registry);\n if (!alive) {\n console.error(`Error: Registry at ${options.registry} is not reachable`);\n process.exit(1);\n }\n console.log(`Registry is alive.`);\n\n // Get auth token\n let token = options.token;\n if (!token) {\n console.log('Creating test user...');\n const auth = await createUser(options.registry);\n token = auth.token;\n console.log(`User \"${auth.user}\" created.`);\n }\n\n // Build adapter list\n const adapters = parseAdapters(options.pm);\n console.log(`Adapters: ${adapters.map((a) => a.name).join(', ')}`);\n\n // Filter tests\n const tests =\n options.test && options.test.length > 0\n ? allTests.filter((t) => options.test!.includes(t.name))\n : allTests;\n\n console.log(`Tests: ${tests.map((t) => t.name).join(', ')}`);\n\n if (options.skipTest && options.skipTest.length > 0) {\n console.log(`Skipping tests: ${options.skipTest.join(', ')}`);\n }\n\n // Run\n const { exitCode } = await runAll(adapters, tests, options.registry, token, {\n timeout: options.timeout,\n concurrency: options.concurrency,\n testFilter: options.test,\n skipTests: options.skipTest,\n });\n\n process.exit(exitCode);\n}\n\n// Re-export for programmatic usage\nexport { allTests } from './tests';\nexport { allScenarios } from './scenarios';\nexport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":";;;;;;;;;;;;;;;;;;CAiBc,GAAA,MAAA,QAAA,CAAW,mBAAmB;AAE5C,SAAS,YAAY,QAAsE;CAEzF,MAAM,QAAQ,OAAO,QAAQ,GAAG;CAChC,IAAI,UAAU,IACZ,OAAO;EAAE,MAAM,OAAO,MAAM,GAAG,KAAK,CAAC,CAAC,YAAY;EAAG,SAAS,OAAO,MAAM,QAAQ,CAAC;CAAE;CAExF,MAAM,QAAQ,OAAO,QAAQ,GAAG;CAChC,IAAI,UAAU,IACZ,OAAO;EAAE,MAAM,OAAO,MAAM,GAAG,KAAK,CAAC,CAAC,YAAY;EAAG,SAAS,OAAO,MAAM,QAAQ,CAAC;CAAE;CAExF,OAAO,EAAE,MAAM,OAAO,YAAY,EAAE;AACtC;AAEA,SAAS,cAAc,WAA+C;CACpE,IAAI,CAAC,aAAa,UAAU,WAAW,GACrC,OAAO,CAAC,YAAA,iBAAiB,CAAC;CAG5B,MAAM,WAAoC,CAAC;CAE3C,KAAK,MAAM,UAAU,WAAW;EAC9B,MAAM,EAAE,MAAM,SAAS,YAAY,YAAY,MAAM;EAErD,IAAI,SAAS,OACX,SAAS,KAAK,YAAA,iBAAiB,SAAS,OAAO,CAAC;OAC3C,IAAI,SAAS,QAClB,SAAS,KAAK,aAAA,kBAAkB,SAAS,OAAO,CAAC;OAC5C,IAAI,SAAS,kBAAkB,SAAS,SAC7C,MAAM,IAAI,MACR,8EACF;OACK,IAAI,SAAS,iBAAiB,SAAS,QAC5C,SAAS,KAAK,oBAAA,wBAAwB,SAAS,OAAO,CAAC;OAClD,IAAI,SAAS,OAClB,SAAS,KAAK,YAAA,iBAAiB,SAAS,OAAO,CAAC;OAC3C,IAAI,SAAS,QAClB,SAAS,KAAK,aAAA,kBAAkB,SAAS,OAAO,CAAC;OAC5C,IAAI,SAAS,OAClB,SAAS,KAAK,YAAA,iBAAiB,SAAS,OAAO,CAAC;OAC3C,IAAI,SAAS,OAClB,SAAS,KAAK,YAAA,iBAAiB,SAAS,OAAO,CAAC;OAEhD,MAAM,IAAI,MACR,6BAA6B,KAAK,0DACpC;CAEJ;CAEA,OAAO;AACT;AAEA,SAAS,UAAU,MAA4B;CAC7C,MAAM,UAAsB;EAC1B,UAAU;EACV,IAAI,CAAC;EACL,MAAM,CAAC;EACP,UAAU,CAAC;EACX,aAAa;EACb,SAAS;EACT,SAAS;CACX;CAEA,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,QAAQ,gBAAgB,QAAQ,MAClC,QAAQ,WAAW,KAAK,EAAE;OACrB,IAAI,QAAQ,QACjB,QAAQ,GAAI,KAAK,KAAK,EAAE,EAAE;OACrB,IAAI,QAAQ,YAAY,QAAQ,MACrC,QAAQ,KAAM,KAAK,KAAK,EAAE,EAAE;OACvB,IAAI,QAAQ,mBAAmB,QAAQ,MAC5C,QAAQ,cAAc,SAAS,KAAK,EAAE,IAAI,EAAE;OACvC,IAAI,QAAQ,aACjB,QAAQ,UAAU,SAAS,KAAK,EAAE,IAAI,EAAE;OACnC,IAAI,QAAQ,WACjB,QAAQ,QAAQ,KAAK,EAAE;OAClB,IAAI,QAAQ,iBACjB,QAAQ,aAAa,SAAS,KAAK,EAAE,IAAI,EAAE;OACtC,IAAI,QAAQ,kBACjB,QAAQ,cAAc;OACjB,IAAI,QAAQ,YACjB,QAAQ,SAAS,KAAK,EAAE;OACnB,IAAI,IAAI,WAAW,WAAW,GACnC,QAAQ,SAAS,IAAI,MAAM,GAAG,CAAC,CAAC;OAC3B,IAAI,QAAQ,eACjB,QAAQ,SAAU,KAAK,KAAK,EAAE,EAAE;OAC3B,IAAI,IAAI,WAAW,cAAc,GACtC,QAAQ,SAAU,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE;OACnC,IAAI,QAAQ,eAAe,QAAQ,MACxC,QAAQ,UAAU;OACb,IAAI,QAAQ,YAAY,QAAQ,MAAM;GAC3C,UAAU;GACV,QAAQ,KAAK,CAAC;EAChB,OAAO,IAAI,IAAI,WAAW,aAAa,GACrC,QAAQ,WAAW,IAAI,MAAM,GAAG,CAAC,CAAC;OAC7B,IAAI,IAAI,WAAW,OAAO,GAC/B,QAAQ,GAAI,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE;OAC7B,IAAI,IAAI,WAAW,SAAS,GACjC,QAAQ,KAAM,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE;OAC/B,IAAI,IAAI,WAAW,UAAU,GAClC,QAAQ,QAAQ,IAAI,MAAM,GAAG,CAAC,CAAC;OAC1B,IAAI,IAAI,WAAW,YAAY,GACpC,QAAQ,UAAU,SAAS,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE;OAC3C,IAAI,IAAI,WAAW,gBAAgB,GACxC,QAAQ,aAAa,SAAS,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE;OAC9C;GACL,QAAQ,MAAM,qBAAqB,KAAK;GACxC,UAAU;GACV,QAAQ,KAAK,CAAC;EAChB;CACF;CAEA,OAAO;AACT;AAEA,SAAS,YAAkB;CACzB,QAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDX;AACH;AAEA,eAAsB,KAAK,OAAiB,QAAQ,MAAqB;CACvE,MAAM,UAAU,UAAU,IAAI;CAE9B,IAAI,QAAQ,aAAa;EACvB,MAAM,EAAE,gBAAgB,oBAAoB,wBAC1C,MAAA,QAAA,QAAA,CAAA,CAAA,WAAA,QAAM,wBAAA,CAAA;EACR,MAAM,SAAS,QAAQ,UAAU;EACjC,IAAI,WAAW,QACb,QAAQ,OAAO,MAAM,mBAAmB,CAAC;OACpC,IAAI,WAAW,aACpB,QAAQ,OAAO,MAAM,eAAe,QAAQ,cAAc,mBAAmB,CAAC;OACzE;GACL,QAAQ,MAAM,sBAAsB,OAAO,8BAA8B;GACzE,QAAQ,KAAK,CAAC;EAChB;EACA,QAAQ,KAAK,CAAC;CAChB;CAGA,IAAI,QAAQ,YACV,QAAQ,IAAI,kBAAkB,OAAO,QAAQ,UAAU;CAGzD,IAAI,CAAC,QAAQ,UAAU;EACrB,QAAQ,MAAM,iCAAiC;EAC/C,UAAU;EACV,QAAQ,KAAK,CAAC;CAChB;CAEA,QAAQ,WAAW,oBAAA,qBAAqB,QAAQ,QAAQ;CAGxD,MAAM,EAAE,qBAAqB,MAAA,QAAA,QAAA,CAAA,CAAA,WAAA,QAAM,qBAAA,CAAA;CACnC,MAAM,SAAS,MAAM,iBAAiB,SAAS;CAC/C,QAAQ,MAAM,MAAM;CAEpB,IAAI,QAAQ,SAAS;EACnB,MAAM,EAAE,eAAe,MAAA,QAAA,QAAA,CAAA,CAAA,WAAA,QAAM,qBAAA,CAAA;EAC7B,WAAW,IAAI;CACjB;CAGA,QAAQ,IAAI,wBAAwB,QAAQ,SAAS,IAAI;CAEzD,IAAI,CAAC,MADe,wBAAA,aAAa,QAAQ,QAAQ,GACrC;EACV,QAAQ,MAAM,sBAAsB,QAAQ,SAAS,kBAAkB;EACvE,QAAQ,KAAK,CAAC;CAChB;CACA,QAAQ,IAAI,oBAAoB;CAGhC,IAAI,QAAQ,QAAQ;CACpB,IAAI,CAAC,OAAO;EACV,QAAQ,IAAI,uBAAuB;EACnC,MAAM,OAAO,MAAM,wBAAA,WAAW,QAAQ,QAAQ;EAC9C,QAAQ,KAAK;EACb,QAAQ,IAAI,SAAS,KAAK,KAAK,WAAW;CAC5C;CAGA,MAAM,WAAW,cAAc,QAAQ,EAAE;CACzC,QAAQ,IAAI,aAAa,SAAS,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,GAAG;CAGjE,MAAM,QACJ,QAAQ,QAAQ,QAAQ,KAAK,SAAS,IAClC,gBAAA,SAAS,QAAQ,MAAM,QAAQ,KAAM,SAAS,EAAE,IAAI,CAAC,IACrD,gBAAA;CAEN,QAAQ,IAAI,UAAU,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,GAAG;CAE3D,IAAI,QAAQ,YAAY,QAAQ,SAAS,SAAS,GAChD,QAAQ,IAAI,mBAAmB,QAAQ,SAAS,KAAK,IAAI,GAAG;CAI9D,MAAM,EAAE,aAAa,MAAM,eAAA,OAAO,UAAU,OAAO,QAAQ,UAAU,OAAO;EAC1E,SAAS,QAAQ;EACjB,aAAa,QAAQ;EACrB,YAAY,QAAQ;EACpB,WAAW,QAAQ;CACrB,CAAC;CAED,QAAQ,KAAK,QAAQ;AACvB"}
@@ -1,4 +1,5 @@
1
1
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_http_client = require("../utils/http-client.cjs");
2
3
  let assert = require("assert");
3
4
  assert = require_runtime.__toESM(assert, 1);
4
5
  //#region src/tests/ping.ts
@@ -12,7 +13,7 @@ async function testPing(ctx) {
12
13
  if (type === "yarn-modern") assert.default.ok(true, "ping succeeded");
13
14
  else {
14
15
  const parsedBody = JSON.parse(resp.stdout);
15
- assert.default.strictEqual(parsedBody.registry, ctx.registryUrl + "/", `Expected registry "${ctx.registryUrl}/" but got "${parsedBody.registry}"`);
16
+ assert.default.strictEqual(require_http_client.stripTrailingSlashes(String(parsedBody.registry)), require_http_client.stripTrailingSlashes(ctx.registryUrl), `Expected registry "${ctx.registryUrl}/" but got "${parsedBody.registry}"`);
16
17
  }
17
18
  }
18
19
  var pingTest = {
@@ -1 +1 @@
1
- {"version":3,"file":"ping.cjs","names":[],"sources":["../../../src/tests/ping.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testPing(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n\n // Always run from a prepared project: its .npmrc carries the fresh token and\n // shields the test from whatever the developer's global ~/.npmrc has for\n // this registry (a stale token there would turn into a spurious E401).\n const { tempFolder } = await ctx.adapter.prepareProject(\n `verdaccio-ping-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n const cwd = tempFolder;\n if (type === 'yarn-modern' && ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tempFolder, 'npm-ping');\n }\n\n const jsonFlag = type === 'yarn-modern' ? [] : ['--json'];\n const resp = await ctx.adapter.exec(\n { cwd },\n 'ping',\n ...jsonFlag,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n // yarn npm ping succeeding (exit code 0) is sufficient proof the registry is reachable\n assert.ok(true, 'ping succeeded');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n assert.strictEqual(\n parsedBody.registry,\n ctx.registryUrl + '/',\n `Expected registry \"${ctx.registryUrl}/\" but got \"${parsedBody.registry}\"`\n );\n }\n}\n\nexport const pingTest: TestDefinition = {\n name: 'ping',\n requires: ['ping'],\n run: testPing,\n};\n"],"mappings":";;;;AAIA,eAAe,SAAS,KAAiC;CACvD,MAAM,OAAO,IAAI,QAAQ;CAKzB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,kBAAkB,IAAI,SACtB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,MAAM;CACZ,IAAI,SAAS,iBAAiB,IAAI,QAAQ,cACxC,MAAM,IAAI,QAAQ,aAAa,YAAY,UAAU;CAGvD,MAAM,WAAW,SAAS,gBAAgB,CAAC,IAAI,CAAC,QAAQ;CACxD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,IAAI,GACN,QACA,GAAG,UACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,SAAS,eAEX,OAAA,QAAO,GAAG,MAAM,gBAAgB;MAC3B;EACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EACzC,OAAA,QAAO,YACL,WAAW,UACX,IAAI,cAAc,KAClB,sBAAsB,IAAI,YAAY,cAAc,WAAW,SAAS,EAC1E;CACF;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
1
+ {"version":3,"file":"ping.cjs","names":[],"sources":["../../../src/tests/ping.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { stripTrailingSlashes } from '../utils/http-client';\n\nasync function testPing(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n\n // Always run from a prepared project: its .npmrc carries the fresh token and\n // shields the test from whatever the developer's global ~/.npmrc has for\n // this registry (a stale token there would turn into a spurious E401).\n const { tempFolder } = await ctx.adapter.prepareProject(\n `verdaccio-ping-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n const cwd = tempFolder;\n if (type === 'yarn-modern' && ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tempFolder, 'npm-ping');\n }\n\n const jsonFlag = type === 'yarn-modern' ? [] : ['--json'];\n const resp = await ctx.adapter.exec(\n { cwd },\n 'ping',\n ...jsonFlag,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n // yarn npm ping succeeding (exit code 0) is sufficient proof the registry is reachable\n assert.ok(true, 'ping succeeded');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n // npm adds the trailing slash to a bare host but not to a path such as pnpr's /npm\n assert.strictEqual(\n stripTrailingSlashes(String(parsedBody.registry)),\n stripTrailingSlashes(ctx.registryUrl),\n `Expected registry \"${ctx.registryUrl}/\" but got \"${parsedBody.registry}\"`\n );\n }\n}\n\nexport const pingTest: TestDefinition = {\n name: 'ping',\n requires: ['ping'],\n run: testPing,\n};\n"],"mappings":";;;;;AAKA,eAAe,SAAS,KAAiC;CACvD,MAAM,OAAO,IAAI,QAAQ;CAKzB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,kBAAkB,IAAI,SACtB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,MAAM;CACZ,IAAI,SAAS,iBAAiB,IAAI,QAAQ,cACxC,MAAM,IAAI,QAAQ,aAAa,YAAY,UAAU;CAGvD,MAAM,WAAW,SAAS,gBAAgB,CAAC,IAAI,CAAC,QAAQ;CACxD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,IAAI,GACN,QACA,GAAG,UACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,SAAS,eAEX,OAAA,QAAO,GAAG,MAAM,gBAAgB;MAC3B;EACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EAEzC,OAAA,QAAO,YACL,oBAAA,qBAAqB,OAAO,WAAW,QAAQ,CAAC,GAChD,oBAAA,qBAAqB,IAAI,WAAW,GACpC,sBAAsB,IAAI,YAAY,cAAc,WAAW,SAAS,EAC1E;CACF;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
@@ -10,6 +10,12 @@ var ABBREVIATED_ACCEPT = "application/vnd.npm.install-v1+json";
10
10
  * Registry URL for a package name. Scoped names are encoded with the same
11
11
  * `%2f` form npm clients use: `@scope%2fname`.
12
12
  */
13
+ /** `url` without trailing slashes; a loop instead of a regex, which CodeQL flags as ReDoS-prone. */
14
+ function stripTrailingSlashes(url) {
15
+ let end = url.length;
16
+ while (end > 0 && url[end - 1] === "/") end--;
17
+ return url.slice(0, end);
18
+ }
13
19
  function packumentUrl(registryUrl, pkgName) {
14
20
  const encoded = pkgName.startsWith("@") ? pkgName.replaceAll("/", "%2f") : pkgName;
15
21
  return `${registryUrl.replace(/\/$/, "")}/${encoded}`;
@@ -107,5 +113,6 @@ exports.computeDist = computeDist;
107
113
  exports.downloadTarball = downloadTarball;
108
114
  exports.fetchPackument = fetchPackument;
109
115
  exports.packumentUrl = packumentUrl;
116
+ exports.stripTrailingSlashes = stripTrailingSlashes;
110
117
 
111
118
  //# sourceMappingURL=http-client.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.cjs","names":[],"sources":["../../../src/utils/http-client.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport buildDebug from 'debug';\nimport { get } from 'http';\n\nconst debug = buildDebug('verdaccio:e2e-cli:http-client');\n\nexport type PackumentResponse = {\n status: number;\n headers: Headers;\n /** Parsed JSON body, or undefined when the response is not JSON (e.g. 304) */\n body?: any;\n};\n\nexport type TarballDownload = {\n status: number;\n headers: Headers;\n /** Total bytes received */\n bytes: number;\n /** hex-encoded sha1 of the received bytes (matches dist.shasum) */\n sha1: string;\n /** `sha512-<base64>` of the received bytes (matches dist.integrity) */\n integrity: string;\n};\n\nexport type FetchPackumentOptions = {\n /** Request the abbreviated (install) metadata format */\n abbreviated?: boolean;\n /** Send If-None-Match with this value */\n etag?: string;\n /** Bearer token */\n token?: string;\n};\n\nexport const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json';\n\n/**\n * Registry URL for a package name. Scoped names are encoded with the same\n * `%2f` form npm clients use: `@scope%2fname`.\n */\nexport function packumentUrl(registryUrl: string, pkgName: string): string {\n const encoded = pkgName.startsWith('@') ? pkgName.replaceAll('/', '%2f') : pkgName;\n return `${registryUrl.replace(/\\/$/, '')}/${encoded}`;\n}\n\nexport async function fetchPackument(\n registryUrl: string,\n pkgName: string,\n options: FetchPackumentOptions = {}\n): Promise<PackumentResponse> {\n const url = packumentUrl(registryUrl, pkgName);\n const headers: Record<string, string> = {};\n if (options.abbreviated) {\n headers['Accept'] = ABBREVIATED_ACCEPT;\n }\n if (options.etag) {\n headers['If-None-Match'] = options.etag;\n }\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET %s %o', url, headers);\n\n // Conditional requests must bypass fetch(): undici appends\n // `cache-control: no-cache` whenever If-None-Match is present, which\n // (correctly, per HTTP semantics) disables the server's 304 revalidation.\n if (options.etag) {\n return new Promise((resolve, reject) => {\n get(url, { headers }, (response) => {\n let text = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => (text += chunk));\n response.on('end', () => {\n let body: any;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n const responseHeaders = new Headers();\n for (const [name, value] of Object.entries(response.headers)) {\n if (value !== undefined) {\n responseHeaders.set(name, Array.isArray(value) ? value.join(', ') : value);\n }\n }\n resolve({ status: response.statusCode || 0, headers: responseHeaders, body });\n });\n }).on('error', reject);\n });\n }\n\n const response = await fetch(url, { headers });\n let body: any;\n if (response.status !== 304) {\n const text = await response.text();\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n }\n return { status: response.status, headers: response.headers, body };\n}\n\nexport type DownloadTarballOptions = {\n signal?: AbortSignal;\n token?: string;\n /** Called after the first chunk of the body arrives (useful to abort mid-stream) */\n onFirstChunk?: () => void;\n /** Called after every chunk with the total bytes received so far (useful to abort at a threshold) */\n onChunk?: (bytesReceived: number) => void;\n};\n\n/**\n * Download a tarball fully, computing sha1/sha512 integrity on the fly.\n * Rejects with the underlying error when aborted or the connection drops.\n */\nexport async function downloadTarball(\n url: string,\n options: DownloadTarballOptions = {}\n): Promise<TarballDownload> {\n const headers: Record<string, string> = {};\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET (tarball) %s', url);\n const response = await fetch(url, { headers, signal: options.signal });\n\n const sha1 = createHash('sha1');\n const sha512 = createHash('sha512');\n let bytes = 0;\n let first = true;\n\n if (response.body) {\n for await (const chunk of response.body as any) {\n const buf = Buffer.from(chunk);\n bytes += buf.length;\n sha1.update(buf);\n sha512.update(buf);\n if (first) {\n first = false;\n options.onFirstChunk?.();\n }\n options.onChunk?.(bytes);\n }\n }\n\n return {\n status: response.status,\n headers: response.headers,\n bytes,\n sha1: sha1.digest('hex'),\n integrity: `sha512-${sha512.digest('base64')}`,\n };\n}\n\n/** Compute npm-style dist values (shasum/integrity) for a buffer. */\nexport function computeDist(buffer: Buffer): { shasum: string; integrity: string } {\n return {\n shasum: createHash('sha1').update(buffer).digest('hex'),\n integrity: `sha512-${createHash('sha512').update(buffer).digest('base64')}`,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"http-client.cjs","names":[],"sources":["../../../src/utils/http-client.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport buildDebug from 'debug';\nimport { get } from 'http';\n\nconst debug = buildDebug('verdaccio:e2e-cli:http-client');\n\nexport type PackumentResponse = {\n status: number;\n headers: Headers;\n /** Parsed JSON body, or undefined when the response is not JSON (e.g. 304) */\n body?: any;\n};\n\nexport type TarballDownload = {\n status: number;\n headers: Headers;\n /** Total bytes received */\n bytes: number;\n /** hex-encoded sha1 of the received bytes (matches dist.shasum) */\n sha1: string;\n /** `sha512-<base64>` of the received bytes (matches dist.integrity) */\n integrity: string;\n};\n\nexport type FetchPackumentOptions = {\n /** Request the abbreviated (install) metadata format */\n abbreviated?: boolean;\n /** Send If-None-Match with this value */\n etag?: string;\n /** Bearer token */\n token?: string;\n};\n\nexport const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json';\n\n/**\n * Registry URL for a package name. Scoped names are encoded with the same\n * `%2f` form npm clients use: `@scope%2fname`.\n */\n/** `url` without trailing slashes; a loop instead of a regex, which CodeQL flags as ReDoS-prone. */\nexport function stripTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === '/') end--;\n return url.slice(0, end);\n}\n\nexport function packumentUrl(registryUrl: string, pkgName: string): string {\n const encoded = pkgName.startsWith('@') ? pkgName.replaceAll('/', '%2f') : pkgName;\n return `${registryUrl.replace(/\\/$/, '')}/${encoded}`;\n}\n\nexport async function fetchPackument(\n registryUrl: string,\n pkgName: string,\n options: FetchPackumentOptions = {}\n): Promise<PackumentResponse> {\n const url = packumentUrl(registryUrl, pkgName);\n const headers: Record<string, string> = {};\n if (options.abbreviated) {\n headers['Accept'] = ABBREVIATED_ACCEPT;\n }\n if (options.etag) {\n headers['If-None-Match'] = options.etag;\n }\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET %s %o', url, headers);\n\n // Conditional requests must bypass fetch(): undici appends\n // `cache-control: no-cache` whenever If-None-Match is present, which\n // (correctly, per HTTP semantics) disables the server's 304 revalidation.\n if (options.etag) {\n return new Promise((resolve, reject) => {\n get(url, { headers }, (response) => {\n let text = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => (text += chunk));\n response.on('end', () => {\n let body: any;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n const responseHeaders = new Headers();\n for (const [name, value] of Object.entries(response.headers)) {\n if (value !== undefined) {\n responseHeaders.set(name, Array.isArray(value) ? value.join(', ') : value);\n }\n }\n resolve({ status: response.statusCode || 0, headers: responseHeaders, body });\n });\n }).on('error', reject);\n });\n }\n\n const response = await fetch(url, { headers });\n let body: any;\n if (response.status !== 304) {\n const text = await response.text();\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n }\n return { status: response.status, headers: response.headers, body };\n}\n\nexport type DownloadTarballOptions = {\n signal?: AbortSignal;\n token?: string;\n /** Called after the first chunk of the body arrives (useful to abort mid-stream) */\n onFirstChunk?: () => void;\n /** Called after every chunk with the total bytes received so far (useful to abort at a threshold) */\n onChunk?: (bytesReceived: number) => void;\n};\n\n/**\n * Download a tarball fully, computing sha1/sha512 integrity on the fly.\n * Rejects with the underlying error when aborted or the connection drops.\n */\nexport async function downloadTarball(\n url: string,\n options: DownloadTarballOptions = {}\n): Promise<TarballDownload> {\n const headers: Record<string, string> = {};\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET (tarball) %s', url);\n const response = await fetch(url, { headers, signal: options.signal });\n\n const sha1 = createHash('sha1');\n const sha512 = createHash('sha512');\n let bytes = 0;\n let first = true;\n\n if (response.body) {\n for await (const chunk of response.body as any) {\n const buf = Buffer.from(chunk);\n bytes += buf.length;\n sha1.update(buf);\n sha512.update(buf);\n if (first) {\n first = false;\n options.onFirstChunk?.();\n }\n options.onChunk?.(bytes);\n }\n }\n\n return {\n status: response.status,\n headers: response.headers,\n bytes,\n sha1: sha1.digest('hex'),\n integrity: `sha512-${sha512.digest('base64')}`,\n };\n}\n\n/** Compute npm-style dist values (shasum/integrity) for a buffer. */\nexport function computeDist(buffer: Buffer): { shasum: string; integrity: string } {\n return {\n shasum: createHash('sha1').update(buffer).digest('hex'),\n integrity: `sha512-${createHash('sha512').update(buffer).digest('base64')}`,\n };\n}\n"],"mappings":"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"}
@@ -40,7 +40,8 @@ function createBunAdapter(binPath, _version) {
40
40
  return ["--cwd", folder];
41
41
  },
42
42
  exec(options, ...args) {
43
- if (args[0] === "info") {
43
+ const cmd = args[0];
44
+ if (cmd === "info") {
44
45
  const pkgName = args[1];
45
46
  const cleaned = args.slice(2).filter((a) => a !== "--registry").filter((a) => !a.startsWith("http"));
46
47
  return exec(options, bin, [
@@ -49,6 +50,7 @@ function createBunAdapter(binPath, _version) {
49
50
  ...cleaned
50
51
  ]);
51
52
  }
53
+ if (cmd === "publish") return exec(options, bin, args.filter((a) => a !== "--json"));
52
54
  return exec(options, bin, args);
53
55
  },
54
56
  async prepareProject(packageName, version, registryUrl, port, token, dependencies = {}, devDependencies = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"bun.js","names":[],"sources":["../../../src/adapters/bun.ts"],"sourcesContent":["import { SpawnOptions, execSync } from 'child_process';\nimport buildDebug from 'debug';\n\nimport { ExecOutput, PackageManagerAdapter } from '../types';\nimport { exec } from '../utils/process';\nimport { prepareGenericEmptyProject } from '../utils/project';\n\nconst debug = buildDebug('verdaccio:e2e-cli:bun');\n\nconst BUN_SUPPORTED_COMMANDS = new Set(['publish', 'install', 'info', 'audit']);\n\nfunction detectVersion(bin: string): string {\n try {\n return execSync(`${bin} --version`, { encoding: 'utf8', timeout: 5000 }).trim();\n } catch {\n return 'unknown';\n }\n}\n\nfunction resolveBunBin(binPath?: string): string {\n if (binPath) return binPath;\n return 'bun';\n}\n\nexport function createBunAdapter(binPath?: string, _version?: string): PackageManagerAdapter {\n const bin = resolveBunBin(binPath);\n const resolved = detectVersion(bin);\n debug('creating bun adapter with bin: %s (%s)', bin, resolved);\n\n const adapter: PackageManagerAdapter = {\n name: `bun@${resolved}`,\n type: 'bun',\n bin,\n supports: BUN_SUPPORTED_COMMANDS,\n\n registryArg(url: string): string[] {\n return ['--registry', url];\n },\n\n prefixArg(folder: string): string[] {\n return ['--cwd', folder];\n },\n\n exec(options: SpawnOptions, ...args: string[]): Promise<ExecOutput> {\n const cmd = args[0];\n\n if (cmd === 'info') {\n // bun info <pkg> reads registry from .npmrc, strip --registry <url>\n const pkgName = args[1];\n const filteredArgs = args.slice(2).filter((a) => a !== '--registry');\n // also remove the URL that follows --registry\n const cleaned = filteredArgs.filter((a) => !a.startsWith('http'));\n return exec(options, bin, ['info', pkgName, ...cleaned]);\n }\n\n return exec(options, bin, args);\n },\n\n async prepareProject(\n packageName: string,\n version: string,\n registryUrl: string,\n port: number,\n token: string,\n dependencies: Record<string, string> = {},\n devDependencies: Record<string, string> = {}\n ): Promise<{ tempFolder: string }> {\n return prepareGenericEmptyProject(\n packageName,\n version,\n port,\n token,\n registryUrl,\n dependencies,\n devDependencies\n );\n },\n };\n\n return adapter;\n}\n"],"mappings":";;;;;AAOA,IAAM,QAAQ,WAAW,uBAAuB;AAEhD,IAAM,yCAAyB,IAAI,IAAI;CAAC;CAAW;CAAW;CAAQ;AAAO,CAAC;AAE9E,SAAS,cAAc,KAAqB;CAC1C,IAAI;EACF,OAAO,SAAS,GAAG,IAAI,aAAa;GAAE,UAAU;GAAQ,SAAS;EAAK,CAAC,CAAC,CAAC,KAAK;CAChF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,SAA0B;CAC/C,IAAI,SAAS,OAAO;CACpB,OAAO;AACT;AAEA,SAAgB,iBAAiB,SAAkB,UAA0C;CAC3F,MAAM,MAAM,cAAc,OAAO;CACjC,MAAM,WAAW,cAAc,GAAG;CAClC,MAAM,0CAA0C,KAAK,QAAQ;CAoD7D,OAAO;EAjDL,MAAM,OAAO;EACb,MAAM;EACN;EACA,UAAU;EAEV,YAAY,KAAuB;GACjC,OAAO,CAAC,cAAc,GAAG;EAC3B;EAEA,UAAU,QAA0B;GAClC,OAAO,CAAC,SAAS,MAAM;EACzB;EAEA,KAAK,SAAuB,GAAG,MAAqC;GAGlE,IAFY,KAAK,OAEL,QAAQ;IAElB,MAAM,UAAU,KAAK;IAGrB,MAAM,UAFe,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,YAEvC,CAAA,CAAa,QAAQ,MAAM,CAAC,EAAE,WAAW,MAAM,CAAC;IAChE,OAAO,KAAK,SAAS,KAAK;KAAC;KAAQ;KAAS,GAAG;IAAO,CAAC;GACzD;GAEA,OAAO,KAAK,SAAS,KAAK,IAAI;EAChC;EAEA,MAAM,eACJ,aACA,SACA,aACA,MACA,OACA,eAAuC,CAAC,GACxC,kBAA0C,CAAC,GACV;GACjC,OAAO,2BACL,aACA,SACA,MACA,OACA,aACA,cACA,eACF;EACF;CAGK;AACT"}
1
+ {"version":3,"file":"bun.js","names":[],"sources":["../../../src/adapters/bun.ts"],"sourcesContent":["import { SpawnOptions, execSync } from 'child_process';\nimport buildDebug from 'debug';\n\nimport { ExecOutput, PackageManagerAdapter } from '../types';\nimport { exec } from '../utils/process';\nimport { prepareGenericEmptyProject } from '../utils/project';\n\nconst debug = buildDebug('verdaccio:e2e-cli:bun');\n\nconst BUN_SUPPORTED_COMMANDS = new Set(['publish', 'install', 'info', 'audit']);\n\nfunction detectVersion(bin: string): string {\n try {\n return execSync(`${bin} --version`, { encoding: 'utf8', timeout: 5000 }).trim();\n } catch {\n return 'unknown';\n }\n}\n\nfunction resolveBunBin(binPath?: string): string {\n if (binPath) return binPath;\n return 'bun';\n}\n\nexport function createBunAdapter(binPath?: string, _version?: string): PackageManagerAdapter {\n const bin = resolveBunBin(binPath);\n const resolved = detectVersion(bin);\n debug('creating bun adapter with bin: %s (%s)', bin, resolved);\n\n const adapter: PackageManagerAdapter = {\n name: `bun@${resolved}`,\n type: 'bun',\n bin,\n supports: BUN_SUPPORTED_COMMANDS,\n\n registryArg(url: string): string[] {\n return ['--registry', url];\n },\n\n prefixArg(folder: string): string[] {\n return ['--cwd', folder];\n },\n\n exec(options: SpawnOptions, ...args: string[]): Promise<ExecOutput> {\n const cmd = args[0];\n\n if (cmd === 'info') {\n // bun info <pkg> reads registry from .npmrc, strip --registry <url>\n const pkgName = args[1];\n const filteredArgs = args.slice(2).filter((a) => a !== '--registry');\n // also remove the URL that follows --registry\n const cleaned = filteredArgs.filter((a) => !a.startsWith('http'));\n return exec(options, bin, ['info', pkgName, ...cleaned]);\n }\n\n if (cmd === 'publish') {\n // bun 1.4.4 rejects `publish --json`; the publish test only checks the exit code for bun\n return exec(\n options,\n bin,\n args.filter((a) => a !== '--json')\n );\n }\n\n return exec(options, bin, args);\n },\n\n async prepareProject(\n packageName: string,\n version: string,\n registryUrl: string,\n port: number,\n token: string,\n dependencies: Record<string, string> = {},\n devDependencies: Record<string, string> = {}\n ): Promise<{ tempFolder: string }> {\n return prepareGenericEmptyProject(\n packageName,\n version,\n port,\n token,\n registryUrl,\n dependencies,\n devDependencies\n );\n },\n };\n\n return adapter;\n}\n"],"mappings":";;;;;AAOA,IAAM,QAAQ,WAAW,uBAAuB;AAEhD,IAAM,yCAAyB,IAAI,IAAI;CAAC;CAAW;CAAW;CAAQ;AAAO,CAAC;AAE9E,SAAS,cAAc,KAAqB;CAC1C,IAAI;EACF,OAAO,SAAS,GAAG,IAAI,aAAa;GAAE,UAAU;GAAQ,SAAS;EAAK,CAAC,CAAC,CAAC,KAAK;CAChF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,SAA0B;CAC/C,IAAI,SAAS,OAAO;CACpB,OAAO;AACT;AAEA,SAAgB,iBAAiB,SAAkB,UAA0C;CAC3F,MAAM,MAAM,cAAc,OAAO;CACjC,MAAM,WAAW,cAAc,GAAG;CAClC,MAAM,0CAA0C,KAAK,QAAQ;CA6D7D,OAAO;EA1DL,MAAM,OAAO;EACb,MAAM;EACN;EACA,UAAU;EAEV,YAAY,KAAuB;GACjC,OAAO,CAAC,cAAc,GAAG;EAC3B;EAEA,UAAU,QAA0B;GAClC,OAAO,CAAC,SAAS,MAAM;EACzB;EAEA,KAAK,SAAuB,GAAG,MAAqC;GAClE,MAAM,MAAM,KAAK;GAEjB,IAAI,QAAQ,QAAQ;IAElB,MAAM,UAAU,KAAK;IAGrB,MAAM,UAFe,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,YAEvC,CAAA,CAAa,QAAQ,MAAM,CAAC,EAAE,WAAW,MAAM,CAAC;IAChE,OAAO,KAAK,SAAS,KAAK;KAAC;KAAQ;KAAS,GAAG;IAAO,CAAC;GACzD;GAEA,IAAI,QAAQ,WAEV,OAAO,KACL,SACA,KACA,KAAK,QAAQ,MAAM,MAAM,QAAQ,CACnC;GAGF,OAAO,KAAK,SAAS,KAAK,IAAI;EAChC;EAEA,MAAM,eACJ,aACA,SACA,aACA,MACA,OACA,eAAuC,CAAC,GACxC,kBAA0C,CAAC,GACV;GACjC,OAAO,2BACL,aACA,SACA,MACA,OACA,aACA,cACA,eACF;EACF;CAGK;AACT"}
@@ -7,6 +7,7 @@ import { createUpmAdapter } from "./adapters/upm.js";
7
7
  import { createYarnModernAdapter } from "./adapters/yarn-modern.js";
8
8
  import "./adapters/index.js";
9
9
  import { runAll, runSuite } from "./runner.js";
10
+ import { stripTrailingSlashes } from "./utils/http-client.js";
10
11
  import { allScenarios } from "./scenarios/index.js";
11
12
  import { createUser, pingRegistry } from "./utils/registry-client.js";
12
13
  import { allTests } from "./tests/index.js";
@@ -162,6 +163,7 @@ async function main(argv = process.argv) {
162
163
  printHelp();
163
164
  process.exit(1);
164
165
  }
166
+ options.registry = stripTrailingSlashes(options.registry);
165
167
  const { createTempFolder } = await import("./utils/project.js");
166
168
  const runDir = await createTempFolder("e2e-run");
167
169
  process.chdir(runDir);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nimport { runAll } from './runner';\nimport { allTests } from './tests';\nimport { CliOptions, PackageManagerAdapter } from './types';\nimport { createUser, pingRegistry } from './utils/registry-client';\n\nconst debug = buildDebug('verdaccio:e2e-cli');\n\nfunction parsePmSpec(filter: string): { name: string; version?: string; binPath?: string } {\n // --pm yarn-modern@3, --pm npm@10, --pm npm=/path/to/bin\n const eqIdx = filter.indexOf('=');\n if (eqIdx !== -1) {\n return { name: filter.slice(0, eqIdx).toLowerCase(), binPath: filter.slice(eqIdx + 1) };\n }\n const atIdx = filter.indexOf('@');\n if (atIdx !== -1) {\n return { name: filter.slice(0, atIdx).toLowerCase(), version: filter.slice(atIdx + 1) };\n }\n return { name: filter.toLowerCase() };\n}\n\nfunction parseAdapters(pmFilters?: string[]): PackageManagerAdapter[] {\n if (!pmFilters || pmFilters.length === 0) {\n return [createNpmAdapter()];\n }\n\n const adapters: PackageManagerAdapter[] = [];\n\n for (const filter of pmFilters) {\n const { name, version, binPath } = parsePmSpec(filter);\n\n if (name === 'npm') {\n adapters.push(createNpmAdapter(binPath, version));\n } else if (name === 'pnpm') {\n adapters.push(createPnpmAdapter(binPath, version));\n } else if (name === 'yarn-classic' || name === 'yarn1') {\n throw new Error(\n 'yarn classic (v1) is no longer supported. Use yarn-modern (Yarn 3+) instead.'\n );\n } else if (name === 'yarn-modern' || name === 'yarn') {\n adapters.push(createYarnModernAdapter(binPath, version));\n } else if (name === 'bun') {\n adapters.push(createBunAdapter(binPath, version));\n } else if (name === 'deno') {\n adapters.push(createDenoAdapter(binPath, version));\n } else if (name === 'upm') {\n adapters.push(createUpmAdapter(binPath, version));\n } else if (name === 'nub') {\n adapters.push(createNubAdapter(binPath, version));\n } else {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno, upm, nub`\n );\n }\n }\n\n return adapters;\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n const options: CliOptions = {\n registry: '',\n pm: [],\n test: [],\n skipTest: [],\n concurrency: 1,\n timeout: 50000,\n verbose: false,\n };\n\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i];\n\n if (arg === '--registry' || arg === '-r') {\n options.registry = argv[++i];\n } else if (arg === '--pm') {\n options.pm!.push(argv[++i]);\n } else if (arg === '--test' || arg === '-t') {\n options.test!.push(argv[++i]);\n } else if (arg === '--concurrency' || arg === '-c') {\n options.concurrency = parseInt(argv[++i], 10);\n } else if (arg === '--timeout') {\n options.timeout = parseInt(argv[++i], 10);\n } else if (arg === '--token') {\n options.token = argv[++i];\n } else if (arg === '--uplink-port') {\n options.uplinkPort = parseInt(argv[++i], 10);\n } else if (arg === '--print-config') {\n options.printConfig = true;\n } else if (arg === '--server') {\n options.server = argv[++i];\n } else if (arg.startsWith('--server=')) {\n options.server = arg.split('=')[1];\n } else if (arg === '--skip-test') {\n options.skipTest!.push(argv[++i]);\n } else if (arg.startsWith('--skip-test=')) {\n options.skipTest!.push(arg.split('=')[1]);\n } else if (arg === '--verbose' || arg === '-v') {\n options.verbose = true;\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n } else if (arg.startsWith('--registry=')) {\n options.registry = arg.split('=')[1];\n } else if (arg.startsWith('--pm=')) {\n options.pm!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--test=')) {\n options.test!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--token=')) {\n options.token = arg.split('=')[1];\n } else if (arg.startsWith('--timeout=')) {\n options.timeout = parseInt(arg.split('=')[1], 10);\n } else if (arg.startsWith('--uplink-port=')) {\n options.uplinkPort = parseInt(arg.split('=')[1], 10);\n } else {\n console.error(`Unknown argument: ${arg}`);\n printHelp();\n process.exit(1);\n }\n }\n\n return options;\n}\n\nfunction printHelp(): void {\n console.log(`\n @verdaccio/e2e-cli - Run Verdaccio e2e tests against any running registry\n\n Usage:\n verdaccio-e2e --registry <url> [options]\n\n Required:\n -r, --registry <url> Verdaccio registry URL (e.g. http://localhost:4873)\n\n Options:\n --pm <name[@version]> Package manager to test (can be repeated)\n Supported: npm (10-12), pnpm (10+),\n yarn-modern (or yarn, Yarn 3+), bun, deno,\n upm (install/ci only), nub (install/ci only)\n Examples: --pm npm@10 --pm pnpm@11\n --pm yarn-modern@4\n --pm yarn-modern@3\n --pm bun\n --pm npm --pm pnpm (uses system version)\n Auto-installs the requested yarn version if needed\n Default: npm\n\n -t, --test <name> Filter tests by name (can be repeated)\n Available: publish, install, ci, audit, info, deprecate,\n dist-tags, ping, search, unpublish\n Scenarios: scenario:install-multiple-deps,\n scenario:minimum-release-age (pnpm 11.1+),\n scenario:tarballs (npm adapter),\n scenario:metadata (npm adapter),\n scenario:search (npm adapter; the\n uplink sub-tests also need\n --uplink-port / E2E_UPLINK_PORT),\n scenario:uplink-failure (npm adapter,\n needs --uplink-port / E2E_UPLINK_PORT\n and a registry started with the config\n from --print-config)\n Default: all supported by the PM\n\n --skip-test <name> Skip a test by name (can be repeated). Unlike\n --test, the rest of the battery still runs and\n the skipped tests are reported as SKIP\n\n --token <token> Auth token (skips user creation)\n --timeout <ms> Per-test timeout (default: 50000)\n --uplink-port <port> Port for the mock uplink used by\n scenario:uplink-failure (default: $E2E_UPLINK_PORT)\n --print-config Print the recommended registry config for the full\n battery (max_body_size, mock uplink) and exit\n --server <name> Registry server the printed config targets:\n verdaccio (default) or pnpr. pnpr's config has no\n mock uplink (its registry namespaces can't claim\n the unscoped dynamic e2e-uplink-* names), so run\n it without --uplink-port and the uplink tests skip\n -v, --verbose Enable debug output\n -h, --help Show this help\n `);\n}\n\nexport async function main(argv: string[] = process.argv): Promise<void> {\n const options = parseArgs(argv);\n\n if (options.printConfig) {\n const { buildE2EConfig, buildPnprE2EConfig, DEFAULT_UPLINK_PORT } =\n await import('./utils/e2e-config');\n const server = options.server ?? 'verdaccio';\n if (server === 'pnpr') {\n process.stdout.write(buildPnprE2EConfig());\n } else if (server === 'verdaccio') {\n process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));\n } else {\n console.error(`Unknown --server: \"${server}\". Supported: verdaccio, pnpr`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Expose the mock uplink port to scenarios (scenario:uplink-failure gates on it).\n if (options.uplinkPort) {\n process.env.E2E_UPLINK_PORT = String(options.uplinkPort);\n }\n\n if (!options.registry) {\n console.error('Error: --registry is required\\n');\n printHelp();\n process.exit(1);\n }\n\n // Run from a temp dir to isolate from the host project's packageManager, .yarnrc.yml, etc.\n const { createTempFolder } = await import('./utils/project');\n const runDir = await createTempFolder('e2e-run');\n process.chdir(runDir);\n\n if (options.verbose) {\n const { setVerbose } = await import('./utils/process');\n setVerbose(true);\n }\n\n // Ensure registry is reachable\n console.log(`Checking registry at ${options.registry}...`);\n const alive = await pingRegistry(options.registry);\n if (!alive) {\n console.error(`Error: Registry at ${options.registry} is not reachable`);\n process.exit(1);\n }\n console.log(`Registry is alive.`);\n\n // Get auth token\n let token = options.token;\n if (!token) {\n console.log('Creating test user...');\n const auth = await createUser(options.registry);\n token = auth.token;\n console.log(`User \"${auth.user}\" created.`);\n }\n\n // Build adapter list\n const adapters = parseAdapters(options.pm);\n console.log(`Adapters: ${adapters.map((a) => a.name).join(', ')}`);\n\n // Filter tests\n const tests =\n options.test && options.test.length > 0\n ? allTests.filter((t) => options.test!.includes(t.name))\n : allTests;\n\n console.log(`Tests: ${tests.map((t) => t.name).join(', ')}`);\n\n if (options.skipTest && options.skipTest.length > 0) {\n console.log(`Skipping tests: ${options.skipTest.join(', ')}`);\n }\n\n // Run\n const { exitCode } = await runAll(adapters, tests, options.registry, token, {\n timeout: options.timeout,\n concurrency: options.concurrency,\n testFilter: options.test,\n skipTests: options.skipTest,\n });\n\n process.exit(exitCode);\n}\n\n// Re-export for programmatic usage\nexport { allTests } from './tests';\nexport { allScenarios } from './scenarios';\nexport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nimport { runAll } from './runner';\nimport { allTests } from './tests';\nimport { CliOptions, PackageManagerAdapter } from './types';\nimport { stripTrailingSlashes } from './utils/http-client';\nimport { createUser, pingRegistry } from './utils/registry-client';\n\nconst debug = buildDebug('verdaccio:e2e-cli');\n\nfunction parsePmSpec(filter: string): { name: string; version?: string; binPath?: string } {\n // --pm yarn-modern@3, --pm npm@10, --pm npm=/path/to/bin\n const eqIdx = filter.indexOf('=');\n if (eqIdx !== -1) {\n return { name: filter.slice(0, eqIdx).toLowerCase(), binPath: filter.slice(eqIdx + 1) };\n }\n const atIdx = filter.indexOf('@');\n if (atIdx !== -1) {\n return { name: filter.slice(0, atIdx).toLowerCase(), version: filter.slice(atIdx + 1) };\n }\n return { name: filter.toLowerCase() };\n}\n\nfunction parseAdapters(pmFilters?: string[]): PackageManagerAdapter[] {\n if (!pmFilters || pmFilters.length === 0) {\n return [createNpmAdapter()];\n }\n\n const adapters: PackageManagerAdapter[] = [];\n\n for (const filter of pmFilters) {\n const { name, version, binPath } = parsePmSpec(filter);\n\n if (name === 'npm') {\n adapters.push(createNpmAdapter(binPath, version));\n } else if (name === 'pnpm') {\n adapters.push(createPnpmAdapter(binPath, version));\n } else if (name === 'yarn-classic' || name === 'yarn1') {\n throw new Error(\n 'yarn classic (v1) is no longer supported. Use yarn-modern (Yarn 3+) instead.'\n );\n } else if (name === 'yarn-modern' || name === 'yarn') {\n adapters.push(createYarnModernAdapter(binPath, version));\n } else if (name === 'bun') {\n adapters.push(createBunAdapter(binPath, version));\n } else if (name === 'deno') {\n adapters.push(createDenoAdapter(binPath, version));\n } else if (name === 'upm') {\n adapters.push(createUpmAdapter(binPath, version));\n } else if (name === 'nub') {\n adapters.push(createNubAdapter(binPath, version));\n } else {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno, upm, nub`\n );\n }\n }\n\n return adapters;\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n const options: CliOptions = {\n registry: '',\n pm: [],\n test: [],\n skipTest: [],\n concurrency: 1,\n timeout: 50000,\n verbose: false,\n };\n\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i];\n\n if (arg === '--registry' || arg === '-r') {\n options.registry = argv[++i];\n } else if (arg === '--pm') {\n options.pm!.push(argv[++i]);\n } else if (arg === '--test' || arg === '-t') {\n options.test!.push(argv[++i]);\n } else if (arg === '--concurrency' || arg === '-c') {\n options.concurrency = parseInt(argv[++i], 10);\n } else if (arg === '--timeout') {\n options.timeout = parseInt(argv[++i], 10);\n } else if (arg === '--token') {\n options.token = argv[++i];\n } else if (arg === '--uplink-port') {\n options.uplinkPort = parseInt(argv[++i], 10);\n } else if (arg === '--print-config') {\n options.printConfig = true;\n } else if (arg === '--server') {\n options.server = argv[++i];\n } else if (arg.startsWith('--server=')) {\n options.server = arg.split('=')[1];\n } else if (arg === '--skip-test') {\n options.skipTest!.push(argv[++i]);\n } else if (arg.startsWith('--skip-test=')) {\n options.skipTest!.push(arg.split('=')[1]);\n } else if (arg === '--verbose' || arg === '-v') {\n options.verbose = true;\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n } else if (arg.startsWith('--registry=')) {\n options.registry = arg.split('=')[1];\n } else if (arg.startsWith('--pm=')) {\n options.pm!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--test=')) {\n options.test!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--token=')) {\n options.token = arg.split('=')[1];\n } else if (arg.startsWith('--timeout=')) {\n options.timeout = parseInt(arg.split('=')[1], 10);\n } else if (arg.startsWith('--uplink-port=')) {\n options.uplinkPort = parseInt(arg.split('=')[1], 10);\n } else {\n console.error(`Unknown argument: ${arg}`);\n printHelp();\n process.exit(1);\n }\n }\n\n return options;\n}\n\nfunction printHelp(): void {\n console.log(`\n @verdaccio/e2e-cli - Run Verdaccio e2e tests against any running registry\n\n Usage:\n verdaccio-e2e --registry <url> [options]\n\n Required:\n -r, --registry <url> Verdaccio registry URL (e.g. http://localhost:4873)\n\n Options:\n --pm <name[@version]> Package manager to test (can be repeated)\n Supported: npm (10-12), pnpm (10+),\n yarn-modern (or yarn, Yarn 3+), bun, deno,\n upm (install/ci only), nub (install/ci only)\n Examples: --pm npm@10 --pm pnpm@11\n --pm yarn-modern@4\n --pm yarn-modern@3\n --pm bun\n --pm npm --pm pnpm (uses system version)\n Auto-installs the requested yarn version if needed\n Default: npm\n\n -t, --test <name> Filter tests by name (can be repeated)\n Available: publish, install, ci, audit, info, deprecate,\n dist-tags, ping, search, unpublish\n Scenarios: scenario:install-multiple-deps,\n scenario:minimum-release-age (pnpm 11.1+),\n scenario:tarballs (npm adapter),\n scenario:metadata (npm adapter),\n scenario:search (npm adapter; the\n uplink sub-tests also need\n --uplink-port / E2E_UPLINK_PORT),\n scenario:uplink-failure (npm adapter,\n needs --uplink-port / E2E_UPLINK_PORT\n and a registry started with the config\n from --print-config)\n Default: all supported by the PM\n\n --skip-test <name> Skip a test by name (can be repeated). Unlike\n --test, the rest of the battery still runs and\n the skipped tests are reported as SKIP\n\n --token <token> Auth token (skips user creation)\n --timeout <ms> Per-test timeout (default: 50000)\n --uplink-port <port> Port for the mock uplink used by\n scenario:uplink-failure (default: $E2E_UPLINK_PORT)\n --print-config Print the recommended registry config for the full\n battery (max_body_size, mock uplink) and exit\n --server <name> Registry server the printed config targets:\n verdaccio (default) or pnpr. pnpr's config has no\n mock uplink (its registry namespaces can't claim\n the unscoped dynamic e2e-uplink-* names), so run\n it without --uplink-port and the uplink tests skip\n -v, --verbose Enable debug output\n -h, --help Show this help\n `);\n}\n\nexport async function main(argv: string[] = process.argv): Promise<void> {\n const options = parseArgs(argv);\n\n if (options.printConfig) {\n const { buildE2EConfig, buildPnprE2EConfig, DEFAULT_UPLINK_PORT } =\n await import('./utils/e2e-config');\n const server = options.server ?? 'verdaccio';\n if (server === 'pnpr') {\n process.stdout.write(buildPnprE2EConfig());\n } else if (server === 'verdaccio') {\n process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));\n } else {\n console.error(`Unknown --server: \"${server}\". Supported: verdaccio, pnpr`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Expose the mock uplink port to scenarios (scenario:uplink-failure gates on it).\n if (options.uplinkPort) {\n process.env.E2E_UPLINK_PORT = String(options.uplinkPort);\n }\n\n if (!options.registry) {\n console.error('Error: --registry is required\\n');\n printHelp();\n process.exit(1);\n }\n // URLs are built as `${registry}/-/...`; a trailing slash (pnpr's `/npm/`) would double it\n options.registry = stripTrailingSlashes(options.registry);\n\n // Run from a temp dir to isolate from the host project's packageManager, .yarnrc.yml, etc.\n const { createTempFolder } = await import('./utils/project');\n const runDir = await createTempFolder('e2e-run');\n process.chdir(runDir);\n\n if (options.verbose) {\n const { setVerbose } = await import('./utils/process');\n setVerbose(true);\n }\n\n // Ensure registry is reachable\n console.log(`Checking registry at ${options.registry}...`);\n const alive = await pingRegistry(options.registry);\n if (!alive) {\n console.error(`Error: Registry at ${options.registry} is not reachable`);\n process.exit(1);\n }\n console.log(`Registry is alive.`);\n\n // Get auth token\n let token = options.token;\n if (!token) {\n console.log('Creating test user...');\n const auth = await createUser(options.registry);\n token = auth.token;\n console.log(`User \"${auth.user}\" created.`);\n }\n\n // Build adapter list\n const adapters = parseAdapters(options.pm);\n console.log(`Adapters: ${adapters.map((a) => a.name).join(', ')}`);\n\n // Filter tests\n const tests =\n options.test && options.test.length > 0\n ? allTests.filter((t) => options.test!.includes(t.name))\n : allTests;\n\n console.log(`Tests: ${tests.map((t) => t.name).join(', ')}`);\n\n if (options.skipTest && options.skipTest.length > 0) {\n console.log(`Skipping tests: ${options.skipTest.join(', ')}`);\n }\n\n // Run\n const { exitCode } = await runAll(adapters, tests, options.registry, token, {\n timeout: options.timeout,\n concurrency: options.concurrency,\n testFilter: options.test,\n skipTests: options.skipTest,\n });\n\n process.exit(exitCode);\n}\n\n// Re-export for programmatic usage\nexport { allTests } from './tests';\nexport { allScenarios } from './scenarios';\nexport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { stripTrailingSlashes } from "../utils/http-client.js";
1
2
  import assert from "assert";
2
3
  //#region src/tests/ping.ts
3
4
  async function testPing(ctx) {
@@ -10,7 +11,7 @@ async function testPing(ctx) {
10
11
  if (type === "yarn-modern") assert.ok(true, "ping succeeded");
11
12
  else {
12
13
  const parsedBody = JSON.parse(resp.stdout);
13
- assert.strictEqual(parsedBody.registry, ctx.registryUrl + "/", `Expected registry "${ctx.registryUrl}/" but got "${parsedBody.registry}"`);
14
+ assert.strictEqual(stripTrailingSlashes(String(parsedBody.registry)), stripTrailingSlashes(ctx.registryUrl), `Expected registry "${ctx.registryUrl}/" but got "${parsedBody.registry}"`);
14
15
  }
15
16
  }
16
17
  var pingTest = {
@@ -1 +1 @@
1
- {"version":3,"file":"ping.js","names":[],"sources":["../../../src/tests/ping.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testPing(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n\n // Always run from a prepared project: its .npmrc carries the fresh token and\n // shields the test from whatever the developer's global ~/.npmrc has for\n // this registry (a stale token there would turn into a spurious E401).\n const { tempFolder } = await ctx.adapter.prepareProject(\n `verdaccio-ping-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n const cwd = tempFolder;\n if (type === 'yarn-modern' && ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tempFolder, 'npm-ping');\n }\n\n const jsonFlag = type === 'yarn-modern' ? [] : ['--json'];\n const resp = await ctx.adapter.exec(\n { cwd },\n 'ping',\n ...jsonFlag,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n // yarn npm ping succeeding (exit code 0) is sufficient proof the registry is reachable\n assert.ok(true, 'ping succeeded');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n assert.strictEqual(\n parsedBody.registry,\n ctx.registryUrl + '/',\n `Expected registry \"${ctx.registryUrl}/\" but got \"${parsedBody.registry}\"`\n );\n }\n}\n\nexport const pingTest: TestDefinition = {\n name: 'ping',\n requires: ['ping'],\n run: testPing,\n};\n"],"mappings":";;AAIA,eAAe,SAAS,KAAiC;CACvD,MAAM,OAAO,IAAI,QAAQ;CAKzB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,kBAAkB,IAAI,SACtB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,MAAM;CACZ,IAAI,SAAS,iBAAiB,IAAI,QAAQ,cACxC,MAAM,IAAI,QAAQ,aAAa,YAAY,UAAU;CAGvD,MAAM,WAAW,SAAS,gBAAgB,CAAC,IAAI,CAAC,QAAQ;CACxD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,IAAI,GACN,QACA,GAAG,UACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,SAAS,eAEX,OAAO,GAAG,MAAM,gBAAgB;MAC3B;EACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EACzC,OAAO,YACL,WAAW,UACX,IAAI,cAAc,KAClB,sBAAsB,IAAI,YAAY,cAAc,WAAW,SAAS,EAC1E;CACF;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
1
+ {"version":3,"file":"ping.js","names":[],"sources":["../../../src/tests/ping.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { stripTrailingSlashes } from '../utils/http-client';\n\nasync function testPing(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n\n // Always run from a prepared project: its .npmrc carries the fresh token and\n // shields the test from whatever the developer's global ~/.npmrc has for\n // this registry (a stale token there would turn into a spurious E401).\n const { tempFolder } = await ctx.adapter.prepareProject(\n `verdaccio-ping-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n const cwd = tempFolder;\n if (type === 'yarn-modern' && ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tempFolder, 'npm-ping');\n }\n\n const jsonFlag = type === 'yarn-modern' ? [] : ['--json'];\n const resp = await ctx.adapter.exec(\n { cwd },\n 'ping',\n ...jsonFlag,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n // yarn npm ping succeeding (exit code 0) is sufficient proof the registry is reachable\n assert.ok(true, 'ping succeeded');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n // npm adds the trailing slash to a bare host but not to a path such as pnpr's /npm\n assert.strictEqual(\n stripTrailingSlashes(String(parsedBody.registry)),\n stripTrailingSlashes(ctx.registryUrl),\n `Expected registry \"${ctx.registryUrl}/\" but got \"${parsedBody.registry}\"`\n );\n }\n}\n\nexport const pingTest: TestDefinition = {\n name: 'ping',\n requires: ['ping'],\n run: testPing,\n};\n"],"mappings":";;;AAKA,eAAe,SAAS,KAAiC;CACvD,MAAM,OAAO,IAAI,QAAQ;CAKzB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,kBAAkB,IAAI,SACtB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,MAAM;CACZ,IAAI,SAAS,iBAAiB,IAAI,QAAQ,cACxC,MAAM,IAAI,QAAQ,aAAa,YAAY,UAAU;CAGvD,MAAM,WAAW,SAAS,gBAAgB,CAAC,IAAI,CAAC,QAAQ;CACxD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,IAAI,GACN,QACA,GAAG,UACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,SAAS,eAEX,OAAO,GAAG,MAAM,gBAAgB;MAC3B;EACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EAEzC,OAAO,YACL,qBAAqB,OAAO,WAAW,QAAQ,CAAC,GAChD,qBAAqB,IAAI,WAAW,GACpC,sBAAsB,IAAI,YAAY,cAAc,WAAW,SAAS,EAC1E;CACF;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
@@ -8,6 +8,12 @@ var ABBREVIATED_ACCEPT = "application/vnd.npm.install-v1+json";
8
8
  * Registry URL for a package name. Scoped names are encoded with the same
9
9
  * `%2f` form npm clients use: `@scope%2fname`.
10
10
  */
11
+ /** `url` without trailing slashes; a loop instead of a regex, which CodeQL flags as ReDoS-prone. */
12
+ function stripTrailingSlashes(url) {
13
+ let end = url.length;
14
+ while (end > 0 && url[end - 1] === "/") end--;
15
+ return url.slice(0, end);
16
+ }
11
17
  function packumentUrl(registryUrl, pkgName) {
12
18
  const encoded = pkgName.startsWith("@") ? pkgName.replaceAll("/", "%2f") : pkgName;
13
19
  return `${registryUrl.replace(/\/$/, "")}/${encoded}`;
@@ -100,6 +106,6 @@ function computeDist(buffer) {
100
106
  };
101
107
  }
102
108
  //#endregion
103
- export { ABBREVIATED_ACCEPT, computeDist, downloadTarball, fetchPackument, packumentUrl };
109
+ export { ABBREVIATED_ACCEPT, computeDist, downloadTarball, fetchPackument, packumentUrl, stripTrailingSlashes };
104
110
 
105
111
  //# sourceMappingURL=http-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.js","names":[],"sources":["../../../src/utils/http-client.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport buildDebug from 'debug';\nimport { get } from 'http';\n\nconst debug = buildDebug('verdaccio:e2e-cli:http-client');\n\nexport type PackumentResponse = {\n status: number;\n headers: Headers;\n /** Parsed JSON body, or undefined when the response is not JSON (e.g. 304) */\n body?: any;\n};\n\nexport type TarballDownload = {\n status: number;\n headers: Headers;\n /** Total bytes received */\n bytes: number;\n /** hex-encoded sha1 of the received bytes (matches dist.shasum) */\n sha1: string;\n /** `sha512-<base64>` of the received bytes (matches dist.integrity) */\n integrity: string;\n};\n\nexport type FetchPackumentOptions = {\n /** Request the abbreviated (install) metadata format */\n abbreviated?: boolean;\n /** Send If-None-Match with this value */\n etag?: string;\n /** Bearer token */\n token?: string;\n};\n\nexport const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json';\n\n/**\n * Registry URL for a package name. Scoped names are encoded with the same\n * `%2f` form npm clients use: `@scope%2fname`.\n */\nexport function packumentUrl(registryUrl: string, pkgName: string): string {\n const encoded = pkgName.startsWith('@') ? pkgName.replaceAll('/', '%2f') : pkgName;\n return `${registryUrl.replace(/\\/$/, '')}/${encoded}`;\n}\n\nexport async function fetchPackument(\n registryUrl: string,\n pkgName: string,\n options: FetchPackumentOptions = {}\n): Promise<PackumentResponse> {\n const url = packumentUrl(registryUrl, pkgName);\n const headers: Record<string, string> = {};\n if (options.abbreviated) {\n headers['Accept'] = ABBREVIATED_ACCEPT;\n }\n if (options.etag) {\n headers['If-None-Match'] = options.etag;\n }\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET %s %o', url, headers);\n\n // Conditional requests must bypass fetch(): undici appends\n // `cache-control: no-cache` whenever If-None-Match is present, which\n // (correctly, per HTTP semantics) disables the server's 304 revalidation.\n if (options.etag) {\n return new Promise((resolve, reject) => {\n get(url, { headers }, (response) => {\n let text = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => (text += chunk));\n response.on('end', () => {\n let body: any;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n const responseHeaders = new Headers();\n for (const [name, value] of Object.entries(response.headers)) {\n if (value !== undefined) {\n responseHeaders.set(name, Array.isArray(value) ? value.join(', ') : value);\n }\n }\n resolve({ status: response.statusCode || 0, headers: responseHeaders, body });\n });\n }).on('error', reject);\n });\n }\n\n const response = await fetch(url, { headers });\n let body: any;\n if (response.status !== 304) {\n const text = await response.text();\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n }\n return { status: response.status, headers: response.headers, body };\n}\n\nexport type DownloadTarballOptions = {\n signal?: AbortSignal;\n token?: string;\n /** Called after the first chunk of the body arrives (useful to abort mid-stream) */\n onFirstChunk?: () => void;\n /** Called after every chunk with the total bytes received so far (useful to abort at a threshold) */\n onChunk?: (bytesReceived: number) => void;\n};\n\n/**\n * Download a tarball fully, computing sha1/sha512 integrity on the fly.\n * Rejects with the underlying error when aborted or the connection drops.\n */\nexport async function downloadTarball(\n url: string,\n options: DownloadTarballOptions = {}\n): Promise<TarballDownload> {\n const headers: Record<string, string> = {};\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET (tarball) %s', url);\n const response = await fetch(url, { headers, signal: options.signal });\n\n const sha1 = createHash('sha1');\n const sha512 = createHash('sha512');\n let bytes = 0;\n let first = true;\n\n if (response.body) {\n for await (const chunk of response.body as any) {\n const buf = Buffer.from(chunk);\n bytes += buf.length;\n sha1.update(buf);\n sha512.update(buf);\n if (first) {\n first = false;\n options.onFirstChunk?.();\n }\n options.onChunk?.(bytes);\n }\n }\n\n return {\n status: response.status,\n headers: response.headers,\n bytes,\n sha1: sha1.digest('hex'),\n integrity: `sha512-${sha512.digest('base64')}`,\n };\n}\n\n/** Compute npm-style dist values (shasum/integrity) for a buffer. */\nexport function computeDist(buffer: Buffer): { shasum: string; integrity: string } {\n return {\n shasum: createHash('sha1').update(buffer).digest('hex'),\n integrity: `sha512-${createHash('sha512').update(buffer).digest('base64')}`,\n };\n}\n"],"mappings":";;;;AAIA,IAAM,QAAQ,WAAW,+BAA+B;AA6BxD,IAAa,qBAAqB;;;;;AAMlC,SAAgB,aAAa,aAAqB,SAAyB;CACzE,MAAM,UAAU,QAAQ,WAAW,GAAG,IAAI,QAAQ,WAAW,KAAK,KAAK,IAAI;CAC3E,OAAO,GAAG,YAAY,QAAQ,OAAO,EAAE,EAAE,GAAG;AAC9C;AAEA,eAAsB,eACpB,aACA,SACA,UAAiC,CAAC,GACN;CAC5B,MAAM,MAAM,aAAa,aAAa,OAAO;CAC7C,MAAM,UAAkC,CAAC;CACzC,IAAI,QAAQ,aACV,QAAQ,YAAY;CAEtB,IAAI,QAAQ,MACV,QAAQ,mBAAmB,QAAQ;CAErC,IAAI,QAAQ,OACV,QAAQ,mBAAmB,UAAU,QAAQ;CAE/C,MAAM,aAAa,KAAK,OAAO;CAK/B,IAAI,QAAQ,MACV,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,KAAK,EAAE,QAAQ,IAAI,aAAa;GAClC,IAAI,OAAO;GACX,SAAS,YAAY,MAAM;GAC3B,SAAS,GAAG,SAAS,UAAW,QAAQ,KAAM;GAC9C,SAAS,GAAG,aAAa;IACvB,IAAI;IACJ,IAAI;KACF,OAAO,KAAK,MAAM,IAAI;IACxB,QAAQ;KACN,OAAO,KAAA;IACT;IACA,MAAM,kBAAkB,IAAI,QAAQ;IACpC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,OAAO,GACzD,IAAI,UAAU,KAAA,GACZ,gBAAgB,IAAI,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK;IAG7E,QAAQ;KAAE,QAAQ,SAAS,cAAc;KAAG,SAAS;KAAiB;IAAK,CAAC;GAC9E,CAAC;EACH,CAAC,CAAC,CAAC,GAAG,SAAS,MAAM;CACvB,CAAC;CAGH,MAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;CAC7C,IAAI;CACJ,IAAI,SAAS,WAAW,KAAK;EAC3B,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,IAAI;GACF,OAAO,KAAK,MAAM,IAAI;EACxB,QAAQ;GACN,OAAO,KAAA;EACT;CACF;CACA,OAAO;EAAE,QAAQ,SAAS;EAAQ,SAAS,SAAS;EAAS;CAAK;AACpE;;;;;AAeA,eAAsB,gBACpB,KACA,UAAkC,CAAC,GACT;CAC1B,MAAM,UAAkC,CAAC;CACzC,IAAI,QAAQ,OACV,QAAQ,mBAAmB,UAAU,QAAQ;CAE/C,MAAM,oBAAoB,GAAG;CAC7B,MAAM,WAAW,MAAM,MAAM,KAAK;EAAE;EAAS,QAAQ,QAAQ;CAAO,CAAC;CAErE,MAAM,OAAO,WAAW,MAAM;CAC9B,MAAM,SAAS,WAAW,QAAQ;CAClC,IAAI,QAAQ;CACZ,IAAI,QAAQ;CAEZ,IAAI,SAAS,MACX,WAAW,MAAM,SAAS,SAAS,MAAa;EAC9C,MAAM,MAAM,OAAO,KAAK,KAAK;EAC7B,SAAS,IAAI;EACb,KAAK,OAAO,GAAG;EACf,OAAO,OAAO,GAAG;EACjB,IAAI,OAAO;GACT,QAAQ;GACR,QAAQ,eAAe;EACzB;EACA,QAAQ,UAAU,KAAK;CACzB;CAGF,OAAO;EACL,QAAQ,SAAS;EACjB,SAAS,SAAS;EAClB;EACA,MAAM,KAAK,OAAO,KAAK;EACvB,WAAW,UAAU,OAAO,OAAO,QAAQ;CAC7C;AACF;;AAGA,SAAgB,YAAY,QAAuD;CACjF,OAAO;EACL,QAAQ,WAAW,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;EACtD,WAAW,UAAU,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,QAAQ;CAC1E;AACF"}
1
+ {"version":3,"file":"http-client.js","names":[],"sources":["../../../src/utils/http-client.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport buildDebug from 'debug';\nimport { get } from 'http';\n\nconst debug = buildDebug('verdaccio:e2e-cli:http-client');\n\nexport type PackumentResponse = {\n status: number;\n headers: Headers;\n /** Parsed JSON body, or undefined when the response is not JSON (e.g. 304) */\n body?: any;\n};\n\nexport type TarballDownload = {\n status: number;\n headers: Headers;\n /** Total bytes received */\n bytes: number;\n /** hex-encoded sha1 of the received bytes (matches dist.shasum) */\n sha1: string;\n /** `sha512-<base64>` of the received bytes (matches dist.integrity) */\n integrity: string;\n};\n\nexport type FetchPackumentOptions = {\n /** Request the abbreviated (install) metadata format */\n abbreviated?: boolean;\n /** Send If-None-Match with this value */\n etag?: string;\n /** Bearer token */\n token?: string;\n};\n\nexport const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json';\n\n/**\n * Registry URL for a package name. Scoped names are encoded with the same\n * `%2f` form npm clients use: `@scope%2fname`.\n */\n/** `url` without trailing slashes; a loop instead of a regex, which CodeQL flags as ReDoS-prone. */\nexport function stripTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === '/') end--;\n return url.slice(0, end);\n}\n\nexport function packumentUrl(registryUrl: string, pkgName: string): string {\n const encoded = pkgName.startsWith('@') ? pkgName.replaceAll('/', '%2f') : pkgName;\n return `${registryUrl.replace(/\\/$/, '')}/${encoded}`;\n}\n\nexport async function fetchPackument(\n registryUrl: string,\n pkgName: string,\n options: FetchPackumentOptions = {}\n): Promise<PackumentResponse> {\n const url = packumentUrl(registryUrl, pkgName);\n const headers: Record<string, string> = {};\n if (options.abbreviated) {\n headers['Accept'] = ABBREVIATED_ACCEPT;\n }\n if (options.etag) {\n headers['If-None-Match'] = options.etag;\n }\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET %s %o', url, headers);\n\n // Conditional requests must bypass fetch(): undici appends\n // `cache-control: no-cache` whenever If-None-Match is present, which\n // (correctly, per HTTP semantics) disables the server's 304 revalidation.\n if (options.etag) {\n return new Promise((resolve, reject) => {\n get(url, { headers }, (response) => {\n let text = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => (text += chunk));\n response.on('end', () => {\n let body: any;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n const responseHeaders = new Headers();\n for (const [name, value] of Object.entries(response.headers)) {\n if (value !== undefined) {\n responseHeaders.set(name, Array.isArray(value) ? value.join(', ') : value);\n }\n }\n resolve({ status: response.statusCode || 0, headers: responseHeaders, body });\n });\n }).on('error', reject);\n });\n }\n\n const response = await fetch(url, { headers });\n let body: any;\n if (response.status !== 304) {\n const text = await response.text();\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n }\n return { status: response.status, headers: response.headers, body };\n}\n\nexport type DownloadTarballOptions = {\n signal?: AbortSignal;\n token?: string;\n /** Called after the first chunk of the body arrives (useful to abort mid-stream) */\n onFirstChunk?: () => void;\n /** Called after every chunk with the total bytes received so far (useful to abort at a threshold) */\n onChunk?: (bytesReceived: number) => void;\n};\n\n/**\n * Download a tarball fully, computing sha1/sha512 integrity on the fly.\n * Rejects with the underlying error when aborted or the connection drops.\n */\nexport async function downloadTarball(\n url: string,\n options: DownloadTarballOptions = {}\n): Promise<TarballDownload> {\n const headers: Record<string, string> = {};\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET (tarball) %s', url);\n const response = await fetch(url, { headers, signal: options.signal });\n\n const sha1 = createHash('sha1');\n const sha512 = createHash('sha512');\n let bytes = 0;\n let first = true;\n\n if (response.body) {\n for await (const chunk of response.body as any) {\n const buf = Buffer.from(chunk);\n bytes += buf.length;\n sha1.update(buf);\n sha512.update(buf);\n if (first) {\n first = false;\n options.onFirstChunk?.();\n }\n options.onChunk?.(bytes);\n }\n }\n\n return {\n status: response.status,\n headers: response.headers,\n bytes,\n sha1: sha1.digest('hex'),\n integrity: `sha512-${sha512.digest('base64')}`,\n };\n}\n\n/** Compute npm-style dist values (shasum/integrity) for a buffer. */\nexport function computeDist(buffer: Buffer): { shasum: string; integrity: string } {\n return {\n shasum: createHash('sha1').update(buffer).digest('hex'),\n integrity: `sha512-${createHash('sha512').update(buffer).digest('base64')}`,\n };\n}\n"],"mappings":"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"}
@@ -27,6 +27,8 @@ export declare const ABBREVIATED_ACCEPT = "application/vnd.npm.install-v1+json";
27
27
  * Registry URL for a package name. Scoped names are encoded with the same
28
28
  * `%2f` form npm clients use: `@scope%2fname`.
29
29
  */
30
+ /** `url` without trailing slashes; a loop instead of a regex, which CodeQL flags as ReDoS-prone. */
31
+ export declare function stripTrailingSlashes(url: string): string;
30
32
  export declare function packumentUrl(registryUrl: string, pkgName: string): string;
31
33
  export declare function fetchPackument(registryUrl: string, pkgName: string, options?: FetchPackumentOptions): Promise<PackumentResponse>;
32
34
  export type DownloadTarballOptions = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdaccio/e2e-cli",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "CLI tool to run Verdaccio e2e tests against any running registry",