@verdaccio/e2e-cli 3.3.0 → 4.0.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.
Files changed (69) hide show
  1. package/build/adapters/index.d.ts +2 -0
  2. package/build/adapters/nub.d.ts +2 -0
  3. package/build/adapters/upm.d.ts +2 -0
  4. package/build/cjs/adapters/index.cjs +4 -0
  5. package/build/cjs/adapters/nub.cjs +67 -0
  6. package/build/cjs/adapters/nub.cjs.map +1 -0
  7. package/build/cjs/adapters/pnpm.cjs +3 -22
  8. package/build/cjs/adapters/pnpm.cjs.map +1 -1
  9. package/build/cjs/adapters/upm.cjs +65 -0
  10. package/build/cjs/adapters/upm.cjs.map +1 -0
  11. package/build/cjs/index.cjs +10 -3
  12. package/build/cjs/index.cjs.map +1 -1
  13. package/build/cjs/scenarios/index.cjs +4 -1
  14. package/build/cjs/scenarios/index.cjs.map +1 -1
  15. package/build/cjs/scenarios/minimum-release-age.cjs.map +1 -1
  16. package/build/cjs/scenarios/reserved-names.cjs +88 -0
  17. package/build/cjs/scenarios/reserved-names.cjs.map +1 -0
  18. package/build/cjs/tests/dist-tags.cjs +12 -0
  19. package/build/cjs/tests/dist-tags.cjs.map +1 -1
  20. package/build/cjs/tests/index.cjs +4 -0
  21. package/build/cjs/tests/index.cjs.map +1 -1
  22. package/build/cjs/tests/login.cjs +24 -0
  23. package/build/cjs/tests/login.cjs.map +1 -1
  24. package/build/cjs/tests/package-names.cjs +47 -0
  25. package/build/cjs/tests/package-names.cjs.map +1 -0
  26. package/build/cjs/utils/e2e-config.cjs +9 -2
  27. package/build/cjs/utils/e2e-config.cjs.map +1 -1
  28. package/build/cjs/utils/mock-uplink.cjs +4 -3
  29. package/build/cjs/utils/mock-uplink.cjs.map +1 -1
  30. package/build/cjs/utils/registry-client.cjs +59 -0
  31. package/build/cjs/utils/registry-client.cjs.map +1 -1
  32. package/build/esm/adapters/index.js +3 -1
  33. package/build/esm/adapters/nub.js +65 -0
  34. package/build/esm/adapters/nub.js.map +1 -0
  35. package/build/esm/adapters/pnpm.js +3 -22
  36. package/build/esm/adapters/pnpm.js.map +1 -1
  37. package/build/esm/adapters/upm.js +63 -0
  38. package/build/esm/adapters/upm.js.map +1 -0
  39. package/build/esm/index.js +9 -4
  40. package/build/esm/index.js.map +1 -1
  41. package/build/esm/scenarios/index.js +4 -2
  42. package/build/esm/scenarios/index.js.map +1 -1
  43. package/build/esm/scenarios/minimum-release-age.js.map +1 -1
  44. package/build/esm/scenarios/reserved-names.js +86 -0
  45. package/build/esm/scenarios/reserved-names.js.map +1 -0
  46. package/build/esm/tests/dist-tags.js +12 -0
  47. package/build/esm/tests/dist-tags.js.map +1 -1
  48. package/build/esm/tests/index.js +4 -1
  49. package/build/esm/tests/index.js.map +1 -1
  50. package/build/esm/tests/login.js +24 -0
  51. package/build/esm/tests/login.js.map +1 -1
  52. package/build/esm/tests/package-names.js +45 -0
  53. package/build/esm/tests/package-names.js.map +1 -0
  54. package/build/esm/utils/e2e-config.js +9 -2
  55. package/build/esm/utils/e2e-config.js.map +1 -1
  56. package/build/esm/utils/mock-uplink.js +4 -3
  57. package/build/esm/utils/mock-uplink.js.map +1 -1
  58. package/build/esm/utils/registry-client.js +57 -1
  59. package/build/esm/utils/registry-client.js.map +1 -1
  60. package/build/index.d.ts +1 -1
  61. package/build/scenarios/index.d.ts +2 -1
  62. package/build/scenarios/reserved-names.d.ts +2 -0
  63. package/build/tests/index.d.ts +3 -2
  64. package/build/tests/package-names.d.ts +2 -0
  65. package/build/types.d.ts +2 -2
  66. package/build/utils/e2e-config.d.ts +3 -2
  67. package/build/utils/mock-uplink.d.ts +2 -2
  68. package/build/utils/registry-client.d.ts +11 -0
  69. package/package.json +2 -2
@@ -0,0 +1,63 @@
1
+ import { exec } from "../utils/process.js";
2
+ import { prepareGenericEmptyProject } from "../utils/project.js";
3
+ import buildDebug from "debug";
4
+ import { execSync } from "child_process";
5
+ //#region src/adapters/upm.ts
6
+ var debug = buildDebug("verdaccio:e2e-cli:upm");
7
+ var UPM_SUPPORTED_COMMANDS = /* @__PURE__ */ new Set(["install", "ci"]);
8
+ function detectVersion(bin) {
9
+ try {
10
+ return execSync(`${bin} --version`, {
11
+ encoding: "utf8",
12
+ timeout: 5e3
13
+ }).trim();
14
+ } catch {
15
+ return "unknown";
16
+ }
17
+ }
18
+ function installUpm(version) {
19
+ const pkg = `upm@${version}`;
20
+ debug("installing %s into temp dir", pkg);
21
+ const tmpDir = execSync("mktemp -d", { encoding: "utf8" }).trim();
22
+ execSync(`npm install --prefix "${tmpDir}" ${pkg} --loglevel=error`, {
23
+ encoding: "utf8",
24
+ timeout: 3e4
25
+ });
26
+ const bin = `${tmpDir}/node_modules/.bin/upm`;
27
+ const installed = detectVersion(bin);
28
+ debug("installed upm %s at %s", installed, bin);
29
+ console.log(` Auto-installed upm ${installed}`);
30
+ return bin;
31
+ }
32
+ function resolveUpmBin(binPath, version) {
33
+ if (binPath) return binPath;
34
+ if (version) return installUpm(version);
35
+ return "upm";
36
+ }
37
+ function createUpmAdapter(binPath, version) {
38
+ const bin = resolveUpmBin(binPath, version);
39
+ const resolved = detectVersion(bin);
40
+ debug("creating upm adapter with bin: %s (%s)", bin, resolved);
41
+ return {
42
+ name: `upm@${resolved}`,
43
+ type: "upm",
44
+ bin,
45
+ supports: UPM_SUPPORTED_COMMANDS,
46
+ registryArg(_url) {
47
+ return [];
48
+ },
49
+ prefixArg(folder) {
50
+ return ["--dir", folder];
51
+ },
52
+ exec(options, ...args) {
53
+ return exec(options, bin, args);
54
+ },
55
+ async prepareProject(packageName, version, registryUrl, port, token, dependencies = {}, devDependencies = {}) {
56
+ return prepareGenericEmptyProject(packageName, version, port, token, registryUrl, dependencies, devDependencies);
57
+ }
58
+ };
59
+ }
60
+ //#endregion
61
+ export { createUpmAdapter };
62
+
63
+ //# sourceMappingURL=upm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upm.js","names":[],"sources":["../../../src/adapters/upm.ts"],"sourcesContent":["import { SpawnOptions, execSync } from 'child_process';\nimport buildDebug from 'debug';\n\nimport { ExecOutput, PackageManagerAdapter } from '../types';\nimport { exec } from '../utils/process';\nimport { prepareGenericEmptyProject } from '../utils/project';\n\nconst debug = buildDebug('verdaccio:e2e-cli:upm');\n\n// upm (https://upm.sh) proxies most commands straight to npm (publish, info,\n// deprecate, dist-tag, ping, search, unpublish…). Only its resolver/installer\n// is upm's own code, so the suite exercises just those native commands.\nconst UPM_SUPPORTED_COMMANDS = new Set(['install', 'ci']);\n\nfunction detectVersion(bin: string): string {\n try {\n return execSync(`${bin} --version`, { encoding: 'utf8', timeout: 5000 }).trim();\n } catch {\n return 'unknown';\n }\n}\n\nfunction installUpm(version: string): string {\n const pkg = `upm@${version}`;\n debug('installing %s into temp dir', pkg);\n const tmpDir = execSync('mktemp -d', { encoding: 'utf8' }).trim();\n execSync(`npm install --prefix \"${tmpDir}\" ${pkg} --loglevel=error`, {\n encoding: 'utf8',\n timeout: 30000,\n });\n const bin = `${tmpDir}/node_modules/.bin/upm`;\n const installed = detectVersion(bin);\n debug('installed upm %s at %s', installed, bin);\n console.log(` Auto-installed upm ${installed}`);\n return bin;\n}\n\nfunction resolveUpmBin(binPath?: string, version?: string): string {\n if (binPath) return binPath;\n if (version) return installUpm(version);\n return 'upm';\n}\n\nexport function createUpmAdapter(binPath?: string, version?: string): PackageManagerAdapter {\n const bin = resolveUpmBin(binPath, version);\n const resolved = detectVersion(bin);\n debug('creating upm adapter with bin: %s (%s)', bin, resolved);\n\n const adapter: PackageManagerAdapter = {\n name: `upm@${resolved}`,\n type: 'upm',\n bin,\n supports: UPM_SUPPORTED_COMMANDS,\n\n registryArg(_url: string): string[] {\n // upm reads registry, _authToken and min-release-age from .npmrc (written\n // by prepareProject). min-release-age=0 is essential: upm holds back\n // versions younger than a day by default, which would reject the\n // just-published fixtures the suite installs.\n return [];\n },\n\n prefixArg(folder: string): string[] {\n return ['--dir', folder];\n },\n\n exec(options: SpawnOptions, ...args: string[]): Promise<ExecOutput> {\n return exec(options, bin, args);\n },\n\n async prepareProject(\n packageName: string,\n version: string,\n registryUrl: string,\n port: number,\n token: string,\n dependencies: Record<string, string> = {},\n devDependencies: Record<string, string> = {}\n ): Promise<{ tempFolder: string }> {\n return prepareGenericEmptyProject(\n packageName,\n version,\n port,\n token,\n registryUrl,\n dependencies,\n devDependencies\n );\n },\n };\n\n return adapter;\n}\n"],"mappings":";;;;;AAOA,IAAM,QAAQ,WAAW,uBAAuB;AAKhD,IAAM,yCAAyB,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC;AAExD,SAAS,cAAc,KAAqB;CAC1C,IAAI;EACF,OAAO,SAAS,GAAG,IAAI,aAAa;GAAE,UAAU;GAAQ,SAAS;EAAK,CAAC,CAAC,CAAC,KAAK;CAChF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,WAAW,SAAyB;CAC3C,MAAM,MAAM,OAAO;CACnB,MAAM,+BAA+B,GAAG;CACxC,MAAM,SAAS,SAAS,aAAa,EAAE,UAAU,OAAO,CAAC,CAAC,CAAC,KAAK;CAChE,SAAS,yBAAyB,OAAO,IAAI,IAAI,oBAAoB;EACnE,UAAU;EACV,SAAS;CACX,CAAC;CACD,MAAM,MAAM,GAAG,OAAO;CACtB,MAAM,YAAY,cAAc,GAAG;CACnC,MAAM,0BAA0B,WAAW,GAAG;CAC9C,QAAQ,IAAI,wBAAwB,WAAW;CAC/C,OAAO;AACT;AAEA,SAAS,cAAc,SAAkB,SAA0B;CACjE,IAAI,SAAS,OAAO;CACpB,IAAI,SAAS,OAAO,WAAW,OAAO;CACtC,OAAO;AACT;AAEA,SAAgB,iBAAiB,SAAkB,SAAyC;CAC1F,MAAM,MAAM,cAAc,SAAS,OAAO;CAC1C,MAAM,WAAW,cAAc,GAAG;CAClC,MAAM,0CAA0C,KAAK,QAAQ;CA6C7D,OAAO;EA1CL,MAAM,OAAO;EACb,MAAM;EACN;EACA,UAAU;EAEV,YAAY,MAAwB;GAKlC,OAAO,CAAC;EACV;EAEA,UAAU,QAA0B;GAClC,OAAO,CAAC,SAAS,MAAM;EACzB;EAEA,KAAK,SAAuB,GAAG,MAAqC;GAClE,OAAO,KAAK,SAAS,KAAK,IAAI;EAChC;EAEA,MAAM,eACJ,aACA,SACA,aACA,MACA,OACA,eAAuC,CAAC,GACxC,kBAA0C,CAAC,GACV;GACjC,OAAO,2BACL,aACA,SACA,MACA,OACA,aACA,cACA,eACF;EACF;CAGK;AACT"}
@@ -1,13 +1,15 @@
1
1
  import { createBunAdapter } from "./adapters/bun.js";
