@verdaccio/e2e-cli 3.2.0 → 3.3.0

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.
@@ -47,6 +47,7 @@ function parseArgs(argv) {
47
47
  registry: "",
48
48
  pm: [],
49
49
  test: [],
50
+ skipTest: [],
50
51
  concurrency: 1,
51
52
  timeout: 5e4,
52
53
  verbose: false
@@ -61,6 +62,10 @@ function parseArgs(argv) {
61
62
  else if (arg === "--token") options.token = argv[++i];
62
63
  else if (arg === "--uplink-port") options.uplinkPort = parseInt(argv[++i], 10);
63
64
  else if (arg === "--print-config") options.printConfig = true;
65
+ else if (arg === "--server") options.server = argv[++i];
66
+ else if (arg.startsWith("--server=")) options.server = arg.split("=")[1];
67
+ else if (arg === "--skip-test") options.skipTest.push(argv[++i]);
68
+ else if (arg.startsWith("--skip-test=")) options.skipTest.push(arg.split("=")[1]);
64
69
  else if (arg === "--verbose" || arg === "-v") options.verbose = true;
65
70
  else if (arg === "--help" || arg === "-h") {
66
71
  printHelp();
@@ -117,12 +122,21 @@ function printHelp() {
117
122
  from --print-config)
118
123
  Default: all supported by the PM
119
124
 
125
+ --skip-test <name> Skip a test by name (can be repeated). Unlike
126
+ --test, the rest of the battery still runs and
127
+ the skipped tests are reported as SKIP
128
+
120
129
  --token <token> Auth token (skips user creation)
121
130
  --timeout <ms> Per-test timeout (default: 50000)
122
131
  --uplink-port <port> Port for the mock uplink used by
123
132
  scenario:uplink-failure (default: $E2E_UPLINK_PORT)
124
- --print-config Print the recommended Verdaccio config for the full
133
+ --print-config Print the recommended registry config for the full
125
134
  battery (max_body_size, mock uplink) and exit
135
+ --server <name> Registry server the printed config targets:
136
+ verdaccio (default) or pnpr. pnpr's config has no
137
+ mock uplink (its registry namespaces can't claim
138
+ the unscoped dynamic e2e-uplink-* names), so run
139
+ it without --uplink-port and the uplink tests skip
126
140
  -v, --verbose Enable debug output
127
141
  -h, --help Show this help
128
142
  `);
@@ -130,8 +144,14 @@ function printHelp() {
130
144
  async function main(argv = process.argv) {
131
145
  const options = parseArgs(argv);
132
146
  if (options.printConfig) {
133
- const { buildE2EConfig, DEFAULT_UPLINK_PORT } = await Promise.resolve().then(() => require("./utils/e2e-config.cjs"));
134
- process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));
147
+ const { buildE2EConfig, buildPnprE2EConfig, DEFAULT_UPLINK_PORT } = await Promise.resolve().then(() => require("./utils/e2e-config.cjs"));
148
+ const server = options.server ?? "verdaccio";
149
+ if (server === "pnpr") process.stdout.write(buildPnprE2EConfig());
150
+ else if (server === "verdaccio") process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));
151
+ else {
152
+ console.error(`Unknown --server: "${server}". Supported: verdaccio, pnpr`);
153
+ process.exit(1);
154
+ }
135
155
  process.exit(0);
136
156
  }
137
157
  if (options.uplinkPort) process.env.E2E_UPLINK_PORT = String(options.uplinkPort);
@@ -164,10 +184,12 @@ async function main(argv = process.argv) {
164
184
  console.log(`Adapters: ${adapters.map((a) => a.name).join(", ")}`);
165
185
  const tests = options.test && options.test.length > 0 ? require_index$2.allTests.filter((t) => options.test.includes(t.name)) : require_index$2.allTests;
166
186
  console.log(`Tests: ${tests.map((t) => t.name).join(", ")}`);
187
+ if (options.skipTest && options.skipTest.length > 0) console.log(`Skipping tests: ${options.skipTest.join(", ")}`);
167
188
  const { exitCode } = await require_runner.runAll(adapters, tests, options.registry, token, {
168
189
  timeout: options.timeout,
169
190
  concurrency: options.concurrency,
170
- testFilter: options.test
191
+ testFilter: options.test,
192
+ skipTests: options.skipTest
171
193
  });
172
194
  process.exit(exitCode);
173
195
  }
@@ -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 createPnpmAdapter,\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 {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno`\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 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 === '--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 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 --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 Verdaccio config for the full\n battery (max_body_size, mock uplink) and exit\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, DEFAULT_UPLINK_PORT } = await import('./utils/e2e-config');\n process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));\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 // Run\n const { exitCode } = await runAll(adapters, tests, options.registry, token, {\n timeout: options.timeout,\n concurrency: options.concurrency,\n testFilter: options.test,\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 createPnpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":";;;;;;;;;;;;;;;CAcc,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;OAEjD,MAAM,IAAI,MACR,6BAA6B,KAAK,gDACpC;CAEJ;CAEA,OAAO;AACT;AAEA,SAAS,UAAU,MAA4B;CAC7C,MAAM,UAAsB;EAC1B,UAAU;EACV,IAAI,CAAC;EACL,MAAM,CAAC;EACP,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,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CX;AACH;AAEA,eAAsB,KAAK,OAAiB,QAAQ,MAAqB;CACvE,MAAM,UAAU,UAAU,IAAI;CAE9B,IAAI,QAAQ,aAAa;EACvB,MAAM,EAAE,gBAAgB,wBAAwB,MAAA,QAAA,QAAA,CAAA,CAAA,WAAA,QAAM,wBAAA,CAAA;EACtD,QAAQ,OAAO,MAAM,eAAe,QAAQ,cAAc,mBAAmB,CAAC;EAC9E,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;CAGA,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;CAG3D,MAAM,EAAE,aAAa,MAAM,eAAA,OAAO,UAAU,OAAO,QAAQ,UAAU,OAAO;EAC1E,SAAS,QAAQ;EACjB,aAAa,QAAQ;EACrB,YAAY,QAAQ;CACtB,CAAC;CAED,QAAQ,KAAK,QAAQ;AACvB"}
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 createPnpmAdapter,\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 {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno`\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 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 createPnpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":"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"}
@@ -93,6 +93,11 @@ async function runSuite(adapter, tests, registryUrl, token, options) {
93
93
  if (options.testFilter && options.testFilter.length > 0) {
94
94
  if (!options.testFilter.includes(test.name)) continue;
95
95
  }
96
+ if (options.skipTests && options.skipTests.includes(test.name)) {
97
+ require_reporter.reportSkipped(test.name);
98
+ skipped++;
99
+ continue;
100
+ }
96
101
  if (test.requires) {
97
102
  if (test.requires.filter((cmd) => !adapter.supports.has(cmd)).length > 0) {
98
103
  require_reporter.reportSkipped(test.name);
@@ -1 +1 @@
1
- {"version":3,"file":"runner.cjs","names":[],"sources":["../../src/runner.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { URL } from 'url';\n\nimport {\n reportSkipped,\n reportSubTestResult,\n reportSuiteStart,\n reportSummary,\n reportTestResult,\n reportTestStart,\n} from './reporter';\nimport {\n PackageManagerAdapter,\n SubTestResult,\n SuiteResult,\n TestContext,\n TestDefinition,\n TestResult,\n} from './types';\n\nconst debug = buildDebug('verdaccio:e2e-cli:runner');\n\nfunction getPort(registryUrl: string): number {\n try {\n const url = new URL(registryUrl);\n return parseInt(url.port, 10) || (url.protocol === 'https:' ? 443 : 80);\n } catch {\n return 4873;\n }\n}\n\nasync function runSingleTest(\n adapter: PackageManagerAdapter,\n test: TestDefinition,\n registryUrl: string,\n token: string,\n timeout: number\n): Promise<TestResult & { hasSubTests: boolean }> {\n const port = getPort(registryUrl);\n let hasSubTests = false;\n const subResults: SubTestResult[] = [];\n const ctx: TestContext = {\n registryUrl,\n token,\n port,\n exec: adapter.exec.bind(adapter),\n adapter,\n runId: `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,\n async subTest(label: string, fn: () => Promise<void>): Promise<void> {\n hasSubTests = true;\n const subStart = Date.now();\n try {\n await fn();\n const duration = Date.now() - subStart;\n subResults.push({ label, passed: true, duration });\n reportSubTestResult(label, true, duration);\n } catch (err) {\n const duration = Date.now() - subStart;\n const error = err instanceof Error ? err.message : String(err);\n subResults.push({ label, passed: false, duration, error });\n reportSubTestResult(label, false, duration);\n throw err;\n }\n },\n };\n\n const start = Date.now();\n\n return new Promise<TestResult & { hasSubTests: boolean }>((resolve) => {\n const timer = setTimeout(() => {\n resolve({\n name: test.name,\n passed: false,\n duration: Date.now() - start,\n error: `Test timed out after ${timeout}ms`,\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n }, timeout);\n\n test\n .run(ctx)\n .then(() => {\n clearTimeout(timer);\n resolve({\n name: test.name,\n passed: true,\n duration: Date.now() - start,\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n })\n .catch((err) => {\n clearTimeout(timer);\n resolve({\n name: test.name,\n passed: false,\n duration: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n });\n });\n}\n\nexport async function runSuite(\n adapter: PackageManagerAdapter,\n tests: TestDefinition[],\n registryUrl: string,\n token: string,\n options: { timeout: number; testFilter?: string[] }\n): Promise<SuiteResult> {\n const suiteStart = Date.now();\n const results: TestResult[] = [];\n let skipped = 0;\n\n reportSuiteStart(adapter.name);\n\n for (const test of tests) {\n // Filter by test name if specified\n if (options.testFilter && options.testFilter.length > 0) {\n if (!options.testFilter.includes(test.name)) {\n continue;\n }\n }\n\n // Check if adapter supports required commands\n if (test.requires) {\n const unsupported = test.requires.filter((cmd) => !adapter.supports.has(cmd));\n if (unsupported.length > 0) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n }\n\n // Check adapter/version gate (e.g. pnpm-only tests)\n if (test.appliesTo && !test.appliesTo(adapter)) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n\n reportTestStart(adapter.name, test.name);\n const timeout = Math.max(options.timeout, test.timeout ?? 0);\n const result = await runSingleTest(adapter, test, registryUrl, token, timeout);\n results.push(result);\n reportTestResult(result, result.hasSubTests);\n }\n\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n\n return {\n adapter: adapter.name,\n tests: results,\n passed,\n failed,\n skipped,\n duration: Date.now() - suiteStart,\n };\n}\n\nexport async function runAll(\n adapters: PackageManagerAdapter[],\n tests: TestDefinition[],\n registryUrl: string,\n token: string,\n options: { timeout: number; concurrency: number; testFilter?: string[] }\n): Promise<{ results: SuiteResult[]; exitCode: number }> {\n debug('running %d adapters with %d tests', adapters.length, tests.length);\n const results: SuiteResult[] = [];\n\n // Run adapters sequentially (each suite is already complex enough)\n for (const adapter of adapters) {\n const result = await runSuite(adapter, tests, registryUrl, token, options);\n results.push(result);\n }\n\n reportSummary(results);\n\n const totalFailed = results.reduce((sum, r) => sum + r.failed, 0);\n return { results, exitCode: totalFailed > 0 ? 1 : 0 };\n}\n"],"mappings":";;;;;;AAoBA,IAAM,WAAA,GAAQ,MAAA,QAAA,CAAW,0BAA0B;AAEnD,SAAS,QAAQ,aAA6B;CAC5C,IAAI;EACF,MAAM,QAAM,IAAI,IAAA,IAAI,WAAW;EAC/B,OAAO,SAAS,MAAI,MAAM,EAAE,MAAM,MAAI,aAAa,WAAW,MAAM;CACtE,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,cACb,SACA,MACA,aACA,OACA,SACgD;CAChD,MAAM,OAAO,QAAQ,WAAW;CAChC,IAAI,cAAc;CAClB,MAAM,aAA8B,CAAC;CACrC,MAAM,MAAmB;EACvB;EACA;EACA;EACA,MAAM,QAAQ,KAAK,KAAK,OAAO;EAC/B;EACA,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;EACzE,MAAM,QAAQ,OAAe,IAAwC;GACnE,cAAc;GACd,MAAM,WAAW,KAAK,IAAI;GAC1B,IAAI;IACF,MAAM,GAAG;IACT,MAAM,WAAW,KAAK,IAAI,IAAI;IAC9B,WAAW,KAAK;KAAE;KAAO,QAAQ;KAAM;IAAS,CAAC;IACjD,iBAAA,oBAAoB,OAAO,MAAM,QAAQ;GAC3C,SAAS,KAAK;IACZ,MAAM,WAAW,KAAK,IAAI,IAAI;IAC9B,MAAM,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;IAC7D,WAAW,KAAK;KAAE;KAAO,QAAQ;KAAO;KAAU;IAAM,CAAC;IACzD,iBAAA,oBAAoB,OAAO,OAAO,QAAQ;IAC1C,MAAM;GACR;EACF;CACF;CAEA,MAAM,QAAQ,KAAK,IAAI;CAEvB,OAAO,IAAI,SAAgD,YAAY;EACrE,MAAM,QAAQ,iBAAiB;GAC7B,QAAQ;IACN,MAAM,KAAK;IACX,QAAQ;IACR,UAAU,KAAK,IAAI,IAAI;IACvB,OAAO,wBAAwB,QAAQ;IACvC;IACA,UAAU,WAAW,SAAS,IAAI,aAAa,KAAA;GACjD,CAAC;EACH,GAAG,OAAO;EAEV,KACG,IAAI,GAAG,CAAC,CACR,WAAW;GACV,aAAa,KAAK;GAClB,QAAQ;IACN,MAAM,KAAK;IACX,QAAQ;IACR,UAAU,KAAK,IAAI,IAAI;IACvB;IACA,UAAU,WAAW,SAAS,IAAI,aAAa,KAAA;GACjD,CAAC;EACH,CAAC,CAAC,CACD,OAAO,QAAQ;GACd,aAAa,KAAK;GAClB,QAAQ;IACN,MAAM,KAAK;IACX,QAAQ;IACR,UAAU,KAAK,IAAI,IAAI;IACvB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;IACtD;IACA,UAAU,WAAW,SAAS,IAAI,aAAa,KAAA;GACjD,CAAC;EACH,CAAC;CACL,CAAC;AACH;AAEA,eAAsB,SACpB,SACA,OACA,aACA,OACA,SACsB;CACtB,MAAM,aAAa,KAAK,IAAI;CAC5B,MAAM,UAAwB,CAAC;CAC/B,IAAI,UAAU;CAEd,iBAAA,iBAAiB,QAAQ,IAAI;CAE7B,KAAK,MAAM,QAAQ,OAAO;EAExB,IAAI,QAAQ,cAAc,QAAQ,WAAW,SAAS,GAChD;OAAA,CAAC,QAAQ,WAAW,SAAS,KAAK,IAAI,GACxC;EAAA;EAKJ,IAAI,KAAK,UACa;OAAA,KAAK,SAAS,QAAQ,QAAQ,CAAC,QAAQ,SAAS,IAAI,GAAG,CACvE,CAAA,CAAY,SAAS,GAAG;IAC1B,iBAAA,cAAc,KAAK,IAAI;IACvB;IACA;GACF;;EAIF,IAAI,KAAK,aAAa,CAAC,KAAK,UAAU,OAAO,GAAG;GAC9C,iBAAA,cAAc,KAAK,IAAI;GACvB;GACA;EACF;EAEA,iBAAA,gBAAgB,QAAQ,MAAM,KAAK,IAAI;EAEvC,MAAM,SAAS,MAAM,cAAc,SAAS,MAAM,aAAa,OAD/C,KAAK,IAAI,QAAQ,SAAS,KAAK,WAAW,CACY,CAAO;EAC7E,QAAQ,KAAK,MAAM;EACnB,iBAAA,iBAAiB,QAAQ,OAAO,WAAW;CAC7C;CAEA,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C,MAAM,SAAS,QAAQ,QAAQ,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;CAEhD,OAAO;EACL,SAAS,QAAQ;EACjB,OAAO;EACP;EACA;EACA;EACA,UAAU,KAAK,IAAI,IAAI;CACzB;AACF;AAEA,eAAsB,OACpB,UACA,OACA,aACA,OACA,SACuD;CACvD,QAAM,qCAAqC,SAAS,QAAQ,MAAM,MAAM;CACxE,MAAM,UAAyB,CAAC;CAGhC,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,SAAS,MAAM,SAAS,SAAS,OAAO,aAAa,OAAO,OAAO;EACzE,QAAQ,KAAK,MAAM;CACrB;CAEA,iBAAA,cAAc,OAAO;CAGrB,OAAO;EAAE;EAAS,UADE,QAAQ,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,CACnC,IAAc,IAAI,IAAI;CAAE;AACtD"}
1
+ {"version":3,"file":"runner.cjs","names":[],"sources":["../../src/runner.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { URL } from 'url';\n\nimport {\n reportSkipped,\n reportSubTestResult,\n reportSuiteStart,\n reportSummary,\n reportTestResult,\n reportTestStart,\n} from './reporter';\nimport {\n PackageManagerAdapter,\n SubTestResult,\n SuiteResult,\n TestContext,\n TestDefinition,\n TestResult,\n} from './types';\n\nconst debug = buildDebug('verdaccio:e2e-cli:runner');\n\nfunction getPort(registryUrl: string): number {\n try {\n const url = new URL(registryUrl);\n return parseInt(url.port, 10) || (url.protocol === 'https:' ? 443 : 80);\n } catch {\n return 4873;\n }\n}\n\nasync function runSingleTest(\n adapter: PackageManagerAdapter,\n test: TestDefinition,\n registryUrl: string,\n token: string,\n timeout: number\n): Promise<TestResult & { hasSubTests: boolean }> {\n const port = getPort(registryUrl);\n let hasSubTests = false;\n const subResults: SubTestResult[] = [];\n const ctx: TestContext = {\n registryUrl,\n token,\n port,\n exec: adapter.exec.bind(adapter),\n adapter,\n runId: `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,\n async subTest(label: string, fn: () => Promise<void>): Promise<void> {\n hasSubTests = true;\n const subStart = Date.now();\n try {\n await fn();\n const duration = Date.now() - subStart;\n subResults.push({ label, passed: true, duration });\n reportSubTestResult(label, true, duration);\n } catch (err) {\n const duration = Date.now() - subStart;\n const error = err instanceof Error ? err.message : String(err);\n subResults.push({ label, passed: false, duration, error });\n reportSubTestResult(label, false, duration);\n throw err;\n }\n },\n };\n\n const start = Date.now();\n\n return new Promise<TestResult & { hasSubTests: boolean }>((resolve) => {\n const timer = setTimeout(() => {\n resolve({\n name: test.name,\n passed: false,\n duration: Date.now() - start,\n error: `Test timed out after ${timeout}ms`,\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n }, timeout);\n\n test\n .run(ctx)\n .then(() => {\n clearTimeout(timer);\n resolve({\n name: test.name,\n passed: true,\n duration: Date.now() - start,\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n })\n .catch((err) => {\n clearTimeout(timer);\n resolve({\n name: test.name,\n passed: false,\n duration: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n });\n });\n}\n\nexport async function runSuite(\n adapter: PackageManagerAdapter,\n tests: TestDefinition[],\n registryUrl: string,\n token: string,\n options: { timeout: number; testFilter?: string[]; skipTests?: string[] }\n): Promise<SuiteResult> {\n const suiteStart = Date.now();\n const results: TestResult[] = [];\n let skipped = 0;\n\n reportSuiteStart(adapter.name);\n\n for (const test of tests) {\n // Filter by test name if specified\n if (options.testFilter && options.testFilter.length > 0) {\n if (!options.testFilter.includes(test.name)) {\n continue;\n }\n }\n\n // Skip-listed tests still show up as SKIP so the gap stays visible\n if (options.skipTests && options.skipTests.includes(test.name)) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n\n // Check if adapter supports required commands\n if (test.requires) {\n const unsupported = test.requires.filter((cmd) => !adapter.supports.has(cmd));\n if (unsupported.length > 0) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n }\n\n // Check adapter/version gate (e.g. pnpm-only tests)\n if (test.appliesTo && !test.appliesTo(adapter)) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n\n reportTestStart(adapter.name, test.name);\n const timeout = Math.max(options.timeout, test.timeout ?? 0);\n const result = await runSingleTest(adapter, test, registryUrl, token, timeout);\n results.push(result);\n reportTestResult(result, result.hasSubTests);\n }\n\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n\n return {\n adapter: adapter.name,\n tests: results,\n passed,\n failed,\n skipped,\n duration: Date.now() - suiteStart,\n };\n}\n\nexport async function runAll(\n adapters: PackageManagerAdapter[],\n tests: TestDefinition[],\n registryUrl: string,\n token: string,\n options: { timeout: number; concurrency: number; testFilter?: string[]; skipTests?: string[] }\n): Promise<{ results: SuiteResult[]; exitCode: number }> {\n debug('running %d adapters with %d tests', adapters.length, tests.length);\n const results: SuiteResult[] = [];\n\n // Run adapters sequentially (each suite is already complex enough)\n for (const adapter of adapters) {\n const result = await runSuite(adapter, tests, registryUrl, token, options);\n results.push(result);\n }\n\n reportSummary(results);\n\n const totalFailed = results.reduce((sum, r) => sum + r.failed, 0);\n return { results, exitCode: totalFailed > 0 ? 1 : 0 };\n}\n"],"mappings":"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"}
@@ -14,10 +14,15 @@ assert = require_runtime.__toESM(assert, 1);
14
14
  * threshold — but packages matching the exclude globs bypass the check.
15
15
  *
16
16
  * minimumReleaseAge: 10080
17
+ * minimumReleaseAgeStrict: true
17
18
  * minimumReleaseAgeExclude:
18
19
  * - '@verdaccio/*'
19
20
  * - 'verdaccio-*'
20
21
  *
22
+ * minimumReleaseAgeStrict is required on pnpm 12: without it, pnpm 12
23
+ * auto-adds new direct dependencies to minimumReleaseAgeExclude instead of
24
+ * failing. pnpm 11 enforces the cooldown unconditionally and ignores the key.
25
+ *
21
26
  * The scenario publishes brand-new (age ~0) packages and asserts:
22
27
  * - an excluded scoped package (@verdaccio/*) installs despite the cooldown
23
28
  * - an excluded unscoped package (verdaccio-*) installs despite the cooldown
@@ -31,6 +36,7 @@ assert = require_runtime.__toESM(assert, 1);
31
36
  var MINIMUM_RELEASE_AGE = 10080;
32
37
  var PNPM_WORKSPACE_YAML = [
33
38
  `minimumReleaseAge: ${MINIMUM_RELEASE_AGE}`,
39
+ "minimumReleaseAgeStrict: true",
34
40
  "minimumReleaseAgeExclude:",
35
41
  ...["@verdaccio/*", "verdaccio-*"].map((glob) => ` - '${glob}'`),
36
42
  ""
@@ -89,7 +95,7 @@ async function testMinimumReleaseAge(ctx) {
89
95
  const tempFolder = await prepareCooldownConsumer(ctx, `e2e-consumer-blocked-${id}`, { [blocked]: "1.0.0" });
90
96
  let errorMessage = "";
91
97
  try {
92
- await ctx.adapter.exec({ cwd: tempFolder }, "install", "--frozen-lockfile=false", ...ctx.adapter.registryArg(ctx.registryUrl));
98
+ await ctx.adapter.exec({ cwd: tempFolder }, "install", "--no-frozen-lockfile", ...ctx.adapter.registryArg(ctx.registryUrl));
93
99
  } catch (err) {
94
100
  errorMessage = err instanceof Error ? err.message : String(err);
95
101
  require_process.trace("install correctly rejected by cooldown: %s", errorMessage.split("\n")[0]);
@@ -1 +1 @@
1
- {"version":3,"file":"minimum-release-age.cjs","names":[],"sources":["../../../src/scenarios/minimum-release-age.ts"],"sourcesContent":["import assert from 'assert';\nimport { writeFile } from 'fs/promises';\nimport { join } from 'path';\n\nimport { PackageManagerAdapter, TestContext, TestDefinition } from '../types';\nimport { trace } from '../utils/process';\n\n/**\n * Scenario: minimum-release-age (pnpm 11.1+ only)\n *\n * Exercises pnpm's `minimumReleaseAge` cooldown together with\n * `minimumReleaseAgeExclude`. With a 7-day (10080 minute) cooldown, freshly\n * published versions are normally not installable until they age past the\n * threshold — but packages matching the exclude globs bypass the check.\n *\n * minimumReleaseAge: 10080\n * minimumReleaseAgeExclude:\n * - '@verdaccio/*'\n * - 'verdaccio-*'\n *\n * The scenario publishes brand-new (age ~0) packages and asserts:\n * - an excluded scoped package (@verdaccio/*) installs despite the cooldown\n * - an excluded unscoped package (verdaccio-*) installs despite the cooldown\n * - a non-excluded package is blocked by the cooldown (proving it is active)\n *\n * pnpm reads these settings from `pnpm-workspace.yaml`, mirroring the\n * configuration used by the repo root. The cooldown is only enforced from\n * pnpm 11.1.0 onward (11.0.x silently ignores it), so the scenario is gated to\n * that version range — see {@link isPnpm11Plus}.\n */\n\nconst MINIMUM_RELEASE_AGE = 10080; // 7 days, in minutes\nconst MINIMUM_RELEASE_AGE_EXCLUDE = ['@verdaccio/*', 'verdaccio-*'];\n\nconst PNPM_WORKSPACE_YAML = [\n `minimumReleaseAge: ${MINIMUM_RELEASE_AGE}`,\n 'minimumReleaseAgeExclude:',\n ...MINIMUM_RELEASE_AGE_EXCLUDE.map((glob) => ` - '${glob}'`),\n '',\n].join('\\n');\n\n/**\n * True only for pnpm versions that actually enforce minimumReleaseAge.\n *\n * The cooldown is honored in pnpm 11.1.0+, but it is silently ignored in\n * 11.0.0 / 11.0.1 (and the 11.0.0 release candidates) — those versions install\n * brand-new packages regardless of the configured cooldown. We scope this\n * scenario to pnpm 11.1.0 and newer so it doesn't false-fail on that regression.\n */\nexport function isPnpm11Plus(adapter: PackageManagerAdapter): boolean {\n if (adapter.type !== 'pnpm') {\n return false;\n }\n const match = adapter.name.match(/pnpm@(\\d+)\\.(\\d+)/);\n if (!match) {\n return false;\n }\n const major = parseInt(match[1], 10);\n const minor = parseInt(match[2], 10);\n return major > 11 || (major === 11 && minor >= 1);\n}\n\nasync function publishPackage(ctx: TestContext, pkgName: string, version: string): Promise<void> {\n trace('publishing %s@%s', pkgName, version);\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n version,\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n trace('published %s@%s', pkgName, version);\n}\n\n/**\n * Build a consumer project with the minimumReleaseAge cooldown configured in\n * pnpm-workspace.yaml, depending on the given packages.\n */\nasync function prepareCooldownConsumer(\n ctx: TestContext,\n consumerName: string,\n dependencies: Record<string, string>\n): Promise<string> {\n const { tempFolder } = await ctx.adapter.prepareProject(\n consumerName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n dependencies\n );\n await writeFile(join(tempFolder, 'pnpm-workspace.yaml'), PNPM_WORKSPACE_YAML);\n trace('prepared cooldown consumer %s at %s', consumerName, tempFolder);\n return tempFolder;\n}\n\nasync function testMinimumReleaseAge(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n\n const excludedScoped = `@verdaccio/release-age-${id}`;\n const excludedUnscoped = `verdaccio-release-age-${id}`;\n const blocked = `e2e-release-age-blocked-${id}`;\n\n // --- Phase 1: Publish brand-new (age ~0) packages ---\n await ctx.subTest('publish fresh packages', async () => {\n await publishPackage(ctx, excludedScoped, '1.0.0');\n await publishPackage(ctx, excludedUnscoped, '1.0.0');\n await publishPackage(ctx, blocked, '1.0.0');\n });\n\n // --- Phase 2: Excluded packages install despite the cooldown ---\n await ctx.subTest('excluded packages bypass the cooldown', async () => {\n const tempFolder = await prepareCooldownConsumer(ctx, `@verdaccio/consumer-excluded-${id}`, {\n [excludedScoped]: '1.0.0',\n [excludedUnscoped]: '1.0.0',\n });\n\n // Should succeed: both deps match the exclude globs.\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n trace('excluded packages installed successfully under cooldown');\n });\n\n // --- Phase 3: Non-excluded fresh package is blocked by the cooldown ---\n await ctx.subTest('non-excluded fresh package is blocked', async () => {\n const tempFolder = await prepareCooldownConsumer(ctx, `e2e-consumer-blocked-${id}`, {\n [blocked]: '1.0.0',\n });\n\n let errorMessage = '';\n try {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n '--frozen-lockfile=false',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n } catch (err) {\n errorMessage = err instanceof Error ? err.message : String(err);\n trace('install correctly rejected by cooldown: %s', errorMessage.split('\\n')[0]);\n }\n\n assert.ok(\n errorMessage,\n `Expected install of fresh non-excluded package \"${blocked}\" to be blocked by ` +\n `minimumReleaseAge (${MINIMUM_RELEASE_AGE} min), but it succeeded`\n );\n // pnpm reports ERR_PNPM_NO_MATURE_MATCHING_VERSION / \"minimumReleaseAge\" when a\n // version is too new. Assert the failure is the cooldown, not an unrelated error.\n assert.ok(\n /minimumReleaseAge|NO_MATURE_MATCHING_VERSION/i.test(errorMessage),\n `Install of \"${blocked}\" failed for an unexpected reason (expected a minimumReleaseAge ` +\n `cooldown error):\\n${errorMessage}`\n );\n });\n}\n\nexport const minimumReleaseAgeScenario: TestDefinition = {\n name: 'scenario:minimum-release-age',\n requires: ['publish', 'install'],\n appliesTo: isPnpm11Plus,\n run: testMinimumReleaseAge,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAM,sBAAsB;AAG5B,IAAM,sBAAsB;CAC1B,sBAAsB;CACtB;CACA,GAAG,CALgC,gBAAgB,aAKhD,CAAA,CAA4B,KAAK,SAAS,QAAQ,KAAK,EAAE;CAC5D;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;;;;AAUX,SAAgB,aAAa,SAAyC;CACpE,IAAI,QAAQ,SAAS,QACnB,OAAO;CAET,MAAM,QAAQ,QAAQ,KAAK,MAAM,mBAAmB;CACpD,IAAI,CAAC,OACH,OAAO;CAET,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,OAAO,QAAQ,MAAO,UAAU,MAAM,SAAS;AACjD;AAEA,eAAe,eAAe,KAAkB,SAAiB,SAAgC;CAC/F,gBAAA,MAAM,oBAAoB,SAAS,OAAO;CAC1C,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,gBAAA,MAAM,mBAAmB,SAAS,OAAO;AAC3C;;;;;AAMA,eAAe,wBACb,KACA,cACA,cACiB;CACjB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,cACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,YACF;CACA,OAAA,GAAM,YAAA,UAAA,EAAA,GAAU,KAAA,KAAA,CAAK,YAAY,qBAAqB,GAAG,mBAAmB;CAC5E,gBAAA,MAAM,uCAAuC,cAAc,UAAU;CACrE,OAAO;AACT;AAEA,eAAe,sBAAsB,KAAiC;CACpE,MAAM,KAAK,IAAI;CAEf,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,mBAAmB,yBAAyB;CAClD,MAAM,UAAU,2BAA2B;CAG3C,MAAM,IAAI,QAAQ,0BAA0B,YAAY;EACtD,MAAM,eAAe,KAAK,gBAAgB,OAAO;EACjD,MAAM,eAAe,KAAK,kBAAkB,OAAO;EACnD,MAAM,eAAe,KAAK,SAAS,OAAO;CAC5C,CAAC;CAGD,MAAM,IAAI,QAAQ,yCAAyC,YAAY;EACrE,MAAM,aAAa,MAAM,wBAAwB,KAAK,gCAAgC,MAAM;IACzF,iBAAiB;IACjB,mBAAmB;EACtB,CAAC;EAGD,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAEA,gBAAA,MAAM,yDAAyD;CACjE,CAAC;CAGD,MAAM,IAAI,QAAQ,yCAAyC,YAAY;EACrE,MAAM,aAAa,MAAM,wBAAwB,KAAK,wBAAwB,MAAM,GACjF,UAAU,QACb,CAAC;EAED,IAAI,eAAe;EACnB,IAAI;GACF,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,2BACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EACF,SAAS,KAAK;GACZ,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC9D,gBAAA,MAAM,8CAA8C,aAAa,MAAM,IAAI,CAAC,CAAC,EAAE;EACjF;EAEA,OAAA,QAAO,GACL,cACA,mDAAmD,QAAQ,wCACnC,oBAAoB,wBAC9C;EAGA,OAAA,QAAO,GACL,gDAAgD,KAAK,YAAY,GACjE,eAAe,QAAQ,oFACA,cACzB;CACF,CAAC;AACH;AAEA,IAAa,4BAA4C;CACvD,MAAM;CACN,UAAU,CAAC,WAAW,SAAS;CAC/B,WAAW;CACX,KAAK;AACP"}
1
+ {"version":3,"file":"minimum-release-age.cjs","names":[],"sources":["../../../src/scenarios/minimum-release-age.ts"],"sourcesContent":["import assert from 'assert';\nimport { writeFile } from 'fs/promises';\nimport { join } from 'path';\n\nimport { PackageManagerAdapter, TestContext, TestDefinition } from '../types';\nimport { trace } from '../utils/process';\n\n/**\n * Scenario: minimum-release-age (pnpm 11.1+ only)\n *\n * Exercises pnpm's `minimumReleaseAge` cooldown together with\n * `minimumReleaseAgeExclude`. With a 7-day (10080 minute) cooldown, freshly\n * published versions are normally not installable until they age past the\n * threshold — but packages matching the exclude globs bypass the check.\n *\n * minimumReleaseAge: 10080\n * minimumReleaseAgeStrict: true\n * minimumReleaseAgeExclude:\n * - '@verdaccio/*'\n * - 'verdaccio-*'\n *\n * minimumReleaseAgeStrict is required on pnpm 12: without it, pnpm 12\n * auto-adds new direct dependencies to minimumReleaseAgeExclude instead of\n * failing. pnpm 11 enforces the cooldown unconditionally and ignores the key.\n *\n * The scenario publishes brand-new (age ~0) packages and asserts:\n * - an excluded scoped package (@verdaccio/*) installs despite the cooldown\n * - an excluded unscoped package (verdaccio-*) installs despite the cooldown\n * - a non-excluded package is blocked by the cooldown (proving it is active)\n *\n * pnpm reads these settings from `pnpm-workspace.yaml`, mirroring the\n * configuration used by the repo root. The cooldown is only enforced from\n * pnpm 11.1.0 onward (11.0.x silently ignores it), so the scenario is gated to\n * that version range — see {@link isPnpm11Plus}.\n */\n\nconst MINIMUM_RELEASE_AGE = 10080; // 7 days, in minutes\nconst MINIMUM_RELEASE_AGE_EXCLUDE = ['@verdaccio/*', 'verdaccio-*'];\n\nconst PNPM_WORKSPACE_YAML = [\n `minimumReleaseAge: ${MINIMUM_RELEASE_AGE}`,\n // pnpm 12 no longer blocks new direct dependencies by default: it auto-adds\n // them to minimumReleaseAgeExclude in pnpm-workspace.yaml and proceeds.\n // minimumReleaseAgeStrict restores the blocking behavior this scenario\n // asserts (ERR_PNPM_NO_MATURE_MATCHING_VERSION); pnpm 11 ignores the key.\n 'minimumReleaseAgeStrict: true',\n 'minimumReleaseAgeExclude:',\n ...MINIMUM_RELEASE_AGE_EXCLUDE.map((glob) => ` - '${glob}'`),\n '',\n].join('\\n');\n\n/**\n * True only for pnpm versions that actually enforce minimumReleaseAge.\n *\n * The cooldown is honored in pnpm 11.1.0+, but it is silently ignored in\n * 11.0.0 / 11.0.1 (and the 11.0.0 release candidates) — those versions install\n * brand-new packages regardless of the configured cooldown. We scope this\n * scenario to pnpm 11.1.0 and newer so it doesn't false-fail on that regression.\n */\nexport function isPnpm11Plus(adapter: PackageManagerAdapter): boolean {\n if (adapter.type !== 'pnpm') {\n return false;\n }\n const match = adapter.name.match(/pnpm@(\\d+)\\.(\\d+)/);\n if (!match) {\n return false;\n }\n const major = parseInt(match[1], 10);\n const minor = parseInt(match[2], 10);\n return major > 11 || (major === 11 && minor >= 1);\n}\n\nasync function publishPackage(ctx: TestContext, pkgName: string, version: string): Promise<void> {\n trace('publishing %s@%s', pkgName, version);\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n version,\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n trace('published %s@%s', pkgName, version);\n}\n\n/**\n * Build a consumer project with the minimumReleaseAge cooldown configured in\n * pnpm-workspace.yaml, depending on the given packages.\n */\nasync function prepareCooldownConsumer(\n ctx: TestContext,\n consumerName: string,\n dependencies: Record<string, string>\n): Promise<string> {\n const { tempFolder } = await ctx.adapter.prepareProject(\n consumerName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n dependencies\n );\n await writeFile(join(tempFolder, 'pnpm-workspace.yaml'), PNPM_WORKSPACE_YAML);\n trace('prepared cooldown consumer %s at %s', consumerName, tempFolder);\n return tempFolder;\n}\n\nasync function testMinimumReleaseAge(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n\n const excludedScoped = `@verdaccio/release-age-${id}`;\n const excludedUnscoped = `verdaccio-release-age-${id}`;\n const blocked = `e2e-release-age-blocked-${id}`;\n\n // --- Phase 1: Publish brand-new (age ~0) packages ---\n await ctx.subTest('publish fresh packages', async () => {\n await publishPackage(ctx, excludedScoped, '1.0.0');\n await publishPackage(ctx, excludedUnscoped, '1.0.0');\n await publishPackage(ctx, blocked, '1.0.0');\n });\n\n // --- Phase 2: Excluded packages install despite the cooldown ---\n await ctx.subTest('excluded packages bypass the cooldown', async () => {\n const tempFolder = await prepareCooldownConsumer(ctx, `@verdaccio/consumer-excluded-${id}`, {\n [excludedScoped]: '1.0.0',\n [excludedUnscoped]: '1.0.0',\n });\n\n // Should succeed: both deps match the exclude globs.\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n trace('excluded packages installed successfully under cooldown');\n });\n\n // --- Phase 3: Non-excluded fresh package is blocked by the cooldown ---\n await ctx.subTest('non-excluded fresh package is blocked', async () => {\n const tempFolder = await prepareCooldownConsumer(ctx, `e2e-consumer-blocked-${id}`, {\n [blocked]: '1.0.0',\n });\n\n let errorMessage = '';\n try {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n // pnpm 12's parser rejects the `--flag=false` form; --no-frozen-lockfile\n // is accepted by pnpm 10, 11 and 12 alike\n '--no-frozen-lockfile',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n } catch (err) {\n errorMessage = err instanceof Error ? err.message : String(err);\n trace('install correctly rejected by cooldown: %s', errorMessage.split('\\n')[0]);\n }\n\n assert.ok(\n errorMessage,\n `Expected install of fresh non-excluded package \"${blocked}\" to be blocked by ` +\n `minimumReleaseAge (${MINIMUM_RELEASE_AGE} min), but it succeeded`\n );\n // pnpm reports ERR_PNPM_NO_MATURE_MATCHING_VERSION / \"minimumReleaseAge\" when a\n // version is too new. Assert the failure is the cooldown, not an unrelated error.\n assert.ok(\n /minimumReleaseAge|NO_MATURE_MATCHING_VERSION/i.test(errorMessage),\n `Install of \"${blocked}\" failed for an unexpected reason (expected a minimumReleaseAge ` +\n `cooldown error):\\n${errorMessage}`\n );\n });\n}\n\nexport const minimumReleaseAgeScenario: TestDefinition = {\n name: 'scenario:minimum-release-age',\n requires: ['publish', 'install'],\n appliesTo: isPnpm11Plus,\n run: testMinimumReleaseAge,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,IAAM,sBAAsB;AAG5B,IAAM,sBAAsB;CAC1B,sBAAsB;CAKtB;CACA;CACA,GAAG,CAVgC,gBAAgB,aAUhD,CAAA,CAA4B,KAAK,SAAS,QAAQ,KAAK,EAAE;CAC5D;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;;;;AAUX,SAAgB,aAAa,SAAyC;CACpE,IAAI,QAAQ,SAAS,QACnB,OAAO;CAET,MAAM,QAAQ,QAAQ,KAAK,MAAM,mBAAmB;CACpD,IAAI,CAAC,OACH,OAAO;CAET,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,OAAO,QAAQ,MAAO,UAAU,MAAM,SAAS;AACjD;AAEA,eAAe,eAAe,KAAkB,SAAiB,SAAgC;CAC/F,gBAAA,MAAM,oBAAoB,SAAS,OAAO;CAC1C,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,gBAAA,MAAM,mBAAmB,SAAS,OAAO;AAC3C;;;;;AAMA,eAAe,wBACb,KACA,cACA,cACiB;CACjB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,cACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,YACF;CACA,OAAA,GAAM,YAAA,UAAA,EAAA,GAAU,KAAA,KAAA,CAAK,YAAY,qBAAqB,GAAG,mBAAmB;CAC5E,gBAAA,MAAM,uCAAuC,cAAc,UAAU;CACrE,OAAO;AACT;AAEA,eAAe,sBAAsB,KAAiC;CACpE,MAAM,KAAK,IAAI;CAEf,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,mBAAmB,yBAAyB;CAClD,MAAM,UAAU,2BAA2B;CAG3C,MAAM,IAAI,QAAQ,0BAA0B,YAAY;EACtD,MAAM,eAAe,KAAK,gBAAgB,OAAO;EACjD,MAAM,eAAe,KAAK,kBAAkB,OAAO;EACnD,MAAM,eAAe,KAAK,SAAS,OAAO;CAC5C,CAAC;CAGD,MAAM,IAAI,QAAQ,yCAAyC,YAAY;EACrE,MAAM,aAAa,MAAM,wBAAwB,KAAK,gCAAgC,MAAM;IACzF,iBAAiB;IACjB,mBAAmB;EACtB,CAAC;EAGD,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAEA,gBAAA,MAAM,yDAAyD;CACjE,CAAC;CAGD,MAAM,IAAI,QAAQ,yCAAyC,YAAY;EACrE,MAAM,aAAa,MAAM,wBAAwB,KAAK,wBAAwB,MAAM,GACjF,UAAU,QACb,CAAC;EAED,IAAI,eAAe;EACnB,IAAI;GACF,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WAGA,wBACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EACF,SAAS,KAAK;GACZ,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC9D,gBAAA,MAAM,8CAA8C,aAAa,MAAM,IAAI,CAAC,CAAC,EAAE;EACjF;EAEA,OAAA,QAAO,GACL,cACA,mDAAmD,QAAQ,wCACnC,oBAAoB,wBAC9C;EAGA,OAAA,QAAO,GACL,gDAAgD,KAAK,YAAY,GACjE,eAAe,QAAQ,oFACA,cACzB;CACF,CAAC;AACH;AAEA,IAAa,4BAA4C;CACvD,MAAM;CACN,UAAU,CAAC,WAAW,SAAS;CAC/B,WAAW;CACX,KAAK;AACP"}
@@ -52,8 +52,43 @@ packages:
52
52
  log: { type: stdout, format: pretty, level: warn }
53
53
  `;
54
54
  }
55
+ /**
56
+ * Recommended pnpr config for the same battery, printed by
57
+ * `verdaccio-e2e --print-config --server pnpr`. pnpr's config is
58
+ * verdaccio-shaped but routes through a `registries:` block instead of a
59
+ * global `packages:` map (which pnpr rejects loudly):
60
+ *
61
+ * - one hosted registry claiming every name (`**`), publish open to
62
+ * authenticated users (pnpr's default) and unpublish loosened to match
63
+ * the verdaccio battery config
64
+ * - no `e2emock` uplink: pnpr registry namespaces only accept exact
65
+ * names, scope wildcards or `**`, so the unscoped dynamic
66
+ * `e2e-uplink-*` names can't be routed to the mock uplink. Run pnpr
67
+ * without --uplink-port and scenario:uplink-failure (plus the uplink
68
+ * search sub-tests) skip, exactly like docker mode.
69
+ * - no max_body_size knob: pnpr's publish body limit is a fixed 100 MB,
70
+ * already above what scenario:tarballs needs
71
+ */
72
+ function buildPnprE2EConfig() {
73
+ return `# Generated by @verdaccio/e2e-cli --print-config --server pnpr
74
+ storage: ./storage
75
+ auth:
76
+ htpasswd:
77
+ file: ./htpasswd
78
+ max_users: 1000
79
+ registries:
80
+ local:
81
+ type: hosted
82
+ access: $all
83
+ packages:
84
+ '**': { unpublish: $authenticated }
85
+ defaultRegistry: local
86
+ log: { type: stdout, format: pretty, level: warn }
87
+ `;
88
+ }
55
89
  //#endregion
56
90
  exports.DEFAULT_UPLINK_PORT = DEFAULT_UPLINK_PORT;
57
91
  exports.buildE2EConfig = buildE2EConfig;
92
+ exports.buildPnprE2EConfig = buildPnprE2EConfig;
58
93
 
59
94
  //# sourceMappingURL=e2e-config.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"e2e-config.cjs","names":[],"sources":["../../../src/utils/e2e-config.ts"],"sourcesContent":["/**\n * Recommended Verdaccio config for running the full e2e-cli battery,\n * printed by `verdaccio-e2e --print-config`. It is what the consuming repos'\n * CI jobs and scripts/run-e2e.sh feed to `verdaccio --config`:\n *\n * - max_body_size raised so scenario:tarballs can publish ~30 MB packages\n * (Verdaccio's default is 10mb)\n * - an `e2emock` uplink + `e2e-uplink-*` package pattern wired to the mock\n * uplink server that scenario:uplink-failure starts on E2E_UPLINK_PORT\n * (short timeout so the \"slow uplink\" case completes quickly; high\n * max_fails so simulated failures don't trip the circuit breaker for\n * later sub-tests)\n * - NO npmjs uplink: the whole suite publishes every package it consumes,\n * so the registry under test never talks to the outside world and the\n * battery runs fully offline (and deterministically in CI)\n *\n * Relative paths (storage/htpasswd) resolve next to the config file, so write\n * it into a disposable directory.\n */\nexport const DEFAULT_UPLINK_PORT = 4874;\n\nexport function buildE2EConfig(uplinkPort: number = DEFAULT_UPLINK_PORT): string {\n return `# Generated by @verdaccio/e2e-cli --print-config\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nweb:\n enable: true\n title: Verdaccio E2E\nmax_body_size: 100mb\nuplinks:\n e2emock:\n url: http://localhost:${uplinkPort}/\n timeout: 3s\n retry: 0\n max_fails: 100\n fail_timeout: 1s\npackages:\n 'e2e-uplink-*':\n access: $all\n proxy: e2emock\n '@*/*':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\n '**':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,IAAa,sBAAsB;AAEnC,SAAgB,eAAe,aAAqB,qBAA6B;CAC/E,OAAO;;;;;;;;;;;;4BAYmB,WAAW;;;;;;;;;;;;;;;;;;;AAmBvC"}
1
+ {"version":3,"file":"e2e-config.cjs","names":[],"sources":["../../../src/utils/e2e-config.ts"],"sourcesContent":["/**\n * Recommended Verdaccio config for running the full e2e-cli battery,\n * printed by `verdaccio-e2e --print-config`. It is what the consuming repos'\n * CI jobs and scripts/run-e2e.sh feed to `verdaccio --config`:\n *\n * - max_body_size raised so scenario:tarballs can publish ~30 MB packages\n * (Verdaccio's default is 10mb)\n * - an `e2emock` uplink + `e2e-uplink-*` package pattern wired to the mock\n * uplink server that scenario:uplink-failure starts on E2E_UPLINK_PORT\n * (short timeout so the \"slow uplink\" case completes quickly; high\n * max_fails so simulated failures don't trip the circuit breaker for\n * later sub-tests)\n * - NO npmjs uplink: the whole suite publishes every package it consumes,\n * so the registry under test never talks to the outside world and the\n * battery runs fully offline (and deterministically in CI)\n *\n * Relative paths (storage/htpasswd) resolve next to the config file, so write\n * it into a disposable directory.\n */\nexport const DEFAULT_UPLINK_PORT = 4874;\n\nexport function buildE2EConfig(uplinkPort: number = DEFAULT_UPLINK_PORT): string {\n return `# Generated by @verdaccio/e2e-cli --print-config\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nweb:\n enable: true\n title: Verdaccio E2E\nmax_body_size: 100mb\nuplinks:\n e2emock:\n url: http://localhost:${uplinkPort}/\n timeout: 3s\n retry: 0\n max_fails: 100\n fail_timeout: 1s\npackages:\n 'e2e-uplink-*':\n access: $all\n proxy: e2emock\n '@*/*':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\n '**':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n\n/**\n * Recommended pnpr config for the same battery, printed by\n * `verdaccio-e2e --print-config --server pnpr`. pnpr's config is\n * verdaccio-shaped but routes through a `registries:` block instead of a\n * global `packages:` map (which pnpr rejects loudly):\n *\n * - one hosted registry claiming every name (`**`), publish open to\n * authenticated users (pnpr's default) and unpublish loosened to match\n * the verdaccio battery config\n * - no `e2emock` uplink: pnpr registry namespaces only accept exact\n * names, scope wildcards or `**`, so the unscoped dynamic\n * `e2e-uplink-*` names can't be routed to the mock uplink. Run pnpr\n * without --uplink-port and scenario:uplink-failure (plus the uplink\n * search sub-tests) skip, exactly like docker mode.\n * - no max_body_size knob: pnpr's publish body limit is a fixed 100 MB,\n * already above what scenario:tarballs needs\n */\nexport function buildPnprE2EConfig(): string {\n return `# Generated by @verdaccio/e2e-cli --print-config --server pnpr\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nregistries:\n local:\n type: hosted\n access: $all\n packages:\n '**': { unpublish: $authenticated }\ndefaultRegistry: local\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,IAAa,sBAAsB;AAEnC,SAAgB,eAAe,aAAqB,qBAA6B;CAC/E,OAAO;;;;;;;;;;;;4BAYmB,WAAW;;;;;;;;;;;;;;;;;;;AAmBvC;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,qBAA6B;CAC3C,OAAO;;;;;;;;;;;;;;;AAeT"}
@@ -44,6 +44,7 @@ function parseArgs(argv) {
44
44
  registry: "",
45
45
  pm: [],
46
46
  test: [],
47
+ skipTest: [],
47
48
  concurrency: 1,
48
49
  timeout: 5e4,
49
50
  verbose: false
@@ -58,6 +59,10 @@ function parseArgs(argv) {
58
59
  else if (arg === "--token") options.token = argv[++i];
59
60
  else if (arg === "--uplink-port") options.uplinkPort = parseInt(argv[++i], 10);
60
61
  else if (arg === "--print-config") options.printConfig = true;
62
+ else if (arg === "--server") options.server = argv[++i];
63
+ else if (arg.startsWith("--server=")) options.server = arg.split("=")[1];
64
+ else if (arg === "--skip-test") options.skipTest.push(argv[++i]);
65
+ else if (arg.startsWith("--skip-test=")) options.skipTest.push(arg.split("=")[1]);
61
66
  else if (arg === "--verbose" || arg === "-v") options.verbose = true;
62
67
  else if (arg === "--help" || arg === "-h") {
63
68
  printHelp();
@@ -114,12 +119,21 @@ function printHelp() {
114
119
  from --print-config)
115
120
  Default: all supported by the PM
116
121
 
122
+ --skip-test <name> Skip a test by name (can be repeated). Unlike
123
+ --test, the rest of the battery still runs and
124
+ the skipped tests are reported as SKIP
125
+
117
126
  --token <token> Auth token (skips user creation)
118
127
  --timeout <ms> Per-test timeout (default: 50000)
119
128
  --uplink-port <port> Port for the mock uplink used by
120
129
  scenario:uplink-failure (default: $E2E_UPLINK_PORT)
121
- --print-config Print the recommended Verdaccio config for the full
130
+ --print-config Print the recommended registry config for the full
122
131
  battery (max_body_size, mock uplink) and exit
132
+ --server <name> Registry server the printed config targets:
133
+ verdaccio (default) or pnpr. pnpr's config has no
134
+ mock uplink (its registry namespaces can't claim
135
+ the unscoped dynamic e2e-uplink-* names), so run
136
+ it without --uplink-port and the uplink tests skip
123
137
  -v, --verbose Enable debug output
124
138
  -h, --help Show this help
125
139
  `);
@@ -127,8 +141,14 @@ function printHelp() {
127
141
  async function main(argv = process.argv) {
128
142
  const options = parseArgs(argv);
129
143
  if (options.printConfig) {
130
- const { buildE2EConfig, DEFAULT_UPLINK_PORT } = await import("./utils/e2e-config.js");
131
- process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));
144
+ const { buildE2EConfig, buildPnprE2EConfig, DEFAULT_UPLINK_PORT } = await import("./utils/e2e-config.js");
145
+ const server = options.server ?? "verdaccio";
146
+ if (server === "pnpr") process.stdout.write(buildPnprE2EConfig());
147
+ else if (server === "verdaccio") process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));
148
+ else {
149
+ console.error(`Unknown --server: "${server}". Supported: verdaccio, pnpr`);
150
+ process.exit(1);
151
+ }
132
152
  process.exit(0);
133
153
  }
134
154
  if (options.uplinkPort) process.env.E2E_UPLINK_PORT = String(options.uplinkPort);
@@ -161,10 +181,12 @@ async function main(argv = process.argv) {
161
181
  console.log(`Adapters: ${adapters.map((a) => a.name).join(", ")}`);
162
182
  const tests = options.test && options.test.length > 0 ? allTests.filter((t) => options.test.includes(t.name)) : allTests;
163
183
  console.log(`Tests: ${tests.map((t) => t.name).join(", ")}`);
184
+ if (options.skipTest && options.skipTest.length > 0) console.log(`Skipping tests: ${options.skipTest.join(", ")}`);
164
185
  const { exitCode } = await runAll(adapters, tests, options.registry, token, {
165
186
  timeout: options.timeout,
166
187
  concurrency: options.concurrency,
167
- testFilter: options.test
188
+ testFilter: options.test,
189
+ skipTests: options.skipTest
168
190
  });
169
191
  process.exit(exitCode);
170
192
  }
@@ -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 createPnpmAdapter,\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 {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno`\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 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 === '--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 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 --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 Verdaccio config for the full\n battery (max_body_size, mock uplink) and exit\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, DEFAULT_UPLINK_PORT } = await import('./utils/e2e-config');\n process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));\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 // Run\n const { exitCode } = await runAll(adapters, tests, options.registry, token, {\n timeout: options.timeout,\n concurrency: options.concurrency,\n testFilter: options.test,\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 createPnpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":";;;;;;;;;;;;AAcc,WAAW,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,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,iBAAiB,SAAS,OAAO,CAAC;OAC3C,IAAI,SAAS,QAClB,SAAS,KAAK,kBAAkB,SAAS,OAAO,CAAC;OAC5C,IAAI,SAAS,kBAAkB,SAAS,SAC7C,MAAM,IAAI,MACR,8EACF;OACK,IAAI,SAAS,iBAAiB,SAAS,QAC5C,SAAS,KAAK,wBAAwB,SAAS,OAAO,CAAC;OAClD,IAAI,SAAS,OAClB,SAAS,KAAK,iBAAiB,SAAS,OAAO,CAAC;OAC3C,IAAI,SAAS,QAClB,SAAS,KAAK,kBAAkB,SAAS,OAAO,CAAC;OAEjD,MAAM,IAAI,MACR,6BAA6B,KAAK,gDACpC;CAEJ;CAEA,OAAO;AACT;AAEA,SAAS,UAAU,MAA4B;CAC7C,MAAM,UAAsB;EAC1B,UAAU;EACV,IAAI,CAAC;EACL,MAAM,CAAC;EACP,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,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CX;AACH;AAEA,eAAsB,KAAK,OAAiB,QAAQ,MAAqB;CACvE,MAAM,UAAU,UAAU,IAAI;CAE9B,IAAI,QAAQ,aAAa;EACvB,MAAM,EAAE,gBAAgB,wBAAwB,MAAM,OAAO;EAC7D,QAAQ,OAAO,MAAM,eAAe,QAAQ,cAAc,mBAAmB,CAAC;EAC9E,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;CAGA,MAAM,EAAE,qBAAqB,MAAM,OAAO;CAC1C,MAAM,SAAS,MAAM,iBAAiB,SAAS;CAC/C,QAAQ,MAAM,MAAM;CAEpB,IAAI,QAAQ,SAAS;EACnB,MAAM,EAAE,eAAe,MAAM,OAAO;EACpC,WAAW,IAAI;CACjB;CAGA,QAAQ,IAAI,wBAAwB,QAAQ,SAAS,IAAI;CAEzD,IAAI,CAAC,MADe,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,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,SAAS,QAAQ,MAAM,QAAQ,KAAM,SAAS,EAAE,IAAI,CAAC,IACrD;CAEN,QAAQ,IAAI,UAAU,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,GAAG;CAG3D,MAAM,EAAE,aAAa,MAAM,OAAO,UAAU,OAAO,QAAQ,UAAU,OAAO;EAC1E,SAAS,QAAQ;EACjB,aAAa,QAAQ;EACrB,YAAY,QAAQ;CACtB,CAAC;CAED,QAAQ,KAAK,QAAQ;AACvB"}
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 createPnpmAdapter,\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 {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno`\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 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 createPnpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":";;;;;;;;;;;;AAcc,WAAW,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,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,iBAAiB,SAAS,OAAO,CAAC;OAC3C,IAAI,SAAS,QAClB,SAAS,KAAK,kBAAkB,SAAS,OAAO,CAAC;OAC5C,IAAI,SAAS,kBAAkB,SAAS,SAC7C,MAAM,IAAI,MACR,8EACF;OACK,IAAI,SAAS,iBAAiB,SAAS,QAC5C,SAAS,KAAK,wBAAwB,SAAS,OAAO,CAAC;OAClD,IAAI,SAAS,OAClB,SAAS,KAAK,iBAAiB,SAAS,OAAO,CAAC;OAC3C,IAAI,SAAS,QAClB,SAAS,KAAK,kBAAkB,SAAS,OAAO,CAAC;OAEjD,MAAM,IAAI,MACR,6BAA6B,KAAK,gDACpC;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDX;AACH;AAEA,eAAsB,KAAK,OAAiB,QAAQ,MAAqB;CACvE,MAAM,UAAU,UAAU,IAAI;CAE9B,IAAI,QAAQ,aAAa;EACvB,MAAM,EAAE,gBAAgB,oBAAoB,wBAC1C,MAAM,OAAO;EACf,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;CAGA,MAAM,EAAE,qBAAqB,MAAM,OAAO;CAC1C,MAAM,SAAS,MAAM,iBAAiB,SAAS;CAC/C,QAAQ,MAAM,MAAM;CAEpB,IAAI,QAAQ,SAAS;EACnB,MAAM,EAAE,eAAe,MAAM,OAAO;EACpC,WAAW,IAAI;CACjB;CAGA,QAAQ,IAAI,wBAAwB,QAAQ,SAAS,IAAI;CAEzD,IAAI,CAAC,MADe,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,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,SAAS,QAAQ,MAAM,QAAQ,KAAM,SAAS,EAAE,IAAI,CAAC,IACrD;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,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"}
@@ -91,6 +91,11 @@ async function runSuite(adapter, tests, registryUrl, token, options) {
91
91
  if (options.testFilter && options.testFilter.length > 0) {
92
92
  if (!options.testFilter.includes(test.name)) continue;
93
93
  }
94
+ if (options.skipTests && options.skipTests.includes(test.name)) {
95
+ reportSkipped(test.name);
96
+ skipped++;
97
+ continue;
98
+ }
94
99
  if (test.requires) {
95
100
  if (test.requires.filter((cmd) => !adapter.supports.has(cmd)).length > 0) {
96
101
  reportSkipped(test.name);
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","names":[],"sources":["../../src/runner.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { URL } from 'url';\n\nimport {\n reportSkipped,\n reportSubTestResult,\n reportSuiteStart,\n reportSummary,\n reportTestResult,\n reportTestStart,\n} from './reporter';\nimport {\n PackageManagerAdapter,\n SubTestResult,\n SuiteResult,\n TestContext,\n TestDefinition,\n TestResult,\n} from './types';\n\nconst debug = buildDebug('verdaccio:e2e-cli:runner');\n\nfunction getPort(registryUrl: string): number {\n try {\n const url = new URL(registryUrl);\n return parseInt(url.port, 10) || (url.protocol === 'https:' ? 443 : 80);\n } catch {\n return 4873;\n }\n}\n\nasync function runSingleTest(\n adapter: PackageManagerAdapter,\n test: TestDefinition,\n registryUrl: string,\n token: string,\n timeout: number\n): Promise<TestResult & { hasSubTests: boolean }> {\n const port = getPort(registryUrl);\n let hasSubTests = false;\n const subResults: SubTestResult[] = [];\n const ctx: TestContext = {\n registryUrl,\n token,\n port,\n exec: adapter.exec.bind(adapter),\n adapter,\n runId: `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,\n async subTest(label: string, fn: () => Promise<void>): Promise<void> {\n hasSubTests = true;\n const subStart = Date.now();\n try {\n await fn();\n const duration = Date.now() - subStart;\n subResults.push({ label, passed: true, duration });\n reportSubTestResult(label, true, duration);\n } catch (err) {\n const duration = Date.now() - subStart;\n const error = err instanceof Error ? err.message : String(err);\n subResults.push({ label, passed: false, duration, error });\n reportSubTestResult(label, false, duration);\n throw err;\n }\n },\n };\n\n const start = Date.now();\n\n return new Promise<TestResult & { hasSubTests: boolean }>((resolve) => {\n const timer = setTimeout(() => {\n resolve({\n name: test.name,\n passed: false,\n duration: Date.now() - start,\n error: `Test timed out after ${timeout}ms`,\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n }, timeout);\n\n test\n .run(ctx)\n .then(() => {\n clearTimeout(timer);\n resolve({\n name: test.name,\n passed: true,\n duration: Date.now() - start,\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n })\n .catch((err) => {\n clearTimeout(timer);\n resolve({\n name: test.name,\n passed: false,\n duration: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n });\n });\n}\n\nexport async function runSuite(\n adapter: PackageManagerAdapter,\n tests: TestDefinition[],\n registryUrl: string,\n token: string,\n options: { timeout: number; testFilter?: string[] }\n): Promise<SuiteResult> {\n const suiteStart = Date.now();\n const results: TestResult[] = [];\n let skipped = 0;\n\n reportSuiteStart(adapter.name);\n\n for (const test of tests) {\n // Filter by test name if specified\n if (options.testFilter && options.testFilter.length > 0) {\n if (!options.testFilter.includes(test.name)) {\n continue;\n }\n }\n\n // Check if adapter supports required commands\n if (test.requires) {\n const unsupported = test.requires.filter((cmd) => !adapter.supports.has(cmd));\n if (unsupported.length > 0) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n }\n\n // Check adapter/version gate (e.g. pnpm-only tests)\n if (test.appliesTo && !test.appliesTo(adapter)) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n\n reportTestStart(adapter.name, test.name);\n const timeout = Math.max(options.timeout, test.timeout ?? 0);\n const result = await runSingleTest(adapter, test, registryUrl, token, timeout);\n results.push(result);\n reportTestResult(result, result.hasSubTests);\n }\n\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n\n return {\n adapter: adapter.name,\n tests: results,\n passed,\n failed,\n skipped,\n duration: Date.now() - suiteStart,\n };\n}\n\nexport async function runAll(\n adapters: PackageManagerAdapter[],\n tests: TestDefinition[],\n registryUrl: string,\n token: string,\n options: { timeout: number; concurrency: number; testFilter?: string[] }\n): Promise<{ results: SuiteResult[]; exitCode: number }> {\n debug('running %d adapters with %d tests', adapters.length, tests.length);\n const results: SuiteResult[] = [];\n\n // Run adapters sequentially (each suite is already complex enough)\n for (const adapter of adapters) {\n const result = await runSuite(adapter, tests, registryUrl, token, options);\n results.push(result);\n }\n\n reportSummary(results);\n\n const totalFailed = results.reduce((sum, r) => sum + r.failed, 0);\n return { results, exitCode: totalFailed > 0 ? 1 : 0 };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runner.js","names":[],"sources":["../../src/runner.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { URL } from 'url';\n\nimport {\n reportSkipped,\n reportSubTestResult,\n reportSuiteStart,\n reportSummary,\n reportTestResult,\n reportTestStart,\n} from './reporter';\nimport {\n PackageManagerAdapter,\n SubTestResult,\n SuiteResult,\n TestContext,\n TestDefinition,\n TestResult,\n} from './types';\n\nconst debug = buildDebug('verdaccio:e2e-cli:runner');\n\nfunction getPort(registryUrl: string): number {\n try {\n const url = new URL(registryUrl);\n return parseInt(url.port, 10) || (url.protocol === 'https:' ? 443 : 80);\n } catch {\n return 4873;\n }\n}\n\nasync function runSingleTest(\n adapter: PackageManagerAdapter,\n test: TestDefinition,\n registryUrl: string,\n token: string,\n timeout: number\n): Promise<TestResult & { hasSubTests: boolean }> {\n const port = getPort(registryUrl);\n let hasSubTests = false;\n const subResults: SubTestResult[] = [];\n const ctx: TestContext = {\n registryUrl,\n token,\n port,\n exec: adapter.exec.bind(adapter),\n adapter,\n runId: `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,\n async subTest(label: string, fn: () => Promise<void>): Promise<void> {\n hasSubTests = true;\n const subStart = Date.now();\n try {\n await fn();\n const duration = Date.now() - subStart;\n subResults.push({ label, passed: true, duration });\n reportSubTestResult(label, true, duration);\n } catch (err) {\n const duration = Date.now() - subStart;\n const error = err instanceof Error ? err.message : String(err);\n subResults.push({ label, passed: false, duration, error });\n reportSubTestResult(label, false, duration);\n throw err;\n }\n },\n };\n\n const start = Date.now();\n\n return new Promise<TestResult & { hasSubTests: boolean }>((resolve) => {\n const timer = setTimeout(() => {\n resolve({\n name: test.name,\n passed: false,\n duration: Date.now() - start,\n error: `Test timed out after ${timeout}ms`,\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n }, timeout);\n\n test\n .run(ctx)\n .then(() => {\n clearTimeout(timer);\n resolve({\n name: test.name,\n passed: true,\n duration: Date.now() - start,\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n })\n .catch((err) => {\n clearTimeout(timer);\n resolve({\n name: test.name,\n passed: false,\n duration: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n hasSubTests,\n subTests: subResults.length > 0 ? subResults : undefined,\n });\n });\n });\n}\n\nexport async function runSuite(\n adapter: PackageManagerAdapter,\n tests: TestDefinition[],\n registryUrl: string,\n token: string,\n options: { timeout: number; testFilter?: string[]; skipTests?: string[] }\n): Promise<SuiteResult> {\n const suiteStart = Date.now();\n const results: TestResult[] = [];\n let skipped = 0;\n\n reportSuiteStart(adapter.name);\n\n for (const test of tests) {\n // Filter by test name if specified\n if (options.testFilter && options.testFilter.length > 0) {\n if (!options.testFilter.includes(test.name)) {\n continue;\n }\n }\n\n // Skip-listed tests still show up as SKIP so the gap stays visible\n if (options.skipTests && options.skipTests.includes(test.name)) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n\n // Check if adapter supports required commands\n if (test.requires) {\n const unsupported = test.requires.filter((cmd) => !adapter.supports.has(cmd));\n if (unsupported.length > 0) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n }\n\n // Check adapter/version gate (e.g. pnpm-only tests)\n if (test.appliesTo && !test.appliesTo(adapter)) {\n reportSkipped(test.name);\n skipped++;\n continue;\n }\n\n reportTestStart(adapter.name, test.name);\n const timeout = Math.max(options.timeout, test.timeout ?? 0);\n const result = await runSingleTest(adapter, test, registryUrl, token, timeout);\n results.push(result);\n reportTestResult(result, result.hasSubTests);\n }\n\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n\n return {\n adapter: adapter.name,\n tests: results,\n passed,\n failed,\n skipped,\n duration: Date.now() - suiteStart,\n };\n}\n\nexport async function runAll(\n adapters: PackageManagerAdapter[],\n tests: TestDefinition[],\n registryUrl: string,\n token: string,\n options: { timeout: number; concurrency: number; testFilter?: string[]; skipTests?: string[] }\n): Promise<{ results: SuiteResult[]; exitCode: number }> {\n debug('running %d adapters with %d tests', adapters.length, tests.length);\n const results: SuiteResult[] = [];\n\n // Run adapters sequentially (each suite is already complex enough)\n for (const adapter of adapters) {\n const result = await runSuite(adapter, tests, registryUrl, token, options);\n results.push(result);\n }\n\n reportSummary(results);\n\n const totalFailed = results.reduce((sum, r) => sum + r.failed, 0);\n return { results, exitCode: totalFailed > 0 ? 1 : 0 };\n}\n"],"mappings":"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"}
@@ -12,10 +12,15 @@ import assert from "assert";
12
12
  * threshold — but packages matching the exclude globs bypass the check.
13
13
  *
14
14
  * minimumReleaseAge: 10080
15
+ * minimumReleaseAgeStrict: true
15
16
  * minimumReleaseAgeExclude:
16
17
  * - '@verdaccio/*'
17
18
  * - 'verdaccio-*'
18
19
  *
20
+ * minimumReleaseAgeStrict is required on pnpm 12: without it, pnpm 12
21
+ * auto-adds new direct dependencies to minimumReleaseAgeExclude instead of
22
+ * failing. pnpm 11 enforces the cooldown unconditionally and ignores the key.
23
+ *
19
24
  * The scenario publishes brand-new (age ~0) packages and asserts:
20
25
  * - an excluded scoped package (@verdaccio/*) installs despite the cooldown
21
26
  * - an excluded unscoped package (verdaccio-*) installs despite the cooldown
@@ -29,6 +34,7 @@ import assert from "assert";
29
34
  var MINIMUM_RELEASE_AGE = 10080;
30
35
  var PNPM_WORKSPACE_YAML = [
31
36
  `minimumReleaseAge: ${MINIMUM_RELEASE_AGE}`,
37
+ "minimumReleaseAgeStrict: true",
32
38
  "minimumReleaseAgeExclude:",
33
39
  ...["@verdaccio/*", "verdaccio-*"].map((glob) => ` - '${glob}'`),
34
40
  ""
@@ -87,7 +93,7 @@ async function testMinimumReleaseAge(ctx) {
87
93
  const tempFolder = await prepareCooldownConsumer(ctx, `e2e-consumer-blocked-${id}`, { [blocked]: "1.0.0" });
88
94
  let errorMessage = "";
89
95
  try {
90
- await ctx.adapter.exec({ cwd: tempFolder }, "install", "--frozen-lockfile=false", ...ctx.adapter.registryArg(ctx.registryUrl));
96
+ await ctx.adapter.exec({ cwd: tempFolder }, "install", "--no-frozen-lockfile", ...ctx.adapter.registryArg(ctx.registryUrl));
91
97
  } catch (err) {
92
98
  errorMessage = err instanceof Error ? err.message : String(err);
93
99
  trace("install correctly rejected by cooldown: %s", errorMessage.split("\n")[0]);
@@ -1 +1 @@
1
- {"version":3,"file":"minimum-release-age.js","names":[],"sources":["../../../src/scenarios/minimum-release-age.ts"],"sourcesContent":["import assert from 'assert';\nimport { writeFile } from 'fs/promises';\nimport { join } from 'path';\n\nimport { PackageManagerAdapter, TestContext, TestDefinition } from '../types';\nimport { trace } from '../utils/process';\n\n/**\n * Scenario: minimum-release-age (pnpm 11.1+ only)\n *\n * Exercises pnpm's `minimumReleaseAge` cooldown together with\n * `minimumReleaseAgeExclude`. With a 7-day (10080 minute) cooldown, freshly\n * published versions are normally not installable until they age past the\n * threshold — but packages matching the exclude globs bypass the check.\n *\n * minimumReleaseAge: 10080\n * minimumReleaseAgeExclude:\n * - '@verdaccio/*'\n * - 'verdaccio-*'\n *\n * The scenario publishes brand-new (age ~0) packages and asserts:\n * - an excluded scoped package (@verdaccio/*) installs despite the cooldown\n * - an excluded unscoped package (verdaccio-*) installs despite the cooldown\n * - a non-excluded package is blocked by the cooldown (proving it is active)\n *\n * pnpm reads these settings from `pnpm-workspace.yaml`, mirroring the\n * configuration used by the repo root. The cooldown is only enforced from\n * pnpm 11.1.0 onward (11.0.x silently ignores it), so the scenario is gated to\n * that version range — see {@link isPnpm11Plus}.\n */\n\nconst MINIMUM_RELEASE_AGE = 10080; // 7 days, in minutes\nconst MINIMUM_RELEASE_AGE_EXCLUDE = ['@verdaccio/*', 'verdaccio-*'];\n\nconst PNPM_WORKSPACE_YAML = [\n `minimumReleaseAge: ${MINIMUM_RELEASE_AGE}`,\n 'minimumReleaseAgeExclude:',\n ...MINIMUM_RELEASE_AGE_EXCLUDE.map((glob) => ` - '${glob}'`),\n '',\n].join('\\n');\n\n/**\n * True only for pnpm versions that actually enforce minimumReleaseAge.\n *\n * The cooldown is honored in pnpm 11.1.0+, but it is silently ignored in\n * 11.0.0 / 11.0.1 (and the 11.0.0 release candidates) — those versions install\n * brand-new packages regardless of the configured cooldown. We scope this\n * scenario to pnpm 11.1.0 and newer so it doesn't false-fail on that regression.\n */\nexport function isPnpm11Plus(adapter: PackageManagerAdapter): boolean {\n if (adapter.type !== 'pnpm') {\n return false;\n }\n const match = adapter.name.match(/pnpm@(\\d+)\\.(\\d+)/);\n if (!match) {\n return false;\n }\n const major = parseInt(match[1], 10);\n const minor = parseInt(match[2], 10);\n return major > 11 || (major === 11 && minor >= 1);\n}\n\nasync function publishPackage(ctx: TestContext, pkgName: string, version: string): Promise<void> {\n trace('publishing %s@%s', pkgName, version);\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n version,\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n trace('published %s@%s', pkgName, version);\n}\n\n/**\n * Build a consumer project with the minimumReleaseAge cooldown configured in\n * pnpm-workspace.yaml, depending on the given packages.\n */\nasync function prepareCooldownConsumer(\n ctx: TestContext,\n consumerName: string,\n dependencies: Record<string, string>\n): Promise<string> {\n const { tempFolder } = await ctx.adapter.prepareProject(\n consumerName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n dependencies\n );\n await writeFile(join(tempFolder, 'pnpm-workspace.yaml'), PNPM_WORKSPACE_YAML);\n trace('prepared cooldown consumer %s at %s', consumerName, tempFolder);\n return tempFolder;\n}\n\nasync function testMinimumReleaseAge(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n\n const excludedScoped = `@verdaccio/release-age-${id}`;\n const excludedUnscoped = `verdaccio-release-age-${id}`;\n const blocked = `e2e-release-age-blocked-${id}`;\n\n // --- Phase 1: Publish brand-new (age ~0) packages ---\n await ctx.subTest('publish fresh packages', async () => {\n await publishPackage(ctx, excludedScoped, '1.0.0');\n await publishPackage(ctx, excludedUnscoped, '1.0.0');\n await publishPackage(ctx, blocked, '1.0.0');\n });\n\n // --- Phase 2: Excluded packages install despite the cooldown ---\n await ctx.subTest('excluded packages bypass the cooldown', async () => {\n const tempFolder = await prepareCooldownConsumer(ctx, `@verdaccio/consumer-excluded-${id}`, {\n [excludedScoped]: '1.0.0',\n [excludedUnscoped]: '1.0.0',\n });\n\n // Should succeed: both deps match the exclude globs.\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n trace('excluded packages installed successfully under cooldown');\n });\n\n // --- Phase 3: Non-excluded fresh package is blocked by the cooldown ---\n await ctx.subTest('non-excluded fresh package is blocked', async () => {\n const tempFolder = await prepareCooldownConsumer(ctx, `e2e-consumer-blocked-${id}`, {\n [blocked]: '1.0.0',\n });\n\n let errorMessage = '';\n try {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n '--frozen-lockfile=false',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n } catch (err) {\n errorMessage = err instanceof Error ? err.message : String(err);\n trace('install correctly rejected by cooldown: %s', errorMessage.split('\\n')[0]);\n }\n\n assert.ok(\n errorMessage,\n `Expected install of fresh non-excluded package \"${blocked}\" to be blocked by ` +\n `minimumReleaseAge (${MINIMUM_RELEASE_AGE} min), but it succeeded`\n );\n // pnpm reports ERR_PNPM_NO_MATURE_MATCHING_VERSION / \"minimumReleaseAge\" when a\n // version is too new. Assert the failure is the cooldown, not an unrelated error.\n assert.ok(\n /minimumReleaseAge|NO_MATURE_MATCHING_VERSION/i.test(errorMessage),\n `Install of \"${blocked}\" failed for an unexpected reason (expected a minimumReleaseAge ` +\n `cooldown error):\\n${errorMessage}`\n );\n });\n}\n\nexport const minimumReleaseAgeScenario: TestDefinition = {\n name: 'scenario:minimum-release-age',\n requires: ['publish', 'install'],\n appliesTo: isPnpm11Plus,\n run: testMinimumReleaseAge,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAM,sBAAsB;AAG5B,IAAM,sBAAsB;CAC1B,sBAAsB;CACtB;CACA,GAAG,CALgC,gBAAgB,aAKhD,CAAA,CAA4B,KAAK,SAAS,QAAQ,KAAK,EAAE;CAC5D;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;;;;AAUX,SAAgB,aAAa,SAAyC;CACpE,IAAI,QAAQ,SAAS,QACnB,OAAO;CAET,MAAM,QAAQ,QAAQ,KAAK,MAAM,mBAAmB;CACpD,IAAI,CAAC,OACH,OAAO;CAET,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,OAAO,QAAQ,MAAO,UAAU,MAAM,SAAS;AACjD;AAEA,eAAe,eAAe,KAAkB,SAAiB,SAAgC;CAC/F,MAAM,oBAAoB,SAAS,OAAO;CAC1C,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,MAAM,mBAAmB,SAAS,OAAO;AAC3C;;;;;AAMA,eAAe,wBACb,KACA,cACA,cACiB;CACjB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,cACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,YACF;CACA,MAAM,UAAU,KAAK,YAAY,qBAAqB,GAAG,mBAAmB;CAC5E,MAAM,uCAAuC,cAAc,UAAU;CACrE,OAAO;AACT;AAEA,eAAe,sBAAsB,KAAiC;CACpE,MAAM,KAAK,IAAI;CAEf,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,mBAAmB,yBAAyB;CAClD,MAAM,UAAU,2BAA2B;CAG3C,MAAM,IAAI,QAAQ,0BAA0B,YAAY;EACtD,MAAM,eAAe,KAAK,gBAAgB,OAAO;EACjD,MAAM,eAAe,KAAK,kBAAkB,OAAO;EACnD,MAAM,eAAe,KAAK,SAAS,OAAO;CAC5C,CAAC;CAGD,MAAM,IAAI,QAAQ,yCAAyC,YAAY;EACrE,MAAM,aAAa,MAAM,wBAAwB,KAAK,gCAAgC,MAAM;IACzF,iBAAiB;IACjB,mBAAmB;EACtB,CAAC;EAGD,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAEA,MAAM,yDAAyD;CACjE,CAAC;CAGD,MAAM,IAAI,QAAQ,yCAAyC,YAAY;EACrE,MAAM,aAAa,MAAM,wBAAwB,KAAK,wBAAwB,MAAM,GACjF,UAAU,QACb,CAAC;EAED,IAAI,eAAe;EACnB,IAAI;GACF,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,2BACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EACF,SAAS,KAAK;GACZ,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC9D,MAAM,8CAA8C,aAAa,MAAM,IAAI,CAAC,CAAC,EAAE;EACjF;EAEA,OAAO,GACL,cACA,mDAAmD,QAAQ,wCACnC,oBAAoB,wBAC9C;EAGA,OAAO,GACL,gDAAgD,KAAK,YAAY,GACjE,eAAe,QAAQ,oFACA,cACzB;CACF,CAAC;AACH;AAEA,IAAa,4BAA4C;CACvD,MAAM;CACN,UAAU,CAAC,WAAW,SAAS;CAC/B,WAAW;CACX,KAAK;AACP"}
1
+ {"version":3,"file":"minimum-release-age.js","names":[],"sources":["../../../src/scenarios/minimum-release-age.ts"],"sourcesContent":["import assert from 'assert';\nimport { writeFile } from 'fs/promises';\nimport { join } from 'path';\n\nimport { PackageManagerAdapter, TestContext, TestDefinition } from '../types';\nimport { trace } from '../utils/process';\n\n/**\n * Scenario: minimum-release-age (pnpm 11.1+ only)\n *\n * Exercises pnpm's `minimumReleaseAge` cooldown together with\n * `minimumReleaseAgeExclude`. With a 7-day (10080 minute) cooldown, freshly\n * published versions are normally not installable until they age past the\n * threshold — but packages matching the exclude globs bypass the check.\n *\n * minimumReleaseAge: 10080\n * minimumReleaseAgeStrict: true\n * minimumReleaseAgeExclude:\n * - '@verdaccio/*'\n * - 'verdaccio-*'\n *\n * minimumReleaseAgeStrict is required on pnpm 12: without it, pnpm 12\n * auto-adds new direct dependencies to minimumReleaseAgeExclude instead of\n * failing. pnpm 11 enforces the cooldown unconditionally and ignores the key.\n *\n * The scenario publishes brand-new (age ~0) packages and asserts:\n * - an excluded scoped package (@verdaccio/*) installs despite the cooldown\n * - an excluded unscoped package (verdaccio-*) installs despite the cooldown\n * - a non-excluded package is blocked by the cooldown (proving it is active)\n *\n * pnpm reads these settings from `pnpm-workspace.yaml`, mirroring the\n * configuration used by the repo root. The cooldown is only enforced from\n * pnpm 11.1.0 onward (11.0.x silently ignores it), so the scenario is gated to\n * that version range — see {@link isPnpm11Plus}.\n */\n\nconst MINIMUM_RELEASE_AGE = 10080; // 7 days, in minutes\nconst MINIMUM_RELEASE_AGE_EXCLUDE = ['@verdaccio/*', 'verdaccio-*'];\n\nconst PNPM_WORKSPACE_YAML = [\n `minimumReleaseAge: ${MINIMUM_RELEASE_AGE}`,\n // pnpm 12 no longer blocks new direct dependencies by default: it auto-adds\n // them to minimumReleaseAgeExclude in pnpm-workspace.yaml and proceeds.\n // minimumReleaseAgeStrict restores the blocking behavior this scenario\n // asserts (ERR_PNPM_NO_MATURE_MATCHING_VERSION); pnpm 11 ignores the key.\n 'minimumReleaseAgeStrict: true',\n 'minimumReleaseAgeExclude:',\n ...MINIMUM_RELEASE_AGE_EXCLUDE.map((glob) => ` - '${glob}'`),\n '',\n].join('\\n');\n\n/**\n * True only for pnpm versions that actually enforce minimumReleaseAge.\n *\n * The cooldown is honored in pnpm 11.1.0+, but it is silently ignored in\n * 11.0.0 / 11.0.1 (and the 11.0.0 release candidates) — those versions install\n * brand-new packages regardless of the configured cooldown. We scope this\n * scenario to pnpm 11.1.0 and newer so it doesn't false-fail on that regression.\n */\nexport function isPnpm11Plus(adapter: PackageManagerAdapter): boolean {\n if (adapter.type !== 'pnpm') {\n return false;\n }\n const match = adapter.name.match(/pnpm@(\\d+)\\.(\\d+)/);\n if (!match) {\n return false;\n }\n const major = parseInt(match[1], 10);\n const minor = parseInt(match[2], 10);\n return major > 11 || (major === 11 && minor >= 1);\n}\n\nasync function publishPackage(ctx: TestContext, pkgName: string, version: string): Promise<void> {\n trace('publishing %s@%s', pkgName, version);\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n version,\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n trace('published %s@%s', pkgName, version);\n}\n\n/**\n * Build a consumer project with the minimumReleaseAge cooldown configured in\n * pnpm-workspace.yaml, depending on the given packages.\n */\nasync function prepareCooldownConsumer(\n ctx: TestContext,\n consumerName: string,\n dependencies: Record<string, string>\n): Promise<string> {\n const { tempFolder } = await ctx.adapter.prepareProject(\n consumerName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n dependencies\n );\n await writeFile(join(tempFolder, 'pnpm-workspace.yaml'), PNPM_WORKSPACE_YAML);\n trace('prepared cooldown consumer %s at %s', consumerName, tempFolder);\n return tempFolder;\n}\n\nasync function testMinimumReleaseAge(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n\n const excludedScoped = `@verdaccio/release-age-${id}`;\n const excludedUnscoped = `verdaccio-release-age-${id}`;\n const blocked = `e2e-release-age-blocked-${id}`;\n\n // --- Phase 1: Publish brand-new (age ~0) packages ---\n await ctx.subTest('publish fresh packages', async () => {\n await publishPackage(ctx, excludedScoped, '1.0.0');\n await publishPackage(ctx, excludedUnscoped, '1.0.0');\n await publishPackage(ctx, blocked, '1.0.0');\n });\n\n // --- Phase 2: Excluded packages install despite the cooldown ---\n await ctx.subTest('excluded packages bypass the cooldown', async () => {\n const tempFolder = await prepareCooldownConsumer(ctx, `@verdaccio/consumer-excluded-${id}`, {\n [excludedScoped]: '1.0.0',\n [excludedUnscoped]: '1.0.0',\n });\n\n // Should succeed: both deps match the exclude globs.\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n trace('excluded packages installed successfully under cooldown');\n });\n\n // --- Phase 3: Non-excluded fresh package is blocked by the cooldown ---\n await ctx.subTest('non-excluded fresh package is blocked', async () => {\n const tempFolder = await prepareCooldownConsumer(ctx, `e2e-consumer-blocked-${id}`, {\n [blocked]: '1.0.0',\n });\n\n let errorMessage = '';\n try {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n // pnpm 12's parser rejects the `--flag=false` form; --no-frozen-lockfile\n // is accepted by pnpm 10, 11 and 12 alike\n '--no-frozen-lockfile',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n } catch (err) {\n errorMessage = err instanceof Error ? err.message : String(err);\n trace('install correctly rejected by cooldown: %s', errorMessage.split('\\n')[0]);\n }\n\n assert.ok(\n errorMessage,\n `Expected install of fresh non-excluded package \"${blocked}\" to be blocked by ` +\n `minimumReleaseAge (${MINIMUM_RELEASE_AGE} min), but it succeeded`\n );\n // pnpm reports ERR_PNPM_NO_MATURE_MATCHING_VERSION / \"minimumReleaseAge\" when a\n // version is too new. Assert the failure is the cooldown, not an unrelated error.\n assert.ok(\n /minimumReleaseAge|NO_MATURE_MATCHING_VERSION/i.test(errorMessage),\n `Install of \"${blocked}\" failed for an unexpected reason (expected a minimumReleaseAge ` +\n `cooldown error):\\n${errorMessage}`\n );\n });\n}\n\nexport const minimumReleaseAgeScenario: TestDefinition = {\n name: 'scenario:minimum-release-age',\n requires: ['publish', 'install'],\n appliesTo: isPnpm11Plus,\n run: testMinimumReleaseAge,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,IAAM,sBAAsB;AAG5B,IAAM,sBAAsB;CAC1B,sBAAsB;CAKtB;CACA;CACA,GAAG,CAVgC,gBAAgB,aAUhD,CAAA,CAA4B,KAAK,SAAS,QAAQ,KAAK,EAAE;CAC5D;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;;;;AAUX,SAAgB,aAAa,SAAyC;CACpE,IAAI,QAAQ,SAAS,QACnB,OAAO;CAET,MAAM,QAAQ,QAAQ,KAAK,MAAM,mBAAmB;CACpD,IAAI,CAAC,OACH,OAAO;CAET,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,OAAO,QAAQ,MAAO,UAAU,MAAM,SAAS;AACjD;AAEA,eAAe,eAAe,KAAkB,SAAiB,SAAgC;CAC/F,MAAM,oBAAoB,SAAS,OAAO;CAC1C,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,MAAM,mBAAmB,SAAS,OAAO;AAC3C;;;;;AAMA,eAAe,wBACb,KACA,cACA,cACiB;CACjB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,cACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,YACF;CACA,MAAM,UAAU,KAAK,YAAY,qBAAqB,GAAG,mBAAmB;CAC5E,MAAM,uCAAuC,cAAc,UAAU;CACrE,OAAO;AACT;AAEA,eAAe,sBAAsB,KAAiC;CACpE,MAAM,KAAK,IAAI;CAEf,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,mBAAmB,yBAAyB;CAClD,MAAM,UAAU,2BAA2B;CAG3C,MAAM,IAAI,QAAQ,0BAA0B,YAAY;EACtD,MAAM,eAAe,KAAK,gBAAgB,OAAO;EACjD,MAAM,eAAe,KAAK,kBAAkB,OAAO;EACnD,MAAM,eAAe,KAAK,SAAS,OAAO;CAC5C,CAAC;CAGD,MAAM,IAAI,QAAQ,yCAAyC,YAAY;EACrE,MAAM,aAAa,MAAM,wBAAwB,KAAK,gCAAgC,MAAM;IACzF,iBAAiB;IACjB,mBAAmB;EACtB,CAAC;EAGD,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAEA,MAAM,yDAAyD;CACjE,CAAC;CAGD,MAAM,IAAI,QAAQ,yCAAyC,YAAY;EACrE,MAAM,aAAa,MAAM,wBAAwB,KAAK,wBAAwB,MAAM,GACjF,UAAU,QACb,CAAC;EAED,IAAI,eAAe;EACnB,IAAI;GACF,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WAGA,wBACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EACF,SAAS,KAAK;GACZ,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC9D,MAAM,8CAA8C,aAAa,MAAM,IAAI,CAAC,CAAC,EAAE;EACjF;EAEA,OAAO,GACL,cACA,mDAAmD,QAAQ,wCACnC,oBAAoB,wBAC9C;EAGA,OAAO,GACL,gDAAgD,KAAK,YAAY,GACjE,eAAe,QAAQ,oFACA,cACzB;CACF,CAAC;AACH;AAEA,IAAa,4BAA4C;CACvD,MAAM;CACN,UAAU,CAAC,WAAW,SAAS;CAC/B,WAAW;CACX,KAAK;AACP"}
@@ -52,7 +52,41 @@ packages:
52
52
  log: { type: stdout, format: pretty, level: warn }
53
53
  `;
54
54
  }
55
+ /**
56
+ * Recommended pnpr config for the same battery, printed by
57
+ * `verdaccio-e2e --print-config --server pnpr`. pnpr's config is
58
+ * verdaccio-shaped but routes through a `registries:` block instead of a
59
+ * global `packages:` map (which pnpr rejects loudly):
60
+ *
61
+ * - one hosted registry claiming every name (`**`), publish open to
62
+ * authenticated users (pnpr's default) and unpublish loosened to match
63
+ * the verdaccio battery config
64
+ * - no `e2emock` uplink: pnpr registry namespaces only accept exact
65
+ * names, scope wildcards or `**`, so the unscoped dynamic
66
+ * `e2e-uplink-*` names can't be routed to the mock uplink. Run pnpr
67
+ * without --uplink-port and scenario:uplink-failure (plus the uplink
68
+ * search sub-tests) skip, exactly like docker mode.
69
+ * - no max_body_size knob: pnpr's publish body limit is a fixed 100 MB,
70
+ * already above what scenario:tarballs needs
71
+ */
72
+ function buildPnprE2EConfig() {
73
+ return `# Generated by @verdaccio/e2e-cli --print-config --server pnpr
74
+ storage: ./storage
75
+ auth:
76
+ htpasswd:
77
+ file: ./htpasswd
78
+ max_users: 1000
79
+ registries:
80
+ local:
81
+ type: hosted
82
+ access: $all
83
+ packages:
84
+ '**': { unpublish: $authenticated }
85
+ defaultRegistry: local
86
+ log: { type: stdout, format: pretty, level: warn }
87
+ `;
88
+ }
55
89
  //#endregion
56
- export { DEFAULT_UPLINK_PORT, buildE2EConfig };
90
+ export { DEFAULT_UPLINK_PORT, buildE2EConfig, buildPnprE2EConfig };
57
91
 
58
92
  //# sourceMappingURL=e2e-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"e2e-config.js","names":[],"sources":["../../../src/utils/e2e-config.ts"],"sourcesContent":["/**\n * Recommended Verdaccio config for running the full e2e-cli battery,\n * printed by `verdaccio-e2e --print-config`. It is what the consuming repos'\n * CI jobs and scripts/run-e2e.sh feed to `verdaccio --config`:\n *\n * - max_body_size raised so scenario:tarballs can publish ~30 MB packages\n * (Verdaccio's default is 10mb)\n * - an `e2emock` uplink + `e2e-uplink-*` package pattern wired to the mock\n * uplink server that scenario:uplink-failure starts on E2E_UPLINK_PORT\n * (short timeout so the \"slow uplink\" case completes quickly; high\n * max_fails so simulated failures don't trip the circuit breaker for\n * later sub-tests)\n * - NO npmjs uplink: the whole suite publishes every package it consumes,\n * so the registry under test never talks to the outside world and the\n * battery runs fully offline (and deterministically in CI)\n *\n * Relative paths (storage/htpasswd) resolve next to the config file, so write\n * it into a disposable directory.\n */\nexport const DEFAULT_UPLINK_PORT = 4874;\n\nexport function buildE2EConfig(uplinkPort: number = DEFAULT_UPLINK_PORT): string {\n return `# Generated by @verdaccio/e2e-cli --print-config\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nweb:\n enable: true\n title: Verdaccio E2E\nmax_body_size: 100mb\nuplinks:\n e2emock:\n url: http://localhost:${uplinkPort}/\n timeout: 3s\n retry: 0\n max_fails: 100\n fail_timeout: 1s\npackages:\n 'e2e-uplink-*':\n access: $all\n proxy: e2emock\n '@*/*':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\n '**':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,IAAa,sBAAsB;AAEnC,SAAgB,eAAe,aAAqB,qBAA6B;CAC/E,OAAO;;;;;;;;;;;;4BAYmB,WAAW;;;;;;;;;;;;;;;;;;;AAmBvC"}
1
+ {"version":3,"file":"e2e-config.js","names":[],"sources":["../../../src/utils/e2e-config.ts"],"sourcesContent":["/**\n * Recommended Verdaccio config for running the full e2e-cli battery,\n * printed by `verdaccio-e2e --print-config`. It is what the consuming repos'\n * CI jobs and scripts/run-e2e.sh feed to `verdaccio --config`:\n *\n * - max_body_size raised so scenario:tarballs can publish ~30 MB packages\n * (Verdaccio's default is 10mb)\n * - an `e2emock` uplink + `e2e-uplink-*` package pattern wired to the mock\n * uplink server that scenario:uplink-failure starts on E2E_UPLINK_PORT\n * (short timeout so the \"slow uplink\" case completes quickly; high\n * max_fails so simulated failures don't trip the circuit breaker for\n * later sub-tests)\n * - NO npmjs uplink: the whole suite publishes every package it consumes,\n * so the registry under test never talks to the outside world and the\n * battery runs fully offline (and deterministically in CI)\n *\n * Relative paths (storage/htpasswd) resolve next to the config file, so write\n * it into a disposable directory.\n */\nexport const DEFAULT_UPLINK_PORT = 4874;\n\nexport function buildE2EConfig(uplinkPort: number = DEFAULT_UPLINK_PORT): string {\n return `# Generated by @verdaccio/e2e-cli --print-config\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nweb:\n enable: true\n title: Verdaccio E2E\nmax_body_size: 100mb\nuplinks:\n e2emock:\n url: http://localhost:${uplinkPort}/\n timeout: 3s\n retry: 0\n max_fails: 100\n fail_timeout: 1s\npackages:\n 'e2e-uplink-*':\n access: $all\n proxy: e2emock\n '@*/*':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\n '**':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n\n/**\n * Recommended pnpr config for the same battery, printed by\n * `verdaccio-e2e --print-config --server pnpr`. pnpr's config is\n * verdaccio-shaped but routes through a `registries:` block instead of a\n * global `packages:` map (which pnpr rejects loudly):\n *\n * - one hosted registry claiming every name (`**`), publish open to\n * authenticated users (pnpr's default) and unpublish loosened to match\n * the verdaccio battery config\n * - no `e2emock` uplink: pnpr registry namespaces only accept exact\n * names, scope wildcards or `**`, so the unscoped dynamic\n * `e2e-uplink-*` names can't be routed to the mock uplink. Run pnpr\n * without --uplink-port and scenario:uplink-failure (plus the uplink\n * search sub-tests) skip, exactly like docker mode.\n * - no max_body_size knob: pnpr's publish body limit is a fixed 100 MB,\n * already above what scenario:tarballs needs\n */\nexport function buildPnprE2EConfig(): string {\n return `# Generated by @verdaccio/e2e-cli --print-config --server pnpr\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nregistries:\n local:\n type: hosted\n access: $all\n packages:\n '**': { unpublish: $authenticated }\ndefaultRegistry: local\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,IAAa,sBAAsB;AAEnC,SAAgB,eAAe,aAAqB,qBAA6B;CAC/E,OAAO;;;;;;;;;;;;4BAYmB,WAAW;;;;;;;;;;;;;;;;;;;AAmBvC;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,qBAA6B;CAC3C,OAAO;;;;;;;;;;;;;;;AAeT"}
package/build/runner.d.ts CHANGED
@@ -2,11 +2,13 @@ import { PackageManagerAdapter, SuiteResult, TestDefinition } from './types';
2
2
  export declare function runSuite(adapter: PackageManagerAdapter, tests: TestDefinition[], registryUrl: string, token: string, options: {
3
3
  timeout: number;
4
4
  testFilter?: string[];
5
+ skipTests?: string[];
5
6
  }): Promise<SuiteResult>;
6
7
  export declare function runAll(adapters: PackageManagerAdapter[], tests: TestDefinition[], registryUrl: string, token: string, options: {
7
8
  timeout: number;
8
9
  concurrency: number;
9
10
  testFilter?: string[];
11
+ skipTests?: string[];
10
12
  }): Promise<{
11
13
  results: SuiteResult[];
12
14
  exitCode: number;
package/build/types.d.ts CHANGED
@@ -88,4 +88,8 @@ export type CliOptions = {
88
88
  uplinkPort?: number;
89
89
  /** Print the recommended registry config for the full battery and exit */
90
90
  printConfig?: boolean;
91
+ /** Registry server --print-config targets: verdaccio (default) or pnpr */
92
+ server?: string;
93
+ /** Tests to skip by name (reported as SKIP, unlike --test which hides the rest) */
94
+ skipTest?: string[];
91
95
  };
@@ -19,3 +19,21 @@
19
19
  */
20
20
  export declare const DEFAULT_UPLINK_PORT = 4874;
21
21
  export declare function buildE2EConfig(uplinkPort?: number): string;
22
+ /**
23
+ * Recommended pnpr config for the same battery, printed by
24
+ * `verdaccio-e2e --print-config --server pnpr`. pnpr's config is
25
+ * verdaccio-shaped but routes through a `registries:` block instead of a
26
+ * global `packages:` map (which pnpr rejects loudly):
27
+ *
28
+ * - one hosted registry claiming every name (`**`), publish open to
29
+ * authenticated users (pnpr's default) and unpublish loosened to match
30
+ * the verdaccio battery config
31
+ * - no `e2emock` uplink: pnpr registry namespaces only accept exact
32
+ * names, scope wildcards or `**`, so the unscoped dynamic
33
+ * `e2e-uplink-*` names can't be routed to the mock uplink. Run pnpr
34
+ * without --uplink-port and scenario:uplink-failure (plus the uplink
35
+ * search sub-tests) skip, exactly like docker mode.
36
+ * - no max_body_size knob: pnpr's publish body limit is a fixed 100 MB,
37
+ * already above what scenario:tarballs needs
38
+ */
39
+ export declare function buildPnprE2EConfig(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdaccio/e2e-cli",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "CLI tool to run Verdaccio e2e tests against any running registry",