@verdaccio/e2e-cli 3.0.0 → 3.2.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 (209) hide show
  1. package/bin/e2e-cli.js +6 -26
  2. package/build/adapters/adapter.d.ts +1 -0
  3. package/build/adapters/bun.d.ts +2 -0
  4. package/build/adapters/deno.d.ts +2 -0
  5. package/build/adapters/index.d.ts +5 -0
  6. package/build/adapters/npm.d.ts +2 -0
  7. package/build/adapters/pnpm.d.ts +2 -0
  8. package/build/adapters/yarn-modern.d.ts +2 -0
  9. package/build/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  10. package/build/cjs/adapters/bun.cjs +64 -0
  11. package/build/cjs/adapters/bun.cjs.map +1 -0
  12. package/build/cjs/adapters/deno.cjs +61 -0
  13. package/build/cjs/adapters/deno.cjs.map +1 -0
  14. package/build/cjs/adapters/index.cjs +10 -0
  15. package/build/cjs/adapters/npm.cjs +85 -0
  16. package/build/cjs/adapters/npm.cjs.map +1 -0
  17. package/build/cjs/adapters/pnpm.cjs +114 -0
  18. package/build/cjs/adapters/pnpm.cjs.map +1 -0
  19. package/build/cjs/adapters/yarn-modern.cjs +207 -0
  20. package/build/cjs/adapters/yarn-modern.cjs.map +1 -0
  21. package/build/cjs/index.cjs +186 -11
  22. package/build/cjs/index.cjs.map +1 -0
  23. package/build/cjs/reporter.cjs +113 -0
  24. package/build/cjs/reporter.cjs.map +1 -0
  25. package/build/cjs/runner.cjs +141 -0
  26. package/build/cjs/runner.cjs.map +1 -0
  27. package/build/cjs/scenarios/index.cjs +25 -0
  28. package/build/cjs/scenarios/index.cjs.map +1 -0
  29. package/build/cjs/scenarios/install-multiple-deps.cjs +119 -0
  30. package/build/cjs/scenarios/install-multiple-deps.cjs.map +1 -0
  31. package/build/cjs/scenarios/metadata.cjs +121 -0
  32. package/build/cjs/scenarios/metadata.cjs.map +1 -0
  33. package/build/cjs/scenarios/minimum-release-age.cjs +111 -0
  34. package/build/cjs/scenarios/minimum-release-age.cjs.map +1 -0
  35. package/build/cjs/scenarios/search.cjs +200 -0
  36. package/build/cjs/scenarios/search.cjs.map +1 -0
  37. package/build/cjs/scenarios/tarballs.cjs +170 -0
  38. package/build/cjs/scenarios/tarballs.cjs.map +1 -0
  39. package/build/cjs/scenarios/uplink-failure.cjs +141 -0
  40. package/build/cjs/scenarios/uplink-failure.cjs.map +1 -0
  41. package/build/cjs/tests/audit.cjs +60 -0
  42. package/build/cjs/tests/audit.cjs.map +1 -0
  43. package/build/cjs/tests/ci.cjs +83 -0
  44. package/build/cjs/tests/ci.cjs.map +1 -0
  45. package/build/cjs/tests/deprecate.cjs +108 -0
  46. package/build/cjs/tests/deprecate.cjs.map +1 -0
  47. package/build/cjs/tests/dist-tags.cjs +43 -0
  48. package/build/cjs/tests/dist-tags.cjs.map +1 -0
  49. package/build/cjs/tests/index.cjs +49 -0
  50. package/build/cjs/tests/index.cjs.map +1 -0
  51. package/build/cjs/tests/info.cjs +31 -0
  52. package/build/cjs/tests/info.cjs.map +1 -0
  53. package/build/cjs/tests/install.cjs +30 -0
  54. package/build/cjs/tests/install.cjs.map +1 -0
  55. package/build/cjs/tests/login.cjs +126 -0
  56. package/build/cjs/tests/login.cjs.map +1 -0
  57. package/build/cjs/tests/ping.cjs +26 -0
  58. package/build/cjs/tests/ping.cjs.map +1 -0
  59. package/build/cjs/tests/publish.cjs +36 -0
  60. package/build/cjs/tests/publish.cjs.map +1 -0
  61. package/build/cjs/tests/search.cjs +23 -0
  62. package/build/cjs/tests/search.cjs.map +1 -0
  63. package/build/cjs/tests/unpublish.cjs +40 -0
  64. package/build/cjs/tests/unpublish.cjs.map +1 -0
  65. package/build/cjs/utils/e2e-config.cjs +59 -0
  66. package/build/cjs/utils/e2e-config.cjs.map +1 -0
  67. package/build/cjs/utils/http-client.cjs +111 -0
  68. package/build/cjs/utils/http-client.cjs.map +1 -0
  69. package/build/cjs/utils/info.cjs +25 -0
  70. package/build/cjs/utils/info.cjs.map +1 -0
  71. package/build/cjs/utils/mock-uplink.cjs +196 -0
  72. package/build/cjs/utils/mock-uplink.cjs.map +1 -0
  73. package/build/cjs/utils/pending-checks.cjs +21 -0
  74. package/build/cjs/utils/pending-checks.cjs.map +1 -0
  75. package/build/cjs/utils/process.cjs +96 -0
  76. package/build/cjs/utils/process.cjs.map +1 -0
  77. package/build/cjs/utils/project.cjs +55 -0
  78. package/build/cjs/utils/project.cjs.map +1 -0
  79. package/build/cjs/utils/publish.cjs +117 -0
  80. package/build/cjs/utils/publish.cjs.map +1 -0
  81. package/build/cjs/utils/registry-client.cjs +56 -0
  82. package/build/cjs/utils/registry-client.cjs.map +1 -0
  83. package/build/esm/adapters/bun.js +62 -0
  84. package/build/esm/adapters/bun.js.map +1 -0
  85. package/build/esm/adapters/deno.js +59 -0
  86. package/build/esm/adapters/deno.js.map +1 -0
  87. package/build/esm/adapters/index.js +6 -0
  88. package/build/esm/adapters/npm.js +83 -0
  89. package/build/esm/adapters/npm.js.map +1 -0
  90. package/build/esm/adapters/pnpm.js +112 -0
  91. package/build/esm/adapters/pnpm.js.map +1 -0
  92. package/build/esm/adapters/yarn-modern.js +204 -0
  93. package/build/esm/adapters/yarn-modern.js.map +1 -0
  94. package/build/esm/index.js +174 -10
  95. package/build/esm/index.js.map +1 -0
  96. package/build/esm/reporter.js +108 -0
  97. package/build/esm/reporter.js.map +1 -0
  98. package/build/esm/runner.js +138 -0
  99. package/build/esm/runner.js.map +1 -0
  100. package/build/esm/scenarios/index.js +19 -0
  101. package/build/esm/scenarios/index.js.map +1 -0
  102. package/build/esm/scenarios/install-multiple-deps.js +117 -0
  103. package/build/esm/scenarios/install-multiple-deps.js.map +1 -0
  104. package/build/esm/scenarios/metadata.js +119 -0
  105. package/build/esm/scenarios/metadata.js.map +1 -0
  106. package/build/esm/scenarios/minimum-release-age.js +108 -0
  107. package/build/esm/scenarios/minimum-release-age.js.map +1 -0
  108. package/build/esm/scenarios/search.js +198 -0
  109. package/build/esm/scenarios/search.js.map +1 -0
  110. package/build/esm/scenarios/tarballs.js +168 -0
  111. package/build/esm/scenarios/tarballs.js.map +1 -0
  112. package/build/esm/scenarios/uplink-failure.js +139 -0
  113. package/build/esm/scenarios/uplink-failure.js.map +1 -0
  114. package/build/esm/tests/audit.js +57 -0
  115. package/build/esm/tests/audit.js.map +1 -0
  116. package/build/esm/tests/ci.js +80 -0
  117. package/build/esm/tests/ci.js.map +1 -0
  118. package/build/esm/tests/deprecate.js +105 -0
  119. package/build/esm/tests/deprecate.js.map +1 -0
  120. package/build/esm/tests/dist-tags.js +41 -0
  121. package/build/esm/tests/dist-tags.js.map +1 -0
  122. package/build/esm/tests/index.js +37 -0
  123. package/build/esm/tests/index.js.map +1 -0
  124. package/build/esm/tests/info.js +29 -0
  125. package/build/esm/tests/info.js.map +1 -0
  126. package/build/esm/tests/install.js +28 -0
  127. package/build/esm/tests/install.js.map +1 -0
  128. package/build/esm/tests/login.js +122 -0
  129. package/build/esm/tests/login.js.map +1 -0
  130. package/build/esm/tests/ping.js +24 -0
  131. package/build/esm/tests/ping.js.map +1 -0
  132. package/build/esm/tests/publish.js +34 -0
  133. package/build/esm/tests/publish.js.map +1 -0
  134. package/build/esm/tests/search.js +21 -0
  135. package/build/esm/tests/search.js.map +1 -0
  136. package/build/esm/tests/unpublish.js +38 -0
  137. package/build/esm/tests/unpublish.js.map +1 -0
  138. package/build/esm/utils/e2e-config.js +58 -0
  139. package/build/esm/utils/e2e-config.js.map +1 -0
  140. package/build/esm/utils/http-client.js +105 -0
  141. package/build/esm/utils/http-client.js.map +1 -0
  142. package/build/esm/utils/info.js +24 -0
  143. package/build/esm/utils/info.js.map +1 -0
  144. package/build/esm/utils/mock-uplink.js +194 -0
  145. package/build/esm/utils/mock-uplink.js.map +1 -0
  146. package/build/esm/utils/pending-checks.js +21 -0
  147. package/build/esm/utils/pending-checks.js.map +1 -0
  148. package/build/esm/utils/process.js +92 -0
  149. package/build/esm/utils/process.js.map +1 -0
  150. package/build/esm/utils/project.js +50 -0
  151. package/build/esm/utils/project.js.map +1 -0
  152. package/build/esm/utils/publish.js +113 -0
  153. package/build/esm/utils/publish.js.map +1 -0
  154. package/build/esm/utils/registry-client.js +53 -0
  155. package/build/esm/utils/registry-client.js.map +1 -0
  156. package/build/index.d.ts +6 -15
  157. package/build/reporter.d.ts +7 -0
  158. package/build/runner.d.ts +13 -0
  159. package/build/scenarios/index.d.ts +9 -0
  160. package/build/scenarios/install-multiple-deps.d.ts +2 -0
  161. package/build/scenarios/metadata.d.ts +2 -0
  162. package/build/scenarios/minimum-release-age.d.ts +11 -0
  163. package/build/scenarios/search.d.ts +2 -0
  164. package/build/scenarios/tarballs.d.ts +2 -0
  165. package/build/scenarios/uplink-failure.d.ts +2 -0
  166. package/build/tests/audit.d.ts +2 -0
  167. package/build/tests/ci.d.ts +2 -0
  168. package/build/tests/deprecate.d.ts +2 -0
  169. package/build/tests/dist-tags.d.ts +2 -0
  170. package/build/tests/index.d.ts +15 -0
  171. package/build/tests/info.d.ts +2 -0
  172. package/build/tests/install.d.ts +2 -0
  173. package/build/tests/login.d.ts +2 -0
  174. package/build/tests/ping.d.ts +2 -0
  175. package/build/tests/publish.d.ts +2 -0
  176. package/build/tests/search.d.ts +2 -0
  177. package/build/tests/unpublish.d.ts +2 -0
  178. package/build/types.d.ts +91 -0
  179. package/build/utils/e2e-config.d.ts +21 -0
  180. package/build/utils/http-client.d.ts +49 -0
  181. package/build/utils/index.d.ts +4 -0
  182. package/build/utils/info.d.ts +14 -0
  183. package/build/utils/mock-uplink.d.ts +47 -0
  184. package/build/utils/pending-checks.d.ts +12 -0
  185. package/build/utils/process.d.ts +11 -0
  186. package/build/utils/project.d.ts +6 -0
  187. package/build/utils/publish.d.ts +17 -0
  188. package/build/utils/registry-client.d.ts +15 -0
  189. package/package.json +16 -14
  190. package/Cargo.lock +0 -1591
  191. package/Cargo.toml +0 -19
  192. package/build/bin/verdaccio-e2e +0 -0
  193. package/scripts/build-js-package.mjs +0 -25
  194. package/src/e2e_tests/audit.rs +0 -51
  195. package/src/e2e_tests/ci.rs +0 -32
  196. package/src/e2e_tests/deprecate.rs +0 -64
  197. package/src/e2e_tests/dist_tags.rs +0 -56
  198. package/src/e2e_tests/info.rs +0 -43
  199. package/src/e2e_tests/install.rs +0 -27
  200. package/src/e2e_tests/login.rs +0 -64
  201. package/src/e2e_tests/mod.rs +0 -104
  202. package/src/e2e_tests/ping.rs +0 -33
  203. package/src/e2e_tests/publish.rs +0 -68
  204. package/src/e2e_tests/search.rs +0 -14
  205. package/src/e2e_tests/unpublish.rs +0 -37
  206. package/src/main.rs +0 -1411
  207. package/src/scenarios/install_multiple_deps.rs +0 -99
  208. package/src/scenarios/minimum_release_age.rs +0 -45
  209. package/src/scenarios/mod.rs +0 -11
