@gjsify/cli 0.36.0 → 0.38.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 +169 -169
- package/lib/actions/build.d.ts +7 -0
- package/lib/actions/build.js +18 -3
- package/lib/bundler-pick.js +61 -87
- 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/config.d.ts +14 -0
- package/lib/config.js +18 -1
- package/lib/templates/install.mjs.tmpl +164 -75
- 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 +137 -221
- package/lib/utils/auth-npmrc.d.ts +5 -10
- package/lib/utils/auth-npmrc.js +11 -21
- package/lib/utils/bin-shim.d.ts +40 -25
- package/lib/utils/bin-shim.js +51 -26
- 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 +89 -122
- package/lib/utils/detect-native-packages.js +119 -169
- 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 +17 -20
- package/lib/utils/gjsify-shim.js +80 -120
- 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.js +40 -53
- 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 +16 -21
- package/lib/utils/run-gjs.js +31 -56
- 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 +21 -33
- package/lib/utils/run-node.js +43 -64
- 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 +36 -27
- package/lib/utils/system-gi.js +85 -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 -18
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);
|
package/lib/utils/bin-shim.d.ts
CHANGED
|
@@ -130,31 +130,16 @@ export declare function buildLauncherShims(opts: {
|
|
|
130
130
|
* script chain: the self-relative `@loader_path` rpath that
|
|
131
131
|
* `check-prebuild-loader-path.mjs` enforces is what actually has to carry it.
|
|
132
132
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* ITSELF survives the following `exec gjs` (an unprotected Homebrew binary) and
|
|
144
|
-
* `Gtk.init()` then succeeds. What is missing is a shape worth having:
|
|
145
|
-
*
|
|
146
|
-
* - BAKING `systemGiLibraryDirs()` in at write time reintroduces precisely the
|
|
147
|
-
* snapshot this function exists to remove — a launcher is routinely written
|
|
148
|
-
* BEFORE `brew install gtk4`, and would bake nothing while saying nothing.
|
|
149
|
-
* - RE-DERIVING it in shell means a THIRD copy of a rule that already has two,
|
|
150
|
-
* pinned in agreement by `system-gi.spec.ts` — and shell can express only one
|
|
151
|
-
* of its three sources (the probed prefixes; `pkg-config` would be a spawn on
|
|
152
|
-
* every launch), so that copy would be knowingly partial, in the one language
|
|
153
|
-
* nothing here type-checks.
|
|
154
|
-
*
|
|
155
|
-
* So the gap is recorded in `status/open-todos.md` with the measurement attached
|
|
156
|
-
* instead of being closed by a mechanism that drifts. Closing it properly means
|
|
157
|
-
* teaching such a launcher to defer to the CLI, not to re-derive.
|
|
133
|
+
* IT DOES export dyld's own default fallback list on darwin — see
|
|
134
|
+
* {@link dyldFallbackPreamble}. That is a CONSTANT, so it carries none of the
|
|
135
|
+
* staleness that rules out the alternatives: BAKING `systemGiLibraryDirs()` in
|
|
136
|
+
* at write time reintroduces the snapshot this function exists to remove (a
|
|
137
|
+
* launcher is routinely written BEFORE `brew install gtk4`), and RE-DERIVING it
|
|
138
|
+
* in shell would be a knowingly partial THIRD copy of a two-copy rule, in the
|
|
139
|
+
* one language nothing here type-checks. The REMAINING gap — a host GI stack
|
|
140
|
+
* under a prefix dyld never defaulted to, `/opt/homebrew/lib` on Apple silicon —
|
|
141
|
+
* stays in `status/open-todos.md`, to be closed by teaching such a launcher to
|
|
142
|
+
* defer to the CLI rather than to re-derive.
|
|
158
143
|
*
|
|
159
144
|
* @param scanRoot Directory whose `node_modules` is globbed at launch time.
|
|
160
145
|
* @param bakedDirs Prebuild dirs found at write time; entries outside `scanRoot`
|
|
@@ -166,6 +151,36 @@ export declare function buildNativeEnvPreamble(scanRoot: string, bakedDirs?: rea
|
|
|
166
151
|
arch?: string;
|
|
167
152
|
libc?: HostLibc | null;
|
|
168
153
|
}): string;
|
|
154
|
+
/**
|
|
155
|
+
* The darwin half of {@link buildNativeEnvPreamble}: put dyld's OWN default
|
|
156
|
+
* fallback list back, from inside the launcher.
|
|
157
|
+
*
|
|
158
|
+
* MEASURED on the macOS 15.7.9 x86_64 VM against the published 0.37.0: every
|
|
159
|
+
* `gjsify` command died in `dlopen(libsoup-3.0.0.dylib)` — before downloading
|
|
160
|
+
* anything, the CLI's own npm fetcher going through Soup — with a dyld search
|
|
161
|
+
* list that ended at `/usr/lib` and never held `/usr/local/lib`, where Homebrew
|
|
162
|
+
* puts every GTK library on Intel macOS. Exporting these four directories here
|
|
163
|
+
* makes the same command resolve, download and render (`OpenGL 4.1`).
|
|
164
|
+
*
|
|
165
|
+
* WHY HERE, and not the caller's job: SIP strips every `DYLD_*` variable when a
|
|
166
|
+
* PROTECTED binary is exec'd, and `/bin/sh` — this script — is protected. A
|
|
167
|
+
* value the USER exports is already gone by the time the preamble runs
|
|
168
|
+
* (measured: exporting the identical value around `gjsify showcase` changes
|
|
169
|
+
* nothing), while one the preamble exports ITSELF survives the following
|
|
170
|
+
* `exec gjs`, Homebrew's gjs being unprotected. This is the last hop that can.
|
|
171
|
+
*
|
|
172
|
+
* A CONSTANT is safe here only because it is dyld's DOCUMENTED DEFAULT: it
|
|
173
|
+
* cannot go stale the way baking `systemGiLibraryDirs()` in would (a launcher is
|
|
174
|
+
* routinely written before `brew install gtk4`), and it re-derives nothing —
|
|
175
|
+
* four literals from `dyld(1)`, shared with {@link dyldDefaultFallbackDirs}.
|
|
176
|
+
* An inherited value stays AHEAD of it; the export only ADDS BACK what dyld
|
|
177
|
+
* would have searched on its own.
|
|
178
|
+
*
|
|
179
|
+
* NOT the host's GI libdirs: `/opt/homebrew/lib` was never in dyld's default
|
|
180
|
+
* either, so an ARM Mac has that gap with or without gjsify —
|
|
181
|
+
* `status/open-todos.md`.
|
|
182
|
+
*/
|
|
183
|
+
export declare function dyldFallbackPreamble(platform: string): string;
|
|
169
184
|
/** POSIX `sh` single-quoting for a launcher's target path. */
|
|
170
185
|
export declare function shQuoteArg(s: string): string;
|
|
171
186
|
/**
|
package/lib/utils/bin-shim.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
// declaring `#!/usr/bin/env node` and one declaring `#!/usr/bin/env -S gjs -m`
|
|
26
26
|
// both work without the caller knowing which it is.
|
|
27
27
|
import { detectHostLibc, libraryPathVar, prebuildDirCandidates } from './detect-native-packages.js';
|
|
28
|
+
import { dyldDefaultFallbackDirs } from './system-gi.js';
|
|
28
29
|
// Matches `#!<prog> <args>` and `#!/usr/bin/env [-S] [K=V ...] <prog> <args>`.
|
|
29
30
|
// Verbatim from cmd-shim's `shebangExpr`.
|
|
30
31
|
const SHEBANG_RE = /^#!\s*(?:\/usr\/bin\/env\s+(?:-S\s+)?((?:[^ \t=]+=[^ \t=]+\s+)*))?([^ \t]+)(.*)$/;
|
|
@@ -340,31 +341,16 @@ function shQuote(s) {
|
|
|
340
341
|
* script chain: the self-relative `@loader_path` rpath that
|
|
341
342
|
* `check-prebuild-loader-path.mjs` enforces is what actually has to carry it.
|
|
342
343
|
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* ITSELF survives the following `exec gjs` (an unprotected Homebrew binary) and
|
|
354
|
-
* `Gtk.init()` then succeeds. What is missing is a shape worth having:
|
|
355
|
-
*
|
|
356
|
-
* - BAKING `systemGiLibraryDirs()` in at write time reintroduces precisely the
|
|
357
|
-
* snapshot this function exists to remove — a launcher is routinely written
|
|
358
|
-
* BEFORE `brew install gtk4`, and would bake nothing while saying nothing.
|
|
359
|
-
* - RE-DERIVING it in shell means a THIRD copy of a rule that already has two,
|
|
360
|
-
* pinned in agreement by `system-gi.spec.ts` — and shell can express only one
|
|
361
|
-
* of its three sources (the probed prefixes; `pkg-config` would be a spawn on
|
|
362
|
-
* every launch), so that copy would be knowingly partial, in the one language
|
|
363
|
-
* nothing here type-checks.
|
|
364
|
-
*
|
|
365
|
-
* So the gap is recorded in `status/open-todos.md` with the measurement attached
|
|
366
|
-
* instead of being closed by a mechanism that drifts. Closing it properly means
|
|
367
|
-
* teaching such a launcher to defer to the CLI, not to re-derive.
|
|
344
|
+
* IT DOES export dyld's own default fallback list on darwin — see
|
|
345
|
+
* {@link dyldFallbackPreamble}. That is a CONSTANT, so it carries none of the
|
|
346
|
+
* staleness that rules out the alternatives: BAKING `systemGiLibraryDirs()` in
|
|
347
|
+
* at write time reintroduces the snapshot this function exists to remove (a
|
|
348
|
+
* launcher is routinely written BEFORE `brew install gtk4`), and RE-DERIVING it
|
|
349
|
+
* in shell would be a knowingly partial THIRD copy of a two-copy rule, in the
|
|
350
|
+
* one language nothing here type-checks. The REMAINING gap — a host GI stack
|
|
351
|
+
* under a prefix dyld never defaulted to, `/opt/homebrew/lib` on Apple silicon —
|
|
352
|
+
* stays in `status/open-todos.md`, to be closed by teaching such a launcher to
|
|
353
|
+
* defer to the CLI rather than to re-derive.
|
|
368
354
|
*
|
|
369
355
|
* @param scanRoot Directory whose `node_modules` is globbed at launch time.
|
|
370
356
|
* @param bakedDirs Prebuild dirs found at write time; entries outside `scanRoot`
|
|
@@ -433,7 +419,46 @@ export function buildNativeEnvPreamble(scanRoot, bakedDirs = [], target = {}) {
|
|
|
433
419
|
` ${libVar}="$gjsify_np\${${libVar}:+${separator}$${libVar}}"\n` +
|
|
434
420
|
` export GI_TYPELIB_PATH ${libVar}\n` +
|
|
435
421
|
`fi\n` +
|
|
436
|
-
`unset gjsify_np gjsify_d gjsify_t\n`
|
|
422
|
+
`unset gjsify_np gjsify_d gjsify_t\n` +
|
|
423
|
+
dyldFallbackPreamble(platform));
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The darwin half of {@link buildNativeEnvPreamble}: put dyld's OWN default
|
|
427
|
+
* fallback list back, from inside the launcher.
|
|
428
|
+
*
|
|
429
|
+
* MEASURED on the macOS 15.7.9 x86_64 VM against the published 0.37.0: every
|
|
430
|
+
* `gjsify` command died in `dlopen(libsoup-3.0.0.dylib)` — before downloading
|
|
431
|
+
* anything, the CLI's own npm fetcher going through Soup — with a dyld search
|
|
432
|
+
* list that ended at `/usr/lib` and never held `/usr/local/lib`, where Homebrew
|
|
433
|
+
* puts every GTK library on Intel macOS. Exporting these four directories here
|
|
434
|
+
* makes the same command resolve, download and render (`OpenGL 4.1`).
|
|
435
|
+
*
|
|
436
|
+
* WHY HERE, and not the caller's job: SIP strips every `DYLD_*` variable when a
|
|
437
|
+
* PROTECTED binary is exec'd, and `/bin/sh` — this script — is protected. A
|
|
438
|
+
* value the USER exports is already gone by the time the preamble runs
|
|
439
|
+
* (measured: exporting the identical value around `gjsify showcase` changes
|
|
440
|
+
* nothing), while one the preamble exports ITSELF survives the following
|
|
441
|
+
* `exec gjs`, Homebrew's gjs being unprotected. This is the last hop that can.
|
|
442
|
+
*
|
|
443
|
+
* A CONSTANT is safe here only because it is dyld's DOCUMENTED DEFAULT: it
|
|
444
|
+
* cannot go stale the way baking `systemGiLibraryDirs()` in would (a launcher is
|
|
445
|
+
* routinely written before `brew install gtk4`), and it re-derives nothing —
|
|
446
|
+
* four literals from `dyld(1)`, shared with {@link dyldDefaultFallbackDirs}.
|
|
447
|
+
* An inherited value stays AHEAD of it; the export only ADDS BACK what dyld
|
|
448
|
+
* would have searched on its own.
|
|
449
|
+
*
|
|
450
|
+
* NOT the host's GI libdirs: `/opt/homebrew/lib` was never in dyld's default
|
|
451
|
+
* either, so an ARM Mac has that gap with or without gjsify —
|
|
452
|
+
* `status/open-todos.md`.
|
|
453
|
+
*/
|
|
454
|
+
export function dyldFallbackPreamble(platform) {
|
|
455
|
+
if (platform !== 'darwin')
|
|
456
|
+
return '';
|
|
457
|
+
// `HOME: '$HOME'` on purpose — the shim must expand it at LAUNCH, not bake
|
|
458
|
+
// the writing machine's home directory into every launcher.
|
|
459
|
+
const dirs = dyldDefaultFallbackDirs({ HOME: '$HOME' }).join(':');
|
|
460
|
+
return (`DYLD_FALLBACK_LIBRARY_PATH="\${DYLD_FALLBACK_LIBRARY_PATH:+$DYLD_FALLBACK_LIBRARY_PATH:}${dirs}"\n` +
|
|
461
|
+
`export DYLD_FALLBACK_LIBRARY_PATH\n`);
|
|
437
462
|
}
|
|
438
463
|
/** POSIX `sh` single-quoting for a launcher's target path. */
|
|
439
464
|
export function shQuoteArg(s) {
|
|
@@ -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[];
|