@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.
- package/build/adapters/index.d.ts +2 -0
- package/build/adapters/nub.d.ts +2 -0
- package/build/adapters/upm.d.ts +2 -0
- package/build/cjs/adapters/index.cjs +4 -0
- package/build/cjs/adapters/nub.cjs +67 -0
- package/build/cjs/adapters/nub.cjs.map +1 -0
- package/build/cjs/adapters/pnpm.cjs +3 -22
- package/build/cjs/adapters/pnpm.cjs.map +1 -1
- package/build/cjs/adapters/upm.cjs +65 -0
- package/build/cjs/adapters/upm.cjs.map +1 -0
- package/build/cjs/index.cjs +10 -3
- package/build/cjs/index.cjs.map +1 -1
- package/build/cjs/scenarios/index.cjs +4 -1
- package/build/cjs/scenarios/index.cjs.map +1 -1
- package/build/cjs/scenarios/minimum-release-age.cjs.map +1 -1
- package/build/cjs/scenarios/reserved-names.cjs +88 -0
- package/build/cjs/scenarios/reserved-names.cjs.map +1 -0
- package/build/cjs/tests/dist-tags.cjs +12 -0
- package/build/cjs/tests/dist-tags.cjs.map +1 -1
- package/build/cjs/tests/index.cjs +4 -0
- package/build/cjs/tests/index.cjs.map +1 -1
- package/build/cjs/tests/login.cjs +24 -0
- package/build/cjs/tests/login.cjs.map +1 -1
- package/build/cjs/tests/package-names.cjs +47 -0
- package/build/cjs/tests/package-names.cjs.map +1 -0
- package/build/cjs/utils/e2e-config.cjs +9 -2
- package/build/cjs/utils/e2e-config.cjs.map +1 -1
- package/build/cjs/utils/mock-uplink.cjs +4 -3
- package/build/cjs/utils/mock-uplink.cjs.map +1 -1
- package/build/cjs/utils/registry-client.cjs +59 -0
- package/build/cjs/utils/registry-client.cjs.map +1 -1
- package/build/esm/adapters/index.js +3 -1
- package/build/esm/adapters/nub.js +65 -0
- package/build/esm/adapters/nub.js.map +1 -0
- package/build/esm/adapters/pnpm.js +3 -22
- package/build/esm/adapters/pnpm.js.map +1 -1
- package/build/esm/adapters/upm.js +63 -0
- package/build/esm/adapters/upm.js.map +1 -0
- package/build/esm/index.js +9 -4
- package/build/esm/index.js.map +1 -1
- package/build/esm/scenarios/index.js +4 -2
- package/build/esm/scenarios/index.js.map +1 -1
- package/build/esm/scenarios/minimum-release-age.js.map +1 -1
- package/build/esm/scenarios/reserved-names.js +86 -0
- package/build/esm/scenarios/reserved-names.js.map +1 -0
- package/build/esm/tests/dist-tags.js +12 -0
- package/build/esm/tests/dist-tags.js.map +1 -1
- package/build/esm/tests/index.js +4 -1
- package/build/esm/tests/index.js.map +1 -1
- package/build/esm/tests/login.js +24 -0
- package/build/esm/tests/login.js.map +1 -1
- package/build/esm/tests/package-names.js +45 -0
- package/build/esm/tests/package-names.js.map +1 -0
- package/build/esm/utils/e2e-config.js +9 -2
- package/build/esm/utils/e2e-config.js.map +1 -1
- package/build/esm/utils/mock-uplink.js +4 -3
- package/build/esm/utils/mock-uplink.js.map +1 -1
- package/build/esm/utils/registry-client.js +57 -1
- package/build/esm/utils/registry-client.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/scenarios/index.d.ts +2 -1
- package/build/scenarios/reserved-names.d.ts +2 -0
- package/build/tests/index.d.ts +3 -2
- package/build/tests/package-names.d.ts +2 -0
- package/build/types.d.ts +2 -2
- package/build/utils/e2e-config.d.ts +3 -2
- package/build/utils/mock-uplink.d.ts +2 -2
- package/build/utils/registry-client.d.ts +11 -0
- package/package.json +2 -2
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* - max_body_size raised so scenario:tarballs can publish ~30 MB packages
|
|
8
8
|
* (Verdaccio's default is 10mb)
|
|
9
|
-
* - an `e2emock` uplink + `e2e-uplink
|
|
10
|
-
* uplink server that scenario:uplink-failure
|
|
9
|
+
* - an `e2emock` uplink + `e2e-uplink-*`, `nul.*` and `@e2e-uplink/*` package
|
|
10
|
+
* patterns wired to the mock uplink server that scenario:uplink-failure and
|
|
11
|
+
* scenario:reserved-names start on E2E_UPLINK_PORT
|
|
11
12
|
* (short timeout so the "slow uplink" case completes quickly; high
|
|
12
13
|
* max_fails so simulated failures don't trip the circuit breaker for
|
|
13
14
|
* later sub-tests)
|
|
@@ -41,6 +42,12 @@ packages:
|
|
|
41
42
|
'e2e-uplink-*':
|
|
42
43
|
access: $all
|
|
43
44
|
proxy: e2emock
|
|
45
|
+
'nul.*':
|
|
46
|
+
access: $all
|
|
47
|
+
proxy: e2emock
|
|
48
|
+
'@e2e-uplink/*':
|
|
49
|
+
access: $all
|
|
50
|
+
proxy: e2emock
|
|
44
51
|
'@*/*':
|
|
45
52
|
access: $all
|
|
46
53
|
publish: $authenticated
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"e2e-config.js","names":[],"sources":["../../../src/utils/e2e-config.ts"],"sourcesContent":["/**\n * Recommended Verdaccio config for running the full e2e-cli battery,\n * printed by `verdaccio-e2e --print-config`. It is what the consuming repos'\n * CI jobs and scripts/run-e2e.sh feed to `verdaccio --config`:\n *\n * - max_body_size raised so scenario:tarballs can publish ~30 MB packages\n * (Verdaccio's default is 10mb)\n * - an `e2emock` uplink + `e2e-uplink
|
|
1
|
+
{"version":3,"file":"e2e-config.js","names":[],"sources":["../../../src/utils/e2e-config.ts"],"sourcesContent":["/**\n * Recommended Verdaccio config for running the full e2e-cli battery,\n * printed by `verdaccio-e2e --print-config`. It is what the consuming repos'\n * CI jobs and scripts/run-e2e.sh feed to `verdaccio --config`:\n *\n * - max_body_size raised so scenario:tarballs can publish ~30 MB packages\n * (Verdaccio's default is 10mb)\n * - an `e2emock` uplink + `e2e-uplink-*`, `nul.*` and `@e2e-uplink/*` package\n * patterns wired to the mock uplink server that scenario:uplink-failure and\n * scenario:reserved-names start on E2E_UPLINK_PORT\n * (short timeout so the \"slow uplink\" case completes quickly; high\n * max_fails so simulated failures don't trip the circuit breaker for\n * later sub-tests)\n * - NO npmjs uplink: the whole suite publishes every package it consumes,\n * so the registry under test never talks to the outside world and the\n * battery runs fully offline (and deterministically in CI)\n *\n * Relative paths (storage/htpasswd) resolve next to the config file, so write\n * it into a disposable directory.\n */\nexport const DEFAULT_UPLINK_PORT = 4874;\n\nexport function buildE2EConfig(uplinkPort: number = DEFAULT_UPLINK_PORT): string {\n return `# Generated by @verdaccio/e2e-cli --print-config\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nweb:\n enable: true\n title: Verdaccio E2E\nmax_body_size: 100mb\nuplinks:\n e2emock:\n url: http://localhost:${uplinkPort}/\n timeout: 3s\n retry: 0\n max_fails: 100\n fail_timeout: 1s\npackages:\n 'e2e-uplink-*':\n access: $all\n proxy: e2emock\n 'nul.*':\n access: $all\n proxy: e2emock\n '@e2e-uplink/*':\n access: $all\n proxy: e2emock\n '@*/*':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\n '**':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n\n/**\n * Recommended pnpr config for the same battery, printed by\n * `verdaccio-e2e --print-config --server pnpr`. pnpr's config is\n * verdaccio-shaped but routes through a `registries:` block instead of a\n * global `packages:` map (which pnpr rejects loudly):\n *\n * - one hosted registry claiming every name (`**`), publish open to\n * authenticated users (pnpr's default) and unpublish loosened to match\n * the verdaccio battery config\n * - no `e2emock` uplink: pnpr registry namespaces only accept exact\n * names, scope wildcards or `**`, so the unscoped dynamic\n * `e2e-uplink-*` names can't be routed to the mock uplink. Run pnpr\n * without --uplink-port and scenario:uplink-failure (plus the uplink\n * search sub-tests) skip, exactly like docker mode.\n * - no max_body_size knob: pnpr's publish body limit is a fixed 100 MB,\n * already above what scenario:tarballs needs\n */\nexport function buildPnprE2EConfig(): string {\n return `# Generated by @verdaccio/e2e-cli --print-config --server pnpr\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nregistries:\n local:\n type: hosted\n access: $all\n packages:\n '**': { unpublish: $authenticated }\ndefaultRegistry: local\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBA,IAAa,sBAAsB;AAEnC,SAAgB,eAAe,aAAqB,qBAA6B;CAC/E,OAAO;;;;;;;;;;;;4BAYmB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;AAyBvC;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,qBAA6B;CAC3C,OAAO;;;;;;;;;;;;;;;AAeT"}
|
|
@@ -21,9 +21,9 @@ var MockUplink = class {
|
|
|
21
21
|
constructor(port) {
|
|
22
22
|
this.port = port;
|
|
23
23
|
}
|
|
24
|
-
/** Register a package served by this uplink; tarball is random (incompressible) bytes. */
|
|
24
|
+
/** Register a package served by this uplink; tarball is random (incompressible) bytes unless one is given. */
|
|
25
25
|
addPackage(name, tarballBytes = 65536) {
|
|
26
|
-
const tarball = randomBytes(tarballBytes);
|
|
26
|
+
const tarball = Buffer.isBuffer(tarballBytes) ? tarballBytes : randomBytes(tarballBytes);
|
|
27
27
|
const { shasum, integrity } = computeDist(tarball);
|
|
28
28
|
const pkg = {
|
|
29
29
|
name,
|
|
@@ -83,7 +83,8 @@ var MockUplink = class {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
packument(pkg) {
|
|
86
|
-
const
|
|
86
|
+
const filename = `${pkg.name.split("/").pop()}-${pkg.version}.tgz`;
|
|
87
|
+
const tarballUrl = `http://localhost:${this.port}/${pkg.name}/-/${filename}`;
|
|
87
88
|
return JSON.stringify({
|
|
88
89
|
name: pkg.name,
|
|
89
90
|
"dist-tags": { latest: pkg.version },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-uplink.js","names":[],"sources":["../../../src/utils/mock-uplink.ts"],"sourcesContent":["import { randomBytes } from 'crypto';\nimport buildDebug from 'debug';\nimport { Server, createServer } from 'http';\n\nimport { computeDist } from './http-client';\n\nconst debug = buildDebug('verdaccio:e2e-cli:mock-uplink');\n\nexport type UplinkMode =\n /** Serve packuments and tarballs normally */\n | 'ok'\n /** Send tarball headers + half the body, then destroy the socket */\n | 'drop-mid-stream'\n /** Delay every response long enough to trip the registry's uplink timeout */\n | 'slow'\n /** Respond 500 to everything */\n | 'error-500';\n\ntype MockPackage = {\n name: string;\n version: string;\n tarball: Buffer;\n shasum: string;\n integrity: string;\n};\n\n/**\n * Minimal controllable npm uplink used by scenario:uplink-failure.\n *\n * The registry under test is configured (see --print-config) to proxy the\n * `e2e-uplink-*` package pattern to this server, so the scenario can flip the\n * uplink between healthy, dropping connections mid-tarball, slow, and down.\n */\nexport class MockUplink {\n public mode: UplinkMode = 'ok';\n public requests: string[] = [];\n private server?: Server;\n private packages = new Map<string, MockPackage>();\n private searchResults: { name: string; version: string }[] = [];\n\n constructor(public readonly port: number) {}\n\n /** Register a package served by this uplink; tarball is random (incompressible) bytes. */\n addPackage(name: string, tarballBytes = 64 * 1024): MockPackage {\n const tarball = randomBytes(tarballBytes);\n const { shasum, integrity } = computeDist(tarball);\n const pkg: MockPackage = { name, version: '1.0.0', tarball, shasum, integrity };\n this.packages.set(name, pkg);\n return pkg;\n }\n\n /** Register a result served by the /-/v1/search endpoint. */\n addSearchResult(name: string, version = '1.0.0'): void {\n this.searchResults.push({ name, version });\n }\n\n /**\n * npmjs-faithful /-/v1/search: filters by `text` substring, applies\n * `from`/`size` itself (like registry.npmjs.org does) and reports the real\n * `total` of matches. Mimicking the upstream offsetting is what exposes\n * double pagination in the registry under test.\n */\n private searchResponse(url: URL): string {\n const text = url.searchParams.get('text') ?? '';\n const from = Math.max(0, parseInt(url.searchParams.get('from') ?? '0', 10) || 0);\n const size = Math.max(0, parseInt(url.searchParams.get('size') ?? '20', 10) || 20);\n const matches = this.searchResults.filter((r) => r.name.includes(text));\n const page = matches.slice(from, from + size);\n return JSON.stringify({\n objects: page.map((r) => ({\n package: {\n name: r.name,\n version: r.version,\n description: 'mock uplink search result',\n date: '2020-01-01T00:00:00.000Z',\n maintainers: [{ username: 'mock-uplink', email: 'mock@example.org' }],\n links: { npm: `http://localhost:${this.port}/${r.name}` },\n },\n score: { final: 1, detail: { quality: 1, popularity: 1, maintenance: 1 } },\n searchScore: 1,\n flags: {},\n })),\n total: matches.length,\n time: new Date('2020-01-01T00:00:00.000Z').toISOString(),\n });\n }\n\n private packument(pkg: MockPackage): string {\n const tarballUrl = `http://localhost:${this.port}/${pkg.name}/-/${pkg.name}-${pkg.version}.tgz`;\n return JSON.stringify({\n name: pkg.name,\n 'dist-tags': { latest: pkg.version },\n versions: {\n [pkg.version]: {\n name: pkg.name,\n version: pkg.version,\n description: 'mock uplink package',\n dist: { tarball: tarballUrl, shasum: pkg.shasum, integrity: pkg.integrity },\n },\n },\n time: {\n created: '2020-01-01T00:00:00.000Z',\n modified: '2020-01-01T00:00:00.000Z',\n [pkg.version]: '2020-01-01T00:00:00.000Z',\n },\n });\n }\n\n start(): Promise<void> {\n this.server = createServer((req, res) => {\n const url = req.url || '';\n this.requests.push(url);\n debug('mock uplink %s %s (mode: %s)', req.method, url, this.mode);\n\n const respond = () => {\n if (this.mode === 'error-500') {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'mock uplink internal error' }));\n return;\n }\n\n // Search route: /-/v1/search?text=...&from=...&size=...\n if (url.startsWith('/-/v1/search')) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(this.searchResponse(new URL(url, `http://localhost:${this.port}`)));\n return;\n }\n\n // Tarball route: /<name>/-/<file>.tgz — parsed without a regex to\n // avoid backtracking on untrusted input (CodeQL js/polynomial-redos).\n const tarballSep = url.indexOf('/-/');\n if (tarballSep > 0 && url.endsWith('.tgz')) {\n const pkg = this.packages.get(decodeURIComponent(url.slice(1, tarballSep)));\n if (!pkg) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'not found' }));\n return;\n }\n if (this.mode === 'drop-mid-stream') {\n res.writeHead(200, {\n 'Content-Type': 'application/octet-stream',\n 'Content-Length': String(pkg.tarball.length),\n });\n res.write(pkg.tarball.subarray(0, Math.floor(pkg.tarball.length / 2)));\n // Kill the connection before the body completes.\n setTimeout(() => res.socket?.destroy(), 50);\n return;\n }\n res.writeHead(200, {\n 'Content-Type': 'application/octet-stream',\n 'Content-Length': String(pkg.tarball.length),\n });\n res.end(pkg.tarball);\n return;\n }\n\n // Packument route: /<name>\n const pkg = this.packages.get(decodeURIComponent(url.slice(1)));\n if (!pkg) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'not found' }));\n return;\n }\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(this.packument(pkg));\n };\n\n // The registry (or a stopped server) may have torn the socket down by the\n // time a delayed response fires — never let that crash the mock.\n const safeRespond = () => {\n try {\n respond();\n } catch (err) {\n debug('mock uplink response failed (socket gone?): %s', err);\n }\n };\n\n if (this.mode === 'slow') {\n // Longer than the uplink timeout configured for the e2e registry (3s).\n const timer = setTimeout(safeRespond, 10_000);\n res.socket?.once('close', () => clearTimeout(timer));\n } else {\n safeRespond();\n }\n });\n\n return new Promise((resolve, reject) => {\n this.server!.once('error', reject);\n this.server!.listen(this.port, () => {\n debug('mock uplink listening on %d', this.port);\n resolve();\n });\n });\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => {\n if (!this.server) {\n resolve();\n return;\n }\n // closeAllConnections drops keep-alive sockets so close() completes.\n this.server.closeAllConnections?.();\n this.server.close(() => {\n debug('mock uplink stopped');\n this.server = undefined;\n resolve();\n });\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mock-uplink.js","names":[],"sources":["../../../src/utils/mock-uplink.ts"],"sourcesContent":["import { randomBytes } from 'crypto';\nimport buildDebug from 'debug';\nimport { Server, createServer } from 'http';\n\nimport { computeDist } from './http-client';\n\nconst debug = buildDebug('verdaccio:e2e-cli:mock-uplink');\n\nexport type UplinkMode =\n /** Serve packuments and tarballs normally */\n | 'ok'\n /** Send tarball headers + half the body, then destroy the socket */\n | 'drop-mid-stream'\n /** Delay every response long enough to trip the registry's uplink timeout */\n | 'slow'\n /** Respond 500 to everything */\n | 'error-500';\n\ntype MockPackage = {\n name: string;\n version: string;\n tarball: Buffer;\n shasum: string;\n integrity: string;\n};\n\n/**\n * Minimal controllable npm uplink used by scenario:uplink-failure.\n *\n * The registry under test is configured (see --print-config) to proxy the\n * `e2e-uplink-*` package pattern to this server, so the scenario can flip the\n * uplink between healthy, dropping connections mid-tarball, slow, and down.\n */\nexport class MockUplink {\n public mode: UplinkMode = 'ok';\n public requests: string[] = [];\n private server?: Server;\n private packages = new Map<string, MockPackage>();\n private searchResults: { name: string; version: string }[] = [];\n\n constructor(public readonly port: number) {}\n\n /** Register a package served by this uplink; tarball is random (incompressible) bytes unless one is given. */\n addPackage(name: string, tarballBytes: number | Buffer = 64 * 1024): MockPackage {\n const tarball = Buffer.isBuffer(tarballBytes) ? tarballBytes : randomBytes(tarballBytes);\n const { shasum, integrity } = computeDist(tarball);\n const pkg: MockPackage = { name, version: '1.0.0', tarball, shasum, integrity };\n this.packages.set(name, pkg);\n return pkg;\n }\n\n /** Register a result served by the /-/v1/search endpoint. */\n addSearchResult(name: string, version = '1.0.0'): void {\n this.searchResults.push({ name, version });\n }\n\n /**\n * npmjs-faithful /-/v1/search: filters by `text` substring, applies\n * `from`/`size` itself (like registry.npmjs.org does) and reports the real\n * `total` of matches. Mimicking the upstream offsetting is what exposes\n * double pagination in the registry under test.\n */\n private searchResponse(url: URL): string {\n const text = url.searchParams.get('text') ?? '';\n const from = Math.max(0, parseInt(url.searchParams.get('from') ?? '0', 10) || 0);\n const size = Math.max(0, parseInt(url.searchParams.get('size') ?? '20', 10) || 20);\n const matches = this.searchResults.filter((r) => r.name.includes(text));\n const page = matches.slice(from, from + size);\n return JSON.stringify({\n objects: page.map((r) => ({\n package: {\n name: r.name,\n version: r.version,\n description: 'mock uplink search result',\n date: '2020-01-01T00:00:00.000Z',\n maintainers: [{ username: 'mock-uplink', email: 'mock@example.org' }],\n links: { npm: `http://localhost:${this.port}/${r.name}` },\n },\n score: { final: 1, detail: { quality: 1, popularity: 1, maintenance: 1 } },\n searchScore: 1,\n flags: {},\n })),\n total: matches.length,\n time: new Date('2020-01-01T00:00:00.000Z').toISOString(),\n });\n }\n\n private packument(pkg: MockPackage): string {\n const filename = `${pkg.name.split('/').pop()}-${pkg.version}.tgz`;\n const tarballUrl = `http://localhost:${this.port}/${pkg.name}/-/${filename}`;\n return JSON.stringify({\n name: pkg.name,\n 'dist-tags': { latest: pkg.version },\n versions: {\n [pkg.version]: {\n name: pkg.name,\n version: pkg.version,\n description: 'mock uplink package',\n dist: { tarball: tarballUrl, shasum: pkg.shasum, integrity: pkg.integrity },\n },\n },\n time: {\n created: '2020-01-01T00:00:00.000Z',\n modified: '2020-01-01T00:00:00.000Z',\n [pkg.version]: '2020-01-01T00:00:00.000Z',\n },\n });\n }\n\n start(): Promise<void> {\n this.server = createServer((req, res) => {\n const url = req.url || '';\n this.requests.push(url);\n debug('mock uplink %s %s (mode: %s)', req.method, url, this.mode);\n\n const respond = () => {\n if (this.mode === 'error-500') {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'mock uplink internal error' }));\n return;\n }\n\n // Search route: /-/v1/search?text=...&from=...&size=...\n if (url.startsWith('/-/v1/search')) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(this.searchResponse(new URL(url, `http://localhost:${this.port}`)));\n return;\n }\n\n // Tarball route: /<name>/-/<file>.tgz — parsed without a regex to\n // avoid backtracking on untrusted input (CodeQL js/polynomial-redos).\n const tarballSep = url.indexOf('/-/');\n if (tarballSep > 0 && url.endsWith('.tgz')) {\n const pkg = this.packages.get(decodeURIComponent(url.slice(1, tarballSep)));\n if (!pkg) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'not found' }));\n return;\n }\n if (this.mode === 'drop-mid-stream') {\n res.writeHead(200, {\n 'Content-Type': 'application/octet-stream',\n 'Content-Length': String(pkg.tarball.length),\n });\n res.write(pkg.tarball.subarray(0, Math.floor(pkg.tarball.length / 2)));\n // Kill the connection before the body completes.\n setTimeout(() => res.socket?.destroy(), 50);\n return;\n }\n res.writeHead(200, {\n 'Content-Type': 'application/octet-stream',\n 'Content-Length': String(pkg.tarball.length),\n });\n res.end(pkg.tarball);\n return;\n }\n\n // Packument route: /<name>\n const pkg = this.packages.get(decodeURIComponent(url.slice(1)));\n if (!pkg) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'not found' }));\n return;\n }\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(this.packument(pkg));\n };\n\n // The registry (or a stopped server) may have torn the socket down by the\n // time a delayed response fires — never let that crash the mock.\n const safeRespond = () => {\n try {\n respond();\n } catch (err) {\n debug('mock uplink response failed (socket gone?): %s', err);\n }\n };\n\n if (this.mode === 'slow') {\n // Longer than the uplink timeout configured for the e2e registry (3s).\n const timer = setTimeout(safeRespond, 10_000);\n res.socket?.once('close', () => clearTimeout(timer));\n } else {\n safeRespond();\n }\n });\n\n return new Promise((resolve, reject) => {\n this.server!.once('error', reject);\n this.server!.listen(this.port, () => {\n debug('mock uplink listening on %d', this.port);\n resolve();\n });\n });\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => {\n if (!this.server) {\n resolve();\n return;\n }\n // closeAllConnections drops keep-alive sockets so close() completes.\n this.server.closeAllConnections?.();\n this.server.close(() => {\n debug('mock uplink stopped');\n this.server = undefined;\n resolve();\n });\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -47,7 +47,63 @@ async function pingRegistry(registryUrl) {
|
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
var LegacyLoginError = class extends Error {
|
|
51
|
+
status;
|
|
52
|
+
constructor(message, status) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.status = status;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Replays the legacy login the way each client sends it. npm and Yarn retry a
|
|
59
|
+
* 409 with Basic (Yarn on the GET too); pnpm sends Basic on a single PUT. A
|
|
60
|
+
* registry that authenticates the first PUT is accepted too.
|
|
61
|
+
*/
|
|
62
|
+
async function legacyLogin(registryUrl, user, password, client) {
|
|
63
|
+
const userUrl = `${registryUrl}/-/user/org.couchdb.user:${encodeURIComponent(user)}`;
|
|
64
|
+
const basic = `Basic ${Buffer.from(`${user}:${password}`).toString("base64")}`;
|
|
65
|
+
const jsonHeaders = { "Content-Type": "application/json" };
|
|
66
|
+
const body = {
|
|
67
|
+
_id: `org.couchdb.user:${user}`,
|
|
68
|
+
name: user,
|
|
69
|
+
password,
|
|
70
|
+
type: "user",
|
|
71
|
+
roles: [],
|
|
72
|
+
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
73
|
+
};
|
|
74
|
+
const put = (url, headers) => fetch(url, {
|
|
75
|
+
method: "PUT",
|
|
76
|
+
headers,
|
|
77
|
+
body: JSON.stringify(body)
|
|
78
|
+
});
|
|
79
|
+
const first = await put(userUrl, client === "pnpm" ? {
|
|
80
|
+
...jsonHeaders,
|
|
81
|
+
Authorization: basic,
|
|
82
|
+
"npm-auth-type": "web"
|
|
83
|
+
} : jsonHeaders);
|
|
84
|
+
const firstBody = await first.json();
|
|
85
|
+
if (first.ok && firstBody.token) {
|
|
86
|
+
debug("%s: first PUT authenticated %s", client, user);
|
|
87
|
+
return firstBody.token;
|
|
88
|
+
}
|
|
89
|
+
if (first.status !== 409) throw new LegacyLoginError(`first PUT answered ${first.status}`, first.status);
|
|
90
|
+
const current = await fetch(`${userUrl}?write=true`, { headers: client === "yarn" ? { Authorization: basic } : {} });
|
|
91
|
+
if (!current.ok) throw new LegacyLoginError(`GET answered ${current.status}`, current.status);
|
|
92
|
+
const currentBody = await current.json();
|
|
93
|
+
for (const [key, value] of Object.entries(currentBody)) if (!body[key] || key === "roles") body[key] = value;
|
|
94
|
+
const second = await put(`${userUrl}/-rev/${body._rev}`, {
|
|
95
|
+
...jsonHeaders,
|
|
96
|
+
Authorization: basic
|
|
97
|
+
});
|
|
98
|
+
const secondBody = await second.json();
|
|
99
|
+
if (!second.ok || !secondBody.token) throw new LegacyLoginError(`PUT with revision answered ${second.status}`, second.status);
|
|
100
|
+
return secondBody.token;
|
|
101
|
+
}
|
|
102
|
+
async function whoami(registryUrl, token) {
|
|
103
|
+
const response = await fetch(`${registryUrl}/-/whoami`, { headers: { Authorization: `Bearer ${token}` } });
|
|
104
|
+
return response.ok ? (await response.json()).username : void 0;
|
|
105
|
+
}
|
|
50
106
|
//#endregion
|
|
51
|
-
export { createUser, pingRegistry };
|
|
107
|
+
export { LegacyLoginError, createUser, legacyLogin, pingRegistry, whoami };
|
|
52
108
|
|
|
53
109
|
//# sourceMappingURL=registry-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-client.js","names":[],"sources":["../../../src/utils/registry-client.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport { ping } from '@verdaccio/registry-cli';\n\nconst debug = buildDebug('verdaccio:e2e-cli:registry-client');\n\nexport type RegistryAuth = {\n token: string;\n user: string;\n};\n\n/**\n * Creates a user on the registry and returns the auth token.\n * Uses a unique username per run to avoid 409 conflicts.\n * Works against any running Verdaccio instance with htpasswd auth.\n */\nexport async function createUser(\n registryUrl: string,\n user = `e2e-user-${Date.now()}`,\n password = 'e2e-test-password'\n): Promise<RegistryAuth> {\n debug('creating user %s on %s', user, registryUrl);\n const url = `${registryUrl}/-/user/org.couchdb.user:${encodeURIComponent(user)}`;\n const response = await fetch(url, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: user,\n password,\n _id: `org.couchdb.user:${user}`,\n type: 'user',\n roles: [],\n date: new Date().toISOString(),\n }),\n });\n\n const body = (await response.json()) as any;\n\n if (response.status === 201 || response.status === 200 || response.status === 409) {\n const token = body.token;\n if (token) {\n debug('user %s authenticated, token obtained', user);\n return { token, user };\n }\n }\n\n throw new Error(\n `Failed to create/login user \"${user}\" on ${registryUrl}: ${response.status} ${JSON.stringify(body)}`\n );\n}\n\n/**\n * Ping the registry to check it's alive.\n * Uses @verdaccio/registry-cli ping.\n */\nexport async function pingRegistry(registryUrl: string): Promise<boolean> {\n debug('pinging %s', registryUrl);\n try {\n const result = await ping(registryUrl);\n return result.ok;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;AAIA,IAAM,QAAQ,WAAW,mCAAmC;;;;;;AAY5D,eAAsB,WACpB,aACA,OAAO,YAAY,KAAK,IAAI,KAC5B,WAAW,qBACY;CACvB,MAAM,0BAA0B,MAAM,WAAW;CACjD,MAAM,MAAM,GAAG,YAAY,2BAA2B,mBAAmB,IAAI;CAC7E,MAAM,WAAW,MAAM,MAAM,KAAK;EAChC,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GACnB,MAAM;GACN;GACA,KAAK,oBAAoB;GACzB,MAAM;GACN,OAAO,CAAC;GACR,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EAC/B,CAAC;CACH,CAAC;CAED,MAAM,OAAQ,MAAM,SAAS,KAAK;CAElC,IAAI,SAAS,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;EACjF,MAAM,QAAQ,KAAK;EACnB,IAAI,OAAO;GACT,MAAM,yCAAyC,IAAI;GACnD,OAAO;IAAE;IAAO;GAAK;EACvB;CACF;CAEA,MAAM,IAAI,MACR,gCAAgC,KAAK,OAAO,YAAY,IAAI,SAAS,OAAO,GAAG,KAAK,UAAU,IAAI,GACpG;AACF;;;;;AAMA,eAAsB,aAAa,aAAuC;CACxE,MAAM,cAAc,WAAW;CAC/B,IAAI;EAEF,QAAO,MADc,KAAK,WAAW,EAAA,CACvB;CAChB,QAAQ;EACN,OAAO;CACT;AACF"}
|
|
1
|
+
{"version":3,"file":"registry-client.js","names":[],"sources":["../../../src/utils/registry-client.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport { ping } from '@verdaccio/registry-cli';\n\nconst debug = buildDebug('verdaccio:e2e-cli:registry-client');\n\nexport type RegistryAuth = {\n token: string;\n user: string;\n};\n\n/**\n * Creates a user on the registry and returns the auth token.\n * Uses a unique username per run to avoid 409 conflicts.\n * Works against any running Verdaccio instance with htpasswd auth.\n */\nexport async function createUser(\n registryUrl: string,\n user = `e2e-user-${Date.now()}`,\n password = 'e2e-test-password'\n): Promise<RegistryAuth> {\n debug('creating user %s on %s', user, registryUrl);\n const url = `${registryUrl}/-/user/org.couchdb.user:${encodeURIComponent(user)}`;\n const response = await fetch(url, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: user,\n password,\n _id: `org.couchdb.user:${user}`,\n type: 'user',\n roles: [],\n date: new Date().toISOString(),\n }),\n });\n\n const body = (await response.json()) as any;\n\n if (response.status === 201 || response.status === 200 || response.status === 409) {\n const token = body.token;\n if (token) {\n debug('user %s authenticated, token obtained', user);\n return { token, user };\n }\n }\n\n throw new Error(\n `Failed to create/login user \"${user}\" on ${registryUrl}: ${response.status} ${JSON.stringify(body)}`\n );\n}\n\n/**\n * Ping the registry to check it's alive.\n * Uses @verdaccio/registry-cli ping.\n */\nexport async function pingRegistry(registryUrl: string): Promise<boolean> {\n debug('pinging %s', registryUrl);\n try {\n const result = await ping(registryUrl);\n return result.ok;\n } catch {\n return false;\n }\n}\n\nexport class LegacyLoginError extends Error {\n constructor(\n message: string,\n readonly status: number\n ) {\n super(message);\n }\n}\n\n/**\n * Replays the legacy login the way each client sends it. npm and Yarn retry a\n * 409 with Basic (Yarn on the GET too); pnpm sends Basic on a single PUT. A\n * registry that authenticates the first PUT is accepted too.\n */\nexport async function legacyLogin(\n registryUrl: string,\n user: string,\n password: string,\n client: 'npm' | 'yarn' | 'pnpm'\n): Promise<string> {\n const userUrl = `${registryUrl}/-/user/org.couchdb.user:${encodeURIComponent(user)}`;\n const basic = `Basic ${Buffer.from(`${user}:${password}`).toString('base64')}`;\n const jsonHeaders = { 'Content-Type': 'application/json' };\n const body: Record<string, unknown> = {\n _id: `org.couchdb.user:${user}`,\n name: user,\n password,\n type: 'user',\n roles: [],\n date: new Date().toISOString(),\n };\n\n const put = (url: string, headers: Record<string, string>) =>\n fetch(url, { method: 'PUT', headers, body: JSON.stringify(body) });\n\n const first = await put(\n userUrl,\n client === 'pnpm'\n ? { ...jsonHeaders, Authorization: basic, 'npm-auth-type': 'web' }\n : jsonHeaders\n );\n const firstBody = (await first.json()) as any;\n if (first.ok && firstBody.token) {\n debug('%s: first PUT authenticated %s', client, user);\n return firstBody.token;\n }\n if (first.status !== 409) {\n throw new LegacyLoginError(`first PUT answered ${first.status}`, first.status);\n }\n\n const current = await fetch(`${userUrl}?write=true`, {\n headers: client === 'yarn' ? { Authorization: basic } : {},\n });\n if (!current.ok) {\n throw new LegacyLoginError(`GET answered ${current.status}`, current.status);\n }\n const currentBody = (await current.json()) as Record<string, unknown>;\n for (const [key, value] of Object.entries(currentBody)) {\n if (!body[key] || key === 'roles') {\n body[key] = value;\n }\n }\n\n const second = await put(`${userUrl}/-rev/${body._rev}`, {\n ...jsonHeaders,\n Authorization: basic,\n });\n const secondBody = (await second.json()) as any;\n if (!second.ok || !secondBody.token) {\n throw new LegacyLoginError(`PUT with revision answered ${second.status}`, second.status);\n }\n return secondBody.token;\n}\n\nexport async function whoami(registryUrl: string, token: string): Promise<string | undefined> {\n const response = await fetch(`${registryUrl}/-/whoami`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n return response.ok ? ((await response.json()) as any).username : undefined;\n}\n"],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare function main(argv?: string[]): Promise<void>;
|
|
2
2
|
export { allTests } from './tests';
|
|
3
3
|
export { allScenarios } from './scenarios';
|
|
4
|
-
export { createBunAdapter, createDenoAdapter, createNpmAdapter, createPnpmAdapter, createYarnModernAdapter, } from './adapters';
|
|
4
|
+
export { createBunAdapter, createDenoAdapter, createNpmAdapter, createNubAdapter, createPnpmAdapter, createUpmAdapter, createYarnModernAdapter, } from './adapters';
|
|
5
5
|
export { runAll, runSuite } from './runner';
|
|
6
6
|
export type { PackageManagerAdapter, TestDefinition, TestContext, CliOptions } from './types';
|
|
@@ -2,8 +2,9 @@ import { TestDefinition } from '../types';
|
|
|
2
2
|
import { installMultipleDepsScenario } from './install-multiple-deps';
|
|
3
3
|
import { metadataScenario } from './metadata';
|
|
4
4
|
import { minimumReleaseAgeScenario } from './minimum-release-age';
|
|
5
|
+
import { reservedNamesScenario } from './reserved-names';
|
|
5
6
|
import { searchScenario } from './search';
|
|
6
7
|
import { tarballsScenario } from './tarballs';
|
|
7
8
|
import { uplinkFailureScenario } from './uplink-failure';
|
|
8
9
|
export declare const allScenarios: TestDefinition[];
|
|
9
|
-
export { installMultipleDepsScenario, minimumReleaseAgeScenario, searchScenario, tarballsScenario, metadataScenario, uplinkFailureScenario, };
|
|
10
|
+
export { installMultipleDepsScenario, minimumReleaseAgeScenario, searchScenario, tarballsScenario, metadataScenario, uplinkFailureScenario, reservedNamesScenario, };
|
package/build/tests/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { allScenarios, installMultipleDepsScenario, metadataScenario, minimumReleaseAgeScenario, searchScenario, tarballsScenario, uplinkFailureScenario } from '../scenarios';
|
|
1
|
+
import { allScenarios, installMultipleDepsScenario, metadataScenario, minimumReleaseAgeScenario, reservedNamesScenario, searchScenario, tarballsScenario, uplinkFailureScenario } from '../scenarios';
|
|
2
2
|
import { TestDefinition } from '../types';
|
|
3
3
|
import { auditTest } from './audit';
|
|
4
4
|
import { ciTest } from './ci';
|
|
@@ -7,9 +7,10 @@ import { distTagsTest } from './dist-tags';
|
|
|
7
7
|
import { infoTest } from './info';
|
|
8
8
|
import { installTest } from './install';
|
|
9
9
|
import { loginTest } from './login';
|
|
10
|
+
import { packageNamesTest } from './package-names';
|
|
10
11
|
import { pingTest } from './ping';
|
|
11
12
|
import { publishTest } from './publish';
|
|
12
13
|
import { searchTest } from './search';
|
|
13
14
|
import { unpublishTest } from './unpublish';
|
|
14
15
|
export declare const allTests: TestDefinition[];
|
|
15
|
-
export { publishTest, installTest, ciTest, auditTest, infoTest, deprecateTest, distTagsTest, loginTest, pingTest, searchTest, unpublishTest, installMultipleDepsScenario, minimumReleaseAgeScenario, tarballsScenario, metadataScenario, searchScenario, uplinkFailureScenario, allScenarios, };
|
|
16
|
+
export { publishTest, installTest, ciTest, auditTest, infoTest, deprecateTest, distTagsTest, packageNamesTest, loginTest, pingTest, searchTest, unpublishTest, installMultipleDepsScenario, minimumReleaseAgeScenario, tarballsScenario, metadataScenario, searchScenario, uplinkFailureScenario, reservedNamesScenario, allScenarios, };
|
package/build/types.d.ts
CHANGED
|
@@ -57,8 +57,8 @@ export type TestDefinition = {
|
|
|
57
57
|
export interface PackageManagerAdapter {
|
|
58
58
|
/** Display name, e.g. "npm@10" */
|
|
59
59
|
name: string;
|
|
60
|
-
/** Package manager type: npm, pnpm, yarn-modern, bun, deno */
|
|
61
|
-
type: 'npm' | 'pnpm' | 'yarn-modern' | 'bun' | 'deno';
|
|
60
|
+
/** Package manager type: npm, pnpm, yarn-modern, bun, deno, upm, nub */
|
|
61
|
+
type: 'npm' | 'pnpm' | 'yarn-modern' | 'bun' | 'deno' | 'upm' | 'nub';
|
|
62
62
|
/** Resolved path to the binary */
|
|
63
63
|
bin: string;
|
|
64
64
|
/** Commands this PM supports */
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* - max_body_size raised so scenario:tarballs can publish ~30 MB packages
|
|
7
7
|
* (Verdaccio's default is 10mb)
|
|
8
|
-
* - an `e2emock` uplink + `e2e-uplink
|
|
9
|
-
* uplink server that scenario:uplink-failure
|
|
8
|
+
* - an `e2emock` uplink + `e2e-uplink-*`, `nul.*` and `@e2e-uplink/*` package
|
|
9
|
+
* patterns wired to the mock uplink server that scenario:uplink-failure and
|
|
10
|
+
* scenario:reserved-names start on E2E_UPLINK_PORT
|
|
10
11
|
* (short timeout so the "slow uplink" case completes quickly; high
|
|
11
12
|
* max_fails so simulated failures don't trip the circuit breaker for
|
|
12
13
|
* later sub-tests)
|
|
@@ -29,8 +29,8 @@ export declare class MockUplink {
|
|
|
29
29
|
private packages;
|
|
30
30
|
private searchResults;
|
|
31
31
|
constructor(port: number);
|
|
32
|
-
/** Register a package served by this uplink; tarball is random (incompressible) bytes. */
|
|
33
|
-
addPackage(name: string, tarballBytes?: number): MockPackage;
|
|
32
|
+
/** Register a package served by this uplink; tarball is random (incompressible) bytes unless one is given. */
|
|
33
|
+
addPackage(name: string, tarballBytes?: number | Buffer): MockPackage;
|
|
34
34
|
/** Register a result served by the /-/v1/search endpoint. */
|
|
35
35
|
addSearchResult(name: string, version?: string): void;
|
|
36
36
|
/**
|
|
@@ -13,3 +13,14 @@ export declare function createUser(registryUrl: string, user?: string, password?
|
|
|
13
13
|
* Uses @verdaccio/registry-cli ping.
|
|
14
14
|
*/
|
|
15
15
|
export declare function pingRegistry(registryUrl: string): Promise<boolean>;
|
|
16
|
+
export declare class LegacyLoginError extends Error {
|
|
17
|
+
readonly status: number;
|
|
18
|
+
constructor(message: string, status: number);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Replays the legacy login the way each client sends it. npm and Yarn retry a
|
|
22
|
+
* 409 with Basic (Yarn on the GET too); pnpm sends Basic on a single PUT. A
|
|
23
|
+
* registry that authenticates the first PUT is accepted too.
|
|
24
|
+
*/
|
|
25
|
+
export declare function legacyLogin(registryUrl: string, user: string, password: string, client: 'npm' | 'yarn' | 'pnpm'): Promise<string>;
|
|
26
|
+
export declare function whoami(registryUrl: string, token: string): Promise<string | undefined>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdaccio/e2e-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "CLI tool to run Verdaccio e2e tests against any running registry",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@verdaccio/registry-cli": "^1.1.0",
|
|
31
31
|
"debug": "4.4.3",
|
|
32
32
|
"get-port": "7.2.0",
|
|
33
|
-
"js-yaml": "5.
|
|
33
|
+
"js-yaml": "5.4.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/debug": "4.1.13",
|