@@ -0,0 +1,122 @@
1
+ import { createTempFolder, getPackageJSON, getREADME } from "../utils/project.js";
2
+ import buildDebug from "debug";
3
+ import { join } from "path";
4
+ import { writeFile } from "fs/promises";
5
+ import * as YAML from "js-yaml";
6
+ import assert from "assert";
7
+ //#region src/tests/login.ts
8
+ var debug = buildDebug("verdaccio:e2e-cli:test:login");
9
+ /**
10
+ * Creates a project with registry config but WITHOUT auth token,
11
+ * so whoami will use the token saved by login to ~/.yarnrc.yml.
12
+ */
13
+ async function prepareLoginProject(ctx, name) {
14
+ debug("preparing project for %s (no auth token)", name);
15
+ const tempFolder = await createTempFolder(name);
16
+ const yamlContent = YAML.dump({
17
+ npmRegistryServer: ctx.registryUrl,
18
+ enableImmutableInstalls: false,
19
+ unsafeHttpWhitelist: ["localhost"]
20
+ });
21
+ await writeFile(join(tempFolder, ".yarnrc.yml"), yamlContent);
22
+ await writeFile(join(tempFolder, "package.json"), JSON.stringify({
23
+ name,
24
+ version: "1.0.0"
25
+ }));
26
+ if (ctx.adapter.importPlugin) {
27
+ debug("importing npm-login plugin into %s", tempFolder);
28
+ await ctx.adapter.importPlugin(tempFolder, "npm-login");
29
+ }
30
+ return tempFolder;
31
+ }
32
+ async function yarnLogin(ctx, cwd, user, password, email) {
33
+ debug("yarn npm login as %s", user);
34
+ return ctx.adapter.exec({ cwd }, "login", "--auth-type=legacy", `--user=${user}`, `--password=${password}`, `--email=${email}`);
35
+ }
36
+ async function yarnWhoami(ctx, cwd) {
37
+ const resp = await ctx.adapter.exec({ cwd }, "whoami");
38
+ debug("yarn npm whoami: %s", resp.stdout);
39
+ return resp.stdout;
40
+ }
41
+ async function testLogin(ctx) {
42
+ const type = ctx.adapter.type;
43
+ const id = ctx.runId;
44
+ if (type !== "yarn-modern") {
45
+ debug("login test only supported for yarn-modern, skipping %s", type);
46
+ return;
47
+ }
48
+ const user1 = `login-a-${id}`;
49
+ const password1 = "test-password-123";
50
+ const email1 = `${user1}@test.example.com`;
51
+ await ctx.subTest("create new user + whoami", async () => {
52
+ const tf = await prepareLoginProject(ctx, `verdaccio-login1-${id}`);
53
+ const loginResp = await yarnLogin(ctx, tf, user1, password1, email1);
54
+ assert.ok(loginResp.stdout.includes("Logged in") || loginResp.stdout.includes("token saved"), `Expected login success message but got "${loginResp.stdout}"`);
55
+ const who = await yarnWhoami(ctx, tf);
56
+ assert.ok(who.includes(user1), `Expected whoami "${user1}" but got "${who}"`);
57
+ });
58
+ await ctx.subTest("legacy re-login is rejected without basic auth", async () => {
59
+ const tf = await prepareLoginProject(ctx, `verdaccio-login2-${id}`);
60
+ let loginFailed = false;
61
+ try {
62
+ await yarnLogin(ctx, tf, user1, password1, email1);
63
+ } catch {
64
+ loginFailed = true;
65
+ }
66
+ assert.ok(loginFailed, "Legacy re-login for an existing user should fail without Basic auth");
67
+ });
68
+ await ctx.subTest("wrong password fails", async () => {
69
+ const tf = await prepareLoginProject(ctx, `verdaccio-login3-${id}`);
70
+ let loginFailed = false;
71
+ try {
72
+ await yarnLogin(ctx, tf, user1, "wrong-password", email1);
73
+ } catch {
74
+ loginFailed = true;
75
+ }
76
+ assert.ok(loginFailed, "Login with wrong password should have failed");
77
+ });
78
+ await ctx.subTest("login then publish", async () => {
79
+ const pkgName = `@verdaccio/login-pub-${id}`;
80
+ const publishUser = `login-pub-${id}`;
81
+ const publishPassword = "publish-password-123";
82
+ const publishEmail = `${publishUser}@test.example.com`;
83
+ const tf = await createTempFolder(`login-pub-${id}`);
84
+ const yamlContent = YAML.dump({
85
+ npmRegistryServer: ctx.registryUrl,
86
+ enableImmutableInstalls: false,
87
+ unsafeHttpWhitelist: ["localhost"]
88
+ });
89
+ await writeFile(join(tf, ".yarnrc.yml"), yamlContent);
90
+ await writeFile(join(tf, "package.json"), getPackageJSON(pkgName, "1.0.0"));
91
+ await writeFile(join(tf, "README.md"), getREADME(pkgName));
92
+ if (ctx.adapter.importPlugin) await ctx.adapter.importPlugin(tf, "npm-login");
93
+ await yarnLogin(ctx, tf, publishUser, publishPassword, publishEmail);
94
+ await ctx.adapter.exec({ cwd: tf }, "install");
95
+ const pubResp = await ctx.adapter.exec({ cwd: tf }, "publish");
96
+ assert.ok(pubResp.stdout.includes("Package archive published"), `Expected publish success but got "${pubResp.stdout}"`);
97
+ });
98
+ await ctx.subTest("switch users", async () => {
99
+ const switchUser1 = `login-switch-a-${id}`;
100
+ const switchPassword1 = "switch-password-123";
101
+ const switchEmail1 = `${switchUser1}@test.example.com`;
102
+ const user2 = `login-b-${id}`;
103
+ const password2 = "other-password-456";
104
+ const email2 = `${user2}@test.example.com`;
105
+ const tf = await prepareLoginProject(ctx, `verdaccio-login5-${id}`);
106
+ await yarnLogin(ctx, tf, switchUser1, switchPassword1, switchEmail1);
107
+ const who1 = await yarnWhoami(ctx, tf);
108
+ assert.ok(who1.includes(switchUser1), `Expected "${switchUser1}" but got "${who1}"`);
109
+ await yarnLogin(ctx, tf, user2, password2, email2);
110
+ const who2 = await yarnWhoami(ctx, tf);
111
+ assert.ok(who2.includes(user2), `Expected "${user2}" after switch but got "${who2}"`);
112
+ });
113
+ }
114
+ var loginTest = {
115
+ name: "login",
116
+ requires: ["login"],
117
+ run: testLogin
118
+ };
119
+ //#endregion
120
+ export { loginTest };
121
+
122
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","names":[],"sources":["../../../src/tests/login.ts"],"sourcesContent":["import assert from 'assert';\nimport buildDebug from 'debug';\nimport { writeFile } from 'fs/promises';\nimport * as YAML from 'js-yaml';\nimport { join } from 'path';\n\nimport { TestContext, TestDefinition } from '../types';\nimport { createTempFolder, getPackageJSON, getREADME } from '../utils/project';\n\nconst debug = buildDebug('verdaccio:e2e-cli:test:login');\n\n/**\n * Creates a project with registry config but WITHOUT auth token,\n * so whoami will use the token saved by login to ~/.yarnrc.yml.\n */\nasync function prepareLoginProject(ctx: TestContext, name: string): Promise<string> {\n debug('preparing project for %s (no auth token)', name);\n const tempFolder = await createTempFolder(name);\n const yamlContent = YAML.dump({\n npmRegistryServer: ctx.registryUrl,\n enableImmutableInstalls: false,\n unsafeHttpWhitelist: ['localhost'],\n });\n await writeFile(join(tempFolder, '.yarnrc.yml'), yamlContent);\n await writeFile(join(tempFolder, 'package.json'), JSON.stringify({ name, version: '1.0.0' }));\n if (ctx.adapter.importPlugin) {\n debug('importing npm-login plugin into %s', tempFolder);\n await ctx.adapter.importPlugin(tempFolder, 'npm-login');\n }\n return tempFolder;\n}\n\nasync function yarnLogin(\n ctx: TestContext,\n cwd: string,\n user: string,\n password: string,\n email: string\n) {\n debug('yarn npm login as %s', user);\n return ctx.adapter.exec(\n { cwd },\n 'login',\n '--auth-type=legacy',\n `--user=${user}`,\n `--password=${password}`,\n `--email=${email}`\n );\n}\n\nasync function yarnWhoami(ctx: TestContext, cwd: string): Promise<string> {\n const resp = await ctx.adapter.exec({ cwd }, 'whoami');\n debug('yarn npm whoami: %s', resp.stdout);\n return resp.stdout;\n}\n\nasync function testLogin(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n const id = ctx.runId;\n\n if (type !== 'yarn-modern') {\n debug('login test only supported for yarn-modern, skipping %s', type);\n return;\n }\n\n const user1 = `login-a-${id}`;\n const password1 = 'test-password-123';\n const email1 = `${user1}@test.example.com`;\n\n await ctx.subTest('create new user + whoami', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login1-${id}`);\n const loginResp = await yarnLogin(ctx, tf, user1, password1, email1);\n assert.ok(\n loginResp.stdout.includes('Logged in') || loginResp.stdout.includes('token saved'),\n `Expected login success message but got \"${loginResp.stdout}\"`\n );\n const who = await yarnWhoami(ctx, tf);\n assert.ok(who.includes(user1), `Expected whoami \"${user1}\" but got \"${who}\"`);\n });\n\n await ctx.subTest('legacy re-login is rejected without basic auth', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login2-${id}`);\n let loginFailed = false;\n try {\n await yarnLogin(ctx, tf, user1, password1, email1);\n } catch {\n loginFailed = true;\n }\n assert.ok(loginFailed, 'Legacy re-login for an existing user should fail without Basic auth');\n });\n\n await ctx.subTest('wrong password fails', async () => {\n const tf = await prepareLoginProject(ctx, `verdaccio-login3-${id}`);\n let loginFailed = false;\n try {\n await yarnLogin(ctx, tf, user1, 'wrong-password', email1);\n } catch {\n loginFailed = true;\n }\n assert.ok(loginFailed, 'Login with wrong password should have failed');\n });\n\n await ctx.subTest('login then publish', async () => {\n const pkgName = `@verdaccio/login-pub-${id}`;\n const publishUser = `login-pub-${id}`;\n const publishPassword = 'publish-password-123';\n const publishEmail = `${publishUser}@test.example.com`;\n const tf = await createTempFolder(`login-pub-${id}`);\n const yamlContent = YAML.dump({\n npmRegistryServer: ctx.registryUrl,\n enableImmutableInstalls: false,\n unsafeHttpWhitelist: ['localhost'],\n });\n await writeFile(join(tf, '.yarnrc.yml'), yamlContent);\n await writeFile(join(tf, 'package.json'), getPackageJSON(pkgName, '1.0.0'));\n await writeFile(join(tf, 'README.md'), getREADME(pkgName));\n if (ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tf, 'npm-login');\n }\n await yarnLogin(ctx, tf, publishUser, publishPassword, publishEmail);\n await ctx.adapter.exec({ cwd: tf }, 'install');\n const pubResp = await ctx.adapter.exec({ cwd: tf }, 'publish');\n assert.ok(\n pubResp.stdout.includes('Package archive published'),\n `Expected publish success but got \"${pubResp.stdout}\"`\n );\n });\n\n await ctx.subTest('switch users', async () => {\n const switchUser1 = `login-switch-a-${id}`;\n const switchPassword1 = 'switch-password-123';\n const switchEmail1 = `${switchUser1}@test.example.com`;\n const user2 = `login-b-${id}`;\n const password2 = 'other-password-456';\n const email2 = `${user2}@test.example.com`;\n const tf = await prepareLoginProject(ctx, `verdaccio-login5-${id}`);\n\n await yarnLogin(ctx, tf, switchUser1, switchPassword1, switchEmail1);\n const who1 = await yarnWhoami(ctx, tf);\n assert.ok(who1.includes(switchUser1), `Expected \"${switchUser1}\" but got \"${who1}\"`);\n\n await yarnLogin(ctx, tf, user2, password2, email2);\n const who2 = await yarnWhoami(ctx, tf);\n assert.ok(who2.includes(user2), `Expected \"${user2}\" after switch but got \"${who2}\"`);\n });\n}\n\nexport const loginTest: TestDefinition = {\n name: 'login',\n requires: ['login'],\n run: testLogin,\n};\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import assert from "assert";
2
+ //#region src/tests/ping.ts
3
+ async function testPing(ctx) {
4
+ const type = ctx.adapter.type;
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");
8
+ const jsonFlag = type === "yarn-modern" ? [] : ["--json"];
9
+ const resp = await ctx.adapter.exec({ cwd }, "ping", ...jsonFlag, ...ctx.adapter.registryArg(ctx.registryUrl));
10
+ if (type === "yarn-modern") assert.ok(true, "ping succeeded");
11
+ else {
12
+ const parsedBody = JSON.parse(resp.stdout);
13
+ assert.strictEqual(parsedBody.registry, ctx.registryUrl + "/", `Expected registry "${ctx.registryUrl}/" but got "${parsedBody.registry}"`);
14
+ }
15
+ }
16
+ var pingTest = {
17
+ name: "ping",
18
+ requires: ["ping"],
19
+ run: testPing
20
+ };
21
+ //#endregion
22
+ export { pingTest };
23
+
24
+ //# sourceMappingURL=ping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ping.js","names":[],"sources":["../../../src/tests/ping.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function testPing(ctx: TestContext): Promise<void> {\n const type = ctx.adapter.type;\n\n // Always run from a prepared project: its .npmrc carries the fresh token and\n // shields the test from whatever the developer's global ~/.npmrc has for\n // this registry (a stale token there would turn into a spurious E401).\n const { tempFolder } = await ctx.adapter.prepareProject(\n `verdaccio-ping-${ctx.runId}`,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n const cwd = tempFolder;\n if (type === 'yarn-modern' && ctx.adapter.importPlugin) {\n await ctx.adapter.importPlugin(tempFolder, 'npm-ping');\n }\n\n const jsonFlag = type === 'yarn-modern' ? [] : ['--json'];\n const resp = await ctx.adapter.exec(\n { cwd },\n 'ping',\n ...jsonFlag,\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n\n if (type === 'yarn-modern') {\n // yarn npm ping succeeding (exit code 0) is sufficient proof the registry is reachable\n assert.ok(true, 'ping succeeded');\n } else {\n const parsedBody = JSON.parse(resp.stdout);\n assert.strictEqual(\n parsedBody.registry,\n ctx.registryUrl + '/',\n `Expected registry \"${ctx.registryUrl}/\" but got \"${parsedBody.registry}\"`\n );\n }\n}\n\nexport const pingTest: TestDefinition = {\n name: 'ping',\n requires: ['ping'],\n run: testPing,\n};\n"],"mappings":";;AAIA,eAAe,SAAS,KAAiC;CACvD,MAAM,OAAO,IAAI,QAAQ;CAKzB,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,kBAAkB,IAAI,SACtB,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,MAAM;CACZ,IAAI,SAAS,iBAAiB,IAAI,QAAQ,cACxC,MAAM,IAAI,QAAQ,aAAa,YAAY,UAAU;CAGvD,MAAM,WAAW,SAAS,gBAAgB,CAAC,IAAI,CAAC,QAAQ;CACxD,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,IAAI,GACN,QACA,GAAG,UACH,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CAEA,IAAI,SAAS,eAEX,OAAO,GAAG,MAAM,gBAAgB;MAC3B;EACL,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM;EACzC,OAAO,YACL,WAAW,UACX,IAAI,cAAc,KAClB,sBAAsB,IAAI,YAAY,cAAc,WAAW,SAAS,EAC1E;CACF;AACF;AAEA,IAAa,WAA2B;CACtC,MAAM;CACN,UAAU,CAAC,MAAM;CACjB,KAAK;AACP"}
@@ -0,0 +1,34 @@
1
+ import assert from "assert";
2
+ //#region src/tests/publish.ts
3
+ async function testPublish(ctx) {
4
+ const packages = [
5
+ `verdaccio-memory-${ctx.runId}`,
6
+ `verdaccio-${ctx.runId}`,
7
+ `@verdaccio/foo-${ctx.runId}`,
8
+ `@verdaccio/some-foo-${ctx.runId}`
9
+ ];
10
+ const type = ctx.adapter.type;
11
+ for (const pkgName of packages) {
12
+ const { tempFolder } = await ctx.adapter.prepareProject(pkgName, "1.0.0", ctx.registryUrl, ctx.port, ctx.token);
13
+ if (type === "yarn-modern") await ctx.adapter.exec({ cwd: tempFolder }, "install");
14
+ const resp = await ctx.adapter.exec({ cwd: tempFolder }, "publish", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
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 === "bun") assert.ok(true, `Publish succeeded for ${pkgName}`);
17
+ else try {
18
+ const parsedBody = JSON.parse(resp.stdout);
19
+ assert.strictEqual(parsedBody.name, pkgName, `Expected package name "${pkgName}" but got "${parsedBody.name}"`);
20
+ assert.ok(parsedBody.files, `Expected files to be defined for ${pkgName}`);
21
+ } catch {
22
+ assert.ok(true, `Publish succeeded for ${pkgName}`);
23
+ }
24
+ }
25
+ }
26
+ var publishTest = {
27
+ name: "publish",
28
+ requires: ["publish"],
29
+ run: testPublish
30
+ };
31
+ //#endregion
32
+ export { publishTest };
33
+
34
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +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 === '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"}
@@ -0,0 +1,21 @@
1
+ import { publishLocalPackage } from "../utils/publish.js";
2
+ import assert from "assert";
3
+ //#region src/tests/search.ts
4
+ async function testSearch(ctx) {
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);
12
+ }
13
+ var searchTest = {
14
+ name: "search",
15
+ requires: ["search", "publish"],
16
+ run: testSearch
17
+ };
18
+ //#endregion
19
+ export { searchTest };
20
+
21
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
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,38 @@
1
+ import assert from "assert";
2
+ //#region src/tests/unpublish.ts
3
+ async function publishPkg(ctx, tempFolder) {
4
+ await ctx.adapter.exec({ cwd: tempFolder }, "publish", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
5
+ }
6
+ async function bumpVersion(ctx, tempFolder, bump) {
7
+ await ctx.adapter.exec({ cwd: tempFolder }, "version", bump, "--no--git-tag-version", "--loglevel=info", ...ctx.adapter.registryArg(ctx.registryUrl));
8
+ }
9
+ async function testUnpublish(ctx) {
10
+ const id = ctx.runId;
11
+ const pkgName = `@verdaccio/unpub1-${id}`;
12
+ const { tempFolder } = await ctx.adapter.prepareProject(pkgName, "1.0.0", ctx.registryUrl, ctx.port, ctx.token);
13
+ await publishPkg(ctx, tempFolder);
14
+ await bumpVersion(ctx, tempFolder, "minor");
15
+ await publishPkg(ctx, tempFolder);
16
+ await bumpVersion(ctx, tempFolder, "minor");
17
+ await publishPkg(ctx, tempFolder);
18
+ await bumpVersion(ctx, tempFolder, "major");
19
+ await publishPkg(ctx, tempFolder);
20
+ const resp = await ctx.adapter.exec({ cwd: tempFolder }, "unpublish", pkgName, "--force", "--loglevel=info", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
21
+ assert.ok(resp.stdout.includes(pkgName), `Expected unpublish output to contain "${pkgName}" but got "${resp.stdout}"`);
22
+ const pkgName2 = `@verdaccio/unpub2-${id}`;
23
+ const { tempFolder: tf2 } = await ctx.adapter.prepareProject(pkgName2, "1.0.0", ctx.registryUrl, ctx.port, ctx.token);
24
+ await publishPkg(ctx, tf2);
25
+ await bumpVersion(ctx, tf2, "minor");
26
+ await publishPkg(ctx, tf2);
27
+ const resp2 = await ctx.adapter.exec({ cwd: tf2 }, "unpublish", `${pkgName2}@1.0.0`, "--force", "--loglevel=info", "--json", ...ctx.adapter.registryArg(ctx.registryUrl));
28
+ assert.ok(resp2.stdout.includes(pkgName2), `Expected unpublish output to contain "${pkgName2}" but got "${resp2.stdout}"`);
29
+ }
30
+ var unpublishTest = {
31
+ name: "unpublish",
32
+ requires: ["unpublish"],
33
+ run: testUnpublish
34
+ };
35
+ //#endregion
36
+ export { unpublishTest };
37
+
38
+ //# sourceMappingURL=unpublish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unpublish.js","names":[],"sources":["../../../src/tests/unpublish.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { TestContext, TestDefinition } from '../types';\n\nasync function publishPkg(ctx: TestContext, tempFolder: string) {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'publish',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n}\n\nasync function bumpVersion(ctx: TestContext, tempFolder: string, bump: string) {\n await ctx.adapter.exec(\n { cwd: tempFolder },\n 'version',\n bump,\n '--no--git-tag-version',\n '--loglevel=info',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n}\n\nasync function testUnpublish(ctx: TestContext): Promise<void> {\n const id = ctx.runId;\n\n // Test 1: unpublish a full package\n const pkgName = `@verdaccio/unpub1-${id}`;\n const { tempFolder } = await ctx.adapter.prepareProject(\n pkgName,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tempFolder);\n await bumpVersion(ctx, tempFolder, 'minor');\n await publishPkg(ctx, tempFolder);\n await bumpVersion(ctx, tempFolder, 'minor');\n await publishPkg(ctx, tempFolder);\n await bumpVersion(ctx, tempFolder, 'major');\n await publishPkg(ctx, tempFolder);\n\n const resp = await ctx.adapter.exec(\n { cwd: tempFolder },\n 'unpublish',\n pkgName,\n '--force',\n '--loglevel=info',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.ok(\n resp.stdout.includes(pkgName),\n `Expected unpublish output to contain \"${pkgName}\" but got \"${resp.stdout}\"`\n );\n\n // Test 2: unpublish a specific version\n const pkgName2 = `@verdaccio/unpub2-${id}`;\n const { tempFolder: tf2 } = await ctx.adapter.prepareProject(\n pkgName2,\n '1.0.0',\n ctx.registryUrl,\n ctx.port,\n ctx.token\n );\n await publishPkg(ctx, tf2);\n await bumpVersion(ctx, tf2, 'minor');\n await publishPkg(ctx, tf2);\n\n const resp2 = await ctx.adapter.exec(\n { cwd: tf2 },\n 'unpublish',\n `${pkgName2}@1.0.0`,\n '--force',\n '--loglevel=info',\n '--json',\n ...ctx.adapter.registryArg(ctx.registryUrl)\n );\n assert.ok(\n resp2.stdout.includes(pkgName2),\n `Expected unpublish output to contain \"${pkgName2}\" but got \"${resp2.stdout}\"`\n );\n}\n\nexport const unpublishTest: TestDefinition = {\n name: 'unpublish',\n requires: ['unpublish'],\n run: testUnpublish,\n};\n"],"mappings":";;AAIA,eAAe,WAAW,KAAkB,YAAoB;CAC9D,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;AACF;AAEA,eAAe,YAAY,KAAkB,YAAoB,MAAc;CAC7E,MAAM,IAAI,QAAQ,KAChB,EAAE,KAAK,WAAW,GAClB,WACA,MACA,yBACA,mBACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;AACF;AAEA,eAAe,cAAc,KAAiC;CAC5D,MAAM,KAAK,IAAI;CAGf,MAAM,UAAU,qBAAqB;CACrC,MAAM,EAAE,eAAe,MAAM,IAAI,QAAQ,eACvC,SACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,UAAU;CAChC,MAAM,YAAY,KAAK,YAAY,OAAO;CAC1C,MAAM,WAAW,KAAK,UAAU;CAChC,MAAM,YAAY,KAAK,YAAY,OAAO;CAC1C,MAAM,WAAW,KAAK,UAAU;CAChC,MAAM,YAAY,KAAK,YAAY,OAAO;CAC1C,MAAM,WAAW,KAAK,UAAU;CAEhC,MAAM,OAAO,MAAM,IAAI,QAAQ,KAC7B,EAAE,KAAK,WAAW,GAClB,aACA,SACA,WACA,mBACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,GACL,KAAK,OAAO,SAAS,OAAO,GAC5B,yCAAyC,QAAQ,aAAa,KAAK,OAAO,EAC5E;CAGA,MAAM,WAAW,qBAAqB;CACtC,MAAM,EAAE,YAAY,QAAQ,MAAM,IAAI,QAAQ,eAC5C,UACA,SACA,IAAI,aACJ,IAAI,MACJ,IAAI,KACN;CACA,MAAM,WAAW,KAAK,GAAG;CACzB,MAAM,YAAY,KAAK,KAAK,OAAO;CACnC,MAAM,WAAW,KAAK,GAAG;CAEzB,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAC9B,EAAE,KAAK,IAAI,GACX,aACA,GAAG,SAAS,SACZ,WACA,mBACA,UACA,GAAG,IAAI,QAAQ,YAAY,IAAI,WAAW,CAC5C;CACA,OAAO,GACL,MAAM,OAAO,SAAS,QAAQ,GAC9B,yCAAyC,SAAS,aAAa,MAAM,OAAO,EAC9E;AACF;AAEA,IAAa,gBAAgC;CAC3C,MAAM;CACN,UAAU,CAAC,WAAW;CACtB,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"}
@@ -0,0 +1,24 @@
1
+ //#region src/utils/info.ts
2
+ /**
3
+ * Normalize the parsed output of `npm/pnpm info <pkg> --json`.
4
+ *
5
+ * npm 12+ wraps the result in a single-element JSON array
6
+ * (e.g. `[{ "name": "verdaccio", ... }]`) for both full-packument and
7
+ * version-specific queries, whereas older npm versions return a bare object.
8
+ * When multiple results match a range, the array holds one entry per match;
9
+ * the last entry is the highest (latest) version, which is what the tests want.
10
+ *
11
+ * Returns the bare object as-is for backward compatibility.
12
+ */
13
+ function normalizeInfo(parsed) {
14
+ if (Array.isArray(parsed)) return parsed[parsed.length - 1];
15
+ return parsed;
16
+ }
17
+ /** Parse `info --json` stdout and normalize to a single info object. */
18
+ function parseInfoJson(stdout) {
19
+ return normalizeInfo(JSON.parse(stdout));
20
+ }
21
+ //#endregion
22
+ export { normalizeInfo, parseInfoJson };
23
+
24
+ //# sourceMappingURL=info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info.js","names":[],"sources":["../../../src/utils/info.ts"],"sourcesContent":["/**\n * Normalize the parsed output of `npm/pnpm info <pkg> --json`.\n *\n * npm 12+ wraps the result in a single-element JSON array\n * (e.g. `[{ \"name\": \"verdaccio\", ... }]`) for both full-packument and\n * version-specific queries, whereas older npm versions return a bare object.\n * When multiple results match a range, the array holds one entry per match;\n * the last entry is the highest (latest) version, which is what the tests want.\n *\n * Returns the bare object as-is for backward compatibility.\n */\nexport function normalizeInfo(parsed: any): any {\n if (Array.isArray(parsed)) {\n return parsed[parsed.length - 1];\n }\n return parsed;\n}\n\n/** Parse `info --json` stdout and normalize to a single info object. */\nexport function parseInfoJson(stdout: string): any {\n return normalizeInfo(JSON.parse(stdout));\n}\n"],"mappings":";;;;;;;;;;;;AAWA,SAAgB,cAAc,QAAkB;CAC9C,IAAI,MAAM,QAAQ,MAAM,GACtB,OAAO,OAAO,OAAO,SAAS;CAEhC,OAAO;AACT;;AAGA,SAAgB,cAAc,QAAqB;CACjD,OAAO,cAAc,KAAK,MAAM,MAAM,CAAC;AACzC"}