@dokebi/cli 0.4.2 → 0.4.27

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 (200) hide show
  1. package/AI.md +279 -57
  2. package/README.md +35 -10
  3. package/catalog.json +2520 -258
  4. package/describe.schema.json +748 -0
  5. package/dist/api-app.d.ts +2 -0
  6. package/dist/api-app.js +35 -0
  7. package/dist/app-urls.js +3 -2
  8. package/dist/bin.js +638 -37
  9. package/dist/capability-guide.d.ts +30 -0
  10. package/dist/capability-guide.js +122 -0
  11. package/dist/catalog.d.ts +16 -2
  12. package/dist/catalog.js +19 -7
  13. package/dist/check/asset-rules.d.ts +6 -0
  14. package/dist/check/asset-rules.js +163 -0
  15. package/dist/check/browser-sources.d.ts +20 -0
  16. package/dist/check/browser-sources.js +89 -0
  17. package/dist/check/console-rules.d.ts +6 -0
  18. package/dist/check/console-rules.js +40 -0
  19. package/dist/check/error-rules.d.ts +21 -0
  20. package/dist/check/error-rules.js +212 -0
  21. package/dist/check/form-rules.d.ts +12 -0
  22. package/dist/check/form-rules.js +69 -6
  23. package/dist/check/formatter-rules.d.ts +2 -0
  24. package/dist/check/formatter-rules.js +71 -0
  25. package/dist/check/i18n-rules.d.ts +27 -0
  26. package/dist/check/i18n-rules.js +353 -0
  27. package/dist/check/index.d.ts +15 -1
  28. package/dist/check/index.js +89 -11
  29. package/dist/check/mail-rules.d.ts +4 -0
  30. package/dist/check/mail-rules.js +72 -0
  31. package/dist/check/module-rules.d.ts +13 -0
  32. package/dist/check/module-rules.js +104 -0
  33. package/dist/check/packages.d.ts +8 -0
  34. package/dist/check/packages.js +16 -0
  35. package/dist/check/pool-rules.d.ts +8 -0
  36. package/dist/check/pool-rules.js +164 -0
  37. package/dist/check/problems.d.ts +6 -1
  38. package/dist/check/problems.js +100 -2
  39. package/dist/check/project-rules.d.ts +23 -3
  40. package/dist/check/project-rules.js +265 -23
  41. package/dist/check/seal-rules.d.ts +6 -0
  42. package/dist/check/seal-rules.js +68 -0
  43. package/dist/check/security-rules.d.ts +10 -0
  44. package/dist/check/security-rules.js +450 -0
  45. package/dist/check/shared-state-rules.d.ts +15 -0
  46. package/dist/check/shared-state-rules.js +117 -0
  47. package/dist/check/source-rules.d.ts +26 -3
  48. package/dist/check/source-rules.js +249 -28
  49. package/dist/check/structure-rules.d.ts +23 -0
  50. package/dist/check/structure-rules.js +502 -0
  51. package/dist/check/timer-rules.d.ts +7 -0
  52. package/dist/check/timer-rules.js +543 -0
  53. package/dist/check/typescript.d.ts +35 -5
  54. package/dist/check/typescript.js +163 -17
  55. package/dist/check/vite-rules.d.ts +2 -0
  56. package/dist/check/vite-rules.js +136 -0
  57. package/dist/check/wait-rules.d.ts +16 -0
  58. package/dist/check/wait-rules.js +337 -0
  59. package/dist/commands.d.ts +7 -0
  60. package/dist/commands.js +381 -36
  61. package/dist/config-diff.d.ts +42 -0
  62. package/dist/config-diff.js +289 -0
  63. package/dist/config-edit.d.ts +35 -2
  64. package/dist/config-edit.js +145 -13
  65. package/dist/config-init.d.ts +15 -0
  66. package/dist/config-init.js +61 -0
  67. package/dist/config-paths.d.ts +22 -0
  68. package/dist/config-paths.js +82 -0
  69. package/dist/config-secrets.d.ts +46 -0
  70. package/dist/config-secrets.js +187 -0
  71. package/dist/config-show.d.ts +82 -0
  72. package/dist/config-show.js +563 -0
  73. package/dist/config-values.d.ts +8 -0
  74. package/dist/config-values.js +68 -0
  75. package/dist/configuration-scaffold.d.ts +42 -0
  76. package/dist/configuration-scaffold.js +258 -4
  77. package/dist/configuration.d.ts +63 -3
  78. package/dist/configuration.js +178 -4
  79. package/dist/deploy-scripts.d.ts +4 -0
  80. package/dist/deploy-scripts.js +361 -0
  81. package/dist/deploy-templates.d.ts +46 -0
  82. package/dist/deploy-templates.js +379 -0
  83. package/dist/deploy.d.ts +46 -0
  84. package/dist/deploy.js +592 -0
  85. package/dist/deployment.d.ts +2 -0
  86. package/dist/deployment.js +111 -0
  87. package/dist/describe-db.d.ts +22 -0
  88. package/dist/describe-db.js +176 -0
  89. package/dist/describe.d.ts +92 -0
  90. package/dist/describe.js +417 -0
  91. package/dist/dev-compose.d.ts +20 -0
  92. package/dist/dev-compose.js +154 -0
  93. package/dist/dev-guard.js +44 -0
  94. package/dist/dev-host.d.ts +13 -0
  95. package/dist/dev-host.js +55 -0
  96. package/dist/dev-infra.d.ts +72 -4
  97. package/dist/dev-infra.js +237 -17
  98. package/dist/dev-watch.d.ts +34 -0
  99. package/dist/dev-watch.js +192 -0
  100. package/dist/diagnostics.d.ts +10 -7
  101. package/dist/diagnostics.js +22 -4
  102. package/dist/drift.d.ts +50 -0
  103. package/dist/drift.js +218 -0
  104. package/dist/env-file.d.ts +25 -1
  105. package/dist/env-file.js +64 -6
  106. package/dist/errors.d.ts +7 -1
  107. package/dist/example-config.d.ts +2 -0
  108. package/dist/example-config.js +3 -0
  109. package/dist/first-admin.d.ts +43 -0
  110. package/dist/first-admin.js +175 -0
  111. package/dist/formatter-ignore.d.ts +37 -0
  112. package/dist/formatter-ignore.js +344 -0
  113. package/dist/generator.d.ts +45 -3
  114. package/dist/generator.js +205 -15
  115. package/dist/guide.d.ts +1 -1
  116. package/dist/guide.js +42 -21
  117. package/dist/install-module.d.ts +20 -2
  118. package/dist/install-module.js +45 -8
  119. package/dist/install-plan.js +5 -3
  120. package/dist/layout.d.ts +12 -1
  121. package/dist/layout.js +19 -6
  122. package/dist/log-writer-control.d.ts +2 -0
  123. package/dist/log-writer-control.js +17 -2
  124. package/dist/log-writer.js +43 -10
  125. package/dist/logging-config.d.ts +8 -3
  126. package/dist/logging-config.js +19 -5
  127. package/dist/manifest.d.ts +29 -1
  128. package/dist/manifest.js +84 -6
  129. package/dist/merge-install-defaults.d.ts +0 -1
  130. package/dist/merge-install-defaults.js +13 -47
  131. package/dist/npm-client.d.ts +40 -4
  132. package/dist/npm-client.js +152 -15
  133. package/dist/npm-paths.js +18 -4
  134. package/dist/openapi.d.ts +27 -0
  135. package/dist/openapi.js +649 -0
  136. package/dist/option-fixes.d.ts +39 -0
  137. package/dist/option-fixes.js +130 -0
  138. package/dist/output.d.ts +1 -1
  139. package/dist/output.js +5 -5
  140. package/dist/plugin-lifecycle.d.ts +34 -2
  141. package/dist/plugin-lifecycle.js +195 -16
  142. package/dist/plugin-options.d.ts +19 -4
  143. package/dist/plugin-options.js +138 -6
  144. package/dist/plugins-command.js +44 -7
  145. package/dist/process-group.d.ts +39 -0
  146. package/dist/process-group.js +132 -0
  147. package/dist/production-doctor.d.ts +3 -0
  148. package/dist/production-doctor.js +176 -0
  149. package/dist/project-guide.d.ts +19 -1
  150. package/dist/project-guide.js +119 -20
  151. package/dist/project-inspector.d.ts +15 -0
  152. package/dist/project-inspector.js +497 -33
  153. package/dist/project-state.d.ts +5 -0
  154. package/dist/project-state.js +8 -0
  155. package/dist/project.d.ts +203 -19
  156. package/dist/project.js +3006 -333
  157. package/dist/recipe-facts.d.ts +127 -0
  158. package/dist/recipe-facts.js +563 -0
  159. package/dist/recipe-login.d.ts +63 -0
  160. package/dist/recipe-login.js +464 -0
  161. package/dist/recipe-mail.d.ts +21 -0
  162. package/dist/recipe-mail.js +214 -0
  163. package/dist/recipe-members.d.ts +35 -0
  164. package/dist/recipe-members.js +178 -0
  165. package/dist/recipe-record.d.ts +82 -7
  166. package/dist/recipe-record.js +226 -22
  167. package/dist/recipe-reset.d.ts +2 -0
  168. package/dist/recipe-reset.js +117 -0
  169. package/dist/recipe-site.d.ts +48 -0
  170. package/dist/recipe-site.js +396 -0
  171. package/dist/recipe-step.d.ts +42 -0
  172. package/dist/recipe-step.js +89 -0
  173. package/dist/recipe.d.ts +56 -20
  174. package/dist/recipe.js +703 -131
  175. package/dist/registry.d.ts +98 -2
  176. package/dist/registry.js +171 -5
  177. package/dist/reloadable.d.ts +35 -0
  178. package/dist/reloadable.js +257 -0
  179. package/dist/remote-verify.d.ts +31 -0
  180. package/dist/remote-verify.js +204 -0
  181. package/dist/server.d.ts +43 -4
  182. package/dist/server.js +601 -52
  183. package/dist/settings.d.ts +48 -0
  184. package/dist/settings.js +168 -0
  185. package/dist/site-auth.d.ts +39 -0
  186. package/dist/site-auth.js +195 -0
  187. package/dist/template-fixes.d.ts +26 -0
  188. package/dist/template-fixes.js +285 -0
  189. package/dist/test-reporter.d.ts +49 -0
  190. package/dist/test-reporter.js +190 -0
  191. package/dist/transaction.d.ts +47 -0
  192. package/dist/transaction.js +241 -7
  193. package/dist/verify-tests.d.ts +98 -0
  194. package/dist/verify-tests.js +449 -0
  195. package/dist/verify.d.ts +103 -3
  196. package/dist/verify.js +272 -21
  197. package/dist/view-choice.d.ts +58 -0
  198. package/dist/view-choice.js +131 -0
  199. package/dist/vite-watch.js +4 -2
  200. package/package.json +8 -6