2
2
  import { createDenoAdapter } from "./adapters/deno.js";
3
3
  import { createNpmAdapter } from "./adapters/npm.js";
4
+ import { createNubAdapter } from "./adapters/nub.js";
4
5
  import { createPnpmAdapter } from "./adapters/pnpm.js";
6
+ import { createUpmAdapter } from "./adapters/upm.js";
5
7
  import { createYarnModernAdapter } from "./adapters/yarn-modern.js";
6
8
  import "./adapters/index.js";
7
9
  import { runAll, runSuite } from "./runner.js";
8
10
  import { allScenarios } from "./scenarios/index.js";
9
- import { allTests } from "./tests/index.js";
10
11
  import { createUser, pingRegistry } from "./utils/registry-client.js";
12
+ import { allTests } from "./tests/index.js";
11
13
  import buildDebug from "debug";
12
14
  //#region src/index.ts
13
15
  buildDebug("verdaccio:e2e-cli");
@@ -35,7 +37,9 @@ function parseAdapters(pmFilters) {
35
37
  else if (name === "yarn-modern" || name === "yarn") adapters.push(createYarnModernAdapter(binPath, version));
36
38
  else if (name === "bun") adapters.push(createBunAdapter(binPath, version));
37
39
  else if (name === "deno") adapters.push(createDenoAdapter(binPath, version));
38
- else throw new Error(`Unknown package manager: "${name}". Supported: npm, pnpm, yarn-modern, bun, deno`);
40
+ else if (name === "upm") adapters.push(createUpmAdapter(binPath, version));
41
+ else if (name === "nub") adapters.push(createNubAdapter(binPath, version));
42
+ else throw new Error(`Unknown package manager: "${name}". Supported: npm, pnpm, yarn-modern, bun, deno, upm, nub`);
39
43
  }
40
44
  return adapters;
41
45
  }
