@verdaccio/e2e-cli 2.10.4 → 3.1.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 (122) hide show
  1. package/build/adapters/index.d.ts +0 -1
  2. package/build/cjs/adapters/index.cjs +0 -2
  3. package/build/cjs/adapters/npm.cjs +10 -0
  4. package/build/cjs/adapters/npm.cjs.map +1 -1
  5. package/build/cjs/adapters/pnpm.cjs +11 -1
  6. package/build/cjs/adapters/pnpm.cjs.map +1 -1
  7. package/build/cjs/index.cjs +28 -8
  8. package/build/cjs/index.cjs.map +1 -1
  9. package/build/cjs/runner.cjs +1 -1
  10. package/build/cjs/runner.cjs.map +1 -1
  11. package/build/cjs/scenarios/index.cjs +16 -1
  12. package/build/cjs/scenarios/index.cjs.map +1 -1
  13. package/build/cjs/scenarios/install-multiple-deps.cjs +25 -23
  14. package/build/cjs/scenarios/install-multiple-deps.cjs.map +1 -1
  15. package/build/cjs/scenarios/metadata.cjs +121 -0
  16. package/build/cjs/scenarios/metadata.cjs.map +1 -0
  17. package/build/cjs/scenarios/minimum-release-age.cjs +6 -8
  18. package/build/cjs/scenarios/minimum-release-age.cjs.map +1 -1
  19. package/build/cjs/scenarios/search.cjs +136 -0
  20. package/build/cjs/scenarios/search.cjs.map +1 -0
  21. package/build/cjs/scenarios/tarballs.cjs +147 -0
  22. package/build/cjs/scenarios/tarballs.cjs.map +1 -0
  23. package/build/cjs/scenarios/uplink-failure.cjs +141 -0
  24. package/build/cjs/scenarios/uplink-failure.cjs.map +1 -0
  25. package/build/cjs/tests/audit.cjs +15 -2
  26. package/build/cjs/tests/audit.cjs.map +1 -1
  27. package/build/cjs/tests/ci.cjs +4 -6
  28. package/build/cjs/tests/ci.cjs.map +1 -1
  29. package/build/cjs/tests/index.cjs +7 -3
  30. package/build/cjs/tests/index.cjs.map +1 -1
  31. package/build/cjs/tests/info.cjs +10 -21
  32. package/build/cjs/tests/info.cjs.map +1 -1
  33. package/build/cjs/tests/install.cjs +4 -1
  34. package/build/cjs/tests/install.cjs.map +1 -1
  35. package/build/cjs/tests/ping.cjs +3 -7
  36. package/build/cjs/tests/ping.cjs.map +1 -1
  37. package/build/cjs/tests/publish.cjs +0 -1
  38. package/build/cjs/tests/publish.cjs.map +1 -1
  39. package/build/cjs/tests/search.cjs +9 -5
  40. package/build/cjs/tests/search.cjs.map +1 -1
  41. package/build/cjs/utils/e2e-config.cjs +59 -0
  42. package/build/cjs/utils/e2e-config.cjs.map +1 -0
  43. package/build/cjs/utils/http-client.cjs +111 -0
  44. package/build/cjs/utils/http-client.cjs.map +1 -0
  45. package/build/cjs/utils/mock-uplink.cjs +196 -0
  46. package/build/cjs/utils/mock-uplink.cjs.map +1 -0
  47. package/build/cjs/utils/process.cjs +11 -0
  48. package/build/cjs/utils/process.cjs.map +1 -1
  49. package/build/cjs/utils/project.cjs +1 -1
  50. package/build/cjs/utils/project.cjs.map +1 -1
  51. package/build/cjs/utils/publish.cjs +117 -0
  52. package/build/cjs/utils/publish.cjs.map +1 -0
  53. package/build/esm/adapters/index.js +1 -2
  54. package/build/esm/adapters/npm.js +10 -0
  55. package/build/esm/adapters/npm.js.map +1 -1
  56. package/build/esm/adapters/pnpm.js +11 -1
  57. package/build/esm/adapters/pnpm.js.map +1 -1
  58. package/build/esm/index.js +29 -8
  59. package/build/esm/index.js.map +1 -1
  60. package/build/esm/runner.js +1 -1
  61. package/build/esm/runner.js.map +1 -1
  62. package/build/esm/scenarios/index.js +13 -2
  63. package/build/esm/scenarios/index.js.map +1 -1
  64. package/build/esm/scenarios/install-multiple-deps.js +25 -22
  65. package/build/esm/scenarios/install-multiple-deps.js.map +1 -1
  66. package/build/esm/scenarios/metadata.js +119 -0
  67. package/build/esm/scenarios/metadata.js.map +1 -0
  68. package/build/esm/scenarios/minimum-release-age.js +6 -7
  69. package/build/esm/scenarios/minimum-release-age.js.map +1 -1
  70. package/build/esm/scenarios/search.js +134 -0
  71. package/build/esm/scenarios/search.js.map +1 -0
  72. package/build/esm/scenarios/tarballs.js +145 -0
  73. package/build/esm/scenarios/tarballs.js.map +1 -0
  74. package/build/esm/scenarios/uplink-failure.js +139 -0
  75. package/build/esm/scenarios/uplink-failure.js.map +1 -0
  76. package/build/esm/tests/audit.js +15 -2
  77. package/build/esm/tests/audit.js.map +1 -1
  78. package/build/esm/tests/ci.js +4 -6
  79. package/build/esm/tests/ci.js.map +1 -1
  80. package/build/esm/tests/index.js +4 -0
  81. package/build/esm/tests/index.js.map +1 -1
  82. package/build/esm/tests/info.js +10 -21
  83. package/build/esm/tests/info.js.map +1 -1
  84. package/build/esm/tests/install.js +4 -1
  85. package/build/esm/tests/install.js.map +1 -1
  86. package/build/esm/tests/ping.js +3 -7
  87. package/build/esm/tests/ping.js.map +1 -1
  88. package/build/esm/tests/publish.js +0 -1
  89. package/build/esm/tests/publish.js.map +1 -1
  90. package/build/esm/tests/search.js +9 -5
  91. package/build/esm/tests/search.js.map +1 -1
  92. package/build/esm/utils/e2e-config.js +58 -0
  93. package/build/esm/utils/e2e-config.js.map +1 -0
  94. package/build/esm/utils/http-client.js +105 -0
  95. package/build/esm/utils/http-client.js.map +1 -0
  96. package/build/esm/utils/mock-uplink.js +194 -0
  97. package/build/esm/utils/mock-uplink.js.map +1 -0
  98. package/build/esm/utils/process.js +11 -1
  99. package/build/esm/utils/process.js.map +1 -1
  100. package/build/esm/utils/project.js +1 -1
  101. package/build/esm/utils/project.js.map +1 -1
  102. package/build/esm/utils/publish.js +113 -0
  103. package/build/esm/utils/publish.js.map +1 -0
  104. package/build/index.d.ts +1 -1
  105. package/build/scenarios/index.d.ts +5 -1
  106. package/build/scenarios/metadata.d.ts +2 -0
  107. package/build/scenarios/search.d.ts +2 -0
  108. package/build/scenarios/tarballs.d.ts +2 -0
  109. package/build/scenarios/uplink-failure.d.ts +2 -0
  110. package/build/tests/index.d.ts +2 -2
  111. package/build/types.d.ts +12 -2
  112. package/build/utils/e2e-config.d.ts +21 -0
  113. package/build/utils/http-client.d.ts +49 -0
  114. package/build/utils/mock-uplink.d.ts +47 -0
  115. package/build/utils/process.d.ts +7 -0
  116. package/build/utils/publish.d.ts +17 -0
  117. package/package.json +1 -1
  118. package/build/adapters/yarn-classic.d.ts +0 -2
  119. package/build/cjs/adapters/yarn-classic.cjs +0 -98
  120. package/build/cjs/adapters/yarn-classic.cjs.map +0 -1
  121. package/build/esm/adapters/yarn-classic.js +0 -96
  122. package/build/esm/adapters/yarn-classic.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uplink-failure.js","names":[],"sources":["../../../src/scenarios/uplink-failure.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 { trace } from '../utils/process';\n\n/**\n * Scenario: uplink-failure\n *\n * Verifies how the registry behaves when its uplink misbehaves. Requires the\n * registry to be configured with an uplink pointing at the mock uplink port\n * and a package pattern proxied to it (see `--print-config`):\n *\n * uplinks:\n * e2emock:\n * url: http://localhost:<E2E_UPLINK_PORT>/\n * timeout: 3s\n * max_fails: 100\n * packages:\n * 'e2e-uplink-*':\n * access: $all\n * proxy: e2emock\n *\n * The scenario starts its own HTTP server on that port and flips it between\n * modes:\n * - healthy: packument + tarball proxied through, tarball URL rewritten\n * - drop-mid-stream: uplink kills the socket halfway through a tarball —\n * the client must not receive a \"successful\" corrupt download, and the\n * tarball must be intact on retry (no poisoned cache)\n * - down: previously fetched packages still served from cache; unknown\n * packages fail cleanly; the registry stays alive\n * - slow: uplink slower than the configured timeout — the registry gives up\n * instead of hanging\n *\n * Gated on E2E_UPLINK_PORT being set (the harness exports it) so runs against\n * arbitrary registries skip it cleanly, and to the npm adapter so it runs once\n * per suite.\n */\n\nfunction uplinkPort(): number {\n return parseInt(process.env.E2E_UPLINK_PORT || '', 10);\n}\n\nasync function testUplinkFailure(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n const mock = new MockUplink(uplinkPort());\n const cachedPkg = `e2e-uplink-cached-${id}`;\n const cached = mock.addPackage(cachedPkg, 512 * 1024);\n\n await mock.start();\n try {\n await ctx.subTest('healthy uplink: packument and tarball proxied through', async () => {\n const { status, body } = await fetchPackument(ctx.registryUrl, cachedPkg);\n trace('healthy proxy %s → %d | keys=%j', cachedPkg, status, Object.keys(body ?? {}));\n assert.strictEqual(status, 200, `Expected 200 proxying ${cachedPkg}, got ${status}`);\n assert.strictEqual(body.name, cachedPkg, 'Proxied packument name mismatch');\n const tarball: string = body.versions['1.0.0'].dist.tarball;\n assert.ok(\n tarball.startsWith(ctx.registryUrl),\n `Expected proxied dist.tarball rewritten to ${ctx.registryUrl}, got ${tarball}`\n );\n const download = await downloadTarball(tarball);\n assert.strictEqual(download.status, 200, 'Proxied tarball download failed');\n assert.strictEqual(download.sha1, cached.shasum, 'Proxied tarball bytes mismatch');\n trace('mock uplink requests so far: %j', mock.requests);\n assert.ok(mock.requests.length > 0, 'Expected the registry to hit the mock uplink');\n });\n\n await ctx.subTest('uplink drops connection mid-tarball', async () => {\n const droppedPkg = `e2e-uplink-dropped-${id}`;\n const dropped = mock.addPackage(droppedPkg, 512 * 1024);\n\n // Packument fetch is unaffected by drop-mid-stream (tarball route only).\n const { status, body } = await fetchPackument(ctx.registryUrl, droppedPkg);\n assert.strictEqual(status, 200, `Expected 200 for ${droppedPkg} packument`);\n const tarball: string = body.versions['1.0.0'].dist.tarball;\n\n mock.mode = 'drop-mid-stream';\n let sawCleanFailure = false;\n try {\n // The registry must terminate the client response promptly when its\n // uplink dies — a client left hanging is a bug, so cap the wait.\n const download = await downloadTarball(tarball, { signal: AbortSignal.timeout(15_000) });\n // A non-200, or a truncated/corrupt body, are both acceptable failures —\n // what must NOT happen is a \"successful\" download with wrong bytes\n // going undetected, so we check integrity ourselves.\n sawCleanFailure = download.status !== 200 || download.sha1 !== dropped.shasum;\n trace(\n 'drop-mid-stream download: status=%d bytes=%d sha1=%s (expected %s)',\n download.status,\n download.bytes,\n download.sha1,\n dropped.shasum\n );\n } catch (err) {\n assert.ok(\n (err as Error).name !== 'TimeoutError',\n 'Registry left the client hanging after the uplink dropped mid-tarball ' +\n '(response never terminated within 15s)'\n );\n sawCleanFailure = true;\n trace(\n 'drop-mid-stream download rejected as expected: %s',\n err instanceof Error ? err.message : err\n );\n }\n assert.ok(\n sawCleanFailure,\n 'Expected the client download to fail while the uplink drops mid-stream'\n );\n\n // Back to healthy: the tarball must be intact (no partial tarball cached).\n mock.mode = 'ok';\n const retry = await downloadTarball(tarball);\n trace('retry after recovery → %d bytes=%d sha1=%s', retry.status, retry.bytes, retry.sha1);\n assert.strictEqual(retry.status, 200, 'Tarball download after uplink recovery failed');\n assert.strictEqual(\n retry.sha1,\n dropped.shasum,\n 'Registry served a corrupt tarball after a mid-stream uplink failure (poisoned cache?)'\n );\n });\n\n await ctx.subTest('slow uplink hits the timeout instead of hanging', async () => {\n const slowPkg = `e2e-uplink-slow-${id}`;\n mock.addPackage(slowPkg);\n mock.mode = 'slow';\n const start = Date.now();\n const { status } = await fetchPackument(ctx.registryUrl, slowPkg);\n const elapsed = Date.now() - start;\n trace('slow uplink: %s → %d in %dms', slowPkg, status, elapsed);\n mock.mode = 'ok';\n assert.ok(status >= 400, `Expected an error status for a timed-out uplink, got ${status}`);\n // Configured uplink timeout is 3s with retry: 0 — anything close to the\n // mock's 10s answer means retries or timeouts are multiplying again\n // (the max_fails-as-retry-limit bug family).\n assert.ok(\n elapsed < 6_000,\n `Expected the registry to time out (~3s), but the request took ${elapsed}ms`\n );\n });\n\n await ctx.subTest('uplink responding 500: cache served, unknown fails cleanly', async () => {\n mock.mode = 'error-500';\n // The already-proxied package must keep working from cache.\n const cachedResp = await fetchPackument(ctx.registryUrl, cachedPkg);\n trace('uplink 500: cached %s → %d', cachedPkg, cachedResp.status);\n assert.strictEqual(\n cachedResp.status,\n 200,\n `Expected cached packument for ${cachedPkg} while the uplink returns 500, got ${cachedResp.status}`\n );\n // An unknown package must fail cleanly, not crash or hang.\n const unknown = await fetchPackument(ctx.registryUrl, `e2e-uplink-500-missing-${id}`);\n trace('uplink 500: unknown package → %d | body=%j', unknown.status, unknown.body);\n assert.ok(\n unknown.status >= 400,\n `Expected a clean 4xx/5xx for an unknown package while the uplink returns 500, got ${unknown.status}`\n );\n const ping = await fetch(`${ctx.registryUrl}/-/ping`);\n assert.strictEqual(ping.status, 200, 'Registry stopped responding after uplink 500s');\n mock.mode = 'ok';\n });\n } finally {\n await mock.stop();\n }\n\n await ctx.subTest('uplink down: cached package still served', async () => {\n const { status, body } = await fetchPackument(ctx.registryUrl, cachedPkg);\n trace('uplink down: cached %s → %d', cachedPkg, status);\n assert.strictEqual(\n status,\n 200,\n `Expected cached packument for ${cachedPkg} while the uplink is down, got ${status}`\n );\n const tarball: string = body.versions['1.0.0'].dist.tarball;\n const download = await downloadTarball(tarball);\n assert.strictEqual(download.status, 200, 'Cached tarball not served while uplink is down');\n assert.strictEqual(download.sha1, cached.shasum, 'Cached tarball bytes mismatch');\n });\n\n await ctx.subTest('uplink down: unknown package fails cleanly', async () => {\n const started = Date.now();\n const { status } = await fetchPackument(ctx.registryUrl, `e2e-uplink-missing-${id}`);\n trace('uplink down: unknown package → %d in %dms', status, Date.now() - started);\n assert.ok(\n status >= 400,\n `Expected a clean 4xx/5xx for an unknown package with the uplink down, got ${status}`\n );\n const ping = await fetch(`${ctx.registryUrl}/-/ping`);\n assert.strictEqual(ping.status, 200, 'Registry stopped responding after uplink failures');\n });\n}\n\nexport const uplinkFailureScenario: TestDefinition = {\n name: 'scenario:uplink-failure',\n // Needs the harness-provided uplink config; skipped cleanly otherwise.\n appliesTo: (adapter) => adapter.type === 'npm' && Number.isFinite(uplinkPort()),\n timeout: 120_000,\n run: testUplinkFailure,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAS,aAAqB;CAC5B,OAAO,SAAS,QAAQ,IAAI,mBAAmB,IAAI,EAAE;AACvD;AAEA,eAAe,kBAAkB,KAAiC;CAChE,MAAM,KAAK,IAAI;CACf,MAAM,OAAO,IAAI,WAAW,WAAW,CAAC;CACxC,MAAM,YAAY,qBAAqB;CACvC,MAAM,SAAS,KAAK,WAAW,WAAW,MAAU;CAEpD,MAAM,KAAK,MAAM;CACjB,IAAI;EACF,MAAM,IAAI,QAAQ,yDAAyD,YAAY;GACrF,MAAM,EAAE,QAAQ,SAAS,MAAM,eAAe,IAAI,aAAa,SAAS;GACxE,MAAM,mCAAmC,WAAW,QAAQ,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC;GACnF,OAAO,YAAY,QAAQ,KAAK,yBAAyB,UAAU,QAAQ,QAAQ;GACnF,OAAO,YAAY,KAAK,MAAM,WAAW,iCAAiC;GAC1E,MAAM,UAAkB,KAAK,SAAS,QAAQ,CAAC,KAAK;GACpD,OAAO,GACL,QAAQ,WAAW,IAAI,WAAW,GAClC,8CAA8C,IAAI,YAAY,QAAQ,SACxE;GACA,MAAM,WAAW,MAAM,gBAAgB,OAAO;GAC9C,OAAO,YAAY,SAAS,QAAQ,KAAK,iCAAiC;GAC1E,OAAO,YAAY,SAAS,MAAM,OAAO,QAAQ,gCAAgC;GACjF,MAAM,mCAAmC,KAAK,QAAQ;GACtD,OAAO,GAAG,KAAK,SAAS,SAAS,GAAG,8CAA8C;EACpF,CAAC;EAED,MAAM,IAAI,QAAQ,uCAAuC,YAAY;GACnE,MAAM,aAAa,sBAAsB;GACzC,MAAM,UAAU,KAAK,WAAW,YAAY,MAAU;GAGtD,MAAM,EAAE,QAAQ,SAAS,MAAM,eAAe,IAAI,aAAa,UAAU;GACzE,OAAO,YAAY,QAAQ,KAAK,oBAAoB,WAAW,WAAW;GAC1E,MAAM,UAAkB,KAAK,SAAS,QAAQ,CAAC,KAAK;GAEpD,KAAK,OAAO;GACZ,IAAI,kBAAkB;GACtB,IAAI;IAGF,MAAM,WAAW,MAAM,gBAAgB,SAAS,EAAE,QAAQ,YAAY,QAAQ,IAAM,EAAE,CAAC;IAIvF,kBAAkB,SAAS,WAAW,OAAO,SAAS,SAAS,QAAQ;IACvE,MACE,sEACA,SAAS,QACT,SAAS,OACT,SAAS,MACT,QAAQ,MACV;GACF,SAAS,KAAK;IACZ,OAAO,GACJ,IAAc,SAAS,gBACxB,8GAEF;IACA,kBAAkB;IAClB,MACE,qDACA,eAAe,QAAQ,IAAI,UAAU,GACvC;GACF;GACA,OAAO,GACL,iBACA,wEACF;GAGA,KAAK,OAAO;GACZ,MAAM,QAAQ,MAAM,gBAAgB,OAAO;GAC3C,MAAM,8CAA8C,MAAM,QAAQ,MAAM,OAAO,MAAM,IAAI;GACzF,OAAO,YAAY,MAAM,QAAQ,KAAK,+CAA+C;GACrF,OAAO,YACL,MAAM,MACN,QAAQ,QACR,uFACF;EACF,CAAC;EAED,MAAM,IAAI,QAAQ,mDAAmD,YAAY;GAC/E,MAAM,UAAU,mBAAmB;GACnC,KAAK,WAAW,OAAO;GACvB,KAAK,OAAO;GACZ,MAAM,QAAQ,KAAK,IAAI;GACvB,MAAM,EAAE,WAAW,MAAM,eAAe,IAAI,aAAa,OAAO;GAChE,MAAM,UAAU,KAAK,IAAI,IAAI;GAC7B,MAAM,gCAAgC,SAAS,QAAQ,OAAO;GAC9D,KAAK,OAAO;GACZ,OAAO,GAAG,UAAU,KAAK,wDAAwD,QAAQ;GAIzF,OAAO,GACL,UAAU,KACV,iEAAiE,QAAQ,GAC3E;EACF,CAAC;EAED,MAAM,IAAI,QAAQ,8DAA8D,YAAY;GAC1F,KAAK,OAAO;GAEZ,MAAM,aAAa,MAAM,eAAe,IAAI,aAAa,SAAS;GAClE,MAAM,8BAA8B,WAAW,WAAW,MAAM;GAChE,OAAO,YACL,WAAW,QACX,KACA,iCAAiC,UAAU,qCAAqC,WAAW,QAC7F;GAEA,MAAM,UAAU,MAAM,eAAe,IAAI,aAAa,0BAA0B,IAAI;GACpF,MAAM,8CAA8C,QAAQ,QAAQ,QAAQ,IAAI;GAChF,OAAO,GACL,QAAQ,UAAU,KAClB,qFAAqF,QAAQ,QAC/F;GACA,MAAM,OAAO,MAAM,MAAM,GAAG,IAAI,YAAY,QAAQ;GACpD,OAAO,YAAY,KAAK,QAAQ,KAAK,+CAA+C;GACpF,KAAK,OAAO;EACd,CAAC;CACH,UAAU;EACR,MAAM,KAAK,KAAK;CAClB;CAEA,MAAM,IAAI,QAAQ,4CAA4C,YAAY;EACxE,MAAM,EAAE,QAAQ,SAAS,MAAM,eAAe,IAAI,aAAa,SAAS;EACxE,MAAM,+BAA+B,WAAW,MAAM;EACtD,OAAO,YACL,QACA,KACA,iCAAiC,UAAU,iCAAiC,QAC9E;EACA,MAAM,UAAkB,KAAK,SAAS,QAAQ,CAAC,KAAK;EACpD,MAAM,WAAW,MAAM,gBAAgB,OAAO;EAC9C,OAAO,YAAY,SAAS,QAAQ,KAAK,gDAAgD;EACzF,OAAO,YAAY,SAAS,MAAM,OAAO,QAAQ,+BAA+B;CAClF,CAAC;CAED,MAAM,IAAI,QAAQ,8CAA8C,YAAY;EAC1E,MAAM,UAAU,KAAK,IAAI;EACzB,MAAM,EAAE,WAAW,MAAM,eAAe,IAAI,aAAa,sBAAsB,IAAI;EACnF,MAAM,6CAA6C,QAAQ,KAAK,IAAI,IAAI,OAAO;EAC/E,OAAO,GACL,UAAU,KACV,6EAA6E,QAC/E;EACA,MAAM,OAAO,MAAM,MAAM,GAAG,IAAI,YAAY,QAAQ;EACpD,OAAO,YAAY,KAAK,QAAQ,KAAK,mDAAmD;CAC1F,CAAC;AACH;AAEA,IAAa,wBAAwC;CACnD,MAAM;CAEN,YAAY,YAAY,QAAQ,SAAS,SAAS,OAAO,SAAS,WAAW,CAAC;CAC9E,SAAS;CACT,KAAK;AACP"}
@@ -1,3 +1,4 @@
1
+ import { publishLocalPackage } from "../utils/publish.js";
1
2
  import buildDebug from "debug";
2
3
  import assert from "assert";
3
4
  //#region src/tests/audit.ts
@@ -8,8 +9,10 @@ async function testAudit(ctx) {
8
9
  return;
9
10
  }
10
11
  const packages = [`verdaccio-audit-${ctx.runId}`, `@verdaccio/audit-${ctx.runId}`];
12
+ const depName = `e2e-audit-dep-${ctx.runId}`;
13
+ await publishLocalPackage(ctx, depName, "1.0.0");
11
14
  for (const pkgName of packages) {
12
- const { tempFolder } = await ctx.adapter.prepareProject(pkgName, "1.0.0", ctx.registryUrl, ctx.port, ctx.token, { jquery: "3.6.1" });
15
+ const { tempFolder } = await ctx.adapter.prepareProject(pkgName, "1.0.0", ctx.registryUrl, ctx.port, ctx.token, { [depName]: "1.0.0" });
13
16
  await ctx.adapter.exec({ cwd: tempFolder }, "install", ...ctx.adapter.registryArg(ctx.registryUrl));
14
17
  try {
15
18
  if ((await fetch(`${ctx.registryUrl}/-/npm/v1/security/audits/quick`, {
@@ -24,7 +27,17 @@ async function testAudit(ctx) {
24
27
  debug("audit endpoint probe failed, skipping");
25
28
  return;
26
29
  }
27
- const resp = await ctx.adapter.exec({ cwd: tempFolder }, "audit", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
30
+ let resp;
31
+ try {
32
+ resp = await ctx.adapter.exec({ cwd: tempFolder }, "audit", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
33
+ } catch (err) {
34
+ const message = err instanceof Error ? err.message : String(err);
35
+ if (/503|ENOTFOUND|EAI_AGAIN|getaddrinfo|ECONNREFUSED|audit/i.test(message)) {
36
+ debug("audit upstream unreachable (offline?), skipping: %s", message);
37
+ return;
38
+ }
39
+ throw err;
40
+ }
28
41
  if (ctx.adapter.type === "bun") assert.ok(resp.stdout.length > 0 || resp.stderr.length > 0, "Expected audit output");
29
42
  else {
30
43
  const parsedBody = JSON.parse(resp.stdout);
@@ -1 +1 @@
1
- {"version":3,"file":"audit.js","names":[],"sources":["../../../src/tests/audit.ts"],"sourcesContent":["import assert from 'assert';\nimport buildDebug from 'debug';\n\nimport { TestContext, TestDefinition } from '../types';\n\nconst debug = buildDebug('verdaccio:e2e-cli:test:audit');\n\nasync function testAudit(ctx: TestContext): Promise<void> {\n // Audit is only reliable with npm and bun — pnpm/yarn audit output varies too much\n if (ctx.adapter.type !== 'npm' && ctx.adapter.type !== 'bun') {\n debug('skipping audit test for %s (npm/bun only)', ctx.adapter.type);\n return;\n }\n\n const packages = [`verdaccio-audit-${ctx.runId}`, `@verdaccio/audit-${ctx.runId}`];\n\n for (const pkgName of packages) {\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n { jquery: '3.6.1' }\n );\n\n // install is required to create package lock file\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n // npm audit exits with code 1 both when vulnerabilities are found AND when\n // the endpoint is unavailable. We need to distinguish between the two.\n // Probe the audit endpoint first to avoid false failures on registries\n // that don't support it (e.g. verdaccio next-7+).\n try {\n const probe = await fetch(`${ctx.registryUrl}/-/npm/v1/security/audits/quick`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (probe.status === 404) {\n debug('audit endpoint not supported by this registry (404), skipping');\n return;\n }\n } catch {\n debug('audit endpoint probe failed, skipping');\n return;\n }\n\n const resp = await ctx.adapter.exec(\n { cwd: tempFolder },\n 'audit',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (ctx.adapter.type === 'bun') {\n // bun audit output may differ — exit code 0 is sufficient for basic validation\n assert.ok(resp.stdout.length > 0 || resp.stderr.length > 0, 'Expected audit output');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n assert.ok(\n parsedBody.auditReportVersion !== undefined,\n 'Expected \"auditReportVersion\" in audit response'\n );\n assert.ok(\n parsedBody.vulnerabilities !== undefined,\n 'Expected \"vulnerabilities\" in audit response'\n );\n }\n }\n}\n\nexport const auditTest: TestDefinition = {\n name: 'audit',\n requires: ['audit'],\n run: testAudit,\n};\n"],"mappings":";;;AAKA,IAAM,QAAQ,WAAW,8BAA8B;AAEvD,eAAe,UAAU,KAAiC;CAExD,IAAI,IAAI,QAAQ,SAAS,SAAS,IAAI,QAAQ,SAAS,OAAO;EAC5D,MAAM,6CAA6C,IAAI,QAAQ,IAAI;EACnE;CACF;CAEA,MAAM,WAAW,CAAC,mBAAmB,IAAI,SAAS,oBAAoB,IAAI,OAAO;CAEjF,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,EAAE,QAAQ,QAAQ,CACpB;EAGA,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAMA,IAAI;GAMF,KAAI,MALgB,MAAM,GAAG,IAAI,YAAY,kCAAkC;IAC7E,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,MAAM;GACR,CAAC,EAAA,CACS,WAAW,KAAK;IACxB,MAAM,+DAA+D;IACrE;GACF;EACF,QAAQ;GACN,MAAM,uCAAuC;GAC7C;EACF;EAEA,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,KAAK,WAAW,GAClB,SACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAEA,IAAI,IAAI,QAAQ,SAAS,OAEvB,OAAO,GAAG,KAAK,OAAO,SAAS,KAAK,KAAK,OAAO,SAAS,GAAG,uBAAuB;OAC9E;GACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;GACzC,OAAO,GACL,WAAW,uBAAuB,KAAA,GAClC,mDACF;GACA,OAAO,GACL,WAAW,oBAAoB,KAAA,GAC/B,gDACF;EACF;CACF;AACF;AAEA,IAAa,YAA4B;CACvC,MAAM;CACN,UAAU,CAAC,OAAO;CAClB,KAAK;AACP"}
1
+ {"version":3,"file":"audit.js","names":[],"sources":["../../../src/tests/audit.ts"],"sourcesContent":["import assert from 'assert';\nimport buildDebug from 'debug';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { publishLocalPackage } from '../utils/publish';\n\nconst debug = buildDebug('verdaccio:e2e-cli:test:audit');\n\nasync function testAudit(ctx: TestContext): Promise<void> {\n // Audit is only reliable with npm and bun — pnpm/yarn audit output varies too much\n if (ctx.adapter.type !== 'npm' && ctx.adapter.type !== 'bun') {\n debug('skipping audit test for %s (npm/bun only)', ctx.adapter.type);\n return;\n }\n\n const packages = [`verdaccio-audit-${ctx.runId}`, `@verdaccio/audit-${ctx.runId}`];\n\n // Publish the dependency locally so metadata/tarball resolution never leaves\n // the registry under test (the suite must run offline). Only the audit\n // report request itself may travel upstream, and that is skipped below when\n // the endpoint is unavailable.\n const depName = `e2e-audit-dep-${ctx.runId}`;\n await publishLocalPackage(ctx, depName, '1.0.0');\n\n for (const pkgName of packages) {\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n { [depName]: '1.0.0' }\n );\n\n // install is required to create package lock file\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n // npm audit exits with code 1 both when vulnerabilities are found AND when\n // the endpoint is unavailable. We need to distinguish between the two.\n // Probe the audit endpoint first to avoid false failures on registries\n // that don't support it (e.g. verdaccio next-7+).\n try {\n const probe = await fetch(`${ctx.registryUrl}/-/npm/v1/security/audits/quick`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (probe.status === 404) {\n debug('audit endpoint not supported by this registry (404), skipping');\n return;\n }\n } catch {\n debug('audit endpoint probe failed, skipping');\n return;\n }\n\n let resp;\n try {\n resp = await ctx.adapter.exec(\n { cwd: tempFolder },\n 'audit',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n } catch (err) {\n // The registry proxies audit reports upstream; when running offline that\n // upstream is unreachable and the audit command fails — that is not a\n // registry bug, so skip instead of failing the suite.\n const message = err instanceof Error ? err.message : String(err);\n if (/503|ENOTFOUND|EAI_AGAIN|getaddrinfo|ECONNREFUSED|audit/i.test(message)) {\n debug('audit upstream unreachable (offline?), skipping: %s', message);\n return;\n }\n throw err;\n }\n\n if (ctx.adapter.type === 'bun') {\n // bun audit output may differ — exit code 0 is sufficient for basic validation\n assert.ok(resp.stdout.length > 0 || resp.stderr.length > 0, 'Expected audit output');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n assert.ok(\n parsedBody.auditReportVersion !== undefined,\n 'Expected \"auditReportVersion\" in audit response'\n );\n assert.ok(\n parsedBody.vulnerabilities !== undefined,\n 'Expected \"vulnerabilities\" in audit response'\n );\n }\n }\n}\n\nexport const auditTest: TestDefinition = {\n name: 'audit',\n requires: ['audit'],\n run: testAudit,\n};\n"],"mappings":";;;;AAMA,IAAM,QAAQ,WAAW,8BAA8B;AAEvD,eAAe,UAAU,KAAiC;CAExD,IAAI,IAAI,QAAQ,SAAS,SAAS,IAAI,QAAQ,SAAS,OAAO;EAC5D,MAAM,6CAA6C,IAAI,QAAQ,IAAI;EACnE;CACF;CAEA,MAAM,WAAW,CAAC,mBAAmB,IAAI,SAAS,oBAAoB,IAAI,OAAO;CAMjF,MAAM,UAAU,iBAAiB,IAAI;CACrC,MAAM,oBAAoB,KAAK,SAAS,OAAO;CAE/C,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,GAAG,UAAU,QAAQ,CACvB;EAGA,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAMA,IAAI;GAMF,KAAI,MALgB,MAAM,GAAG,IAAI,YAAY,kCAAkC;IAC7E,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,MAAM;GACR,CAAC,EAAA,CACS,WAAW,KAAK;IACxB,MAAM,+DAA+D;IACrE;GACF;EACF,QAAQ;GACN,MAAM,uCAAuC;GAC7C;EACF;EAEA,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,IAAI,QAAQ,KACvB,EAAE,KAAK,WAAW,GAClB,SACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EACF,SAAS,KAAK;GAIZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC/D,IAAI,0DAA0D,KAAK,OAAO,GAAG;IAC3E,MAAM,uDAAuD,OAAO;IACpE;GACF;GACA,MAAM;EACR;EAEA,IAAI,IAAI,QAAQ,SAAS,OAEvB,OAAO,GAAG,KAAK,OAAO,SAAS,KAAK,KAAK,OAAO,SAAS,GAAG,uBAAuB;OAC9E;GACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;GACzC,OAAO,GACL,WAAW,uBAAuB,KAAA,GAClC,mDACF;GACA,OAAO,GACL,WAAW,oBAAoB,KAAA,GAC/B,gDACF;EACF;CACF;AACF;AAEA,IAAa,YAA4B;CACvC,MAAM;CACN,UAAU,CAAC,OAAO;CAClB,KAAK;AACP"}
@@ -1,3 +1,4 @@
1
+ import { publishLocalPackage } from "../utils/publish.js";
1
2
  import buildDebug from "debug";
2
3
  import { join } from "path";
3
4
  import { rm } from "fs/promises";
@@ -26,11 +27,6 @@ function getCiArgs(ctx) {
26
27
  "--frozen-lockfile",
27
28
  ...ctx.adapter.registryArg(ctx.registryUrl)
28
29
  ];
29
- case "yarn-classic": return [
30
- "install",
31
- "--frozen-lockfile",
32
- ...ctx.adapter.registryArg(ctx.registryUrl)
33
- ];
34
30
  case "yarn-modern": return ["install", "--immutable"];
35
31
  case "bun": return [
36
32
  "install",
@@ -45,7 +41,9 @@ async function testCi(ctx) {
45
41
  debug("skipping ci test for deno (no ci command)");
46
42
  return;
47
43
  }
48
- const { tempFolder } = await ctx.adapter.prepareProject(`ci-test-${ctx.runId}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token, { "is-odd": "1.0.0" });
44
+ const depName = `e2e-ci-dep-${ctx.runId}`;
45
+ await publishLocalPackage(ctx, depName, "1.0.0");
46
+ const { tempFolder } = await ctx.adapter.prepareProject(`ci-test-${ctx.runId}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token, { [depName]: "1.0.0" });
49
47
  debug("running initial install to generate lockfile in %s", tempFolder);
50
48
  await ctx.adapter.exec({ cwd: tempFolder }, "install", ...ctx.adapter.registryArg(ctx.registryUrl));
51
49
  debug("lockfile generated, removing node_modules");
@@ -1 +1 @@
1
- {"version":3,"file":"ci.js","names":[],"sources":["../../../src/tests/ci.ts"],"sourcesContent":["import assert from 'assert';\nimport buildDebug from 'debug';\nimport { rm } from 'fs/promises';\nimport { join } from 'path';\n\nimport { TestContext, TestDefinition } from '../types';\n\nconst debug = buildDebug('verdaccio:e2e-cli:test:ci');\n\n/**\n * Test: ci\n *\n * Verifies that lockfile-based install works correctly.\n * Unlike `install`, CI commands skip metadata fetches and resolve\n * tarballs directly from the URLs stored in the lockfile.\n *\n * Flow:\n * 1. Prepare a project with dependencies\n * 2. Run `install` to generate the lockfile\n * 3. Remove node_modules\n * 4. Run the CI-equivalent command (npm ci / pnpm install --frozen-lockfile / yarn --frozen-lockfile / yarn --immutable)\n * 5. Verify it completes successfully\n */\n\nfunction getCiArgs(ctx: TestContext): string[] {\n switch (ctx.adapter.type) {\n case 'npm':\n // `npm ci` — separate command, reads package-lock.json\n return ['ci', ...ctx.adapter.registryArg(ctx.registryUrl)];\n case 'pnpm':\n // `pnpm install --frozen-lockfile` — same command, strict mode\n return ['install', '--frozen-lockfile', ...ctx.adapter.registryArg(ctx.registryUrl)];\n case 'yarn-classic':\n // `yarn install --frozen-lockfile`\n return ['install', '--frozen-lockfile', ...ctx.adapter.registryArg(ctx.registryUrl)];\n case 'yarn-modern':\n // `yarn install --immutable`\n return ['install', '--immutable'];\n case 'bun':\n // `bun install --frozen-lockfile`\n return ['install', '--frozen-lockfile', ...ctx.adapter.registryArg(ctx.registryUrl)];\n default:\n return ['ci', ...ctx.adapter.registryArg(ctx.registryUrl)];\n }\n}\n\nasync function testCi(ctx: TestContext): Promise<void> {\n // deno doesn't have a CI/frozen-lockfile equivalent\n if (ctx.adapter.type === 'deno') {\n debug('skipping ci test for deno (no ci command)');\n return;\n }\n\n const { tempFolder } = await ctx.adapter.prepareProject(\n `ci-test-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n { 'is-odd': '1.0.0' }\n );\n\n debug('running initial install to generate lockfile in %s', tempFolder);\n\n // Step 1: Run install to generate the lockfile\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n debug('lockfile generated, removing node_modules');\n\n // Step 2: Remove node_modules to force a clean install from lockfile\n const nodeModulesPath = join(tempFolder, 'node_modules');\n await rm(nodeModulesPath, { recursive: true, force: true });\n\n // For pnpm, also clean the virtual store\n const pnpmStorePath = join(tempFolder, '.pnpm-store');\n await rm(pnpmStorePath, { recursive: true, force: true });\n\n debug('node_modules removed, running ci command');\n\n // Step 3: Run CI-equivalent command (tarball-only, no metadata fetches)\n const ciArgs = getCiArgs(ctx);\n debug('ci args: %o', ciArgs);\n const resp = await ctx.adapter.exec({ cwd: tempFolder }, ...ciArgs);\n\n // Step 4: Verify success\n if (ctx.adapter.type === 'npm') {\n // npm ci with --json is not reliable, just check exit code 0\n // and verify output mentions added packages\n const output = resp.stdout + resp.stderr;\n debug('npm ci output: %s', output);\n assert.ok(true, 'npm ci completed successfully');\n } else {\n debug('ci completed successfully for %s', ctx.adapter.type);\n assert.ok(true, `CI install completed successfully for ${ctx.adapter.type}`);\n }\n}\n\nexport const ciTest: TestDefinition = {\n name: 'ci',\n requires: ['install'],\n run: testCi,\n};\n"],"mappings":";;;;;AAOA,IAAM,QAAQ,WAAW,2BAA2B;;;;;;;;;;;;;;;AAiBpD,SAAS,UAAU,KAA4B;CAC7C,QAAQ,IAAI,QAAQ,MAApB;EACE,KAAK,OAEH,OAAO,CAAC,MAAM,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAAC;EAC3D,KAAK,QAEH,OAAO;GAAC;GAAW;GAAqB,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW;EAAC;EACrF,KAAK,gBAEH,OAAO;GAAC;GAAW;GAAqB,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW;EAAC;EACrF,KAAK,eAEH,OAAO,CAAC,WAAW,aAAa;EAClC,KAAK,OAEH,OAAO;GAAC;GAAW;GAAqB,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW;EAAC;EACrF,SACE,OAAO,CAAC,MAAM,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAAC;CAC7D;AACF;AAEA,eAAe,OAAO,KAAiC;CAErD,IAAI,IAAI,QAAQ,SAAS,QAAQ;EAC/B,MAAM,2CAA2C;EACjD;CACF;CAEA,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,WAAW,IAAI,SACf,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,EAAE,UAAU,QAAQ,CACtB;CAEA,MAAM,sDAAsD,UAAU;CAGtE,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,MAAM,2CAA2C;CAGjD,MAAM,kBAAkB,KAAK,YAAY,cAAc;CACvD,MAAM,GAAG,iBAAiB;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC;CAG1D,MAAM,gBAAgB,KAAK,YAAY,aAAa;CACpD,MAAM,GAAG,eAAe;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC;CAExD,MAAM,0CAA0C;CAGhD,MAAM,SAAS,UAAU,GAAG;CAC5B,MAAM,eAAe,MAAM;CAC3B,MAAM,OAAO,MAAM,IAAI,QAAQ,KAAK,EAAE,KAAK,WAAW,GAAG,GAAG,MAAM;CAGlE,IAAI,IAAI,QAAQ,SAAS,OAAO;EAI9B,MAAM,qBADS,KAAK,SAAS,KAAK,MACD;EACjC,OAAO,GAAG,MAAM,+BAA+B;CACjD,OAAO;EACL,MAAM,oCAAoC,IAAI,QAAQ,IAAI;EAC1D,OAAO,GAAG,MAAM,yCAAyC,IAAI,QAAQ,MAAM;CAC7E;AACF;AAEA,IAAa,SAAyB;CACpC,MAAM;CACN,UAAU,CAAC,SAAS;CACpB,KAAK;AACP"}
1
+ {"version":3,"file":"ci.js","names":[],"sources":["../../../src/tests/ci.ts"],"sourcesContent":["import assert from 'assert';\nimport buildDebug from 'debug';\nimport { rm } from 'fs/promises';\nimport { join } from 'path';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { publishLocalPackage } from '../utils/publish';\n\nconst debug = buildDebug('verdaccio:e2e-cli:test:ci');\n\n/**\n * Test: ci\n *\n * Verifies that lockfile-based install works correctly.\n * Unlike `install`, CI commands skip metadata fetches and resolve\n * tarballs directly from the URLs stored in the lockfile.\n *\n * Flow:\n * 1. Prepare a project with dependencies\n * 2. Run `install` to generate the lockfile\n * 3. Remove node_modules\n * 4. Run the CI-equivalent command (npm ci / pnpm install --frozen-lockfile / yarn --frozen-lockfile / yarn --immutable)\n * 5. Verify it completes successfully\n */\n\nfunction getCiArgs(ctx: TestContext): string[] {\n switch (ctx.adapter.type) {\n case 'npm':\n // `npm ci` — separate command, reads package-lock.json\n return ['ci', ...ctx.adapter.registryArg(ctx.registryUrl)];\n case 'pnpm':\n // `pnpm install --frozen-lockfile` — same command, strict mode\n return ['install', '--frozen-lockfile', ...ctx.adapter.registryArg(ctx.registryUrl)];\n case 'yarn-modern':\n // `yarn install --immutable`\n return ['install', '--immutable'];\n case 'bun':\n // `bun install --frozen-lockfile`\n return ['install', '--frozen-lockfile', ...ctx.adapter.registryArg(ctx.registryUrl)];\n default:\n return ['ci', ...ctx.adapter.registryArg(ctx.registryUrl)];\n }\n}\n\nasync function testCi(ctx: TestContext): Promise<void> {\n // deno doesn't have a CI/frozen-lockfile equivalent\n if (ctx.adapter.type === 'deno') {\n debug('skipping ci test for deno (no ci command)');\n return;\n }\n\n // Publish the dependency locally first — the suite must run offline\n const depName = `e2e-ci-dep-${ctx.runId}`;\n await publishLocalPackage(ctx, depName, '1.0.0');\n\n const { tempFolder } = await ctx.adapter.prepareProject(\n `ci-test-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n { [depName]: '1.0.0' }\n );\n\n debug('running initial install to generate lockfile in %s', tempFolder);\n\n // Step 1: Run install to generate the lockfile\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'install',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n debug('lockfile generated, removing node_modules');\n\n // Step 2: Remove node_modules to force a clean install from lockfile\n const nodeModulesPath = join(tempFolder, 'node_modules');\n await rm(nodeModulesPath, { recursive: true, force: true });\n\n // For pnpm, also clean the virtual store\n const pnpmStorePath = join(tempFolder, '.pnpm-store');\n await rm(pnpmStorePath, { recursive: true, force: true });\n\n debug('node_modules removed, running ci command');\n\n // Step 3: Run CI-equivalent command (tarball-only, no metadata fetches)\n const ciArgs = getCiArgs(ctx);\n debug('ci args: %o', ciArgs);\n const resp = await ctx.adapter.exec({ cwd: tempFolder }, ...ciArgs);\n\n // Step 4: Verify success\n if (ctx.adapter.type === 'npm') {\n // npm ci with --json is not reliable, just check exit code 0\n // and verify output mentions added packages\n const output = resp.stdout + resp.stderr;\n debug('npm ci output: %s', output);\n assert.ok(true, 'npm ci completed successfully');\n } else {\n debug('ci completed successfully for %s', ctx.adapter.type);\n assert.ok(true, `CI install completed successfully for ${ctx.adapter.type}`);\n }\n}\n\nexport const ciTest: TestDefinition = {\n name: 'ci',\n requires: ['install'],\n run: testCi,\n};\n"],"mappings":";;;;;;AAQA,IAAM,QAAQ,WAAW,2BAA2B;;;;;;;;;;;;;;;AAiBpD,SAAS,UAAU,KAA4B;CAC7C,QAAQ,IAAI,QAAQ,MAApB;EACE,KAAK,OAEH,OAAO,CAAC,MAAM,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAAC;EAC3D,KAAK,QAEH,OAAO;GAAC;GAAW;GAAqB,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW;EAAC;EACrF,KAAK,eAEH,OAAO,CAAC,WAAW,aAAa;EAClC,KAAK,OAEH,OAAO;GAAC;GAAW;GAAqB,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW;EAAC;EACrF,SACE,OAAO,CAAC,MAAM,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAAC;CAC7D;AACF;AAEA,eAAe,OAAO,KAAiC;CAErD,IAAI,IAAI,QAAQ,SAAS,QAAQ;EAC/B,MAAM,2CAA2C;EACjD;CACF;CAGA,MAAM,UAAU,cAAc,IAAI;CAClC,MAAM,oBAAoB,KAAK,SAAS,OAAO;CAE/C,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,WAAW,IAAI,SACf,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,GAAG,UAAU,QAAQ,CACvB;CAEA,MAAM,sDAAsD,UAAU;CAGtE,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,MAAM,2CAA2C;CAGjD,MAAM,kBAAkB,KAAK,YAAY,cAAc;CACvD,MAAM,GAAG,iBAAiB;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC;CAG1D,MAAM,gBAAgB,KAAK,YAAY,aAAa;CACpD,MAAM,GAAG,eAAe;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC;CAExD,MAAM,0CAA0C;CAGhD,MAAM,SAAS,UAAU,GAAG;CAC5B,MAAM,eAAe,MAAM;CAC3B,MAAM,OAAO,MAAM,IAAI,QAAQ,KAAK,EAAE,KAAK,WAAW,GAAG,GAAG,MAAM;CAGlE,IAAI,IAAI,QAAQ,SAAS,OAAO;EAI9B,MAAM,qBADS,KAAK,SAAS,KAAK,MACD;EACjC,OAAO,GAAG,MAAM,+BAA+B;CACjD,OAAO;EACL,MAAM,oCAAoC,IAAI,QAAQ,IAAI;EAC1D,OAAO,GAAG,MAAM,yCAAyC,IAAI,QAAQ,MAAM;CAC7E;AACF;AAEA,IAAa,SAAyB;CACpC,MAAM;CACN,UAAU,CAAC,SAAS;CACpB,KAAK;AACP"}
@@ -1,5 +1,9 @@
1
1
  import "../scenarios/install-multiple-deps.js";
2
+ import "../scenarios/metadata.js";
2
3
  import "../scenarios/minimum-release-age.js";
4
+ import "../scenarios/search.js";
5
+ import "../scenarios/tarballs.js";
6
+ import "../scenarios/uplink-failure.js";
3
7
  import { allScenarios } from "../scenarios/index.js";
4
8
  import { auditTest } from "./audit.js";
5
9
  import { ciTest } from "./ci.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/tests/index.ts"],"sourcesContent":["import { allScenarios, installMultipleDepsScenario, minimumReleaseAgeScenario } 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 allScenarios,\n};\n"],"mappings":";;;;;;;;;;;;;;;AAcA,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 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,31 +1,20 @@
1
- import { createTempFolder } from "../utils/project.js";
2
1
  import { parseInfoJson } from "../utils/info.js";
3
- import { join } from "path";
4
- import { writeFile } from "fs/promises";
2
+ import { publishLocalPackage } from "../utils/publish.js";
5
3
  import assert from "assert";
6
4
  //#region src/tests/info.ts
7
5
  async function testInfo(ctx) {
8
- const tempFolder = await createTempFolder("info-test");
9
- await writeFile(join(tempFolder, "package.json"), JSON.stringify({
10
- name: "info-test",
11
- version: "1.0.0"
12
- }));
13
- const resp = await ctx.adapter.exec({ cwd: tempFolder }, "info", "verdaccio", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
14
- if (ctx.adapter.type === "yarn-classic") {
15
- const dataLine = resp.stdout.split("\n").filter(Boolean).find((l) => {
16
- try {
17
- return JSON.parse(l).type === "inspect";
18
- } catch {
19
- return false;
20
- }
21
- });
22
- assert.ok(dataLine, "Expected yarn info NDJSON to contain an \"inspect\" entry");
23
- } else if (ctx.adapter.type === "deno") {
6
+ const depName = `e2e-info-dep-${ctx.runId}`;
7
+ const pkgName = `e2e-info-${ctx.runId}`;
8
+ await publishLocalPackage(ctx, depName, "1.0.0");
9
+ await publishLocalPackage(ctx, pkgName, "1.0.0", { [depName]: "1.0.0" });
10
+ const { tempFolder } = await ctx.adapter.prepareProject(`info-client-${ctx.runId}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token);
11
+ const resp = await ctx.adapter.exec({ cwd: tempFolder }, "info", pkgName, "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
12
+ if (ctx.adapter.type === "deno") {
24
13
  const output = resp.stdout + resp.stderr;
25
- assert.ok(output.includes("verdaccio"), "Expected deno info output to reference verdaccio");
14
+ assert.ok(output.includes(pkgName), `Expected deno info output to reference ${pkgName}`);
26
15
  } else {
27
16
  const parsedBody = parseInfoJson(resp.stdout);
28
- assert.strictEqual(parsedBody.name, "verdaccio", "Expected package name \"verdaccio\"");
17
+ assert.strictEqual(parsedBody.name, pkgName, `Expected package name "${pkgName}"`);
29
18
  assert.ok(parsedBody.dependencies !== void 0, "Expected \"dependencies\" to be defined");
30
19
  }
31
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"info.js","names":[],"sources":["../../../src/tests/info.ts"],"sourcesContent":["import assert from 'assert';\nimport { writeFile } from 'fs/promises';\nimport { join } from 'path';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { parseInfoJson } from '../utils/info';\nimport { createTempFolder } from '../utils/project';\n\nasync function testInfo(ctx: TestContext): Promise<void> {\n // Create a minimal temp project so yarn modern has a project context\n // and yarn classic doesn't pick up the repo's packageManager field\n const tempFolder = await createTempFolder('info-test');\n await writeFile(\n join(tempFolder, 'package.json'),\n JSON.stringify({ name: 'info-test', version: '1.0.0' })\n );\n\n const resp = await ctx.adapter.exec(\n { cwd: tempFolder },\n 'info',\n 'verdaccio',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (ctx.adapter.type === 'yarn-classic') {\n const lines = resp.stdout.split('\\n').filter(Boolean);\n const dataLine = lines.find((l) => {\n try {\n const obj = JSON.parse(l);\n return obj.type === 'inspect';\n } catch {\n return false;\n }\n });\n assert.ok(dataLine, 'Expected yarn info NDJSON to contain an \"inspect\" entry');\n } else if (ctx.adapter.type === 'deno') {\n // deno info npm:<pkg> outputs a text dependency tree\n const output = resp.stdout + resp.stderr;\n assert.ok(output.includes('verdaccio'), 'Expected deno info output to reference verdaccio');\n } else {\n const parsedBody = parseInfoJson(resp.stdout);\n assert.strictEqual(parsedBody.name, 'verdaccio', 'Expected package name \"verdaccio\"');\n assert.ok(parsedBody.dependencies !== undefined, 'Expected \"dependencies\" to be defined');\n }\n}\n\nexport const infoTest: TestDefinition = {\n name: 'info',\n requires: ['info'],\n run: testInfo,\n};\n"],"mappings":";;;;;;AAQA,eAAe,SAAS,KAAiC;CAGvD,MAAM,aAAa,MAAM,iBAAiB,WAAW;CACrD,MAAM,UACJ,KAAK,YAAY,cAAc,GAC/B,KAAK,UAAU;EAAE,MAAM;EAAa,SAAS;CAAQ,CAAC,CACxD;CAEA,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,KAAK,WAAW,GAClB,QACA,aACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,IAAI,QAAQ,SAAS,gBAAgB;EAEvC,MAAM,WADQ,KAAK,OAAO,MAAM,IAAI,CAAC,CAAC,OAAO,OAC5B,CAAA,CAAM,MAAM,MAAM;GACjC,IAAI;IAEF,OADY,KAAK,MAAM,CAChB,CAAA,CAAI,SAAS;GACtB,QAAQ;IACN,OAAO;GACT;EACF,CAAC;EACD,OAAO,GAAG,UAAU,2DAAyD;CAC/E,OAAO,IAAI,IAAI,QAAQ,SAAS,QAAQ;EAEtC,MAAM,SAAS,KAAK,SAAS,KAAK;EAClC,OAAO,GAAG,OAAO,SAAS,WAAW,GAAG,kDAAkD;CAC5F,OAAO;EACL,MAAM,aAAa,cAAc,KAAK,MAAM;EAC5C,OAAO,YAAY,WAAW,MAAM,aAAa,qCAAmC;EACpF,OAAO,GAAG,WAAW,iBAAiB,KAAA,GAAW,yCAAuC;CAC1F;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
1
+ {"version":3,"file":"info.js","names":[],"sources":["../../../src/tests/info.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { parseInfoJson } from '../utils/info';\nimport { publishLocalPackage } from '../utils/publish';\n\nasync function testInfo(ctx: TestContext): Promise<void> {\n // Publish the package we inspect (with one dependency, also local) — asking\n // for a real npmjs package would need an uplink and the suite must run\n // offline. deno resolves the dependency tree, so the dep must exist too.\n const depName = `e2e-info-dep-${ctx.runId}`;\n const pkgName = `e2e-info-${ctx.runId}`;\n await publishLocalPackage(ctx, depName, '1.0.0');\n await publishLocalPackage(ctx, pkgName, '1.0.0', { [depName]: '1.0.0' });\n\n // Run from a prepared project: it gives yarn modern a project context, and\n // its .npmrc (fresh token) shields the test from stale credentials in\n // ~/.npmrc.\n const { tempFolder } = await ctx.adapter.prepareProject(\n `info-client-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n\n const resp = await ctx.adapter.exec(\n { cwd: tempFolder },\n 'info',\n pkgName,\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (ctx.adapter.type === 'deno') {\n // deno info npm:<pkg> outputs a text dependency tree\n const output = resp.stdout + resp.stderr;\n assert.ok(output.includes(pkgName), `Expected deno info output to reference ${pkgName}`);\n } else {\n const parsedBody = parseInfoJson(resp.stdout);\n assert.strictEqual(parsedBody.name, pkgName, `Expected package name \"${pkgName}\"`);\n assert.ok(parsedBody.dependencies !== undefined, 'Expected \"dependencies\" to be defined');\n }\n}\n\nexport const infoTest: TestDefinition = {\n name: 'info',\n requires: ['info'],\n run: testInfo,\n};\n"],"mappings":";;;;AAMA,eAAe,SAAS,KAAiC;CAIvD,MAAM,UAAU,gBAAgB,IAAI;CACpC,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,oBAAoB,KAAK,SAAS,OAAO;CAC/C,MAAM,oBAAoB,KAAK,SAAS,SAAS,GAAG,UAAU,QAAQ,CAAC;CAKvE,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,eAAe,IAAI,SACnB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CAEA,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,KAAK,WAAW,GAClB,QACA,SACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,IAAI,QAAQ,SAAS,QAAQ;EAE/B,MAAM,SAAS,KAAK,SAAS,KAAK;EAClC,OAAO,GAAG,OAAO,SAAS,OAAO,GAAG,0CAA0C,SAAS;CACzF,OAAO;EACL,MAAM,aAAa,cAAc,KAAK,MAAM;EAC5C,OAAO,YAAY,WAAW,MAAM,SAAS,0BAA0B,QAAQ,EAAE;EACjF,OAAO,GAAG,WAAW,iBAAiB,KAAA,GAAW,yCAAuC;CAC1F;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
@@ -1,7 +1,10 @@
1
+ import { publishLocalPackage } from "../utils/publish.js";
1
2
  import assert from "assert";
2
3
  //#region src/tests/install.ts
3
4
  async function testInstall(ctx) {
4
- const { tempFolder } = await ctx.adapter.prepareProject(`install-test-${ctx.runId}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token, { react: "18.2.0" });
5
+ const depName = `e2e-install-dep-${ctx.runId}`;
6
+ await publishLocalPackage(ctx, depName, "1.0.0");
7
+ const { tempFolder } = await ctx.adapter.prepareProject(`install-test-${ctx.runId}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token, { [depName]: "1.0.0" });
5
8
  const isNpm = ctx.adapter.type === "npm";
6
9
  const args = isNpm ? [
7
10
  "install",
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","names":[],"sources":["../../../src/tests/install.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testInstall(ctx: TestContext): Promise<void> {\n const { tempFolder } = await ctx.adapter.prepareProject(\n `install-test-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n { react: '18.2.0' }\n );\n\n const isNpm = ctx.adapter.type === 'npm';\n\n // pnpm doesn't support --json on install\n const args = isNpm\n ? ['install', '--json', ...ctx.adapter.registryArg(ctx.registryUrl)]\n : ['install', ...ctx.adapter.registryArg(ctx.registryUrl)];\n\n const resp = await ctx.adapter.exec({ cwd: tempFolder }, ...args);\n\n if (isNpm) {\n const parsedBody = JSON.parse(resp.stdout);\n assert.ok(parsedBody.added !== undefined, 'Expected \"added\" field in install response');\n } else {\n // for pnpm/yarn just verify it completed without error (exit code 0)\n assert.ok(true, 'Install completed successfully');\n }\n}\n\nexport const installTest: TestDefinition = {\n name: 'install',\n requires: ['install'],\n run: testInstall,\n};\n"],"mappings":";;AAIA,eAAe,YAAY,KAAiC;CAC1D,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,gBAAgB,IAAI,SACpB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,EAAE,OAAO,SAAS,CACpB;CAEA,MAAM,QAAQ,IAAI,QAAQ,SAAS;CAGnC,MAAM,OAAO,QACT;EAAC;EAAW;EAAU,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW;CAAC,IACjE,CAAC,WAAW,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAAC;CAE3D,MAAM,OAAO,MAAM,IAAI,QAAQ,KAAK,EAAE,KAAK,WAAW,GAAG,GAAG,IAAI;CAEhE,IAAI,OAAO;EACT,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EACzC,OAAO,GAAG,WAAW,UAAU,KAAA,GAAW,8CAA4C;CACxF,OAEE,OAAO,GAAG,MAAM,gCAAgC;AAEpD;AAEA,IAAa,cAA8B;CACzC,MAAM;CACN,UAAU,CAAC,SAAS;CACpB,KAAK;AACP"}
1
+ {"version":3,"file":"install.js","names":[],"sources":["../../../src/tests/install.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { publishLocalPackage } from '../utils/publish';\n\nasync function testInstall(ctx: TestContext): Promise<void> {\n // Publish the dependency locally first — the suite must work offline, so\n // nothing is ever resolved from an external registry.\n const depName = `e2e-install-dep-${ctx.runId}`;\n await publishLocalPackage(ctx, depName, '1.0.0');\n\n const { tempFolder } = await ctx.adapter.prepareProject(\n `install-test-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token,\n { [depName]: '1.0.0' }\n );\n\n const isNpm = ctx.adapter.type === 'npm';\n\n // pnpm doesn't support --json on install\n const args = isNpm\n ? ['install', '--json', ...ctx.adapter.registryArg(ctx.registryUrl)]\n : ['install', ...ctx.adapter.registryArg(ctx.registryUrl)];\n\n const resp = await ctx.adapter.exec({ cwd: tempFolder }, ...args);\n\n if (isNpm) {\n const parsedBody = JSON.parse(resp.stdout);\n assert.ok(parsedBody.added !== undefined, 'Expected \"added\" field in install response');\n } else {\n // for pnpm/yarn just verify it completed without error (exit code 0)\n assert.ok(true, 'Install completed successfully');\n }\n}\n\nexport const installTest: TestDefinition = {\n name: 'install',\n requires: ['install'],\n run: testInstall,\n};\n"],"mappings":";;;AAKA,eAAe,YAAY,KAAiC;CAG1D,MAAM,UAAU,mBAAmB,IAAI;CACvC,MAAM,oBAAoB,KAAK,SAAS,OAAO;CAE/C,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,gBAAgB,IAAI,SACpB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,OACJ,GAAG,UAAU,QAAQ,CACvB;CAEA,MAAM,QAAQ,IAAI,QAAQ,SAAS;CAGnC,MAAM,OAAO,QACT;EAAC;EAAW;EAAU,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW;CAAC,IACjE,CAAC,WAAW,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAAC;CAE3D,MAAM,OAAO,MAAM,IAAI,QAAQ,KAAK,EAAE,KAAK,WAAW,GAAG,GAAG,IAAI;CAEhE,IAAI,OAAO;EACT,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EACzC,OAAO,GAAG,WAAW,UAAU,KAAA,GAAW,8CAA4C;CACxF,OAEE,OAAO,GAAG,MAAM,gCAAgC;AAEpD;AAEA,IAAa,cAA8B;CACzC,MAAM;CACN,UAAU,CAAC,SAAS;CACpB,KAAK;AACP"}
@@ -2,13 +2,9 @@ import assert from "assert";
2
2
  //#region src/tests/ping.ts
3
3
  async function testPing(ctx) {
4
4
  const type = ctx.adapter.type;
5
- const needsProject = type === "yarn-modern";
6
- let cwd;
7
- if (needsProject) {
8
- const { tempFolder } = await ctx.adapter.prepareProject(`verdaccio-ping-${ctx.runId}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token);
9
- cwd = tempFolder;
10
- if (ctx.adapter.importPlugin) await ctx.adapter.importPlugin(tempFolder, "npm-ping");
11
- }
5
+ const { tempFolder } = await ctx.adapter.prepareProject(`verdaccio-ping-${ctx.runId}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token);
6
+ const cwd = tempFolder;
7
+ if (type === "yarn-modern" && ctx.adapter.importPlugin) await ctx.adapter.importPlugin(tempFolder, "npm-ping");
12
8
  const jsonFlag = type === "yarn-modern" ? [] : ["--json"];
13
9
  const resp = await ctx.adapter.exec({ cwd }, "ping", ...jsonFlag, ...ctx.adapter.registryArg(ctx.registryUrl));
14
10
  if (type === "yarn-modern") assert.ok(true, "ping succeeded");
@@ -1 +1 @@
1
- {"version":3,"file":"ping.js","names":[],"sources":["../../../src/tests/ping.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testPing(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n const needsProject = type === 'yarn-modern';\n let cwd: string | undefined;\n\n if (needsProject) {\n const { tempFolder } = await ctx.adapter.prepareProject(\n `verdaccio-ping-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n cwd = tempFolder;\n if (ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tempFolder, 'npm-ping');\n }\n }\n\n const jsonFlag = type === 'yarn-modern' ? [] : ['--json'];\n const resp = await ctx.adapter.exec(\n { cwd },\n 'ping',\n ...jsonFlag,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n // yarn npm ping succeeding (exit code 0) is sufficient proof the registry is reachable\n assert.ok(true, 'ping succeeded');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n assert.strictEqual(\n parsedBody.registry,\n ctx.registryUrl + '/',\n `Expected registry \"${ctx.registryUrl}/\" but got \"${parsedBody.registry}\"`\n );\n }\n}\n\nexport const pingTest: TestDefinition = {\n name: 'ping',\n requires: ['ping'],\n run: testPing,\n};\n"],"mappings":";;AAIA,eAAe,SAAS,KAAiC;CACvD,MAAM,OAAO,IAAI,QAAQ;CACzB,MAAM,eAAe,SAAS;CAC9B,IAAI;CAEJ,IAAI,cAAc;EAChB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,kBAAkB,IAAI,SACtB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;EACA,MAAM;EACN,IAAI,IAAI,QAAQ,cACd,MAAM,IAAI,QAAQ,aAAa,YAAY,UAAU;CAEzD;CAEA,MAAM,WAAW,SAAS,gBAAgB,CAAC,IAAI,CAAC,QAAQ;CACxD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,IAAI,GACN,QACA,GAAG,UACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,SAAS,eAEX,OAAO,GAAG,MAAM,gBAAgB;MAC3B;EACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EACzC,OAAO,YACL,WAAW,UACX,IAAI,cAAc,KAClB,sBAAsB,IAAI,YAAY,cAAc,WAAW,SAAS,EAC1E;CACF;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
1
+ {"version":3,"file":"ping.js","names":[],"sources":["../../../src/tests/ping.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testPing(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n\n // Always run from a prepared project: its .npmrc carries the fresh token and\n // shields the test from whatever the developer's global ~/.npmrc has for\n // this registry (a stale token there would turn into a spurious E401).\n const { tempFolder } = await ctx.adapter.prepareProject(\n `verdaccio-ping-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n const cwd = tempFolder;\n if (type === 'yarn-modern' && ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tempFolder, 'npm-ping');\n }\n\n const jsonFlag = type === 'yarn-modern' ? [] : ['--json'];\n const resp = await ctx.adapter.exec(\n { cwd },\n 'ping',\n ...jsonFlag,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n // yarn npm ping succeeding (exit code 0) is sufficient proof the registry is reachable\n assert.ok(true, 'ping succeeded');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n assert.strictEqual(\n parsedBody.registry,\n ctx.registryUrl + '/',\n `Expected registry \"${ctx.registryUrl}/\" but got \"${parsedBody.registry}\"`\n );\n }\n}\n\nexport const pingTest: TestDefinition = {\n name: 'ping',\n requires: ['ping'],\n run: testPing,\n};\n"],"mappings":";;AAIA,eAAe,SAAS,KAAiC;CACvD,MAAM,OAAO,IAAI,QAAQ;CAKzB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,kBAAkB,IAAI,SACtB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,MAAM;CACZ,IAAI,SAAS,iBAAiB,IAAI,QAAQ,cACxC,MAAM,IAAI,QAAQ,aAAa,YAAY,UAAU;CAGvD,MAAM,WAAW,SAAS,gBAAgB,CAAC,IAAI,CAAC,QAAQ;CACxD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,IAAI,GACN,QACA,GAAG,UACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,SAAS,eAEX,OAAO,GAAG,MAAM,gBAAgB;MAC3B;EACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EACzC,OAAO,YACL,WAAW,UACX,IAAI,cAAc,KAClB,sBAAsB,IAAI,YAAY,cAAc,WAAW,SAAS,EAC1E;CACF;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
@@ -13,7 +13,6 @@ async function testPublish(ctx) {
13
13
  if (type === "yarn-modern") await ctx.adapter.exec({ cwd: tempFolder }, "install");
14
14
  const resp = await ctx.adapter.exec({ cwd: tempFolder }, "publish", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
15
15
  if (type === "yarn-modern") assert.ok(resp.stdout.match(/Package archive published/), `Expected "Package archive published" for ${pkgName} but got "${resp.stdout}"`);
16
- else if (type === "yarn-classic") assert.ok(resp.stdout.length > 0, `Expected publish output for ${pkgName}`);
17
16
  else if (type === "bun") assert.ok(true, `Publish succeeded for ${pkgName}`);
18
17
  else try {
19
18
  const parsedBody = JSON.parse(resp.stdout);
@@ -1 +1 @@
1
- {"version":3,"file":"publish.js","names":[],"sources":["../../../src/tests/publish.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testPublish(ctx: TestContext): Promise<void> {\n const packages = [\n `verdaccio-memory-${ctx.runId}`,\n `verdaccio-${ctx.runId}`,\n `@verdaccio/foo-${ctx.runId}`,\n `@verdaccio/some-foo-${ctx.runId}`,\n ];\n\n const type = ctx.adapter.type;\n\n for (const pkgName of packages) {\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n\n // yarn modern requires install before publish to generate lockfile\n if (type === 'yarn-modern') {\n await ctx.adapter.exec({ cwd: tempFolder }, 'install');\n }\n\n const resp = await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n assert.ok(\n resp.stdout.match(/Package archive published/),\n `Expected \"Package archive published\" for ${pkgName} but got \"${resp.stdout}\"`\n );\n } else if (type === 'yarn-classic') {\n // yarn classic --json outputs NDJSON — just verify no error exit (exit code 0 is enough)\n assert.ok(resp.stdout.length > 0, `Expected publish output for ${pkgName}`);\n } else if (type === 'bun') {\n // bun publish outputs text confirmation — exit code 0 is sufficient\n assert.ok(true, `Publish succeeded for ${pkgName}`);\n } else {\n // npm / pnpm\n try {\n const parsedBody = JSON.parse(resp.stdout);\n assert.strictEqual(\n parsedBody.name,\n pkgName,\n `Expected package name \"${pkgName}\" but got \"${parsedBody.name}\"`\n );\n assert.ok(parsedBody.files, `Expected files to be defined for ${pkgName}`);\n } catch {\n // pnpm v11+ native publish doesn't output JSON — exit code 0 is sufficient\n assert.ok(true, `Publish succeeded for ${pkgName}`);\n }\n }\n }\n}\n\nexport const publishTest: TestDefinition = {\n name: 'publish',\n requires: ['publish'],\n run: testPublish,\n};\n"],"mappings":";;AAIA,eAAe,YAAY,KAAiC;CAC1D,MAAM,WAAW;EACf,oBAAoB,IAAI;EACxB,aAAa,IAAI;EACjB,kBAAkB,IAAI;EACtB,uBAAuB,IAAI;CAC7B;CAEA,MAAM,OAAO,IAAI,QAAQ;CAEzB,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;EAGA,IAAI,SAAS,eACX,MAAM,IAAI,QAAQ,KAAK,EAAE,KAAK,WAAW,GAAG,SAAS;EAGvD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,KAAK,WAAW,GAClB,WACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAEA,IAAI,SAAS,eACX,OAAO,GACL,KAAK,OAAO,MAAM,2BAA2B,GAC7C,4CAA4C,QAAQ,YAAY,KAAK,OAAO,EAC9E;OACK,IAAI,SAAS,gBAElB,OAAO,GAAG,KAAK,OAAO,SAAS,GAAG,+BAA+B,SAAS;OACrE,IAAI,SAAS,OAElB,OAAO,GAAG,MAAM,yBAAyB,SAAS;OAGlD,IAAI;GACF,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;GACzC,OAAO,YACL,WAAW,MACX,SACA,0BAA0B,QAAQ,aAAa,WAAW,KAAK,EACjE;GACA,OAAO,GAAG,WAAW,OAAO,oCAAoC,SAAS;EAC3E,QAAQ;GAEN,OAAO,GAAG,MAAM,yBAAyB,SAAS;EACpD;CAEJ;AACF;AAEA,IAAa,cAA8B;CACzC,MAAM;CACN,UAAU,CAAC,SAAS;CACpB,KAAK;AACP"}
1
+ {"version":3,"file":"publish.js","names":[],"sources":["../../../src/tests/publish.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testPublish(ctx: TestContext): Promise<void> {\n const packages = [\n `verdaccio-memory-${ctx.runId}`,\n `verdaccio-${ctx.runId}`,\n `@verdaccio/foo-${ctx.runId}`,\n `@verdaccio/some-foo-${ctx.runId}`,\n ];\n\n const type = ctx.adapter.type;\n\n for (const pkgName of packages) {\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n\n // yarn modern requires install before publish to generate lockfile\n if (type === 'yarn-modern') {\n await ctx.adapter.exec({ cwd: tempFolder }, 'install');\n }\n\n const resp = await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n assert.ok(\n resp.stdout.match(/Package archive published/),\n `Expected \"Package archive published\" for ${pkgName} but got \"${resp.stdout}\"`\n );\n } else if (type === 'bun') {\n // bun publish outputs text confirmation — exit code 0 is sufficient\n assert.ok(true, `Publish succeeded for ${pkgName}`);\n } else {\n // npm / pnpm\n try {\n const parsedBody = JSON.parse(resp.stdout);\n assert.strictEqual(\n parsedBody.name,\n pkgName,\n `Expected package name \"${pkgName}\" but got \"${parsedBody.name}\"`\n );\n assert.ok(parsedBody.files, `Expected files to be defined for ${pkgName}`);\n } catch {\n // pnpm v11+ native publish doesn't output JSON — exit code 0 is sufficient\n assert.ok(true, `Publish succeeded for ${pkgName}`);\n }\n }\n }\n}\n\nexport const publishTest: TestDefinition = {\n name: 'publish',\n requires: ['publish'],\n run: testPublish,\n};\n"],"mappings":";;AAIA,eAAe,YAAY,KAAiC;CAC1D,MAAM,WAAW;EACf,oBAAoB,IAAI;EACxB,aAAa,IAAI;EACjB,kBAAkB,IAAI;EACtB,uBAAuB,IAAI;CAC7B;CAEA,MAAM,OAAO,IAAI,QAAQ;CAEzB,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;EAGA,IAAI,SAAS,eACX,MAAM,IAAI,QAAQ,KAAK,EAAE,KAAK,WAAW,GAAG,SAAS;EAGvD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,KAAK,WAAW,GAClB,WACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;EAEA,IAAI,SAAS,eACX,OAAO,GACL,KAAK,OAAO,MAAM,2BAA2B,GAC7C,4CAA4C,QAAQ,YAAY,KAAK,OAAO,EAC9E;OACK,IAAI,SAAS,OAElB,OAAO,GAAG,MAAM,yBAAyB,SAAS;OAGlD,IAAI;GACF,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;GACzC,OAAO,YACL,WAAW,MACX,SACA,0BAA0B,QAAQ,aAAa,WAAW,KAAK,EACjE;GACA,OAAO,GAAG,WAAW,OAAO,oCAAoC,SAAS;EAC3E,QAAQ;GAEN,OAAO,GAAG,MAAM,yBAAyB,SAAS;EACpD;CAEJ;AACF;AAEA,IAAa,cAA8B;CACzC,MAAM;CACN,UAAU,CAAC,SAAS;CACpB,KAAK;AACP"}
@@ -1,14 +1,18 @@
1
+ import { publishLocalPackage } from "../utils/publish.js";
1
2
  import assert from "assert";
2
3
  //#region src/tests/search.ts
3
4
  async function testSearch(ctx) {
4
- const resp = await ctx.adapter.exec({}, "search", "@verdaccio/cli", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
5
- const pkgFind = JSON.parse(resp.stdout).find((item) => item.name === "@verdaccio/cli");
6
- assert.ok(pkgFind, "Expected to find @verdaccio/cli in search results");
7
- assert.strictEqual(pkgFind.name, "@verdaccio/cli");
5
+ const pkgName = `verdaccio-search-${ctx.runId}`;
6
+ await publishLocalPackage(ctx, pkgName, "1.0.0");
7
+ const { tempFolder } = await ctx.adapter.prepareProject(`search-client-${ctx.runId}`, "1.0.0", ctx.registryUrl, ctx.port, ctx.token);
8
+ const resp = await ctx.adapter.exec({ cwd: tempFolder }, "search", pkgName, "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
9
+ const pkgFind = JSON.parse(resp.stdout).find((item) => item.name === pkgName);
10
+ assert.ok(pkgFind, `Expected to find ${pkgName} in search results`);
11
+ assert.strictEqual(pkgFind.name, pkgName);
8
12
  }
9
13
  var searchTest = {
10
14
  name: "search",
11
- requires: ["search"],
15
+ requires: ["search", "publish"],
12
16
  run: testSearch
13
17
  };
14
18
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":[],"sources":["../../../src/tests/search.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testSearch(ctx: TestContext): Promise<void> {\n const resp = await ctx.adapter.exec(\n {},\n 'search',\n '@verdaccio/cli',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n const parsedBody = JSON.parse(resp.stdout);\n const pkgFind = parsedBody.find((item: any) => item.name === '@verdaccio/cli');\n assert.ok(pkgFind, 'Expected to find @verdaccio/cli in search results');\n assert.strictEqual(pkgFind.name, '@verdaccio/cli');\n}\n\nexport const searchTest: TestDefinition = {\n name: 'search',\n requires: ['search'],\n run: testSearch,\n};\n"],"mappings":";;AAIA,eAAe,WAAW,KAAiC;CACzD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,CAAC,GACD,UACA,kBACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,MAAM,UADa,KAAK,MAAM,KAAK,MACnB,CAAA,CAAW,MAAM,SAAc,KAAK,SAAS,gBAAgB;CAC7E,OAAO,GAAG,SAAS,mDAAmD;CACtE,OAAO,YAAY,QAAQ,MAAM,gBAAgB;AACnD;AAEA,IAAa,aAA6B;CACxC,MAAM;CACN,UAAU,CAAC,QAAQ;CACnB,KAAK;AACP"}
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../../src/tests/search.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { publishLocalPackage } from '../utils/publish';\n\nasync function testSearch(ctx: TestContext): Promise<void> {\n // Publish the package we search for — searching an npmjs package would need\n // an uplink and the suite must run offline.\n const pkgName = `verdaccio-search-${ctx.runId}`;\n await publishLocalPackage(ctx, pkgName, '1.0.0');\n\n // Run from a prepared project so its .npmrc (fresh token) shields the test\n // from stale credentials in the developer's global ~/.npmrc.\n const { tempFolder } = await ctx.adapter.prepareProject(\n `search-client-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n\n const resp = await ctx.adapter.exec(\n { cwd: tempFolder },\n 'search',\n pkgName,\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n const parsedBody = JSON.parse(resp.stdout);\n const pkgFind = parsedBody.find((item: any) => item.name === pkgName);\n assert.ok(pkgFind, `Expected to find ${pkgName} in search results`);\n assert.strictEqual(pkgFind.name, pkgName);\n}\n\nexport const searchTest: TestDefinition = {\n name: 'search',\n requires: ['search', 'publish'],\n run: testSearch,\n};\n"],"mappings":";;;AAKA,eAAe,WAAW,KAAiC;CAGzD,MAAM,UAAU,oBAAoB,IAAI;CACxC,MAAM,oBAAoB,KAAK,SAAS,OAAO;CAI/C,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,iBAAiB,IAAI,SACrB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CAEA,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,KAAK,WAAW,GAClB,UACA,SACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,MAAM,UADa,KAAK,MAAM,KAAK,MACnB,CAAA,CAAW,MAAM,SAAc,KAAK,SAAS,OAAO;CACpE,OAAO,GAAG,SAAS,oBAAoB,QAAQ,mBAAmB;CAClE,OAAO,YAAY,QAAQ,MAAM,OAAO;AAC1C;AAEA,IAAa,aAA6B;CACxC,MAAM;CACN,UAAU,CAAC,UAAU,SAAS;CAC9B,KAAK;AACP"}
@@ -0,0 +1,58 @@
1
+ //#region src/utils/e2e-config.ts
2
+ /**
3
+ * Recommended Verdaccio config for running the full e2e-cli battery,
4
+ * printed by `verdaccio-e2e --print-config`. It is what the consuming repos'
5
+ * CI jobs and scripts/run-e2e.sh feed to `verdaccio --config`:
6
+ *
7
+ * - max_body_size raised so scenario:tarballs can publish ~30 MB packages
8
+ * (Verdaccio's default is 10mb)
9
+ * - an `e2emock` uplink + `e2e-uplink-*` package pattern wired to the mock
10
+ * uplink server that scenario:uplink-failure starts on E2E_UPLINK_PORT
11
+ * (short timeout so the "slow uplink" case completes quickly; high
12
+ * max_fails so simulated failures don't trip the circuit breaker for
13
+ * later sub-tests)
14
+ * - NO npmjs uplink: the whole suite publishes every package it consumes,
15
+ * so the registry under test never talks to the outside world and the
16
+ * battery runs fully offline (and deterministically in CI)
17
+ *
18
+ * Relative paths (storage/htpasswd) resolve next to the config file, so write
19
+ * it into a disposable directory.
20
+ */
21
+ var DEFAULT_UPLINK_PORT = 4874;
22
+ function buildE2EConfig(uplinkPort = DEFAULT_UPLINK_PORT) {
23
+ return `# Generated by @verdaccio/e2e-cli --print-config
24
+ storage: ./storage
25
+ auth:
26
+ htpasswd:
27
+ file: ./htpasswd
28
+ max_users: 1000
29
+ web:
30
+ enable: true
31
+ title: Verdaccio E2E
32
+ max_body_size: 100mb
33
+ uplinks:
34
+ e2emock:
35
+ url: http://localhost:${uplinkPort}/
36
+ timeout: 3s
37
+ retry: 0
38
+ max_fails: 100
39
+ fail_timeout: 1s
40
+ packages:
41
+ 'e2e-uplink-*':
42
+ access: $all
43
+ proxy: e2emock
44
+ '@*/*':
45
+ access: $all
46
+ publish: $authenticated
47
+ unpublish: $authenticated
48
+ '**':
49
+ access: $all
50
+ publish: $authenticated
51
+ unpublish: $authenticated
52
+ log: { type: stdout, format: pretty, level: warn }
53
+ `;
54
+ }
55
+ //#endregion
56
+ export { DEFAULT_UPLINK_PORT, buildE2EConfig };
57
+
58
+ //# sourceMappingURL=e2e-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2e-config.js","names":[],"sources":["../../../src/utils/e2e-config.ts"],"sourcesContent":["/**\n * Recommended Verdaccio config for running the full e2e-cli battery,\n * printed by `verdaccio-e2e --print-config`. It is what the consuming repos'\n * CI jobs and scripts/run-e2e.sh feed to `verdaccio --config`:\n *\n * - max_body_size raised so scenario:tarballs can publish ~30 MB packages\n * (Verdaccio's default is 10mb)\n * - an `e2emock` uplink + `e2e-uplink-*` package pattern wired to the mock\n * uplink server that scenario:uplink-failure starts on E2E_UPLINK_PORT\n * (short timeout so the \"slow uplink\" case completes quickly; high\n * max_fails so simulated failures don't trip the circuit breaker for\n * later sub-tests)\n * - NO npmjs uplink: the whole suite publishes every package it consumes,\n * so the registry under test never talks to the outside world and the\n * battery runs fully offline (and deterministically in CI)\n *\n * Relative paths (storage/htpasswd) resolve next to the config file, so write\n * it into a disposable directory.\n */\nexport const DEFAULT_UPLINK_PORT = 4874;\n\nexport function buildE2EConfig(uplinkPort: number = DEFAULT_UPLINK_PORT): string {\n return `# Generated by @verdaccio/e2e-cli --print-config\nstorage: ./storage\nauth:\n htpasswd:\n file: ./htpasswd\n max_users: 1000\nweb:\n enable: true\n title: Verdaccio E2E\nmax_body_size: 100mb\nuplinks:\n e2emock:\n url: http://localhost:${uplinkPort}/\n timeout: 3s\n retry: 0\n max_fails: 100\n fail_timeout: 1s\npackages:\n 'e2e-uplink-*':\n access: $all\n proxy: e2emock\n '@*/*':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\n '**':\n access: $all\n publish: $authenticated\n unpublish: $authenticated\nlog: { type: stdout, format: pretty, level: warn }\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,IAAa,sBAAsB;AAEnC,SAAgB,eAAe,aAAqB,qBAA6B;CAC/E,OAAO;;;;;;;;;;;;4BAYmB,WAAW;;;;;;;;;;;;;;;;;;;AAmBvC"}
@@ -0,0 +1,105 @@
1
+ import buildDebug from "debug";
2
+ import { createHash } from "crypto";
3
+ import { get } from "http";
4
+ //#region src/utils/http-client.ts
5
+ var debug = buildDebug("verdaccio:e2e-cli:http-client");
6
+ var ABBREVIATED_ACCEPT = "application/vnd.npm.install-v1+json";
7
+ /**
8
+ * Registry URL for a package name. Scoped names are encoded with the same
9
+ * `%2f` form npm clients use: `@scope%2fname`.
10
+ */
11
+ function packumentUrl(registryUrl, pkgName) {
12
+ const encoded = pkgName.startsWith("@") ? pkgName.replaceAll("/", "%2f") : pkgName;
13
+ return `${registryUrl.replace(/\/$/, "")}/${encoded}`;
14
+ }
15
+ async function fetchPackument(registryUrl, pkgName, options = {}) {
16
+ const url = packumentUrl(registryUrl, pkgName);
17
+ const headers = {};
18
+ if (options.abbreviated) headers["Accept"] = ABBREVIATED_ACCEPT;
19
+ if (options.etag) headers["If-None-Match"] = options.etag;
20
+ if (options.token) headers["Authorization"] = `Bearer ${options.token}`;
21
+ debug("GET %s %o", url, headers);
22
+ if (options.etag) return new Promise((resolve, reject) => {
23
+ get(url, { headers }, (response) => {
24
+ let text = "";
25
+ response.setEncoding("utf8");
26
+ response.on("data", (chunk) => text += chunk);
27
+ response.on("end", () => {
28
+ let body;
29
+ try {
30
+ body = JSON.parse(text);
31
+ } catch {
32
+ body = void 0;
33
+ }
34
+ const responseHeaders = new Headers();
35
+ for (const [name, value] of Object.entries(response.headers)) if (value !== void 0) responseHeaders.set(name, Array.isArray(value) ? value.join(", ") : value);
36
+ resolve({
37
+ status: response.statusCode || 0,
38
+ headers: responseHeaders,
39
+ body
40
+ });
41
+ });
42
+ }).on("error", reject);
43
+ });
44
+ const response = await fetch(url, { headers });
45
+ let body;
46
+ if (response.status !== 304) {
47
+ const text = await response.text();
48
+ try {
49
+ body = JSON.parse(text);
50
+ } catch {
51
+ body = void 0;
52
+ }
53
+ }
54
+ return {
55
+ status: response.status,
56
+ headers: response.headers,
57
+ body
58
+ };
59
+ }
60
+ /**
61
+ * Download a tarball fully, computing sha1/sha512 integrity on the fly.
62
+ * Rejects with the underlying error when aborted or the connection drops.
63
+ */
64
+ async function downloadTarball(url, options = {}) {
65
+ const headers = {};
66
+ if (options.token) headers["Authorization"] = `Bearer ${options.token}`;
67
+ debug("GET (tarball) %s", url);
68
+ const response = await fetch(url, {
69
+ headers,
70
+ signal: options.signal
71
+ });
72
+ const sha1 = createHash("sha1");
73
+ const sha512 = createHash("sha512");
74
+ let bytes = 0;
75
+ let first = true;
76
+ if (response.body) for await (const chunk of response.body) {
77
+ const buf = Buffer.from(chunk);
78
+ bytes += buf.length;
79
+ sha1.update(buf);
80
+ sha512.update(buf);
81
+ if (first) {
82
+ first = false;
83
+ options.onFirstChunk?.();
84
+ }
85
+ options.onChunk?.(bytes);
86
+ }
87
+ return {
88
+ status: response.status,
89
+ headers: response.headers,
90
+ bytes,
91
+ sha1: sha1.digest("hex"),
92
+ integrity: `sha512-${sha512.digest("base64")}`
93
+ };
94
+ }
95
+ /** Compute npm-style dist values (shasum/integrity) for a buffer. */
96
+ function computeDist(buffer) {
97
+ return {
98
+ shasum: createHash("sha1").update(buffer).digest("hex"),
99
+ integrity: `sha512-${createHash("sha512").update(buffer).digest("base64")}`
100
+ };
101
+ }
102
+ //#endregion
103
+ export { ABBREVIATED_ACCEPT, computeDist, downloadTarball, fetchPackument, packumentUrl };
104
+
105
+ //# sourceMappingURL=http-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-client.js","names":[],"sources":["../../../src/utils/http-client.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport buildDebug from 'debug';\nimport { get } from 'http';\n\nconst debug = buildDebug('verdaccio:e2e-cli:http-client');\n\nexport type PackumentResponse = {\n status: number;\n headers: Headers;\n /** Parsed JSON body, or undefined when the response is not JSON (e.g. 304) */\n body?: any;\n};\n\nexport type TarballDownload = {\n status: number;\n headers: Headers;\n /** Total bytes received */\n bytes: number;\n /** hex-encoded sha1 of the received bytes (matches dist.shasum) */\n sha1: string;\n /** `sha512-<base64>` of the received bytes (matches dist.integrity) */\n integrity: string;\n};\n\nexport type FetchPackumentOptions = {\n /** Request the abbreviated (install) metadata format */\n abbreviated?: boolean;\n /** Send If-None-Match with this value */\n etag?: string;\n /** Bearer token */\n token?: string;\n};\n\nexport const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json';\n\n/**\n * Registry URL for a package name. Scoped names are encoded with the same\n * `%2f` form npm clients use: `@scope%2fname`.\n */\nexport function packumentUrl(registryUrl: string, pkgName: string): string {\n const encoded = pkgName.startsWith('@') ? pkgName.replaceAll('/', '%2f') : pkgName;\n return `${registryUrl.replace(/\\/$/, '')}/${encoded}`;\n}\n\nexport async function fetchPackument(\n registryUrl: string,\n pkgName: string,\n options: FetchPackumentOptions = {}\n): Promise<PackumentResponse> {\n const url = packumentUrl(registryUrl, pkgName);\n const headers: Record<string, string> = {};\n if (options.abbreviated) {\n headers['Accept'] = ABBREVIATED_ACCEPT;\n }\n if (options.etag) {\n headers['If-None-Match'] = options.etag;\n }\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET %s %o', url, headers);\n\n // Conditional requests must bypass fetch(): undici appends\n // `cache-control: no-cache` whenever If-None-Match is present, which\n // (correctly, per HTTP semantics) disables the server's 304 revalidation.\n if (options.etag) {\n return new Promise((resolve, reject) => {\n get(url, { headers }, (response) => {\n let text = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => (text += chunk));\n response.on('end', () => {\n let body: any;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n const responseHeaders = new Headers();\n for (const [name, value] of Object.entries(response.headers)) {\n if (value !== undefined) {\n responseHeaders.set(name, Array.isArray(value) ? value.join(', ') : value);\n }\n }\n resolve({ status: response.statusCode || 0, headers: responseHeaders, body });\n });\n }).on('error', reject);\n });\n }\n\n const response = await fetch(url, { headers });\n let body: any;\n if (response.status !== 304) {\n const text = await response.text();\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n }\n return { status: response.status, headers: response.headers, body };\n}\n\nexport type DownloadTarballOptions = {\n signal?: AbortSignal;\n token?: string;\n /** Called after the first chunk of the body arrives (useful to abort mid-stream) */\n onFirstChunk?: () => void;\n /** Called after every chunk with the total bytes received so far (useful to abort at a threshold) */\n onChunk?: (bytesReceived: number) => void;\n};\n\n/**\n * Download a tarball fully, computing sha1/sha512 integrity on the fly.\n * Rejects with the underlying error when aborted or the connection drops.\n */\nexport async function downloadTarball(\n url: string,\n options: DownloadTarballOptions = {}\n): Promise<TarballDownload> {\n const headers: Record<string, string> = {};\n if (options.token) {\n headers['Authorization'] = `Bearer ${options.token}`;\n }\n debug('GET (tarball) %s', url);\n const response = await fetch(url, { headers, signal: options.signal });\n\n const sha1 = createHash('sha1');\n const sha512 = createHash('sha512');\n let bytes = 0;\n let first = true;\n\n if (response.body) {\n for await (const chunk of response.body as any) {\n const buf = Buffer.from(chunk);\n bytes += buf.length;\n sha1.update(buf);\n sha512.update(buf);\n if (first) {\n first = false;\n options.onFirstChunk?.();\n }\n options.onChunk?.(bytes);\n }\n }\n\n return {\n status: response.status,\n headers: response.headers,\n bytes,\n sha1: sha1.digest('hex'),\n integrity: `sha512-${sha512.digest('base64')}`,\n };\n}\n\n/** Compute npm-style dist values (shasum/integrity) for a buffer. */\nexport function computeDist(buffer: Buffer): { shasum: string; integrity: string } {\n return {\n shasum: createHash('sha1').update(buffer).digest('hex'),\n integrity: `sha512-${createHash('sha512').update(buffer).digest('base64')}`,\n };\n}\n"],"mappings":";;;;AAIA,IAAM,QAAQ,WAAW,+BAA+B;AA6BxD,IAAa,qBAAqB;;;;;AAMlC,SAAgB,aAAa,aAAqB,SAAyB;CACzE,MAAM,UAAU,QAAQ,WAAW,GAAG,IAAI,QAAQ,WAAW,KAAK,KAAK,IAAI;CAC3E,OAAO,GAAG,YAAY,QAAQ,OAAO,EAAE,EAAE,GAAG;AAC9C;AAEA,eAAsB,eACpB,aACA,SACA,UAAiC,CAAC,GACN;CAC5B,MAAM,MAAM,aAAa,aAAa,OAAO;CAC7C,MAAM,UAAkC,CAAC;CACzC,IAAI,QAAQ,aACV,QAAQ,YAAY;CAEtB,IAAI,QAAQ,MACV,QAAQ,mBAAmB,QAAQ;CAErC,IAAI,QAAQ,OACV,QAAQ,mBAAmB,UAAU,QAAQ;CAE/C,MAAM,aAAa,KAAK,OAAO;CAK/B,IAAI,QAAQ,MACV,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,KAAK,EAAE,QAAQ,IAAI,aAAa;GAClC,IAAI,OAAO;GACX,SAAS,YAAY,MAAM;GAC3B,SAAS,GAAG,SAAS,UAAW,QAAQ,KAAM;GAC9C,SAAS,GAAG,aAAa;IACvB,IAAI;IACJ,IAAI;KACF,OAAO,KAAK,MAAM,IAAI;IACxB,QAAQ;KACN,OAAO,KAAA;IACT;IACA,MAAM,kBAAkB,IAAI,QAAQ;IACpC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,OAAO,GACzD,IAAI,UAAU,KAAA,GACZ,gBAAgB,IAAI,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK;IAG7E,QAAQ;KAAE,QAAQ,SAAS,cAAc;KAAG,SAAS;KAAiB;IAAK,CAAC;GAC9E,CAAC;EACH,CAAC,CAAC,CAAC,GAAG,SAAS,MAAM;CACvB,CAAC;CAGH,MAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;CAC7C,IAAI;CACJ,IAAI,SAAS,WAAW,KAAK;EAC3B,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,IAAI;GACF,OAAO,KAAK,MAAM,IAAI;EACxB,QAAQ;GACN,OAAO,KAAA;EACT;CACF;CACA,OAAO;EAAE,QAAQ,SAAS;EAAQ,SAAS,SAAS;EAAS;CAAK;AACpE;;;;;AAeA,eAAsB,gBACpB,KACA,UAAkC,CAAC,GACT;CAC1B,MAAM,UAAkC,CAAC;CACzC,IAAI,QAAQ,OACV,QAAQ,mBAAmB,UAAU,QAAQ;CAE/C,MAAM,oBAAoB,GAAG;CAC7B,MAAM,WAAW,MAAM,MAAM,KAAK;EAAE;EAAS,QAAQ,QAAQ;CAAO,CAAC;CAErE,MAAM,OAAO,WAAW,MAAM;CAC9B,MAAM,SAAS,WAAW,QAAQ;CAClC,IAAI,QAAQ;CACZ,IAAI,QAAQ;CAEZ,IAAI,SAAS,MACX,WAAW,MAAM,SAAS,SAAS,MAAa;EAC9C,MAAM,MAAM,OAAO,KAAK,KAAK;EAC7B,SAAS,IAAI;EACb,KAAK,OAAO,GAAG;EACf,OAAO,OAAO,GAAG;EACjB,IAAI,OAAO;GACT,QAAQ;GACR,QAAQ,eAAe;EACzB;EACA,QAAQ,UAAU,KAAK;CACzB;CAGF,OAAO;EACL,QAAQ,SAAS;EACjB,SAAS,SAAS;EAClB;EACA,MAAM,KAAK,OAAO,KAAK;EACvB,WAAW,UAAU,OAAO,OAAO,QAAQ;CAC7C;AACF;;AAGA,SAAgB,YAAY,QAAuD;CACjF,OAAO;EACL,QAAQ,WAAW,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;EACtD,WAAW,UAAU,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,QAAQ;CAC1E;AACF"}