@@ -1,14 +1,81 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { readdir, readFile, writeFile } from 'node:fs/promises';
3
- import { isAbsolute, resolve } from 'node:path';
3
+ import { isAbsolute, posix, resolve } from 'node:path';
4
+ import { redaction } from '@dokebi/core/libraries';
4
5
  import { debug, isMissing } from './diagnostics.js';
5
6
  import { CliError } from './errors.js';
6
7
  import { json, object, parseObject } from './filesystem.js';
7
8
  import { PACKAGE_NAME } from './manifest.js';
8
9
  import { normalizeLockPaths, normalizePackagePaths } from './npm-paths.js';
10
+ import { forwardTermination, killGroup, PROCESS_GROUPS } from './process-group.js';
11
+ /** How long one npm install, pack or workspace install may take by default (2 minutes). */
12
+ export const NPM_TIMEOUT_MS = 120_000;
13
+ /** The environment variable that sets that limit in milliseconds (D-0110 §4). */
14
+ export const NPM_TIMEOUT_ENV = 'DOKEBI_NPM_TIMEOUT_MS';
15
+ /** The shortest and the longest limit the variable may set: 1 second and 24 hours. */
16
+ export const NPM_TIMEOUT_RANGE = { min: 1000, max: 86_400_000 };
17
+ /**
18
+ * The time limit of the npm commands NpmClient runs: `DOKEBI_NPM_TIMEOUT_MS` (a whole number of
19
+ * milliseconds within NPM_TIMEOUT_RANGE) or NPM_TIMEOUT_MS. A slow registry or a large first
20
+ * install may need more; anything else in the variable is INVALID_ENV, never a silent default.
21
+ */
22
+ export function npmTimeoutMs(env = process.env) {
23
+ const text = env[NPM_TIMEOUT_ENV]?.trim();
24
+ if (text === undefined || text === '')
25
+ return NPM_TIMEOUT_MS;
26
+ const value = Number(text);
27
+ if (!/^[0-9]+$/.test(text) ||
28
+ !Number.isSafeInteger(value) ||
29
+ value < NPM_TIMEOUT_RANGE.min ||
30
+ value > NPM_TIMEOUT_RANGE.max)
31
+ throw new CliError('INVALID_ENV', `${NPM_TIMEOUT_ENV} must be a whole number of milliseconds from ${NPM_TIMEOUT_RANGE.min} to ${NPM_TIMEOUT_RANGE.max}`, {
32
+ details: { variable: NPM_TIMEOUT_ENV, value: text, ...NPM_TIMEOUT_RANGE },
33
+ phase: 'arguments',
34
+ });
35
+ debug('npm time limit from the environment', { variable: NPM_TIMEOUT_ENV, timeoutMs: value });
36
+ return value;
37
+ }
38
+ /**
39
+ * D-0138: what NPM_FAILED says when npm's code is ERESOLVE. The newest catalog packages fit each
40
+ * other (scripts/catalog/peers.ts), but a newer one may need a newer version of a package the
41
+ * project has (session 0.4.4 needs web ^0.4.3); add and upgrade without a version pick one that
42
+ * fits, and a named version gets details.fix (project.ts #peerConflict). Otherwise the conflict
43
+ * comes from another package or from a registry that is not the one the project was installed
44
+ * from; neither is fixed by forcing npm.
45
+ */
46
+ export const ERESOLVE_HINT = "npm could not fit the packages' peer ranges (stderr names them: 'Found: <installed>' and 'peer <package>@<range> from <new package>'). Do not retry with --legacy-peer-deps or --force: they install a combination the packages do not support. When the new package needs a newer version of one the project has, upgrade that one first (npx dokebi upgrade <its plugin id>) and repeat the command. Otherwise check that npm config get @dokebi:registry names the registry in package-lock.json's resolved URLs, and that the package named in stderr is a version this project's core supports.";
47
+ /**
48
+ * Whether npm reads the path of a `file:` spec in the home folder: npm-package-arg fromFile takes
49
+ * a URL path whose first segment is `~` for it, relative (`~/x.tgz`, `./~/x.tgz`) or written
50
+ * absolute (`/~/x.tgz`, `//~/x.tgz`, `/../~/x.tgz`), never a folder of the project or of the
51
+ * root (R6-cli-1, R7-cli-1: the absolute ones were taken for the path /~/x.tgz). Backslashes
52
+ * count as separators, as on Windows.
53
+ */
54
+ function homeTarball(path) {
55
+ return /^\/?~(?:\/|$)/.test(posix.normalize(path.replace(/\\/g, '/')));
56
+ }
57
+ /**
58
+ * Whether npm takes a backslash in the path of a `file:` spec for part of a file name: outside
59
+ * Windows npm-package-arg encodes it as %5C (`file:vendor\\x.tgz` reads the one file
60
+ * `vendor\\x.tgz`) but saves the spec with `/` (`file:vendor/x.tgz`), another path (R7-cli-3).
61
+ * On Windows it is a separator for npm, as for the CLI (R5C-2).
62
+ */
63
+ function backslashName(path) {
64
+ return process.platform !== 'win32' && path.includes('\\');
65
+ }
9
66
  export function packageSpec(spec, cwd) {
10
- if (spec.endsWith('.tgz'))
11
- return `file:${resolve(cwd, spec.replace(/^file:/, ''))}`;
67
+ if (spec.endsWith('.tgz')) {
68
+ const path = resolve(cwd, spec.replace(/^file:/, ''));
69
+ if (backslashName(path)) {
70
+ debug('A tarball path with a backslash is refused outside Windows', { spec, path });
71
+ throw new CliError('INVALID_SPEC', `npm reads the backslash in ${path} as part of a file name, and would save another path: name the tarball with / or rename the file`);
72
+ }
73
+ if (homeTarball(path)) {
74
+ debug('A tarball path npm reads in the home folder is refused', { spec, path });
75
+ throw new CliError('INVALID_SPEC', `npm reads ${path} in the home folder, not at that path: name the tarball by its full path`);
76
+ }
77
+ return `file:${path}`;
78
+ }
12
79
  const match = /^((?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*)(?:@([a-zA-Z0-9.*^~<>=| -]+))?$/.exec(spec);
13
80
  if (!match?.[1] || !PACKAGE_NAME.test(match[1]))
14
81
  throw new CliError('INVALID_SPEC', 'Use a registry package or local .tgz');
@@ -23,6 +90,29 @@ export function dependencyMap(pkg) {
23
90
  }
24
91
  return result;
25
92
  }
93
+ /**
94
+ * The project-relative path of a `file:<path>.tgz` dependency that stays inside the project
95
+ * (`file:vendor/web-0.4.3.tgz`, as npm writes a tarball installed from the project), else undefined.
96
+ * The staging install gets a copy at the same path, so package.json and package-lock.json keep it
97
+ * relative and the project can move (2026-10-28).
98
+ */
99
+ export function projectTarball(value) {
100
+ if (!value.startsWith('file:') || !value.endsWith('.tgz'))
101
+ return undefined;
102
+ const path = value.slice(5);
103
+ if (isAbsolute(path) || /^[a-zA-Z]:/.test(path))
104
+ return undefined;
105
+ // Backslashes first: on POSIX normalize() takes `vendor\..\..\x.tgz` for one name, and the
106
+ // `..` came back as `vendor/../../x.tgz`, a path out of the project (R5C-2).
107
+ const inside = posix.normalize(path.replace(/\\/g, '/'));
108
+ if (inside === '..' || inside.startsWith('../') || inside.startsWith('/'))
109
+ return undefined;
110
+ // npm reads a path whose first segment is `~` (also `./~/x.tgz`, `vendor/../~/x.tgz`) as the
111
+ // home folder (npm-package-arg fromFile), never a folder of the project (R6-cli-1).
112
+ if (homeTarball(inside))
113
+ return undefined;
114
+ return inside;
115
+ }
26
116
  export function validateProjectPackage(pkg) {
27
117
  if (pkg.workspaces !== undefined)
28
118
  throw new CliError('UNSUPPORTED_PROJECT', 'Workspace projects are not supported');
@@ -32,20 +122,37 @@ export function validateProjectPackage(pkg) {
32
122
  value.startsWith('workspace:') ||
33
123
  value.startsWith('link:') ||
34
124
  (value.startsWith('file:')
35
- ? !isAbsolute(value.slice(5)) || !value.endsWith('.tgz')
125
+ ? !value.endsWith('.tgz') ||
126
+ (isAbsolute(value.slice(5))
127
+ ? homeTarball(value.slice(5))
128
+ : projectTarball(value) === undefined)
36
129
  : !/^[a-zA-Z0-9.*^~<>=| -]+$/.test(value))) {
37
- throw new CliError('UNSUPPORTED_PROJECT', 'Use registry dependencies or absolute file tarballs');
130
+ throw new CliError('UNSUPPORTED_PROJECT', 'Use registry dependencies or file tarballs (an absolute path, or a path inside the project)');
131
+ }
132
+ // After the check above, so a backslash path out of the project is refused as one (R5C-2).
133
+ if (value.startsWith('file:') && backslashName(value.slice(5))) {
134
+ const path = value.slice(5);
135
+ debug('A file: tarball with a backslash is refused outside Windows', { path });
136
+ throw new CliError('UNSUPPORTED_PROJECT', `npm reads the backslash in ${value} as part of a file name and saves the path with /`, {
137
+ details: {
138
+ path,
139
+ fix: `write it with / in package.json (file:${path.replace(/\\/g, '/')}), or rename the file so it has no backslash`,
140
+ },
141
+ });
38
142
  }
39
143
  }
40
144
  }
