@gjsify/cli 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.gjs.mjs +136 -126
- package/lib/actions/build.d.ts +19 -0
- package/lib/actions/build.js +2 -2
- package/lib/bundler-pick.js +72 -98
- package/lib/cli-fail.d.ts +4 -7
- package/lib/cli-fail.js +29 -55
- package/lib/commands/affected.js +161 -286
- package/lib/commands/install.d.ts +63 -66
- package/lib/commands/install.js +372 -536
- package/lib/commands/run.d.ts +1 -0
- package/lib/commands/run.js +37 -0
- package/lib/templates/install.mjs.tmpl +38 -68
- package/lib/types/cli-build-options.d.ts +33 -67
- package/lib/types/command.d.ts +9 -12
- package/lib/types/config-data.d.ts +125 -221
- package/lib/utils/auth-npmrc.d.ts +5 -10
- package/lib/utils/auth-npmrc.js +11 -21
- package/lib/utils/check-system-deps.d.ts +16 -0
- package/lib/utils/check-system-deps.js +49 -0
- package/lib/utils/clear-targets.d.ts +8 -11
- package/lib/utils/clear-targets.js +22 -29
- package/lib/utils/copy-targets.d.ts +13 -19
- package/lib/utils/copy-targets.js +31 -44
- package/lib/utils/dep-aggregation.d.ts +7 -15
- package/lib/utils/dep-aggregation.js +9 -22
- package/lib/utils/detect-native-packages.d.ts +83 -116
- package/lib/utils/detect-native-packages.js +111 -149
- package/lib/utils/dir-link.d.ts +29 -48
- package/lib/utils/dir-link.js +33 -56
- package/lib/utils/discover-showcases.d.ts +6 -7
- package/lib/utils/discover-showcases.js +11 -15
- package/lib/utils/dlx-cache.d.ts +29 -38
- package/lib/utils/dlx-cache.js +53 -63
- package/lib/utils/force-exit.d.ts +0 -3
- package/lib/utils/force-exit.js +13 -24
- package/lib/utils/gi-search-path.d.ts +16 -24
- package/lib/utils/gi-search-path.js +54 -82
- package/lib/utils/gjs-bundle-guard.d.ts +5 -13
- package/lib/utils/gjs-bundle-guard.js +35 -57
- package/lib/utils/gjs-source-escape.d.ts +2 -2
- package/lib/utils/gjs-source-escape.js +21 -39
- package/lib/utils/gjsify-shim.d.ts +26 -8
- package/lib/utils/gjsify-shim.js +128 -80
- package/lib/utils/html-entry.d.ts +8 -12
- package/lib/utils/html-entry.js +19 -28
- package/lib/utils/install-backend-native.d.ts +123 -161
- package/lib/utils/install-backend-native.js +536 -737
- package/lib/utils/install-backend.d.ts +42 -57
- package/lib/utils/install-backend.js +16 -21
- package/lib/utils/install-cache-fs.d.ts +10 -15
- package/lib/utils/install-cache-fs.js +18 -26
- package/lib/utils/install-lock.d.ts +5 -6
- package/lib/utils/install-lock.js +46 -65
- package/lib/utils/install-packument-cache.d.ts +27 -33
- package/lib/utils/install-packument-cache.js +44 -57
- package/lib/utils/install-progress.d.ts +2 -9
- package/lib/utils/install-progress.js +14 -39
- package/lib/utils/install-tarball-cache.d.ts +14 -20
- package/lib/utils/install-tarball-cache.js +41 -59
- package/lib/utils/library-output.d.ts +9 -22
- package/lib/utils/library-output.js +19 -37
- package/lib/utils/load-npmrc.d.ts +5 -6
- package/lib/utils/load-npmrc.js +18 -27
- package/lib/utils/node-bundle-guard.d.ts +4 -8
- package/lib/utils/node-bundle-guard.js +21 -35
- package/lib/utils/node-script.d.ts +12 -0
- package/lib/utils/node-script.js +95 -0
- package/lib/utils/node-version.d.ts +5 -14
- package/lib/utils/node-version.js +15 -24
- package/lib/utils/normalize-bundler-options.d.ts +7 -11
- package/lib/utils/normalize-bundler-options.js +24 -42
- package/lib/utils/npm-oidc.d.ts +23 -37
- package/lib/utils/npm-oidc.js +28 -53
- package/lib/utils/npm-otp-cache.d.ts +4 -4
- package/lib/utils/npm-otp-cache.js +20 -25
- package/lib/utils/npm-otp.d.ts +20 -30
- package/lib/utils/npm-otp.js +32 -51
- package/lib/utils/onboard-probe.d.ts +7 -13
- package/lib/utils/onboard-probe.js +16 -27
- package/lib/utils/parse-spec.d.ts +3 -3
- package/lib/utils/parse-spec.js +4 -5
- package/lib/utils/pkg-json-edit.d.ts +7 -12
- package/lib/utils/pkg-json-edit.js +13 -26
- package/lib/utils/pkg-json.d.ts +11 -18
- package/lib/utils/pkg-json.js +19 -28
- package/lib/utils/prefixed-output.d.ts +3 -4
- package/lib/utils/prefixed-output.js +13 -19
- package/lib/utils/prompt.d.ts +3 -4
- package/lib/utils/prompt.js +14 -18
- package/lib/utils/publish-diagnose.d.ts +11 -16
- package/lib/utils/publish-diagnose.js +15 -28
- package/lib/utils/publish-headers.d.ts +7 -11
- package/lib/utils/publish-headers.js +36 -65
- package/lib/utils/publishable-packages.d.ts +4 -6
- package/lib/utils/publishable-packages.js +14 -18
- package/lib/utils/resolve-gjs-entry.d.ts +5 -8
- package/lib/utils/resolve-gjs-entry.js +10 -27
- package/lib/utils/resolve-npm-package.d.ts +6 -11
- package/lib/utils/resolve-npm-package.js +29 -63
- package/lib/utils/resolve-plugin-by-name.d.ts +11 -19
- package/lib/utils/resolve-plugin-by-name.js +9 -14
- package/lib/utils/run-gjs.d.ts +19 -17
- package/lib/utils/run-gjs.js +37 -51
- package/lib/utils/run-lifecycle-script.d.ts +5 -9
- package/lib/utils/run-lifecycle-script.js +23 -42
- package/lib/utils/run-node.d.ts +23 -33
- package/lib/utils/run-node.js +56 -60
- package/lib/utils/runtimes.d.ts +25 -35
- package/lib/utils/runtimes.js +34 -46
- package/lib/utils/spawn.d.ts +32 -48
- package/lib/utils/spawn.js +51 -84
- package/lib/utils/stdin.d.ts +2 -2
- package/lib/utils/stdin.js +10 -25
- package/lib/utils/suggest.d.ts +6 -7
- package/lib/utils/suggest.js +8 -10
- package/lib/utils/system-gi.d.ts +18 -27
- package/lib/utils/system-gi.js +60 -114
- package/lib/utils/trust-registry.d.ts +4 -8
- package/lib/utils/trust-registry.js +7 -15
- package/lib/utils/win32-command.d.ts +8 -14
- package/lib/utils/win32-command.js +29 -43
- package/lib/utils/workspace-root.js +7 -12
- package/package.json +18 -17
- package/showcases.json +6 -0
package/lib/utils/auth-npmrc.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
// `
|
|
3
|
-
//
|
|
4
|
-
// npm stores the token under a "nerf-dart" key: the registry URL with the
|
|
5
|
-
// protocol stripped, e.g. `//registry.npmjs.org/:_authToken=<token>`. We upsert
|
|
6
|
-
// exactly that line in the userconfig file (`$NPM_CONFIG_USERCONFIG` or
|
|
7
|
-
// `~/.npmrc`), preserving every other line, and chmod it to 0600 (it holds a
|
|
8
|
-
// credential).
|
|
1
|
+
// The write side of `load-npmrc.ts`: `gjsify login`/`logout` upsert or drop one
|
|
2
|
+
// `_authToken` line in the userconfig `.npmrc` (`$NPM_CONFIG_USERCONFIG` or
|
|
3
|
+
// `~/.npmrc`), preserving every other line and chmod'ing the file to 0600.
|
|
9
4
|
import { existsSync, readFileSync, writeFileSync, chmodSync, mkdirSync } from 'node:fs';
|
|
10
5
|
import { homedir } from 'node:os';
|
|
11
6
|
import { join, dirname } from 'node:path';
|
|
@@ -14,8 +9,9 @@ export function userconfigNpmrcPath() {
|
|
|
14
9
|
return process.env.NPM_CONFIG_USERCONFIG || join(homedir(), '.npmrc');
|
|
15
10
|
}
|
|
16
11
|
/**
|
|
17
|
-
* The npm "nerf-dart" key for a registry
|
|
18
|
-
*
|
|
12
|
+
* The npm "nerf-dart" key for a registry — the URL with the protocol removed,
|
|
13
|
+
* host + path, trailing slash: `//registry.npmjs.org/`. npm keys credentials by
|
|
14
|
+
* this, so the exact shape is what makes the token findable.
|
|
19
15
|
*/
|
|
20
16
|
export function nerfDart(registry) {
|
|
21
17
|
const u = new URL(registry);
|
|
@@ -42,10 +38,7 @@ function writeUserconfig(path, content) {
|
|
|
42
38
|
/* non-POSIX filesystem */
|
|
43
39
|
}
|
|
44
40
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Upsert `<nerf-dart>:_authToken=<token>` in the userconfig `.npmrc`, preserving
|
|
47
|
-
* all other lines. Returns the file path written.
|
|
48
|
-
*/
|
|
41
|
+
/** Upsert `<nerf-dart>:_authToken=<token>`; returns the file path written. */
|
|
49
42
|
export function writeAuthToken(registry, token) {
|
|
50
43
|
const path = userconfigNpmrcPath();
|
|
51
44
|
const key = authTokenKey(registry);
|
|
@@ -54,15 +47,15 @@ export function writeAuthToken(registry, token) {
|
|
|
54
47
|
const lines = existing.length ? existing.split('\n') : [];
|
|
55
48
|
let replaced = false;
|
|
56
49
|
const next = lines.map((l) => {
|
|
57
|
-
//
|
|
58
|
-
//
|
|
50
|
+
// Matches on the KEY only, so a `${VAR}` placeholder value is overwritten
|
|
51
|
+
// with the concrete token like any other.
|
|
59
52
|
if (l.replace(/\s+/g, '').startsWith(`${key}=`)) {
|
|
60
53
|
replaced = true;
|
|
61
54
|
return line;
|
|
62
55
|
}
|
|
63
56
|
return l;
|
|
64
57
|
});
|
|
65
|
-
//
|
|
58
|
+
// Or they accumulate across rewrites.
|
|
66
59
|
while (next.length && next[next.length - 1].trim() === '')
|
|
67
60
|
next.pop();
|
|
68
61
|
if (!replaced)
|
|
@@ -70,10 +63,7 @@ export function writeAuthToken(registry, token) {
|
|
|
70
63
|
writeUserconfig(path, next.join('\n') + '\n');
|
|
71
64
|
return path;
|
|
72
65
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Remove the `<nerf-dart>:_authToken=` line for a registry from the userconfig
|
|
75
|
-
* `.npmrc`. Returns `{ path, removed }` — `removed` is false if no line matched.
|
|
76
|
-
*/
|
|
66
|
+
/** Drop the registry's `_authToken` line; `removed` is false when none matched. */
|
|
77
67
|
export function removeAuthToken(registry) {
|
|
78
68
|
const path = userconfigNpmrcPath();
|
|
79
69
|
const key = authTokenKey(registry);
|
|
@@ -130,6 +130,22 @@ export declare function runAllChecks(cwd: string): DepCheck[];
|
|
|
130
130
|
* Used by `gjsify showcase` for examples that have no native deps.
|
|
131
131
|
*/
|
|
132
132
|
export declare function runMinimalChecks(): DepCheck[];
|
|
133
|
+
/**
|
|
134
|
+
* Is a blueprint-compiler reachable, by the SAME rule the build uses?
|
|
135
|
+
*
|
|
136
|
+
* Delegates to `@gjsify/vite-plugin-blueprint/resolve` rather than probing PATH
|
|
137
|
+
* here, because the two answers must not be able to disagree: on win32 the
|
|
138
|
+
* compiler is normally an MSYS2 script that is deliberately NOT on PATH, so a
|
|
139
|
+
* PATH-only check would report "missing" on a host where every `.blp` builds
|
|
140
|
+
* fine — a check failing for the wrong reason, which is the failure mode this
|
|
141
|
+
* file's own history is made of.
|
|
142
|
+
*
|
|
143
|
+
* `--version` is not run. MSYS2 ships blueprint-compiler as a shebang script
|
|
144
|
+
* with no `.exe`, so `checkBinary` cannot execute it on Windows; presence of the
|
|
145
|
+
* resolved pair is the honest answer, and the `source` is worth reporting
|
|
146
|
+
* because "found via msys2" explains an install PATH does not show.
|
|
147
|
+
*/
|
|
148
|
+
export declare function checkBlueprintCompiler(requiredBy?: string[]): DepCheck;
|
|
133
149
|
/** Check gwebgl npm package (project first, CLI fallback). Optional — only needed by @gjsify/webgl users. */
|
|
134
150
|
export declare function checkGwebgl(cwd: string): DepCheck;
|
|
135
151
|
/**
|
|
@@ -15,6 +15,9 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
15
15
|
import { createRequire } from 'node:module';
|
|
16
16
|
import { pathToFileURL } from 'node:url';
|
|
17
17
|
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
18
|
+
// The zero-dependency subpath, deliberately: importing the plugin root would
|
|
19
|
+
// pull vite/execa/minify-xml into a system CHECK.
|
|
20
|
+
import { resolveBlueprintCompiler } from '@gjsify/vite-plugin-blueprint/resolve';
|
|
18
21
|
/** Run a binary and return its stdout trimmed, or null if it fails. */
|
|
19
22
|
function tryExecFile(binary, args) {
|
|
20
23
|
try {
|
|
@@ -392,6 +395,32 @@ export function runMinimalChecks() {
|
|
|
392
395
|
results.push(checkMsvcRuntime());
|
|
393
396
|
return results;
|
|
394
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Is a blueprint-compiler reachable, by the SAME rule the build uses?
|
|
400
|
+
*
|
|
401
|
+
* Delegates to `@gjsify/vite-plugin-blueprint/resolve` rather than probing PATH
|
|
402
|
+
* here, because the two answers must not be able to disagree: on win32 the
|
|
403
|
+
* compiler is normally an MSYS2 script that is deliberately NOT on PATH, so a
|
|
404
|
+
* PATH-only check would report "missing" on a host where every `.blp` builds
|
|
405
|
+
* fine — a check failing for the wrong reason, which is the failure mode this
|
|
406
|
+
* file's own history is made of.
|
|
407
|
+
*
|
|
408
|
+
* `--version` is not run. MSYS2 ships blueprint-compiler as a shebang script
|
|
409
|
+
* with no `.exe`, so `checkBinary` cannot execute it on Windows; presence of the
|
|
410
|
+
* resolved pair is the honest answer, and the `source` is worth reporting
|
|
411
|
+
* because "found via msys2" explains an install PATH does not show.
|
|
412
|
+
*/
|
|
413
|
+
export function checkBlueprintCompiler(requiredBy) {
|
|
414
|
+
const resolved = resolveBlueprintCompiler();
|
|
415
|
+
return {
|
|
416
|
+
id: 'blueprint-compiler',
|
|
417
|
+
name: 'blueprint-compiler (.blp templates)',
|
|
418
|
+
found: resolved !== null,
|
|
419
|
+
version: resolved ? `found via ${resolved.source}` : undefined,
|
|
420
|
+
severity: 'optional',
|
|
421
|
+
requiredBy,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
395
424
|
/** Check gwebgl npm package (project first, CLI fallback). Optional — only needed by @gjsify/webgl users. */
|
|
396
425
|
export function checkGwebgl(cwd) {
|
|
397
426
|
return checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd, 'optional', ['@gjsify/webgl']);
|
|
@@ -454,7 +483,13 @@ function runNativeBuildToolchainChecks() {
|
|
|
454
483
|
// Narrower on purpose: only the three bridges that link an upstream Rust
|
|
455
484
|
// crate by Cargo path-dependency need a Rust toolchain.
|
|
456
485
|
const rustBridges = ['@gjsify/lightningcss-native', '@gjsify/oxfmt-native', '@gjsify/rolldown-native'];
|
|
486
|
+
// Every showcase/app whose window comes from a `.blp` template. NOT a Vala
|
|
487
|
+
// bridge and nothing to do with meson — it is a BUILD-time toolchain in the
|
|
488
|
+
// same sense, and it belongs here so `gjsify system-check` names it before a
|
|
489
|
+
// build does.
|
|
490
|
+
const blueprintConsumers = ['@gjsify/vite-plugin-blueprint'];
|
|
457
491
|
return [
|
|
492
|
+
checkBlueprintCompiler(blueprintConsumers),
|
|
458
493
|
checkBinary('ninja', 'Ninja', 'ninja', ['--version'], 'optional', undefined, valaBridges),
|
|
459
494
|
checkBinary('vala', 'Vala', 'valac', ['--version'], 'optional', (out) => out.replace(/^Vala\s+/i, ''), valaBridges),
|
|
460
495
|
checkBinary('cargo', 'Cargo (Rust)', 'cargo', ['--version'], 'optional', (out) => out.replace(/^cargo\s+/i, '').split(' ')[0] ?? out, rustBridges),
|
|
@@ -697,11 +732,24 @@ export function buildInstallCommand(pm, missing) {
|
|
|
697
732
|
const pkgMap = PM_PACKAGES[pm];
|
|
698
733
|
const pkgs = [];
|
|
699
734
|
const npmDeps = [];
|
|
735
|
+
const standalone = [];
|
|
700
736
|
for (const dep of missing) {
|
|
701
737
|
if (dep.id === 'gwebgl') {
|
|
702
738
|
npmDeps.push('@gjsify/webgl');
|
|
703
739
|
continue;
|
|
704
740
|
}
|
|
741
|
+
// blueprint-compiler on Windows is the one dep whose install command is
|
|
742
|
+
// not this host's package manager. winget has no package for it, and it
|
|
743
|
+
// cannot get one that works: the tool needs PyGObject, which publishes
|
|
744
|
+
// no Windows wheel, so MSYS2 — which ships Python, PyGObject and the
|
|
745
|
+
// typelibs already fitted together — is the only source. Its command is
|
|
746
|
+
// not `winget install`-shaped, so it cannot live in PM_PACKAGES without
|
|
747
|
+
// producing a line that looks right and fails.
|
|
748
|
+
if (dep.id === 'blueprint-compiler' && process.platform === 'win32') {
|
|
749
|
+
standalone.push('pacman -S mingw-w64-ucrt-x86_64-blueprint-compiler mingw-w64-ucrt-x86_64-gtk4 ' +
|
|
750
|
+
'mingw-w64-ucrt-x86_64-libadwaita # inside MSYS2; found automatically afterwards');
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
705
753
|
if (dep.id === 'nodejs')
|
|
706
754
|
continue; // can't be missing if we're running
|
|
707
755
|
const pkg = pkgMap[dep.id];
|
|
@@ -715,6 +763,7 @@ export function buildInstallCommand(pm, missing) {
|
|
|
715
763
|
if (npmDeps.length > 0) {
|
|
716
764
|
lines.push(`npm install ${npmDeps.join(' ')}`);
|
|
717
765
|
}
|
|
766
|
+
lines.push(...standalone);
|
|
718
767
|
return lines.length > 0 ? lines.join('\n ') : null;
|
|
719
768
|
}
|
|
720
769
|
/**
|
|
@@ -8,9 +8,9 @@ export interface ClearContext {
|
|
|
8
8
|
/**
|
|
9
9
|
* Does this path segment carry a shell wildcard?
|
|
10
10
|
*
|
|
11
|
-
* `*` and `?` only — what `rm -rf *.log` used. Deliberately NOT a glob engine:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `*` and `?` only — what `rm -rf *.log` used. Deliberately NOT a glob engine: a `clear` script
|
|
12
|
+
* names build output, and `**` or character classes would let a line delete more than its
|
|
13
|
+
* author can see at a glance.
|
|
14
14
|
*/
|
|
15
15
|
export declare function hasWildcard(segment: string): boolean;
|
|
16
16
|
/** Compile one wildcard segment to an anchored regex, escaping every other metachar. */
|
|
@@ -18,13 +18,10 @@ export declare function segmentToRegExp(segment: string): RegExp;
|
|
|
18
18
|
/**
|
|
19
19
|
* Expand and validate every target, resolving wildcards against `readdir`.
|
|
20
20
|
*
|
|
21
|
-
* ALL targets are resolved before the caller deletes any
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* A
|
|
25
|
-
*
|
|
26
|
-
* the recursive traversal. A pattern matching nothing yields no targets, which
|
|
27
|
-
* is `rm -rf`'s behaviour under `--force` and why a package that was never built
|
|
28
|
-
* clears cleanly.
|
|
21
|
+
* ALL targets are resolved before the caller deletes any — a typo in the fourth path must not
|
|
22
|
+
* be discovered after the first three are gone. A wildcard is honoured in the LAST segment
|
|
23
|
+
* only, matched against a single directory read: shell semantics without recursive traversal.
|
|
24
|
+
* A pattern matching nothing yields no targets, which is `rm -rf --force`'s behaviour and why a
|
|
25
|
+
* package that was never built clears cleanly.
|
|
29
26
|
*/
|
|
30
27
|
export declare function resolveClearTargets(paths: readonly string[], ctx: ClearContext): string[];
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
// Target resolution for `gjsify clear` — the safety-critical half, kept pure.
|
|
2
2
|
//
|
|
3
|
-
// A recursive delete driven by strings from
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// that is exactly what a test must not do.
|
|
3
|
+
// A recursive delete driven by strings from every workspace's package.json is a footgun, so
|
|
4
|
+
// the "which paths does this actually delete" decision lives here with the directory read
|
|
5
|
+
// INJECTED. That makes every rule below unit-testable without a real filesystem, which is the
|
|
6
|
+
// point: the tests that matter are the ones for paths that must be REFUSED, and creating a
|
|
7
|
+
// fixture outside the cwd to prove that is exactly what a test must not do.
|
|
9
8
|
//
|
|
10
9
|
// See `commands/clear.ts` for why the command exists at all.
|
|
11
10
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
12
11
|
/**
|
|
13
12
|
* Does this path segment carry a shell wildcard?
|
|
14
13
|
*
|
|
15
|
-
* `*` and `?` only — what `rm -rf *.log` used. Deliberately NOT a glob engine:
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* `*` and `?` only — what `rm -rf *.log` used. Deliberately NOT a glob engine: a `clear` script
|
|
15
|
+
* names build output, and `**` or character classes would let a line delete more than its
|
|
16
|
+
* author can see at a glance.
|
|
18
17
|
*/
|
|
19
18
|
export function hasWildcard(segment) {
|
|
20
19
|
return segment.includes('*') || segment.includes('?');
|
|
@@ -30,9 +29,8 @@ export function segmentToRegExp(segment) {
|
|
|
30
29
|
/**
|
|
31
30
|
* Reject a target that is not strictly inside `cwd`.
|
|
32
31
|
*
|
|
33
|
-
* A hard error, never a skip: a typo that would otherwise delete a sibling
|
|
34
|
-
*
|
|
35
|
-
* middle of a 224-package `foreach` run.
|
|
32
|
+
* A hard error, never a skip: a typo that would otherwise delete a sibling package — or
|
|
33
|
+
* `$HOME` — must not depend on anyone reading a warning mid-`foreach` across every workspace.
|
|
36
34
|
*/
|
|
37
35
|
function assertInsideCwd(absolute, original, cwd) {
|
|
38
36
|
const rel = relative(cwd, absolute);
|
|
@@ -47,14 +45,11 @@ function assertInsideCwd(absolute, original, cwd) {
|
|
|
47
45
|
/**
|
|
48
46
|
* Expand and validate every target, resolving wildcards against `readdir`.
|
|
49
47
|
*
|
|
50
|
-
* ALL targets are resolved before the caller deletes any
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* A
|
|
54
|
-
*
|
|
55
|
-
* the recursive traversal. A pattern matching nothing yields no targets, which
|
|
56
|
-
* is `rm -rf`'s behaviour under `--force` and why a package that was never built
|
|
57
|
-
* clears cleanly.
|
|
48
|
+
* ALL targets are resolved before the caller deletes any — a typo in the fourth path must not
|
|
49
|
+
* be discovered after the first three are gone. A wildcard is honoured in the LAST segment
|
|
50
|
+
* only, matched against a single directory read: shell semantics without recursive traversal.
|
|
51
|
+
* A pattern matching nothing yields no targets, which is `rm -rf --force`'s behaviour and why a
|
|
52
|
+
* package that was never built clears cleanly.
|
|
58
53
|
*/
|
|
59
54
|
export function resolveClearTargets(paths, ctx) {
|
|
60
55
|
const out = [];
|
|
@@ -73,17 +68,16 @@ export function resolveClearTargets(paths, ctx) {
|
|
|
73
68
|
function expandTarget(target, ctx) {
|
|
74
69
|
const leaf = basename(target);
|
|
75
70
|
const parent = dirname(target);
|
|
76
|
-
// Checked BEFORE the no-wildcard shortcut: `*/dist` has a wildcard-free
|
|
77
|
-
//
|
|
78
|
-
//
|
|
71
|
+
// Checked BEFORE the no-wildcard shortcut: `*/dist` has a wildcard-free basename, and
|
|
72
|
+
// treating it as a literal path would silently delete nothing instead of telling the
|
|
73
|
+
// author the shape is unsupported.
|
|
79
74
|
if (hasWildcard(parent)) {
|
|
80
75
|
throw new Error(`gjsify clear: a wildcard is only supported in the last path segment — got "${target}".`);
|
|
81
76
|
}
|
|
82
77
|
if (!hasWildcard(leaf))
|
|
83
78
|
return [resolve(ctx.cwd, target)];
|
|
84
|
-
// The parent is validated too: `../*.log`
|
|
85
|
-
//
|
|
86
|
-
// expansion — the matches themselves are all inside it.
|
|
79
|
+
// The parent is validated too: `../*.log` is only refused if the containing DIRECTORY is
|
|
80
|
+
// checked against cwd before expansion — the matches themselves are all inside it.
|
|
87
81
|
const dir = resolve(ctx.cwd, parent);
|
|
88
82
|
assertInsideCwdOrSelf(dir, target, ctx.cwd);
|
|
89
83
|
const re = segmentToRegExp(leaf);
|
|
@@ -92,9 +86,8 @@ function expandTarget(target, ctx) {
|
|
|
92
86
|
names = ctx.readdir(dir);
|
|
93
87
|
}
|
|
94
88
|
catch {
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// target still surfaces from the delete itself.
|
|
89
|
+
// Absent or unreadable containing directory — nothing to clear, like `rm -rf` on a
|
|
90
|
+
// missing path. A permission problem on an EXISTING target still surfaces from the delete.
|
|
98
91
|
return [];
|
|
99
92
|
}
|
|
100
93
|
return names
|
|
@@ -13,29 +13,23 @@ export interface CopyOperation {
|
|
|
13
13
|
/**
|
|
14
14
|
* Is the destination a DIRECTORY to copy into, rather than the target path?
|
|
15
15
|
*
|
|
16
|
-
* Decided from the ARGUMENTS ALONE — never from what is on disk. `cp` decides it
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* so they were already non-idempotent on Linux. A build step must not be.
|
|
16
|
+
* Decided from the ARGUMENTS ALONE — never from what is on disk. `cp` decides it by stat'ing the
|
|
17
|
+
* destination, which is why `cp -r src/assets dist/res` copies the directory to `dist/res` the
|
|
18
|
+
* first time and to `dist/res/assets` the second: the same script means two things depending on
|
|
19
|
+
* whether it has run before. Several scripts this command replaces carried that shape and were
|
|
20
|
+
* already non-idempotent on Linux. A build step must not be.
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* - more than one source;
|
|
26
|
-
* - any source carrying a wildcard.
|
|
22
|
+
* Directory mode iff: a trailing `/` (or `\`) on the destination — the explicit spelling; more
|
|
23
|
+
* than one source; or any source carrying a wildcard.
|
|
27
24
|
*
|
|
28
|
-
* The last two
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* Both land in directory mode, so the same script means the same thing on both.
|
|
25
|
+
* The last two keep the platforms in agreement: a POSIX shell expands `src/public/*` before
|
|
26
|
+
* gjsify starts (many sources), while `cmd.exe` passes the pattern through (one source, a
|
|
27
|
+
* wildcard). Both land in directory mode, so the same script means the same thing on both.
|
|
32
28
|
*/
|
|
33
29
|
export declare function isDirectoryDestination(sources: readonly string[], dest: string): boolean;
|
|
34
30
|
/**
|
|
35
|
-
* Resolve `gjsify copy <sources…> <destination>` into the exact list of copies.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* argument in position four cannot be discovered after three files have already
|
|
39
|
-
* been written.
|
|
31
|
+
* Resolve `gjsify copy <sources…> <destination>` into the exact list of copies. ALL operations are
|
|
32
|
+
* planned before the caller performs any, so a bad argument in position four cannot be discovered
|
|
33
|
+
* after three files have been written.
|
|
40
34
|
*/
|
|
41
35
|
export declare function planCopy(args: readonly string[], ctx: CopyContext): CopyOperation[];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Target resolution for `gjsify copy` — the deciding half, kept pure.
|
|
2
2
|
//
|
|
3
|
-
// Same split as `clear-targets.ts`, for the same reason: the paths come from
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// Same split as `clear-targets.ts`, for the same reason: the paths come from package.json files
|
|
4
|
+
// rather than from a human at a prompt, so "which file lands where" must be testable without a
|
|
5
|
+
// filesystem — including the cases that must be REFUSED, which cannot be proven by writing
|
|
6
|
+
// outside the working directory.
|
|
7
7
|
//
|
|
8
8
|
// See `commands/copy.ts` for why the command exists at all.
|
|
9
9
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
@@ -11,22 +11,18 @@ import { hasWildcard, segmentToRegExp } from './clear-targets.js';
|
|
|
11
11
|
/**
|
|
12
12
|
* Is the destination a DIRECTORY to copy into, rather than the target path?
|
|
13
13
|
*
|
|
14
|
-
* Decided from the ARGUMENTS ALONE — never from what is on disk. `cp` decides it
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* so they were already non-idempotent on Linux. A build step must not be.
|
|
14
|
+
* Decided from the ARGUMENTS ALONE — never from what is on disk. `cp` decides it by stat'ing the
|
|
15
|
+
* destination, which is why `cp -r src/assets dist/res` copies the directory to `dist/res` the
|
|
16
|
+
* first time and to `dist/res/assets` the second: the same script means two things depending on
|
|
17
|
+
* whether it has run before. Several scripts this command replaces carried that shape and were
|
|
18
|
+
* already non-idempotent on Linux. A build step must not be.
|
|
20
19
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* - more than one source;
|
|
24
|
-
* - any source carrying a wildcard.
|
|
20
|
+
* Directory mode iff: a trailing `/` (or `\`) on the destination — the explicit spelling; more
|
|
21
|
+
* than one source; or any source carrying a wildcard.
|
|
25
22
|
*
|
|
26
|
-
* The last two
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* Both land in directory mode, so the same script means the same thing on both.
|
|
23
|
+
* The last two keep the platforms in agreement: a POSIX shell expands `src/public/*` before
|
|
24
|
+
* gjsify starts (many sources), while `cmd.exe` passes the pattern through (one source, a
|
|
25
|
+
* wildcard). Both land in directory mode, so the same script means the same thing on both.
|
|
30
26
|
*/
|
|
31
27
|
export function isDirectoryDestination(sources, dest) {
|
|
32
28
|
if (dest.endsWith('/') || dest.endsWith('\\'))
|
|
@@ -38,15 +34,13 @@ export function isDirectoryDestination(sources, dest) {
|
|
|
38
34
|
/**
|
|
39
35
|
* Reject a destination that is not inside `cwd`.
|
|
40
36
|
*
|
|
41
|
-
* A hard error, never a skip —
|
|
42
|
-
*
|
|
43
|
-
* outside it. `allowSelf` covers `gjsify copy ../shared/x.wasm ./`, where the
|
|
37
|
+
* A hard error, never a skip — mirroring `clear`: it must not DELETE outside the package, `copy`
|
|
38
|
+
* must not WRITE outside it. `allowSelf` covers `gjsify copy ../shared/x.wasm ./`, where the
|
|
44
39
|
* package root legitimately IS the destination directory.
|
|
45
40
|
*
|
|
46
|
-
* SOURCES are deliberately NOT checked
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* dangerous direction.
|
|
41
|
+
* SOURCES are deliberately NOT checked: reading from a sibling package
|
|
42
|
+
* (`../../../packages/infra/lightningcss-wasm/…`) is the normal shape for staging a workspace
|
|
43
|
+
* artifact, and reading is not the dangerous direction.
|
|
50
44
|
*/
|
|
51
45
|
function assertWritableDestination(absolute, original, cwd, allowSelf) {
|
|
52
46
|
const rel = relative(cwd, absolute);
|
|
@@ -62,15 +56,12 @@ function assertWritableDestination(absolute, original, cwd, allowSelf) {
|
|
|
62
56
|
}
|
|
63
57
|
}
|
|
64
58
|
/**
|
|
65
|
-
* Expand one source argument. A wildcard is honoured in the LAST segment only,
|
|
66
|
-
*
|
|
67
|
-
* and for the same reason: a build script names files, and `**` there would copy
|
|
68
|
-
* more than its author can see at a glance.
|
|
59
|
+
* Expand one source argument. A wildcard is honoured in the LAST segment only, against a single
|
|
60
|
+
* directory read — the same restricted grammar `clear` uses, and for the same reason.
|
|
69
61
|
*
|
|
70
|
-
* A pattern matching nothing yields NO operations
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* error, `cp *.html dist/` on an empty directory is not.
|
|
62
|
+
* A pattern matching nothing yields NO operations; a literal path that does not exist yields one,
|
|
63
|
+
* which then fails loudly in the copy itself. The asymmetry mirrors the shell: `cp missing.html
|
|
64
|
+
* dist/` is an error, `cp *.html dist/` on an empty directory is not.
|
|
74
65
|
*/
|
|
75
66
|
function expandSource(source, ctx) {
|
|
76
67
|
const leaf = basename(source);
|
|
@@ -95,11 +86,9 @@ function expandSource(source, ctx) {
|
|
|
95
86
|
.map((name) => join(dir, name));
|
|
96
87
|
}
|
|
97
88
|
/**
|
|
98
|
-
* Resolve `gjsify copy <sources…> <destination>` into the exact list of copies.
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* argument in position four cannot be discovered after three files have already
|
|
102
|
-
* been written.
|
|
89
|
+
* Resolve `gjsify copy <sources…> <destination>` into the exact list of copies. ALL operations are
|
|
90
|
+
* planned before the caller performs any, so a bad argument in position four cannot be discovered
|
|
91
|
+
* after three files have been written.
|
|
103
92
|
*/
|
|
104
93
|
export function planCopy(args, ctx) {
|
|
105
94
|
if (args.length < 2) {
|
|
@@ -120,9 +109,8 @@ export function planCopy(args, ctx) {
|
|
|
120
109
|
expanded.push(...expandSource(source, ctx));
|
|
121
110
|
}
|
|
122
111
|
if (!intoDirectory && expanded.length !== 1) {
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
// make a future change to `expandSource` silently copy the wrong file.
|
|
112
|
+
// Unreachable today (a single wildcard-free source always expands to one path), but
|
|
113
|
+
// leaving it unstated would let a future `expandSource` change silently copy the wrong file.
|
|
126
114
|
throw new Error(`gjsify copy: "${dest}" names a single destination, but ${expanded.length} sources resolved.`);
|
|
127
115
|
}
|
|
128
116
|
const ops = [];
|
|
@@ -133,9 +121,8 @@ export function planCopy(args, ctx) {
|
|
|
133
121
|
throw new Error(`gjsify copy: "${from}" is its own destination.`);
|
|
134
122
|
}
|
|
135
123
|
if (seenDest.has(to)) {
|
|
136
|
-
// Two sources with the same basename
|
|
137
|
-
//
|
|
138
|
-
// argument order. Name it instead.
|
|
124
|
+
// Two sources with the same basename into one directory: the second would silently
|
|
125
|
+
// overwrite the first, and which wins depends on argument order.
|
|
139
126
|
throw new Error(`gjsify copy: two sources would both be written to ${to}.`);
|
|
140
127
|
}
|
|
141
128
|
seenDest.add(to);
|
|
@@ -4,40 +4,32 @@ export interface DepDeclaration {
|
|
|
4
4
|
workspace: string;
|
|
5
5
|
/** Absolute path to the workspace's `package.json`. */
|
|
6
6
|
workspaceLocation: string;
|
|
7
|
-
/** Dependency name (e.g. `rolldown`). */
|
|
8
7
|
name: string;
|
|
9
|
-
/** Which manifest field this declaration lives in. */
|
|
10
8
|
field: 'dependencies' | 'devDependencies' | 'optionalDependencies' | 'peerDependencies';
|
|
11
9
|
/** The original range string (e.g. `^1.0.4`). */
|
|
12
10
|
currentRange: string;
|
|
13
|
-
/**
|
|
11
|
+
/** Max-satisfying numeric version inside the range, or `null` if unparseable. */
|
|
14
12
|
currentVersion: string | null;
|
|
15
13
|
/** Range prefix preserved on write-back (`^`, `~`, `>=`, …; `""` for literal). */
|
|
16
14
|
prefix: string;
|
|
17
15
|
}
|
|
18
16
|
/** Aggregated view of one external dep across all workspaces that declare it. */
|
|
19
17
|
export interface DependencyGroup {
|
|
20
|
-
/** Dependency name. */
|
|
21
18
|
name: string;
|
|
22
|
-
/** Every declaration of this dep across the workspace. */
|
|
23
19
|
occurrences: DepDeclaration[];
|
|
24
|
-
/**
|
|
20
|
+
/** Unique declared ranges — size > 1 IS the inconsistency. */
|
|
25
21
|
declaredRanges: Set<string>;
|
|
26
|
-
/** Range
|
|
22
|
+
/** Range declared by the most workspaces: the de-facto consensus. */
|
|
27
23
|
dominantRange: string;
|
|
28
|
-
/**
|
|
24
|
+
/** Highest declared semver across the group. */
|
|
29
25
|
highestVersion: string | null;
|
|
30
26
|
}
|
|
31
27
|
/**
|
|
32
|
-
* Group a flat list of `DepDeclaration`s by dep name
|
|
33
|
-
*
|
|
34
|
-
* declared version) so callers don't have to re-scan.
|
|
28
|
+
* Group a flat list of `DepDeclaration`s by dep name, pre-computing the aggregates
|
|
29
|
+
* so no caller has to re-scan the occurrences.
|
|
35
30
|
*/
|
|
36
31
|
export declare function groupByDependency(decls: readonly DepDeclaration[]): DependencyGroup[];
|
|
37
32
|
/** `true` when the group's workspaces declare the dep at >1 distinct range. */
|
|
38
33
|
export declare function isInconsistent(group: DependencyGroup): boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Filter to just the inconsistent groups (>1 distinct range across workspaces).
|
|
41
|
-
* Convenience wrapper for `--align` and `--check` modes.
|
|
42
|
-
*/
|
|
34
|
+
/** The inconsistent groups only — what `--align` and `--check` operate on. */
|
|
43
35
|
export declare function findInconsistencies(groups: readonly DependencyGroup[]): DependencyGroup[];
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
// Workspace-aware dependency aggregation for `gjsify upgrade
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// - `gjsify upgrade` interactive table — shows fan-out (count of workspaces
|
|
8
|
-
// declaring the dep) and flags inconsistent declarations with `⚠`.
|
|
9
|
-
// - `gjsify upgrade --align` — offline mode that detects inconsistencies
|
|
10
|
-
// and proposes a single range (the highest semver-satisfying one) without
|
|
11
|
-
// hitting the registry.
|
|
12
|
-
// - `gjsify upgrade --check` — CI gate that exits non-zero when any
|
|
13
|
-
// inconsistency exists.
|
|
1
|
+
// Workspace-aware dependency aggregation for `gjsify upgrade`: group per-workspace
|
|
2
|
+
// declarations by package name and surface inconsistencies — the same package
|
|
3
|
+
// declared at different ranges across workspaces. Drives the interactive table's
|
|
4
|
+
// fan-out column, `--align` (propose one range offline, no registry hit) and
|
|
5
|
+
// `--check` (CI gate, non-zero on any inconsistency).
|
|
14
6
|
import { compare, parse } from '@gjsify/semver';
|
|
15
7
|
/** Local `gt` shim: `@gjsify/semver` exports `compare` returning -1|0|1. */
|
|
16
8
|
function semverGt(a, b) {
|
|
@@ -22,9 +14,8 @@ function semverGt(a, b) {
|
|
|
22
14
|
}
|
|
23
15
|
}
|
|
24
16
|
/**
|
|
25
|
-
* Group a flat list of `DepDeclaration`s by dep name
|
|
26
|
-
*
|
|
27
|
-
* declared version) so callers don't have to re-scan.
|
|
17
|
+
* Group a flat list of `DepDeclaration`s by dep name, pre-computing the aggregates
|
|
18
|
+
* so no caller has to re-scan the occurrences.
|
|
28
19
|
*/
|
|
29
20
|
export function groupByDependency(decls) {
|
|
30
21
|
const map = new Map();
|
|
@@ -48,8 +39,7 @@ export function groupByDependency(decls) {
|
|
|
48
39
|
}
|
|
49
40
|
}
|
|
50
41
|
}
|
|
51
|
-
//
|
|
52
|
-
// ties resolve by highest semver, then by lexicographic order.
|
|
42
|
+
// dominantRange = most occurrences; ties break on highest semver, then lexically.
|
|
53
43
|
for (const g of map.values()) {
|
|
54
44
|
const counts = new Map();
|
|
55
45
|
for (const occ of g.occurrences) {
|
|
@@ -77,10 +67,7 @@ export function groupByDependency(decls) {
|
|
|
77
67
|
export function isInconsistent(group) {
|
|
78
68
|
return group.declaredRanges.size > 1;
|
|
79
69
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Filter to just the inconsistent groups (>1 distinct range across workspaces).
|
|
82
|
-
* Convenience wrapper for `--align` and `--check` modes.
|
|
83
|
-
*/
|
|
70
|
+
/** The inconsistent groups only — what `--align` and `--check` operate on. */
|
|
84
71
|
export function findInconsistencies(groups) {
|
|
85
72
|
return groups.filter(isInconsistent);
|
|
86
73
|
}
|