@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.
- package/AI.md +279 -57
- package/README.md +35 -10
- package/catalog.json +2520 -258
- package/describe.schema.json +748 -0
- package/dist/api-app.d.ts +2 -0
- package/dist/api-app.js +35 -0
- package/dist/app-urls.js +3 -2
- package/dist/bin.js +638 -37
- package/dist/capability-guide.d.ts +30 -0
- package/dist/capability-guide.js +122 -0
- package/dist/catalog.d.ts +16 -2
- package/dist/catalog.js +19 -7
- package/dist/check/asset-rules.d.ts +6 -0
- package/dist/check/asset-rules.js +163 -0
- package/dist/check/browser-sources.d.ts +20 -0
- package/dist/check/browser-sources.js +89 -0
- package/dist/check/console-rules.d.ts +6 -0
- package/dist/check/console-rules.js +40 -0
- package/dist/check/error-rules.d.ts +21 -0
- package/dist/check/error-rules.js +212 -0
- package/dist/check/form-rules.d.ts +12 -0
- package/dist/check/form-rules.js +69 -6
- package/dist/check/formatter-rules.d.ts +2 -0
- package/dist/check/formatter-rules.js +71 -0
- package/dist/check/i18n-rules.d.ts +27 -0
- package/dist/check/i18n-rules.js +353 -0
- package/dist/check/index.d.ts +15 -1
- package/dist/check/index.js +89 -11
- package/dist/check/mail-rules.d.ts +4 -0
- package/dist/check/mail-rules.js +72 -0
- package/dist/check/module-rules.d.ts +13 -0
- package/dist/check/module-rules.js +104 -0
- package/dist/check/packages.d.ts +8 -0
- package/dist/check/packages.js +16 -0
- package/dist/check/pool-rules.d.ts +8 -0
- package/dist/check/pool-rules.js +164 -0
- package/dist/check/problems.d.ts +6 -1
- package/dist/check/problems.js +100 -2
- package/dist/check/project-rules.d.ts +23 -3
- package/dist/check/project-rules.js +265 -23
- package/dist/check/seal-rules.d.ts +6 -0
- package/dist/check/seal-rules.js +68 -0
- package/dist/check/security-rules.d.ts +10 -0
- package/dist/check/security-rules.js +450 -0
- package/dist/check/shared-state-rules.d.ts +15 -0
- package/dist/check/shared-state-rules.js +117 -0
- package/dist/check/source-rules.d.ts +26 -3
- package/dist/check/source-rules.js +249 -28
- package/dist/check/structure-rules.d.ts +23 -0
- package/dist/check/structure-rules.js +502 -0
- package/dist/check/timer-rules.d.ts +7 -0
- package/dist/check/timer-rules.js +543 -0
- package/dist/check/typescript.d.ts +35 -5
- package/dist/check/typescript.js +163 -17
- package/dist/check/vite-rules.d.ts +2 -0
- package/dist/check/vite-rules.js +136 -0
- package/dist/check/wait-rules.d.ts +16 -0
- package/dist/check/wait-rules.js +337 -0
- package/dist/commands.d.ts +7 -0
- package/dist/commands.js +381 -36
- package/dist/config-diff.d.ts +42 -0
- package/dist/config-diff.js +289 -0
- package/dist/config-edit.d.ts +35 -2
- package/dist/config-edit.js +145 -13
- package/dist/config-init.d.ts +15 -0
- package/dist/config-init.js +61 -0
- package/dist/config-paths.d.ts +22 -0
- package/dist/config-paths.js +82 -0
- package/dist/config-secrets.d.ts +46 -0
- package/dist/config-secrets.js +187 -0
- package/dist/config-show.d.ts +82 -0
- package/dist/config-show.js +563 -0
- package/dist/config-values.d.ts +8 -0
- package/dist/config-values.js +68 -0
- package/dist/configuration-scaffold.d.ts +42 -0
- package/dist/configuration-scaffold.js +258 -4
- package/dist/configuration.d.ts +63 -3
- package/dist/configuration.js +178 -4
- package/dist/deploy-scripts.d.ts +4 -0
- package/dist/deploy-scripts.js +361 -0
- package/dist/deploy-templates.d.ts +46 -0
- package/dist/deploy-templates.js +379 -0
- package/dist/deploy.d.ts +46 -0
- package/dist/deploy.js +592 -0
- package/dist/deployment.d.ts +2 -0
- package/dist/deployment.js +111 -0
- package/dist/describe-db.d.ts +22 -0
- package/dist/describe-db.js +176 -0
- package/dist/describe.d.ts +92 -0
- package/dist/describe.js +417 -0
- package/dist/dev-compose.d.ts +20 -0
- package/dist/dev-compose.js +154 -0
- package/dist/dev-guard.js +44 -0
- package/dist/dev-host.d.ts +13 -0
- package/dist/dev-host.js +55 -0
- package/dist/dev-infra.d.ts +72 -4
- package/dist/dev-infra.js +237 -17
- package/dist/dev-watch.d.ts +34 -0
- package/dist/dev-watch.js +192 -0
- package/dist/diagnostics.d.ts +10 -7
- package/dist/diagnostics.js +22 -4
- package/dist/drift.d.ts +50 -0
- package/dist/drift.js +218 -0
- package/dist/env-file.d.ts +25 -1
- package/dist/env-file.js +64 -6
- package/dist/errors.d.ts +7 -1
- package/dist/example-config.d.ts +2 -0
- package/dist/example-config.js +3 -0
- package/dist/first-admin.d.ts +43 -0
- package/dist/first-admin.js +175 -0
- package/dist/formatter-ignore.d.ts +37 -0
- package/dist/formatter-ignore.js +344 -0
- package/dist/generator.d.ts +45 -3
- package/dist/generator.js +205 -15
- package/dist/guide.d.ts +1 -1
- package/dist/guide.js +42 -21
- package/dist/install-module.d.ts +20 -2
- package/dist/install-module.js +45 -8
- package/dist/install-plan.js +5 -3
- package/dist/layout.d.ts +12 -1
- package/dist/layout.js +19 -6
- package/dist/log-writer-control.d.ts +2 -0
- package/dist/log-writer-control.js +17 -2
- package/dist/log-writer.js +43 -10
- package/dist/logging-config.d.ts +8 -3
- package/dist/logging-config.js +19 -5
- package/dist/manifest.d.ts +29 -1
- package/dist/manifest.js +84 -6
- package/dist/merge-install-defaults.d.ts +0 -1
- package/dist/merge-install-defaults.js +13 -47
- package/dist/npm-client.d.ts +40 -4
- package/dist/npm-client.js +152 -15
- package/dist/npm-paths.js +18 -4
- package/dist/openapi.d.ts +27 -0
- package/dist/openapi.js +649 -0
- package/dist/option-fixes.d.ts +39 -0
- package/dist/option-fixes.js +130 -0
- package/dist/output.d.ts +1 -1
- package/dist/output.js +5 -5
- package/dist/plugin-lifecycle.d.ts +34 -2
- package/dist/plugin-lifecycle.js +195 -16
- package/dist/plugin-options.d.ts +19 -4
- package/dist/plugin-options.js +138 -6
- package/dist/plugins-command.js +44 -7
- package/dist/process-group.d.ts +39 -0
- package/dist/process-group.js +132 -0
- package/dist/production-doctor.d.ts +3 -0
- package/dist/production-doctor.js +176 -0
- package/dist/project-guide.d.ts +19 -1
- package/dist/project-guide.js +119 -20
- package/dist/project-inspector.d.ts +15 -0
- package/dist/project-inspector.js +497 -33
- package/dist/project-state.d.ts +5 -0
- package/dist/project-state.js +8 -0
- package/dist/project.d.ts +203 -19
- package/dist/project.js +3006 -333
- package/dist/recipe-facts.d.ts +127 -0
- package/dist/recipe-facts.js +563 -0
- package/dist/recipe-login.d.ts +63 -0
- package/dist/recipe-login.js +464 -0
- package/dist/recipe-mail.d.ts +21 -0
- package/dist/recipe-mail.js +214 -0
- package/dist/recipe-members.d.ts +35 -0
- package/dist/recipe-members.js +178 -0
- package/dist/recipe-record.d.ts +82 -7
- package/dist/recipe-record.js +226 -22
- package/dist/recipe-reset.d.ts +2 -0
- package/dist/recipe-reset.js +117 -0
- package/dist/recipe-site.d.ts +48 -0
- package/dist/recipe-site.js +396 -0
- package/dist/recipe-step.d.ts +42 -0
- package/dist/recipe-step.js +89 -0
- package/dist/recipe.d.ts +56 -20
- package/dist/recipe.js +703 -131
- package/dist/registry.d.ts +98 -2
- package/dist/registry.js +171 -5
- package/dist/reloadable.d.ts +35 -0
- package/dist/reloadable.js +257 -0
- package/dist/remote-verify.d.ts +31 -0
- package/dist/remote-verify.js +204 -0
- package/dist/server.d.ts +43 -4
- package/dist/server.js +601 -52
- package/dist/settings.d.ts +48 -0
- package/dist/settings.js +168 -0
- package/dist/site-auth.d.ts +39 -0
- package/dist/site-auth.js +195 -0
- package/dist/template-fixes.d.ts +26 -0
- package/dist/template-fixes.js +285 -0
- package/dist/test-reporter.d.ts +49 -0
- package/dist/test-reporter.js +190 -0
- package/dist/transaction.d.ts +47 -0
- package/dist/transaction.js +241 -7
- package/dist/verify-tests.d.ts +98 -0
- package/dist/verify-tests.js +449 -0
- package/dist/verify.d.ts +103 -3
- package/dist/verify.js +272 -21
- package/dist/view-choice.d.ts +58 -0
- package/dist/view-choice.js +131 -0
- package/dist/vite-watch.js +4 -2
- package/package.json +8 -6
package/dist/npm-client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
? !
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
292
|
-
*
|
|
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
|
|
296
|
-
.
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 {};
|