@@ -94,7 +98,8 @@ function printHelp() {
94
98
  Options:
95
99
  --pm <name[@version]> Package manager to test (can be repeated)
96
100
  Supported: npm (10-12), pnpm (10+),
97
- yarn-modern (or yarn, Yarn 3+), bun, deno
101
+ yarn-modern (or yarn, Yarn 3+), bun, deno,
102
+ upm (install/ci only), nub (install/ci only)
98
103
  Examples: --pm npm@10 --pm pnpm@11
99
104
  --pm yarn-modern@4
100
105
  --pm yarn-modern@3
@@ -191,6 +196,6 @@ async function main(argv = process.argv) {
191
196
  process.exit(exitCode);
192
197
  }
193
198
  //#endregion
194
- export { allScenarios, allTests, createBunAdapter, createDenoAdapter, createNpmAdapter, createPnpmAdapter, createYarnModernAdapter, main, runAll, runSuite };
199
+ export { allScenarios, allTests, createBunAdapter, createDenoAdapter, createNpmAdapter, createNubAdapter, createPnpmAdapter, createUpmAdapter, createYarnModernAdapter, main, runAll, runSuite };
195
200
 
196
201
  //# sourceMappingURL=index.js.map
@@ -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 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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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nimport { runAll } from './runner';\nimport { allTests } from './tests';\nimport { CliOptions, PackageManagerAdapter } from './types';\nimport { createUser, pingRegistry } from './utils/registry-client';\n\nconst debug = buildDebug('verdaccio:e2e-cli');\n\nfunction parsePmSpec(filter: string): { name: string; version?: string; binPath?: string } {\n // --pm yarn-modern@3, --pm npm@10, --pm npm=/path/to/bin\n const eqIdx = filter.indexOf('=');\n if (eqIdx !== -1) {\n return { name: filter.slice(0, eqIdx).toLowerCase(), binPath: filter.slice(eqIdx + 1) };\n }\n const atIdx = filter.indexOf('@');\n if (atIdx !== -1) {\n return { name: filter.slice(0, atIdx).toLowerCase(), version: filter.slice(atIdx + 1) };\n }\n return { name: filter.toLowerCase() };\n}\n\nfunction parseAdapters(pmFilters?: string[]): PackageManagerAdapter[] {\n if (!pmFilters || pmFilters.length === 0) {\n return [createNpmAdapter()];\n }\n\n const adapters: PackageManagerAdapter[] = [];\n\n for (const filter of pmFilters) {\n const { name, version, binPath } = parsePmSpec(filter);\n\n if (name === 'npm') {\n adapters.push(createNpmAdapter(binPath, version));\n } else if (name === 'pnpm') {\n adapters.push(createPnpmAdapter(binPath, version));\n } else if (name === 'yarn-classic' || name === 'yarn1') {\n throw new Error(\n 'yarn classic (v1) is no longer supported. Use yarn-modern (Yarn 3+) instead.'\n );\n } else if (name === 'yarn-modern' || name === 'yarn') {\n adapters.push(createYarnModernAdapter(binPath, version));\n } else if (name === 'bun') {\n adapters.push(createBunAdapter(binPath, version));\n } else if (name === 'deno') {\n adapters.push(createDenoAdapter(binPath, version));\n } else if (name === 'upm') {\n adapters.push(createUpmAdapter(binPath, version));\n } else if (name === 'nub') {\n adapters.push(createNubAdapter(binPath, version));\n } else {\n throw new Error(\n `Unknown package manager: \"${name}\". Supported: npm, pnpm, yarn-modern, bun, deno, upm, nub`\n );\n }\n }\n\n return adapters;\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n const options: CliOptions = {\n registry: '',\n pm: [],\n test: [],\n skipTest: [],\n concurrency: 1,\n timeout: 50000,\n verbose: false,\n };\n\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i];\n\n if (arg === '--registry' || arg === '-r') {\n options.registry = argv[++i];\n } else if (arg === '--pm') {\n options.pm!.push(argv[++i]);\n } else if (arg === '--test' || arg === '-t') {\n options.test!.push(argv[++i]);\n } else if (arg === '--concurrency' || arg === '-c') {\n options.concurrency = parseInt(argv[++i], 10);\n } else if (arg === '--timeout') {\n options.timeout = parseInt(argv[++i], 10);\n } else if (arg === '--token') {\n options.token = argv[++i];\n } else if (arg === '--uplink-port') {\n options.uplinkPort = parseInt(argv[++i], 10);\n } else if (arg === '--print-config') {\n options.printConfig = true;\n } else if (arg === '--server') {\n options.server = argv[++i];\n } else if (arg.startsWith('--server=')) {\n options.server = arg.split('=')[1];\n } else if (arg === '--skip-test') {\n options.skipTest!.push(argv[++i]);\n } else if (arg.startsWith('--skip-test=')) {\n options.skipTest!.push(arg.split('=')[1]);\n } else if (arg === '--verbose' || arg === '-v') {\n options.verbose = true;\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n } else if (arg.startsWith('--registry=')) {\n options.registry = arg.split('=')[1];\n } else if (arg.startsWith('--pm=')) {\n options.pm!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--test=')) {\n options.test!.push(arg.split('=')[1]);\n } else if (arg.startsWith('--token=')) {\n options.token = arg.split('=')[1];\n } else if (arg.startsWith('--timeout=')) {\n options.timeout = parseInt(arg.split('=')[1], 10);\n } else if (arg.startsWith('--uplink-port=')) {\n options.uplinkPort = parseInt(arg.split('=')[1], 10);\n } else {\n console.error(`Unknown argument: ${arg}`);\n printHelp();\n process.exit(1);\n }\n }\n\n return options;\n}\n\nfunction printHelp(): void {\n console.log(`\n @verdaccio/e2e-cli - Run Verdaccio e2e tests against any running registry\n\n Usage:\n verdaccio-e2e --registry <url> [options]\n\n Required:\n -r, --registry <url> Verdaccio registry URL (e.g. http://localhost:4873)\n\n Options:\n --pm <name[@version]> Package manager to test (can be repeated)\n Supported: npm (10-12), pnpm (10+),\n yarn-modern (or yarn, Yarn 3+), bun, deno,\n upm (install/ci only), nub (install/ci only)\n Examples: --pm npm@10 --pm pnpm@11\n --pm yarn-modern@4\n --pm yarn-modern@3\n --pm bun\n --pm npm --pm pnpm (uses system version)\n Auto-installs the requested yarn version if needed\n Default: npm\n\n -t, --test <name> Filter tests by name (can be repeated)\n Available: publish, install, ci, audit, info, deprecate,\n dist-tags, ping, search, unpublish\n Scenarios: scenario:install-multiple-deps,\n scenario:minimum-release-age (pnpm 11.1+),\n scenario:tarballs (npm adapter),\n scenario:metadata (npm adapter),\n scenario:search (npm adapter; the\n uplink sub-tests also need\n --uplink-port / E2E_UPLINK_PORT),\n scenario:uplink-failure (npm adapter,\n needs --uplink-port / E2E_UPLINK_PORT\n and a registry started with the config\n from --print-config)\n Default: all supported by the PM\n\n --skip-test <name> Skip a test by name (can be repeated). Unlike\n --test, the rest of the battery still runs and\n the skipped tests are reported as SKIP\n\n --token <token> Auth token (skips user creation)\n --timeout <ms> Per-test timeout (default: 50000)\n --uplink-port <port> Port for the mock uplink used by\n scenario:uplink-failure (default: $E2E_UPLINK_PORT)\n --print-config Print the recommended registry config for the full\n battery (max_body_size, mock uplink) and exit\n --server <name> Registry server the printed config targets:\n verdaccio (default) or pnpr. pnpr's config has no\n mock uplink (its registry namespaces can't claim\n the unscoped dynamic e2e-uplink-* names), so run\n it without --uplink-port and the uplink tests skip\n -v, --verbose Enable debug output\n -h, --help Show this help\n `);\n}\n\nexport async function main(argv: string[] = process.argv): Promise<void> {\n const options = parseArgs(argv);\n\n if (options.printConfig) {\n const { buildE2EConfig, buildPnprE2EConfig, DEFAULT_UPLINK_PORT } =\n await import('./utils/e2e-config');\n const server = options.server ?? 'verdaccio';\n if (server === 'pnpr') {\n process.stdout.write(buildPnprE2EConfig());\n } else if (server === 'verdaccio') {\n process.stdout.write(buildE2EConfig(options.uplinkPort ?? DEFAULT_UPLINK_PORT));\n } else {\n console.error(`Unknown --server: \"${server}\". Supported: verdaccio, pnpr`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Expose the mock uplink port to scenarios (scenario:uplink-failure gates on it).\n if (options.uplinkPort) {\n process.env.E2E_UPLINK_PORT = String(options.uplinkPort);\n }\n\n if (!options.registry) {\n console.error('Error: --registry is required\\n');\n printHelp();\n process.exit(1);\n }\n\n // Run from a temp dir to isolate from the host project's packageManager, .yarnrc.yml, etc.\n const { createTempFolder } = await import('./utils/project');\n const runDir = await createTempFolder('e2e-run');\n process.chdir(runDir);\n\n if (options.verbose) {\n const { setVerbose } = await import('./utils/process');\n setVerbose(true);\n }\n\n // Ensure registry is reachable\n console.log(`Checking registry at ${options.registry}...`);\n const alive = await pingRegistry(options.registry);\n if (!alive) {\n console.error(`Error: Registry at ${options.registry} is not reachable`);\n process.exit(1);\n }\n console.log(`Registry is alive.`);\n\n // Get auth token\n let token = options.token;\n if (!token) {\n console.log('Creating test user...');\n const auth = await createUser(options.registry);\n token = auth.token;\n console.log(`User \"${auth.user}\" created.`);\n }\n\n // Build adapter list\n const adapters = parseAdapters(options.pm);\n console.log(`Adapters: ${adapters.map((a) => a.name).join(', ')}`);\n\n // Filter tests\n const tests =\n options.test && options.test.length > 0\n ? allTests.filter((t) => options.test!.includes(t.name))\n : allTests;\n\n console.log(`Tests: ${tests.map((t) => t.name).join(', ')}`);\n\n if (options.skipTest && options.skipTest.length > 0) {\n console.log(`Skipping tests: ${options.skipTest.join(', ')}`);\n }\n\n // Run\n const { exitCode } = await runAll(adapters, tests, options.registry, token, {\n timeout: options.timeout,\n concurrency: options.concurrency,\n testFilter: options.test,\n skipTests: options.skipTest,\n });\n\n process.exit(exitCode);\n}\n\n// Re-export for programmatic usage\nexport { allTests } from './tests';\nexport { allScenarios } from './scenarios';\nexport {\n createBunAdapter,\n createDenoAdapter,\n createNpmAdapter,\n createNubAdapter,\n createPnpmAdapter,\n createUpmAdapter,\n createYarnModernAdapter,\n} from './adapters';\nexport { runAll, runSuite } from './runner';\nexport type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';\n"],"mappings":";;;;;;;;;;;;;;AAgBc,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;OAC5C,IAAI,SAAS,OAClB,SAAS,KAAK,iBAAiB,SAAS,OAAO,CAAC;OAC3C,IAAI,SAAS,OAClB,SAAS,KAAK,iBAAiB,SAAS,OAAO,CAAC;OAEhD,MAAM,IAAI,MACR,6BAA6B,KAAK,0DACpC;CAEJ;CAEA,OAAO;AACT;AAEA,SAAS,UAAU,MAA4B;CAC7C,MAAM,UAAsB;EAC1B,UAAU;EACV,IAAI,CAAC;EACL,MAAM,CAAC;EACP,UAAU,CAAC;EACX,aAAa;EACb,SAAS;EACT,SAAS;CACX;CAEA,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,QAAQ,gBAAgB,QAAQ,MAClC,QAAQ,WAAW,KAAK,EAAE;OACrB,IAAI,QAAQ,QACjB,QAAQ,GAAI,KAAK,KAAK,EAAE,EAAE;OACrB,IAAI,QAAQ,YAAY,QAAQ,MACrC,QAAQ,KAAM,KAAK,KAAK,EAAE,EAAE;OACvB,IAAI,QAAQ,mBAAmB,QAAQ,MAC5C,QAAQ,cAAc,SAAS,KAAK,EAAE,IAAI,EAAE;OACvC,IAAI,QAAQ,aACjB,QAAQ,UAAU,SAAS,KAAK,EAAE,IAAI,EAAE;OACnC,IAAI,QAAQ,WACjB,QAAQ,QAAQ,KAAK,EAAE;OAClB,IAAI,QAAQ,iBACjB,QAAQ,aAAa,SAAS,KAAK,EAAE,IAAI,EAAE;OACtC,IAAI,QAAQ,kBACjB,QAAQ,cAAc;OACjB,IAAI,QAAQ,YACjB,QAAQ,SAAS,KAAK,EAAE;OACnB,IAAI,IAAI,WAAW,WAAW,GACnC,QAAQ,SAAS,IAAI,MAAM,GAAG,CAAC,CAAC;OAC3B,IAAI,QAAQ,eACjB,QAAQ,SAAU,KAAK,KAAK,EAAE,EAAE;OAC3B,IAAI,IAAI,WAAW,cAAc,GACtC,QAAQ,SAAU,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE;OACnC,IAAI,QAAQ,eAAe,QAAQ,MACxC,QAAQ,UAAU;OACb,IAAI,QAAQ,YAAY,QAAQ,MAAM;GAC3C,UAAU;GACV,QAAQ,KAAK,CAAC;EAChB,OAAO,IAAI,IAAI,WAAW,aAAa,GACrC,QAAQ,WAAW,IAAI,MAAM,GAAG,CAAC,CAAC;OAC7B,IAAI,IAAI,WAAW,OAAO,GAC/B,QAAQ,GAAI,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE;OAC7B,IAAI,IAAI,WAAW,SAAS,GACjC,QAAQ,KAAM,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE;OAC/B,IAAI,IAAI,WAAW,UAAU,GAClC,QAAQ,QAAQ,IAAI,MAAM,GAAG,CAAC,CAAC;OAC1B,IAAI,IAAI,WAAW,YAAY,GACpC,QAAQ,UAAU,SAAS,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE;OAC3C,IAAI,IAAI,WAAW,gBAAgB,GACxC,QAAQ,aAAa,SAAS,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE;OAC9C;GACL,QAAQ,MAAM,qBAAqB,KAAK;GACxC,UAAU;GACV,QAAQ,KAAK,CAAC;EAChB;CACF;CAEA,OAAO;AACT;AAEA,SAAS,YAAkB;CACzB,QAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDX;AACH;AAEA,eAAsB,KAAK,OAAiB,QAAQ,MAAqB;CACvE,MAAM,UAAU,UAAU,IAAI;CAE9B,IAAI,QAAQ,aAAa;EACvB,MAAM,EAAE,gBAAgB,oBAAoB,wBAC1C,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"}
@@ -1,6 +1,7 @@
1
1
  import { installMultipleDepsScenario } from "./install-multiple-deps.js";
2
2
  import { metadataScenario } from "./metadata.js";
3
3
  import { minimumReleaseAgeScenario } from "./minimum-release-age.js";
4
+ import { reservedNamesScenario } from "./reserved-names.js";
4
5
  import { searchScenario } from "./search.js";
5
6
  import { tarballsScenario } from "./tarballs.js";
6
7
  import { uplinkFailureScenario } from "./uplink-failure.js";
@@ -11,9 +12,10 @@ var allScenarios = [
11
12
  tarballsScenario,
12
13
  metadataScenario,
13
14
  searchScenario,
14
- uplinkFailureScenario
15
+ uplinkFailureScenario,
16
+ reservedNamesScenario
15
17
  ];
16
18
  //#endregion
17
- export { allScenarios, installMultipleDepsScenario, metadataScenario, minimumReleaseAgeScenario, searchScenario, tarballsScenario, uplinkFailureScenario };
19
+ export { allScenarios, installMultipleDepsScenario, metadataScenario, minimumReleaseAgeScenario, reservedNamesScenario, searchScenario, tarballsScenario, uplinkFailureScenario };
18
20
 
19
21
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/scenarios/index.ts"],"sourcesContent":["import { TestDefinition } from '../types';\nimport { installMultipleDepsScenario } from './install-multiple-deps';\nimport { metadataScenario } from './metadata';\nimport { minimumReleaseAgeScenario } from './minimum-release-age';\nimport { searchScenario } from './search';\nimport { tarballsScenario } from './tarballs';\nimport { uplinkFailureScenario } from './uplink-failure';\n\nexport const allScenarios: TestDefinition[] = [\n installMultipleDepsScenario,\n minimumReleaseAgeScenario,\n tarballsScenario,\n metadataScenario,\n searchScenario,\n uplinkFailureScenario,\n];\n\nexport {\n installMultipleDepsScenario,\n minimumReleaseAgeScenario,\n searchScenario,\n tarballsScenario,\n metadataScenario,\n uplinkFailureScenario,\n};\n"],"mappings":";;;;;;;AAQA,IAAa,eAAiC;CAC5C;CACA;CACA;CACA;CACA;CACA;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/scenarios/index.ts"],"sourcesContent":["import { TestDefinition } from '../types';\nimport { installMultipleDepsScenario } from './install-multiple-deps';\nimport { metadataScenario } from './metadata';\nimport { minimumReleaseAgeScenario } from './minimum-release-age';\nimport { reservedNamesScenario } from './reserved-names';\nimport { searchScenario } from './search';\nimport { tarballsScenario } from './tarballs';\nimport { uplinkFailureScenario } from './uplink-failure';\n\nexport const allScenarios: TestDefinition[] = [\n installMultipleDepsScenario,\n minimumReleaseAgeScenario,\n tarballsScenario,\n metadataScenario,\n searchScenario,\n uplinkFailureScenario,\n reservedNamesScenario,\n];\n\nexport {\n installMultipleDepsScenario,\n minimumReleaseAgeScenario,\n searchScenario,\n tarballsScenario,\n metadataScenario,\n uplinkFailureScenario,\n reservedNamesScenario,\n};\n"],"mappings":";;;;;;;;AASA,IAAa,eAAiC;CAC5C;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
@@ -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 * 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"}
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 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"}
@@ -0,0 +1,86 @@
1
+ import { downloadTarball, fetchPackument } from "../utils/http-client.js";
2
+ import { MockUplink } from "../utils/mock-uplink.js";
3
+ import { pendingContractChecksEnabled } from "../utils/pending-checks.js";
4
+ import { buildPackageTarball, publishViaHttp } from "../utils/publish.js";
5
+ import assert from "assert";
6
+ //#region src/scenarios/reserved-names.ts
7
+ /**
8
+ * Scenario: reserved-names
9
+ *
10
+ * Packages named like Windows device names (`nul`, `con`, `aux`, with or without
11
+ * an extension) exist on npmjs. The registry must proxy and install them, and stay
12
+ * healthy when they are requested with the uplink down or published locally. Uses
13
+ * the `nul.*` and `@e2e-uplink/*` patterns of the battery config (see `--print-config`).
14
+ */
15
+ function uplinkPort() {
16
+ return parseInt(process.env.E2E_UPLINK_PORT || "", 10);
17
+ }
18
+ async function expectRegistryAlive(ctx) {
19
+ const ping = await fetch(`${ctx.registryUrl}/-/ping`);
20
+ assert.strictEqual(ping.status, 200, "Registry stopped responding");
21
+ }
22
+ async function testReservedNames(ctx) {
23
+ const id = ctx.runId;
24
+ const proxied = [`nul.${id}`, `@e2e-uplink/con.${id}`];
25
+ const mock = new MockUplink(uplinkPort());
26
+ const packages = proxied.map((name) => mock.addPackage(name, buildPackageTarball(name, "1.0.0")));
27
+ await mock.start();
28
+ try {
29
+ await ctx.subTest("reserved device names are proxied from the uplink", async () => {
30
+ for (const pkg of packages) {
31
+ const { status, body } = await fetchPackument(ctx.registryUrl, pkg.name);
32
+ assert.strictEqual(status, 200, `Expected 200 proxying ${pkg.name}, got ${status}`);
33
+ assert.strictEqual(body.name, pkg.name, `Proxied packument name mismatch for ${pkg.name}`);
34
+ }
35
+ });
36
+ if (pendingContractChecksEnabled("reserved-names")) {
37
+ await ctx.subTest("reserved device name tarballs are proxied", async () => {
38
+ for (const pkg of packages) {
39
+ const { body } = await fetchPackument(ctx.registryUrl, pkg.name);
40
+ const download = await downloadTarball(body.versions["1.0.0"].dist.tarball);
41
+ assert.strictEqual(download.status, 200, `Proxied tarball failed for ${pkg.name}`);
42
+ assert.strictEqual(download.sha1, pkg.shasum, `Proxied tarball bytes mismatch for ${pkg.name}`);
43
+ }
44
+ });
45
+ await ctx.subTest("reserved device names install with the client", async () => {
46
+ const { tempFolder } = await ctx.adapter.prepareProject(`e2e-reserved-consumer-${id}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token, Object.fromEntries(proxied.map((name) => [name, "1.0.0"])));
47
+ await ctx.adapter.exec({ cwd: tempFolder }, "install", ...ctx.adapter.registryArg(ctx.registryUrl));
48
+ });
49
+ }
50
+ } finally {
51
+ await mock.stop();
52
+ }
53
+ await ctx.subTest("uplink down: reserved device names fail cleanly", async () => {
54
+ for (const name of proxied) {
55
+ const { status } = await fetchPackument(ctx.registryUrl, name);
56
+ assert.ok([200, 404].includes(status), `Expected 200 or 404 for ${name}, got ${status}`);
57
+ }
58
+ await expectRegistryAlive(ctx);
59
+ });
60
+ if (pendingContractChecksEnabled("reserved-names")) await ctx.subTest("publishing a reserved device name keeps the registry healthy", async () => {
61
+ const name = `aux.${id}`;
62
+ let published = true;
63
+ try {
64
+ await publishViaHttp(ctx, name);
65
+ } catch (err) {
66
+ published = false;
67
+ assert.match(String(err), / 404 /, `Expected a 404 refusal for ${name}, got ${err}`);
68
+ }
69
+ if (published) {
70
+ const { status, body } = await fetchPackument(ctx.registryUrl, name);
71
+ assert.strictEqual(status, 200, `Expected 200 for published ${name}, got ${status}`);
72
+ assert.strictEqual(body.name, name, `Packument name mismatch for ${name}`);
73
+ }
74
+ await expectRegistryAlive(ctx);
75
+ });
76
+ }
77
+ var reservedNamesScenario = {
78
+ name: "scenario:reserved-names",
79
+ appliesTo: (adapter) => adapter.type === "npm" && Number.isFinite(uplinkPort()),
80
+ timeout: 6e4,
81
+ run: testReservedNames
82
+ };
83
+ //#endregion
84
+ export { reservedNamesScenario };
85
+
86
+ //# sourceMappingURL=reserved-names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reserved-names.js","names":[],"sources":["../../../src/scenarios/reserved-names.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { downloadTarball, fetchPackument } from '../utils/http-client';\nimport { MockUplink } from '../utils/mock-uplink';\nimport { pendingContractChecksEnabled } from '../utils/pending-checks';\nimport { buildPackageTarball, publishViaHttp } from '../utils/publish';\n\n/**\n * Scenario: reserved-names\n *\n * Packages named like Windows device names (`nul`, `con`, `aux`, with or without\n * an extension) exist on npmjs. The registry must proxy and install them, and stay\n * healthy when they are requested with the uplink down or published locally. Uses\n * the `nul.*` and `@e2e-uplink/*` patterns of the battery config (see `--print-config`).\n */\nfunction uplinkPort(): number {\n return parseInt(process.env.E2E_UPLINK_PORT || '', 10);\n}\n\nasync function expectRegistryAlive(ctx: TestContext): Promise<void> {\n const ping = await fetch(`${ctx.registryUrl}/-/ping`);\n assert.strictEqual(ping.status, 200, 'Registry stopped responding');\n}\n\nasync function testReservedNames(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n const proxied = [`nul.${id}`, `@e2e-uplink/con.${id}`];\n const mock = new MockUplink(uplinkPort());\n const packages = proxied.map((name) => mock.addPackage(name, buildPackageTarball(name, '1.0.0')));\n\n await mock.start();\n try {\n await ctx.subTest('reserved device names are proxied from the uplink', async () => {\n for (const pkg of packages) {\n const { status, body } = await fetchPackument(ctx.registryUrl, pkg.name);\n assert.strictEqual(status, 200, `Expected 200 proxying ${pkg.name}, got ${status}`);\n assert.strictEqual(body.name, pkg.name, `Proxied packument name mismatch for ${pkg.name}`);\n }\n });\n\n if (pendingContractChecksEnabled('reserved-names')) {\n await ctx.subTest('reserved device name tarballs are proxied', async () => {\n for (const pkg of packages) {\n const { body } = await fetchPackument(ctx.registryUrl, pkg.name);\n const download = await downloadTarball(body.versions['1.0.0'].dist.tarball);\n assert.strictEqual(download.status, 200, `Proxied tarball failed for ${pkg.name}`);\n assert.strictEqual(\n download.sha1,\n pkg.shasum,\n `Proxied tarball bytes mismatch for ${pkg.name}`\n );\n }\n });\n\n await ctx.subTest('reserved device names install with the client', async () => {\n const { tempFolder } = await ctx.adapter.prepareProject(\n `e2e-reserved-consumer-${id}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n Object.fromEntries(proxied.map((name) => [name, '1.0.0']))\n );\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n });\n }\n } finally {\n await mock.stop();\n }\n\n await ctx.subTest('uplink down: reserved device names fail cleanly', async () => {\n // Served from the local cache (200) or not cached for these names (404), never a 5xx.\n for (const name of proxied) {\n const { status } = await fetchPackument(ctx.registryUrl, name);\n assert.ok([200, 404].includes(status), `Expected 200 or 404 for ${name}, got ${status}`);\n }\n await expectRegistryAlive(ctx);\n });\n\n if (pendingContractChecksEnabled('reserved-names')) {\n await ctx.subTest('publishing a reserved device name keeps the registry healthy', async () => {\n const name = `aux.${id}`;\n let published = true;\n try {\n await publishViaHttp(ctx, name);\n } catch (err) {\n // Registries whose storage has no folder for such a name refuse it with a 404.\n published = false;\n assert.match(String(err), / 404 /, `Expected a 404 refusal for ${name}, got ${err}`);\n }\n if (published) {\n const { status, body } = await fetchPackument(ctx.registryUrl, name);\n assert.strictEqual(status, 200, `Expected 200 for published ${name}, got ${status}`);\n assert.strictEqual(body.name, name, `Packument name mismatch for ${name}`);\n }\n await expectRegistryAlive(ctx);\n });\n }\n}\n\nexport const reservedNamesScenario: TestDefinition = {\n name: 'scenario:reserved-names',\n // Needs the harness-provided uplink config; skipped cleanly otherwise.\n appliesTo: (adapter) => adapter.type === 'npm' && Number.isFinite(uplinkPort()),\n timeout: 60_000,\n run: testReservedNames,\n};\n"],"mappings":"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"}
@@ -29,6 +29,18 @@ async function testDistTags(ctx) {
29
29
  await publishPkg(ctx, tf3);
30
30
  const resp3 = await ctx.adapter.exec({ cwd: tf3 }, "dist-tag", "add", `${pkgName3}@1.1.0`, "alfa", ...ctx.adapter.registryArg(ctx.registryUrl));
31
31
  assert.strictEqual(resp3.stdout, `+alfa: ${pkgName3}@1.1.0`);
32
+ const pkgName4 = `@verdaccio/dt4-${id}`;
33
+ const { tempFolder: tf4 } = await ctx.adapter.prepareProject(pkgName4, "1.0.0", ctx.registryUrl, ctx.port, ctx.token);
34
+ await publishPkg(ctx, tf4);
35
+ for (const tag of ["aux", "con"]) {
36
+ const added = await ctx.adapter.exec({ cwd: tf4 }, "dist-tag", "add", `${pkgName4}@1.0.0`, tag, ...ctx.adapter.registryArg(ctx.registryUrl));
37
+ assert.strictEqual(added.stdout, `+${tag}: ${pkgName4}@1.0.0`);
38
+ }
39
+ const listed = await ctx.adapter.exec({ cwd: tf4 }, "dist-tag", "ls", ...ctx.adapter.registryArg(ctx.registryUrl));
40
+ assert.ok(listed.stdout.includes("aux: 1.0.0"), `Expected aux tag in: ${listed.stdout}`);
41
+ assert.ok(listed.stdout.includes("con: 1.0.0"), `Expected con tag in: ${listed.stdout}`);
42
+ const removed = await ctx.adapter.exec({ cwd: tf4 }, "dist-tag", "rm", pkgName4, "aux", ...ctx.adapter.registryArg(ctx.registryUrl));
43
+ assert.strictEqual(removed.stdout, `-aux: ${pkgName4}@1.0.0`);
32
44
  }
33
45
  var distTagsTest = {
34
46
  name: "dist-tags",
@@ -1 +1 @@
1
- {"version":3,"file":"dist-tags.js","names":[],"sources":["../../../src/tests/dist-tags.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function publishPkg(ctx: TestContext, tempFolder: string, extraArgs: string[] = []) {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n ...extraArgs,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n}\n\nasync function bumpVersion(ctx: TestContext, tempFolder: string) {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'version',\n 'minor',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n}\n\nasync function testDistTags(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n\n // Test 1: list dist-tags\n const pkgName1 = `@foo/dt1-${id}`;\n const { tempFolder: tf1 } = await ctx.adapter.prepareProject(\n pkgName1,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tf1);\n await bumpVersion(ctx, tf1);\n await publishPkg(ctx, tf1, ['--tag', 'beta']);\n const resp1 = await ctx.adapter.exec(\n { cwd: tf1 },\n 'dist-tag',\n 'ls',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.strictEqual(resp1.stdout, 'beta: 1.1.0latest: 1.0.0');\n\n // Test 2: remove tag\n const pkgName2 = `@verdaccio/dt2-${id}`;\n const { tempFolder: tf2 } = await ctx.adapter.prepareProject(\n pkgName2,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tf2);\n await bumpVersion(ctx, tf2);\n await publishPkg(ctx, tf2, ['--tag', 'beta']);\n const resp2 = await ctx.adapter.exec(\n { cwd: tf2 },\n 'dist-tag',\n 'rm',\n `${pkgName2}@1.1.0`,\n 'beta',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.strictEqual(resp2.stdout, `-beta: ${pkgName2}@1.1.0`);\n\n // Test 3: add tag\n const pkgName3 = `@verdaccio/dt3-${id}`;\n const { tempFolder: tf3 } = await ctx.adapter.prepareProject(\n pkgName3,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tf3);\n await bumpVersion(ctx, tf3);\n await publishPkg(ctx, tf3);\n const resp3 = await ctx.adapter.exec(\n { cwd: tf3 },\n 'dist-tag',\n 'add',\n `${pkgName3}@1.1.0`,\n 'alfa',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.strictEqual(resp3.stdout, `+alfa: ${pkgName3}@1.1.0`);\n}\n\nexport const distTagsTest: TestDefinition = {\n name: 'dist-tags',\n requires: ['dist-tag'],\n run: testDistTags,\n};\n"],"mappings":";;AAIA,eAAe,WAAW,KAAkB,YAAoB,YAAsB,CAAC,GAAG;CACxF,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,WACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;AACF;AAEA,eAAe,YAAY,KAAkB,YAAoB;CAC/D,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,SACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;AACF;AAEA,eAAe,aAAa,KAAiC;CAC3D,MAAM,KAAK,IAAI;CAGf,MAAM,WAAW,YAAY;CAC7B,MAAM,EAAE,YAAY,QAAQ,MAAM,IAAI,QAAQ,eAC5C,UACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,YAAY,KAAK,GAAG;CAC1B,MAAM,WAAW,KAAK,KAAK,CAAC,SAAS,MAAM,CAAC;CAC5C,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAC9B,EAAE,KAAK,IAAI,GACX,YACA,MACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,YAAY,MAAM,QAAQ,0BAA0B;CAG3D,MAAM,WAAW,kBAAkB;CACnC,MAAM,EAAE,YAAY,QAAQ,MAAM,IAAI,QAAQ,eAC5C,UACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,YAAY,KAAK,GAAG;CAC1B,MAAM,WAAW,KAAK,KAAK,CAAC,SAAS,MAAM,CAAC;CAC5C,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAC9B,EAAE,KAAK,IAAI,GACX,YACA,MACA,GAAG,SAAS,SACZ,QACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,YAAY,MAAM,QAAQ,UAAU,SAAS,OAAO;CAG3D,MAAM,WAAW,kBAAkB;CACnC,MAAM,EAAE,YAAY,QAAQ,MAAM,IAAI,QAAQ,eAC5C,UACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,YAAY,KAAK,GAAG;CAC1B,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAC9B,EAAE,KAAK,IAAI,GACX,YACA,OACA,GAAG,SAAS,SACZ,QACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,YAAY,MAAM,QAAQ,UAAU,SAAS,OAAO;AAC7D;AAEA,IAAa,eAA+B;CAC1C,MAAM;CACN,UAAU,CAAC,UAAU;CACrB,KAAK;AACP"}
1
+ {"version":3,"file":"dist-tags.js","names":[],"sources":["../../../src/tests/dist-tags.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function publishPkg(ctx: TestContext, tempFolder: string, extraArgs: string[] = []) {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n ...extraArgs,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n}\n\nasync function bumpVersion(ctx: TestContext, tempFolder: string) {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'version',\n 'minor',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n}\n\nasync function testDistTags(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n\n // Test 1: list dist-tags\n const pkgName1 = `@foo/dt1-${id}`;\n const { tempFolder: tf1 } = await ctx.adapter.prepareProject(\n pkgName1,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tf1);\n await bumpVersion(ctx, tf1);\n await publishPkg(ctx, tf1, ['--tag', 'beta']);\n const resp1 = await ctx.adapter.exec(\n { cwd: tf1 },\n 'dist-tag',\n 'ls',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.strictEqual(resp1.stdout, 'beta: 1.1.0latest: 1.0.0');\n\n // Test 2: remove tag\n const pkgName2 = `@verdaccio/dt2-${id}`;\n const { tempFolder: tf2 } = await ctx.adapter.prepareProject(\n pkgName2,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tf2);\n await bumpVersion(ctx, tf2);\n await publishPkg(ctx, tf2, ['--tag', 'beta']);\n const resp2 = await ctx.adapter.exec(\n { cwd: tf2 },\n 'dist-tag',\n 'rm',\n `${pkgName2}@1.1.0`,\n 'beta',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.strictEqual(resp2.stdout, `-beta: ${pkgName2}@1.1.0`);\n\n // Test 3: add tag\n const pkgName3 = `@verdaccio/dt3-${id}`;\n const { tempFolder: tf3 } = await ctx.adapter.prepareProject(\n pkgName3,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tf3);\n await bumpVersion(ctx, tf3);\n await publishPkg(ctx, tf3);\n const resp3 = await ctx.adapter.exec(\n { cwd: tf3 },\n 'dist-tag',\n 'add',\n `${pkgName3}@1.1.0`,\n 'alfa',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.strictEqual(resp3.stdout, `+alfa: ${pkgName3}@1.1.0`);\n\n // Test 4: tags named like Windows device names are plain metadata keys\n const pkgName4 = `@verdaccio/dt4-${id}`;\n const { tempFolder: tf4 } = await ctx.adapter.prepareProject(\n pkgName4,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tf4);\n for (const tag of ['aux', 'con']) {\n const added = await ctx.adapter.exec(\n { cwd: tf4 },\n 'dist-tag',\n 'add',\n `${pkgName4}@1.0.0`,\n tag,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.strictEqual(added.stdout, `+${tag}: ${pkgName4}@1.0.0`);\n }\n const listed = await ctx.adapter.exec(\n { cwd: tf4 },\n 'dist-tag',\n 'ls',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.ok(listed.stdout.includes('aux: 1.0.0'), `Expected aux tag in: ${listed.stdout}`);\n assert.ok(listed.stdout.includes('con: 1.0.0'), `Expected con tag in: ${listed.stdout}`);\n const removed = await ctx.adapter.exec(\n { cwd: tf4 },\n 'dist-tag',\n 'rm',\n pkgName4,\n 'aux',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.strictEqual(removed.stdout, `-aux: ${pkgName4}@1.0.0`);\n}\n\nexport const distTagsTest: TestDefinition = {\n name: 'dist-tags',\n requires: ['dist-tag'],\n run: testDistTags,\n};\n"],"mappings":";;AAIA,eAAe,WAAW,KAAkB,YAAoB,YAAsB,CAAC,GAAG;CACxF,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,WACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;AACF;AAEA,eAAe,YAAY,KAAkB,YAAoB;CAC/D,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,SACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;AACF;AAEA,eAAe,aAAa,KAAiC;CAC3D,MAAM,KAAK,IAAI;CAGf,MAAM,WAAW,YAAY;CAC7B,MAAM,EAAE,YAAY,QAAQ,MAAM,IAAI,QAAQ,eAC5C,UACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,YAAY,KAAK,GAAG;CAC1B,MAAM,WAAW,KAAK,KAAK,CAAC,SAAS,MAAM,CAAC;CAC5C,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAC9B,EAAE,KAAK,IAAI,GACX,YACA,MACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,YAAY,MAAM,QAAQ,0BAA0B;CAG3D,MAAM,WAAW,kBAAkB;CACnC,MAAM,EAAE,YAAY,QAAQ,MAAM,IAAI,QAAQ,eAC5C,UACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,YAAY,KAAK,GAAG;CAC1B,MAAM,WAAW,KAAK,KAAK,CAAC,SAAS,MAAM,CAAC;CAC5C,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAC9B,EAAE,KAAK,IAAI,GACX,YACA,MACA,GAAG,SAAS,SACZ,QACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,YAAY,MAAM,QAAQ,UAAU,SAAS,OAAO;CAG3D,MAAM,WAAW,kBAAkB;CACnC,MAAM,EAAE,YAAY,QAAQ,MAAM,IAAI,QAAQ,eAC5C,UACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,YAAY,KAAK,GAAG;CAC1B,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAC9B,EAAE,KAAK,IAAI,GACX,YACA,OACA,GAAG,SAAS,SACZ,QACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,YAAY,MAAM,QAAQ,UAAU,SAAS,OAAO;CAG3D,MAAM,WAAW,kBAAkB;CACnC,MAAM,EAAE,YAAY,QAAQ,MAAM,IAAI,QAAQ,eAC5C,UACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,GAAG;CACzB,KAAK,MAAM,OAAO,CAAC,OAAO,KAAK,GAAG;EAChC,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAC9B,EAAE,KAAK,IAAI,GACX,YACA,OACA,GAAG,SAAS,SACZ,KACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EACA,OAAO,YAAY,MAAM,QAAQ,IAAI,IAAI,IAAI,SAAS,OAAO;CAC/D;CACA,MAAM,SAAS,MAAM,IAAI,QAAQ,KAC/B,EAAE,KAAK,IAAI,GACX,YACA,MACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,GAAG,OAAO,OAAO,SAAS,YAAY,GAAG,wBAAwB,OAAO,QAAQ;CACvF,OAAO,GAAG,OAAO,OAAO,SAAS,YAAY,GAAG,wBAAwB,OAAO,QAAQ;CACvF,MAAM,UAAU,MAAM,IAAI,QAAQ,KAChC,EAAE,KAAK,IAAI,GACX,YACA,MACA,UACA,OACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,YAAY,QAAQ,QAAQ,SAAS,SAAS,OAAO;AAC9D;AAEA,IAAa,eAA+B;CAC1C,MAAM;CACN,UAAU,CAAC,UAAU;CACrB,KAAK;AACP"}
@@ -1,6 +1,7 @@
1
1
  import "../scenarios/install-multiple-deps.js";
2
2
  import "../scenarios/metadata.js";
3
3
  import "../scenarios/minimum-release-age.js";
4
+ import "../scenarios/reserved-names.js";
4
5
  import "../scenarios/search.js";
5
6
  import "../scenarios/tarballs.js";
6
7
  import "../scenarios/uplink-failure.js";
@@ -12,6 +13,7 @@ import { distTagsTest } from "./dist-tags.js";
12
13
  import { infoTest } from "./info.js";
13
14
  import { installTest } from "./install.js";
14
15
  import { loginTest } from "./login.js";
16
+ import { packageNamesTest } from "./package-names.js";
15
17
  import { pingTest } from "./ping.js";
16
18
  import { publishTest } from "./publish.js";
17
19
  import { searchTest } from "./search.js";
@@ -25,6 +27,7 @@ var allTests = [
25
27
  infoTest,
26
28
  deprecateTest,
27
29
  distTagsTest,
30
+ packageNamesTest,
28
31
  loginTest,
29
32
  pingTest,
30
33
  searchTest,
@@ -32,6 +35,6 @@ var allTests = [
32
35
  ...allScenarios
33
36
  ];
34
37
  //#endregion
35
- export { allScenarios, allTests, auditTest, ciTest, deprecateTest, distTagsTest, infoTest, installTest, loginTest, pingTest, publishTest, searchTest, unpublishTest };
38
+ export { allScenarios, allTests, auditTest, ciTest, deprecateTest, distTagsTest, infoTest, installTest, loginTest, packageNamesTest, pingTest, publishTest, searchTest, unpublishTest };
36
39
 
37
40
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/tests/index.ts"],"sourcesContent":["import {\n allScenarios,\n installMultipleDepsScenario,\n metadataScenario,\n minimumReleaseAgeScenario,\n searchScenario,\n tarballsScenario,\n uplinkFailureScenario,\n} from '../scenarios';\nimport { TestDefinition } from '../types';\nimport { auditTest } from './audit';\nimport { ciTest } from './ci';\nimport { deprecateTest } from './deprecate';\nimport { distTagsTest } from './dist-tags';\nimport { infoTest } from './info';\nimport { installTest } from './install';\nimport { loginTest } from './login';\nimport { pingTest } from './ping';\nimport { publishTest } from './publish';\nimport { searchTest } from './search';\nimport { unpublishTest } from './unpublish';\n\nexport const allTests: TestDefinition[] = [\n publishTest,\n installTest,\n ciTest,\n auditTest,\n infoTest,\n deprecateTest,\n distTagsTest,\n loginTest,\n pingTest,\n searchTest,\n unpublishTest,\n // Scenarios (complex, multi-step tests)\n ...allScenarios,\n];\n\nexport {\n publishTest,\n installTest,\n ciTest,\n auditTest,\n infoTest,\n deprecateTest,\n distTagsTest,\n loginTest,\n pingTest,\n searchTest,\n unpublishTest,\n // Scenarios\n installMultipleDepsScenario,\n minimumReleaseAgeScenario,\n tarballsScenario,\n metadataScenario,\n searchScenario,\n uplinkFailureScenario,\n allScenarios,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsBA,IAAa,WAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,GAAG;AACL"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/tests/index.ts"],"sourcesContent":["import {\n allScenarios,\n installMultipleDepsScenario,\n metadataScenario,\n minimumReleaseAgeScenario,\n reservedNamesScenario,\n searchScenario,\n tarballsScenario,\n uplinkFailureScenario,\n} from '../scenarios';\nimport { TestDefinition } from '../types';\nimport { auditTest } from './audit';\nimport { ciTest } from './ci';\nimport { deprecateTest } from './deprecate';\nimport { distTagsTest } from './dist-tags';\nimport { infoTest } from './info';\nimport { installTest } from './install';\nimport { loginTest } from './login';\nimport { packageNamesTest } from './package-names';\nimport { pingTest } from './ping';\nimport { publishTest } from './publish';\nimport { searchTest } from './search';\nimport { unpublishTest } from './unpublish';\n\nexport const allTests: TestDefinition[] = [\n publishTest,\n installTest,\n ciTest,\n auditTest,\n infoTest,\n deprecateTest,\n distTagsTest,\n packageNamesTest,\n loginTest,\n pingTest,\n searchTest,\n unpublishTest,\n // Scenarios (complex, multi-step tests)\n ...allScenarios,\n];\n\nexport {\n publishTest,\n installTest,\n ciTest,\n auditTest,\n infoTest,\n deprecateTest,\n distTagsTest,\n packageNamesTest,\n loginTest,\n pingTest,\n searchTest,\n unpublishTest,\n // Scenarios\n installMultipleDepsScenario,\n minimumReleaseAgeScenario,\n tarballsScenario,\n metadataScenario,\n searchScenario,\n uplinkFailureScenario,\n reservedNamesScenario,\n allScenarios,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwBA,IAAa,WAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,GAAG;AACL"}
@@ -1,4 +1,5 @@
1
1
  import { createTempFolder, getPackageJSON, getREADME } from "../utils/project.js";
2
+ import { LegacyLoginError, createUser, legacyLogin, whoami } from "../utils/registry-client.js";
2
3
  import buildDebug from "debug";
3
4
  import { join } from "path";
4
5
  import { writeFile } from "fs/promises";
@@ -110,6 +111,29 @@ async function testLogin(ctx) {
110
111
  const who2 = await yarnWhoami(ctx, tf);
111
112
  assert.ok(who2.includes(user2), `Expected "${user2}" after switch but got "${who2}"`);
112
113
  });
114
+ for (const client of [
115
+ "npm",
116
+ "yarn",
117
+ "pnpm"
118
+ ]) {
119
+ await ctx.subTest(`${client} logs an existing user in with the Basic retry`, async () => {
120
+ const user = `login-retry-${client}-${id}`;
121
+ const password = "retry-password-123";
122
+ await createUser(ctx.registryUrl, user, password);
123
+ const token = await legacyLogin(ctx.registryUrl, user, password, client);
124
+ assert.strictEqual(await whoami(ctx.registryUrl, token), user);
125
+ });
126
+ await ctx.subTest(`${client} registers a new user`, async () => {
127
+ const user = `login-new-${client}-${id}`;
128
+ const token = await legacyLogin(ctx.registryUrl, user, "retry-password-123", client);
129
+ assert.strictEqual(await whoami(ctx.registryUrl, token), user);
130
+ });
131
+ await ctx.subTest(`${client} cannot log an existing user in with a wrong password`, async () => {
132
+ const user = `login-wrong-${client}-${id}`;
133
+ await createUser(ctx.registryUrl, user, "retry-password-123");
134
+ await assert.rejects(legacyLogin(ctx.registryUrl, user, "wrong-password", client), (error) => error instanceof LegacyLoginError && error.status === 401);
135
+ });
136
+ }
113
137
  }
114
138
  var loginTest = {
115
139
  name: "login",
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","names":[],"sources":["../../../src/tests/login.ts"],"sourcesContent":["import assert from 'assert';\nimport buildDebug from 'debug';\nimport { writeFile } from 'fs/promises';\nimport * as YAML from 'js-yaml';\nimport { join } from 'path';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { createTempFolder, getPackageJSON, getREADME } from '../utils/project';\n\nconst debug = buildDebug('verdaccio:e2e-cli:test:login');\n\n/**\n * Creates a project with registry config but WITHOUT auth token,\n * so whoami will use the token saved by login to ~/.yarnrc.yml.\n */\nasync function prepareLoginProject(ctx: TestContext, name: string): Promise<string> {\n debug('preparing project for %s (no auth token)', name);\n const tempFolder = await createTempFolder(name);\n const yamlContent = YAML.dump({\n npmRegistryServer: ctx.registryUrl,\n enableImmutableInstalls: false,\n unsafeHttpWhitelist: ['localhost'],\n });\n await writeFile(join(tempFolder, '.yarnrc.yml'), yamlContent);\n await writeFile(join(tempFolder, 'package.json'), JSON.stringify({ name, version: '1.0.0' }));\n if (ctx.adapter.importPlugin) {\n debug('importing npm-login plugin into %s', tempFolder);\n await ctx.adapter.importPlugin(tempFolder, 'npm-login');\n }\n return tempFolder;\n}\n\nasync function yarnLogin(\n ctx: TestContext,\n cwd: string,\n user: string,\n password: string,\n email: string\n) {\n debug('yarn npm login as %s', user);\n return ctx.adapter.exec(\n { cwd },\n 'login',\n '--auth-type=legacy',\n `--user=${user}`,\n `--password=${password}`,\n `--email=${email}`\n );\n}\n\nasync function yarnWhoami(ctx: TestContext, cwd: string): Promise<string> {\n const resp = await ctx.adapter.exec({ cwd }, 'whoami');\n debug('yarn npm whoami: %s', resp.stdout);\n return resp.stdout;\n}\n\nasync function testLogin(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n const id = ctx.runId;\n\n if (type !== 'yarn-modern') {\n debug('login test only supported for yarn-modern, skipping %s', type);\n return;\n }\n\n const user1 = `login-a-${id}`;\n const password1 = 'test-password-123';\n const email1 = `${user1}@test.example.com`;\n\n await ctx.subTest('create new user + whoami', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login1-${id}`);\n const loginResp = await yarnLogin(ctx, tf, user1, password1, email1);\n assert.ok(\n loginResp.stdout.includes('Logged in') || loginResp.stdout.includes('token saved'),\n `Expected login success message but got \"${loginResp.stdout}\"`\n );\n const who = await yarnWhoami(ctx, tf);\n assert.ok(who.includes(user1), `Expected whoami \"${user1}\" but got \"${who}\"`);\n });\n\n await ctx.subTest('legacy re-login is rejected without basic auth', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login2-${id}`);\n let loginFailed = false;\n try {\n await yarnLogin(ctx, tf, user1, password1, email1);\n } catch {\n loginFailed = true;\n }\n assert.ok(loginFailed, 'Legacy re-login for an existing user should fail without Basic auth');\n });\n\n await ctx.subTest('wrong password fails', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login3-${id}`);\n let loginFailed = false;\n try {\n await yarnLogin(ctx, tf, user1, 'wrong-password', email1);\n } catch {\n loginFailed = true;\n }\n assert.ok(loginFailed, 'Login with wrong password should have failed');\n });\n\n await ctx.subTest('login then publish', async () => {\n const pkgName = `@verdaccio/login-pub-${id}`;\n const publishUser = `login-pub-${id}`;\n const publishPassword = 'publish-password-123';\n const publishEmail = `${publishUser}@test.example.com`;\n const tf = await createTempFolder(`login-pub-${id}`);\n const yamlContent = YAML.dump({\n npmRegistryServer: ctx.registryUrl,\n enableImmutableInstalls: false,\n unsafeHttpWhitelist: ['localhost'],\n });\n await writeFile(join(tf, '.yarnrc.yml'), yamlContent);\n await writeFile(join(tf, 'package.json'), getPackageJSON(pkgName, '1.0.0'));\n await writeFile(join(tf, 'README.md'), getREADME(pkgName));\n if (ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tf, 'npm-login');\n }\n await yarnLogin(ctx, tf, publishUser, publishPassword, publishEmail);\n await ctx.adapter.exec({ cwd: tf }, 'install');\n const pubResp = await ctx.adapter.exec({ cwd: tf }, 'publish');\n assert.ok(\n pubResp.stdout.includes('Package archive published'),\n `Expected publish success but got \"${pubResp.stdout}\"`\n );\n });\n\n await ctx.subTest('switch users', async () => {\n const switchUser1 = `login-switch-a-${id}`;\n const switchPassword1 = 'switch-password-123';\n const switchEmail1 = `${switchUser1}@test.example.com`;\n const user2 = `login-b-${id}`;\n const password2 = 'other-password-456';\n const email2 = `${user2}@test.example.com`;\n const tf = await prepareLoginProject(ctx, `verdaccio-login5-${id}`);\n\n await yarnLogin(ctx, tf, switchUser1, switchPassword1, switchEmail1);\n const who1 = await yarnWhoami(ctx, tf);\n assert.ok(who1.includes(switchUser1), `Expected \"${switchUser1}\" but got \"${who1}\"`);\n\n await yarnLogin(ctx, tf, user2, password2, email2);\n const who2 = await yarnWhoami(ctx, tf);\n assert.ok(who2.includes(user2), `Expected \"${user2}\" after switch but got \"${who2}\"`);\n });\n}\n\nexport const loginTest: TestDefinition = {\n name: 'login',\n requires: ['login'],\n run: testLogin,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"login.js","names":[],"sources":["../../../src/tests/login.ts"],"sourcesContent":["import assert from 'assert';\nimport buildDebug from 'debug';\nimport { writeFile } from 'fs/promises';\nimport * as YAML from 'js-yaml';\nimport { join } from 'path';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { createTempFolder, getPackageJSON, getREADME } from '../utils/project';\nimport { LegacyLoginError, createUser, legacyLogin, whoami } from '../utils/registry-client';\n\nconst debug = buildDebug('verdaccio:e2e-cli:test:login');\n\n/**\n * Creates a project with registry config but WITHOUT auth token,\n * so whoami will use the token saved by login to ~/.yarnrc.yml.\n */\nasync function prepareLoginProject(ctx: TestContext, name: string): Promise<string> {\n debug('preparing project for %s (no auth token)', name);\n const tempFolder = await createTempFolder(name);\n const yamlContent = YAML.dump({\n npmRegistryServer: ctx.registryUrl,\n enableImmutableInstalls: false,\n unsafeHttpWhitelist: ['localhost'],\n });\n await writeFile(join(tempFolder, '.yarnrc.yml'), yamlContent);\n await writeFile(join(tempFolder, 'package.json'), JSON.stringify({ name, version: '1.0.0' }));\n if (ctx.adapter.importPlugin) {\n debug('importing npm-login plugin into %s', tempFolder);\n await ctx.adapter.importPlugin(tempFolder, 'npm-login');\n }\n return tempFolder;\n}\n\nasync function yarnLogin(\n ctx: TestContext,\n cwd: string,\n user: string,\n password: string,\n email: string\n) {\n debug('yarn npm login as %s', user);\n return ctx.adapter.exec(\n { cwd },\n 'login',\n '--auth-type=legacy',\n `--user=${user}`,\n `--password=${password}`,\n `--email=${email}`\n );\n}\n\nasync function yarnWhoami(ctx: TestContext, cwd: string): Promise<string> {\n const resp = await ctx.adapter.exec({ cwd }, 'whoami');\n debug('yarn npm whoami: %s', resp.stdout);\n return resp.stdout;\n}\n\nasync function testLogin(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n const id = ctx.runId;\n\n if (type !== 'yarn-modern') {\n debug('login test only supported for yarn-modern, skipping %s', type);\n return;\n }\n\n const user1 = `login-a-${id}`;\n const password1 = 'test-password-123';\n const email1 = `${user1}@test.example.com`;\n\n await ctx.subTest('create new user + whoami', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login1-${id}`);\n const loginResp = await yarnLogin(ctx, tf, user1, password1, email1);\n assert.ok(\n loginResp.stdout.includes('Logged in') || loginResp.stdout.includes('token saved'),\n `Expected login success message but got \"${loginResp.stdout}\"`\n );\n const who = await yarnWhoami(ctx, tf);\n assert.ok(who.includes(user1), `Expected whoami \"${user1}\" but got \"${who}\"`);\n });\n\n await ctx.subTest('legacy re-login is rejected without basic auth', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login2-${id}`);\n let loginFailed = false;\n try {\n await yarnLogin(ctx, tf, user1, password1, email1);\n } catch {\n loginFailed = true;\n }\n assert.ok(loginFailed, 'Legacy re-login for an existing user should fail without Basic auth');\n });\n\n await ctx.subTest('wrong password fails', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login3-${id}`);\n let loginFailed = false;\n try {\n await yarnLogin(ctx, tf, user1, 'wrong-password', email1);\n } catch {\n loginFailed = true;\n }\n assert.ok(loginFailed, 'Login with wrong password should have failed');\n });\n\n await ctx.subTest('login then publish', async () => {\n const pkgName = `@verdaccio/login-pub-${id}`;\n const publishUser = `login-pub-${id}`;\n const publishPassword = 'publish-password-123';\n const publishEmail = `${publishUser}@test.example.com`;\n const tf = await createTempFolder(`login-pub-${id}`);\n const yamlContent = YAML.dump({\n npmRegistryServer: ctx.registryUrl,\n enableImmutableInstalls: false,\n unsafeHttpWhitelist: ['localhost'],\n });\n await writeFile(join(tf, '.yarnrc.yml'), yamlContent);\n await writeFile(join(tf, 'package.json'), getPackageJSON(pkgName, '1.0.0'));\n await writeFile(join(tf, 'README.md'), getREADME(pkgName));\n if (ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tf, 'npm-login');\n }\n await yarnLogin(ctx, tf, publishUser, publishPassword, publishEmail);\n await ctx.adapter.exec({ cwd: tf }, 'install');\n const pubResp = await ctx.adapter.exec({ cwd: tf }, 'publish');\n assert.ok(\n pubResp.stdout.includes('Package archive published'),\n `Expected publish success but got \"${pubResp.stdout}\"`\n );\n });\n\n await ctx.subTest('switch users', async () => {\n const switchUser1 = `login-switch-a-${id}`;\n const switchPassword1 = 'switch-password-123';\n const switchEmail1 = `${switchUser1}@test.example.com`;\n const user2 = `login-b-${id}`;\n const password2 = 'other-password-456';\n const email2 = `${user2}@test.example.com`;\n const tf = await prepareLoginProject(ctx, `verdaccio-login5-${id}`);\n\n await yarnLogin(ctx, tf, switchUser1, switchPassword1, switchEmail1);\n const who1 = await yarnWhoami(ctx, tf);\n assert.ok(who1.includes(switchUser1), `Expected \"${switchUser1}\" but got \"${who1}\"`);\n\n await yarnLogin(ctx, tf, user2, password2, email2);\n const who2 = await yarnWhoami(ctx, tf);\n assert.ok(who2.includes(user2), `Expected \"${user2}\" after switch but got \"${who2}\"`);\n });\n\n // replayed over HTTP: what `npm login`, native `yarn npm login` and `pnpm login` send\n for (const client of ['npm', 'yarn', 'pnpm'] as const) {\n await ctx.subTest(`${client} logs an existing user in with the Basic retry`, async () => {\n const user = `login-retry-${client}-${id}`;\n const password = 'retry-password-123';\n await createUser(ctx.registryUrl, user, password);\n\n const token = await legacyLogin(ctx.registryUrl, user, password, client);\n assert.strictEqual(await whoami(ctx.registryUrl, token), user);\n });\n\n await ctx.subTest(`${client} registers a new user`, async () => {\n const user = `login-new-${client}-${id}`;\n\n const token = await legacyLogin(ctx.registryUrl, user, 'retry-password-123', client);\n assert.strictEqual(await whoami(ctx.registryUrl, token), user);\n });\n\n await ctx.subTest(\n `${client} cannot log an existing user in with a wrong password`,\n async () => {\n const user = `login-wrong-${client}-${id}`;\n await createUser(ctx.registryUrl, user, 'retry-password-123');\n\n await assert.rejects(\n legacyLogin(ctx.registryUrl, user, 'wrong-password', client),\n (error: unknown) => error instanceof LegacyLoginError && error.status === 401\n );\n }\n );\n }\n}\n\nexport const loginTest: TestDefinition = {\n name: 'login',\n requires: ['login'],\n run: testLogin,\n};\n"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { downloadTarball, fetchPackument } from "../utils/http-client.js";
2
+ import { pendingContractChecksEnabled } from "../utils/pending-checks.js";
3
+ import { publishViaHttp } from "../utils/publish.js";
4
+ import assert from "assert";
5
+ //#region src/tests/package-names.ts
6
+ async function testPackageNames(ctx) {
7
+ const id = ctx.runId;
8
+ const installable = [
9
+ `JSONStream-${id}`,
10
+ `@e2e-names/_x-${id}`,
11
+ `@con/x-${id}`
12
+ ];
13
+ await ctx.subTest("unusual valid names are published and served", async () => {
14
+ for (const name of [...installable, `-x-${id}`]) {
15
+ await publishViaHttp(ctx, name);
16
+ const { status, body } = await fetchPackument(ctx.registryUrl, name);
17
+ assert.strictEqual(status, 200, `Expected 200 for ${name}, got ${status}`);
18
+ assert.strictEqual(body.name, name, `Packument name mismatch for ${name}`);
19
+ const download = await downloadTarball(body.versions["1.0.0"].dist.tarball);
20
+ assert.strictEqual(download.status, 200, `Tarball download failed for ${name}`);
21
+ }
22
+ });
23
+ if (ctx.adapter.supports.has("install")) await ctx.subTest("unusual valid names install with the client", async () => {
24
+ const { tempFolder } = await ctx.adapter.prepareProject(`e2e-names-consumer-${id}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token, Object.fromEntries(installable.map((name) => [name, "1.0.0"])));
25
+ await ctx.adapter.exec({ cwd: tempFolder }, "install", ...ctx.adapter.registryArg(ctx.registryUrl));
26
+ });
27
+ if (pendingContractChecksEnabled("package-names")) await ctx.subTest("names npm does not accept are rejected", async () => {
28
+ for (const name of [
29
+ `_x-${id}`,
30
+ `@x-${id}`,
31
+ `x-${id}@y`
32
+ ]) {
33
+ const response = await fetch(`${ctx.registryUrl}/${encodeURIComponent(name)}`);
34
+ assert.strictEqual(response.status, 400, `Expected 400 for ${name}, got ${response.status}`);
35
+ }
36
+ });
37
+ }
38
+ var packageNamesTest = {
39
+ name: "package-names",
40
+ run: testPackageNames
41
+ };
42
+ //#endregion
43
+ export { packageNamesTest };
44
+
45
+ //# sourceMappingURL=package-names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-names.js","names":[],"sources":["../../../src/tests/package-names.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { downloadTarball, fetchPackument } from '../utils/http-client';\nimport { pendingContractChecksEnabled } from '../utils/pending-checks';\nimport { publishViaHttp } from '../utils/publish';\n\nasync function testPackageNames(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n // Valid for existing packages, though some clients refuse to publish them (capitals).\n const installable = [`JSONStream-${id}`, `@e2e-names/_x-${id}`, `@con/x-${id}`];\n\n await ctx.subTest('unusual valid names are published and served', async () => {\n // A leading hyphen is valid for existing packages but npm clients no longer parse it.\n for (const name of [...installable, `-x-${id}`]) {\n await publishViaHttp(ctx, name);\n const { status, body } = await fetchPackument(ctx.registryUrl, name);\n assert.strictEqual(status, 200, `Expected 200 for ${name}, got ${status}`);\n assert.strictEqual(body.name, name, `Packument name mismatch for ${name}`);\n const download = await downloadTarball(body.versions['1.0.0'].dist.tarball);\n assert.strictEqual(download.status, 200, `Tarball download failed for ${name}`);\n }\n });\n\n if (ctx.adapter.supports.has('install')) {\n await ctx.subTest('unusual valid names install with the client', async () => {\n const { tempFolder } = await ctx.adapter.prepareProject(\n `e2e-names-consumer-${id}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n Object.fromEntries(installable.map((name) => [name, '1.0.0']))\n );\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n });\n }\n\n if (pendingContractChecksEnabled('package-names')) {\n await ctx.subTest('names npm does not accept are rejected', async () => {\n for (const name of [`_x-${id}`, `@x-${id}`, `x-${id}@y`]) {\n const response = await fetch(`${ctx.registryUrl}/${encodeURIComponent(name)}`);\n assert.strictEqual(\n response.status,\n 400,\n `Expected 400 for ${name}, got ${response.status}`\n );\n }\n });\n }\n}\n\nexport const packageNamesTest: TestDefinition = {\n name: 'package-names',\n run: testPackageNames,\n};\n"],"mappings":";;;;;AAOA,eAAe,iBAAiB,KAAiC;CAC/D,MAAM,KAAK,IAAI;CAEf,MAAM,cAAc;EAAC,cAAc;EAAM,iBAAiB;EAAM,UAAU;CAAI;CAE9E,MAAM,IAAI,QAAQ,gDAAgD,YAAY;EAE5E,KAAK,MAAM,QAAQ,CAAC,GAAG,aAAa,MAAM,IAAI,GAAG;GAC/C,MAAM,eAAe,KAAK,IAAI;GAC9B,MAAM,EAAE,QAAQ,SAAS,MAAM,eAAe,IAAI,aAAa,IAAI;GACnE,OAAO,YAAY,QAAQ,KAAK,oBAAoB,KAAK,QAAQ,QAAQ;GACzE,OAAO,YAAY,KAAK,MAAM,MAAM,+BAA+B,MAAM;GACzE,MAAM,WAAW,MAAM,gBAAgB,KAAK,SAAS,QAAQ,CAAC,KAAK,OAAO;GAC1E,OAAO,YAAY,SAAS,QAAQ,KAAK,+BAA+B,MAAM;EAChF;CACF,CAAC;CAED,IAAI,IAAI,QAAQ,SAAS,IAAI,SAAS,GACpC,MAAM,IAAI,QAAQ,+CAA+C,YAAY;EAC3E,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,sBAAsB,MACtB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,OAAO,YAAY,YAAY,KAAK,SAAS,CAAC,MAAM,OAAO,CAAC,CAAC,CAC/D;EACA,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACF,CAAC;CAGH,IAAI,6BAA6B,eAAe,GAC9C,MAAM,IAAI,QAAQ,0CAA0C,YAAY;EACtE,KAAK,MAAM,QAAQ;GAAC,MAAM;GAAM,MAAM;GAAM,KAAK,GAAG;EAAG,GAAG;GACxD,MAAM,WAAW,MAAM,MAAM,GAAG,IAAI,YAAY,GAAG,mBAAmB,IAAI,GAAG;GAC7E,OAAO,YACL,SAAS,QACT,KACA,oBAAoB,KAAK,QAAQ,SAAS,QAC5C;EACF;CACF,CAAC;AAEL;AAEA,IAAa,mBAAmC;CAC9C,MAAM;CACN,KAAK;AACP"}