41
145
  }
42
146
  export class NpmClient {
43
- /** Installs into a standalone directory (the project or the CLI's staging). */
147
+ /**
148
+ * Installs into a standalone directory (the project or the CLI's staging): the package.json as
149
+ * it is, plus `spec` (one spec, or several in one npm run).
150
+ */
44
151
  async install(directory, spec) {
45
152
  try {
46
153
  await this.#run(directory, [
47
154
  'install',
48
- ...(spec ? [spec] : []),
155
+ ...(spec === undefined ? [] : typeof spec === 'string' ? (spec ? [spec] : []) : spec),
49
156
  '--ignore-scripts',
50
157
  '--save-exact',
51
158
  '--no-audit',
@@ -71,7 +178,9 @@ export class NpmClient {
71
178
  network: true,
72
179
  hint: 'the registry was not reachable: retry when online, or with packages from the npm cache: npm_config_prefer_offline=true npx dokebi …',
73
180
  }
74
- : {}),
181
+ : details.npmCode === 'ERESOLVE'
182
+ ? { hint: ERESOLVE_HINT }
183
+ : {}),
75
184
  },
76
185
  phase: 'install',
77
186
  });
@@ -99,7 +208,8 @@ export class NpmClient {
99
208
  await this.#run(root, ['install', '--ignore-scripts', '--no-audit', '--no-fund']);
100
209
  }
101
210
  async #run(directory, npmArgs) {
102
- const result = await runNpm(directory, npmArgs, { timeoutMs: 120_000 });
211
+ const timeoutMs = npmTimeoutMs();
212
+ const result = await runNpm(directory, npmArgs, { timeoutMs });
103
213
  if (result.exitCode === 0 && !result.timedOut)
104
214
  return;
105
215
  throw new CliError('NPM_FAILED', result.timedOut ? 'npm timed out' : `npm failed with exit code ${result.exitCode}`, {
@@ -108,6 +218,12 @@ export class NpmClient {
108
218
  exitCode: result.exitCode,
109
219
  signal: result.signal,
110
220
  timedOut: result.timedOut,
221
+ ...(result.timedOut
222
+ ? {
223
+ timeoutMs,
224
+ hint: `raise the limit with ${NPM_TIMEOUT_ENV}=<ms> when npm needs longer`,
225
+ }
226
+ : {}),
111
227
  stderr: result.stderr,
112
228
  stdout: result.stdout,
113
229
  ...result.notes,
@@ -132,7 +248,15 @@ export function runNpm(directory, npmArgs, options) {
132
248
  const began = performance.now();
133
249
  debug('Running npm', { args: npmArgs.slice(0, 16), directory });
134
250
  return new Promise((resolvePromise, reject) => {
135
- const child = spawn(command, args, { cwd: directory, stdio: ['ignore', 'pipe', 'pipe'] });
251
+ // npm runs in a process group of its own (process-group.ts): the time limit ends npm and
252
+ // everything it started (git, a lifecycle script, node-gyp), not npm alone. Those children
253
+ // hold its pipes, so killing npm alone left the command waiting for them as well.
254
+ const child = spawn(command, args, {
255
+ cwd: directory,
256
+ stdio: ['ignore', 'pipe', 'pipe'],
257
+ detached: PROCESS_GROUPS,
258
+ });
259
+ const forwarding = forwardTermination(child.pid);
136
260
  // npm's own words are the cause of a failure: keep the end of both streams (bounded).
137
261
  const stdout = new OutputTail();
138
262
  const stderr = new OutputTail();
@@ -145,10 +269,17 @@ export function runNpm(directory, npmArgs, options) {
145
269
  let timedOut = false;
146
270
  const timer = setTimeout(() => {
147
271
  timedOut = true;
148
- child.kill('SIGKILL');
272
+ debug('npm reached its time limit; killing its process group', {
273
+ command: npmArgs[0],
274
+ pid: child.pid,
275
+ timeoutMs: options.timeoutMs,
276
+ });
277
+ // Windows has no process groups: there killGroup ends npm itself (process-group.ts).
278
+ killGroup(child.pid, 'SIGKILL');
149
279
  }, options.timeoutMs);
150
280
  child.on('error', (cause) => {
151
281
  clearTimeout(timer);
282
+ forwarding();
152
283
  reject(new CliError('NPM_FAILED', 'Could not execute npm', {
153
284
  cause,
154
285
  details: { command: npmArgs[0], directory },
@@ -156,6 +287,10 @@ export function runNpm(directory, npmArgs, options) {
156
287
  });
157
288
  child.on('close', (code, signal) => {
158
289
  clearTimeout(timer);
290
+ forwarding();
291
+ // A child npm left behind in its group (a script that forked) does not outlive the command.
292
+ if (!timedOut && killGroup(child.pid, 'SIGKILL'))
293
+ debug('npm left processes behind; killed them', { command: npmArgs[0], pid: child.pid });
159
294
  debug('npm finished', {
160
295
  command: npmArgs[0],
161
296
  exitCode: code,
@@ -288,12 +423,14 @@ export class NpmFailureNotes {
288
423
  }
289
424
  }
290
425
  /**
291
- * Removes credentials npm may print: `user:password@` in URLs, `_authToken=`/`_auth=`/
292
- * `_password=` values (npmrc lines), bearer tokens and npm access tokens (`npm_…`).
426
+ * Removes credentials npm may print: `user:password@` in URLs (core's rule, which also takes a
427
+ * raw `/` inside a password: `//app:Zm9v/YmFy@db` kept the part after the slash before),
428
+ * `_authToken=`/`_auth=`/`_password=` values (npmrc lines), bearer tokens and npm access tokens
429
+ * (`npm_…`).
293
430
  */
294
431
  export function redactNpmOutput(text) {
295
- return text
296
- .replace(/\/\/[^\s/@]*@/g, '//***@')
432
+ return redaction
433
+ .maskCredentials(text)
297
434
  .replace(/(_authToken|_auth|_password)(\s*[=:]\s*)("?)[^\s"]+/gi, '$1$2$3***')
298
435
  .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, '$1 ***')
299
436
  .replace(/\bnpm_[A-Za-z0-9]{20,}\b/g, 'npm_***');
package/dist/npm-paths.js CHANGED
@@ -1,4 +1,4 @@
1
- import { resolve } from 'node:path';
1
+ import { isAbsolute, relative, resolve } from 'node:path';
2
2
  import { object } from './filesystem.js';
3
3
  const DEPENDENCY_FIELDS = [
4
4
  'dependencies',
@@ -6,10 +6,24 @@ const DEPENDENCY_FIELDS = [
6
6
  'optionalDependencies',
7
7
  'peerDependencies',
8
8
  ];
9
+ /**
10
+ * A `file:` reference npm wrote in a staging folder, made valid in the project it is copied to:
11
+ * one that leaves the folder (`file:../../x.tgz`, npm's way to name an absolute tarball) becomes
12
+ * absolute; one inside it stays relative (`file:vendor/web.tgz`: the project's own tarball, which
13
+ * staging holds at the same path, so the project keeps it relative and can move, 2026-10-28).
14
+ */
9
15
  function localReference(value, root) {
10
- return typeof value === 'string' && value.startsWith('file:')
11
- ? `file:${resolve(root, value.slice(5))}`
12
- : value;
16
+ if (typeof value !== 'string' || !value.startsWith('file:'))
17
+ return value;
18
+ const path = value.slice(5);
19
+ const absolute = resolve(root, path);
20
+ const inside = relative(root, absolute);
21
+ // `..` as the first segment only: `..vendor/x.tgz` is a folder of the project (R6-cli-3: taken
22
+ // for a way out, it became an absolute path into the staging folder, deleted after the commit).
23
+ const first = inside.split(/[\\/]/)[0];
24
+ if (!isAbsolute(path) && inside !== '' && first !== '..' && !isAbsolute(inside))
25
+ return `file:${inside.split('\\').join('/')}`;
26
+ return `file:${absolute}`;
13
27
  }
14
28
  export function normalizePackagePaths(pkg, root) {
15
29
  for (const field of DEPENDENCY_FIELDS) {
@@ -0,0 +1,27 @@
1
+ import type { DescribeResult } from './describe.js';
2
+ export declare const OPENAPI_VERSION = "3.1.0";
3
+ type Json = Record<string, unknown>;
4
+ /** The D-0074 error body. */
5
+ export declare const ERROR_BODY_SCHEMA: Json;
6
+ /** `/posts/:id(^\\d+)/*` → `/posts/{id}/{wildcard}` and the path parameter names. */
7
+ export declare function openApiPath(url: string): {
8
+ path: string;
9
+ names: string[];
10
+ };
11
+ /** `get_posts_id`: the deterministic name of a route without `config.id`. */
12
+ export declare function operationName(method: string, url: string): string;
13
+ export interface OpenApiOptions {
14
+ /** One app's document; UNKNOWN_APP when the project has no such app. */
15
+ readonly app?: string;
16
+ /** `info.version` (the project's package.json version; default 0.0.0). */
17
+ readonly version?: string;
18
+ }
19
+ /** `{ apps: { name: document } }` without `--app` when the project has several apps. */
20
+ export type OpenApiResult = Json | {
21
+ readonly apps: Record<string, Json>;
22
+ };
23
+ /** OpenAPI documents of the described project (one per app). */
24
+ export declare function openApi(result: DescribeResult, options?: OpenApiOptions): OpenApiResult;
25
+ /** The project's package.json version, for `info.version`. */
26
+ export declare function projectVersion(root: string): string | undefined;
27
+ export {};