@gjsify/cli 0.36.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 +3 -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/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/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 +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 +33 -54
- 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 +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 -18
package/lib/utils/dir-link.d.ts
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Which kind of directory link this platform uses.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* kind" — the POSIX branch was unexercisable on Windows, and the win32 branch
|
|
10
|
-
* unexercisable anywhere else. The suite read as if it covered both and did
|
|
11
|
-
* not, which is precisely the shape the header above blames for the `'dir'`
|
|
12
|
-
* bug reaching a release.
|
|
4
|
+
* The POSIX case is spelled with a NAME rather than `undefined` so both branches
|
|
5
|
+
* stay explicitly requestable: `dirLinkTarget` takes this as a DEFAULTED
|
|
6
|
+
* parameter, and a defaulted parameter cannot receive `undefined` — passing it IS
|
|
7
|
+
* how you ask for the host's kind. Spelled `undefined`, each branch was
|
|
8
|
+
* unexercisable off its own platform while the suite read as if it covered both.
|
|
13
9
|
*/
|
|
14
10
|
export type DirLinkKind = 'junction' | 'symlink';
|
|
15
11
|
/** The link kind directory links use on this platform. */
|
|
16
12
|
export declare const DIR_LINK_KIND: DirLinkKind;
|
|
17
13
|
/**
|
|
18
|
-
* The link type a directory link must pass to `fs.symlink
|
|
19
|
-
*
|
|
20
|
-
* `undefined` on POSIX is deliberate: Node's default is correct there, and
|
|
21
|
-
* passing a type would be noise. Derived from {@link DIR_LINK_KIND} so the
|
|
14
|
+
* The link type a directory link must pass to `fs.symlink` — `undefined` on POSIX,
|
|
15
|
+
* where Node's default is correct. Derived from {@link DIR_LINK_KIND} so the
|
|
22
16
|
* platform decision has exactly one source.
|
|
23
17
|
*/
|
|
24
18
|
export declare const DIR_LINK_TYPE: 'junction' | undefined;
|
|
@@ -31,34 +25,26 @@ export declare const dirLinksAreJunctions: () => boolean;
|
|
|
31
25
|
* on POSIX so the tree stays movable (gotcha 2).
|
|
32
26
|
*
|
|
33
27
|
* `linkKind` is a PARAMETER, defaulting to the host's, for the same reason
|
|
34
|
-
* `detect-native-packages.ts` takes `platform`/`arch`: the Windows branch is
|
|
35
|
-
*
|
|
36
|
-
* tests. A branch nobody can exercise is how the `'dir'` bug survived to a
|
|
37
|
-
* release — and see {@link DirLinkKind} for why the POSIX side had to be named
|
|
38
|
-
* rather than spelled `undefined` before that promise actually held.
|
|
28
|
+
* `detect-native-packages.ts` takes `platform`/`arch`: the Windows branch is then
|
|
29
|
+
* unit-testable from a Linux host, the only place anyone runs the tests.
|
|
39
30
|
*
|
|
40
|
-
* BOTH paths must be in the SAME canonical space
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* path between them walks out of the real tree. `realpathSync` whichever side
|
|
48
|
-
* is not already canonical before calling this.
|
|
31
|
+
* BOTH paths must be in the SAME canonical space. The POSIX target is relative to
|
|
32
|
+
* the link's own directory, but the kernel resolves it from the link's REAL
|
|
33
|
+
* directory, so mixing the two yields a link pointing nowhere. macOS is where this
|
|
34
|
+
* bites: `os.tmpdir()` is `/var/folders/…` and `/var` is a symlink to
|
|
35
|
+
* `/private/var`, so a `linkPath` from `tmpdir()` and an `absTarget` that went
|
|
36
|
+
* through `realpathSync` disagree and the relative path between them walks out of
|
|
37
|
+
* the real tree. `realpathSync` whichever side is not already canonical.
|
|
49
38
|
*
|
|
50
|
-
* @param linkPath where the link itself will live
|
|
51
39
|
* @param absTarget the directory it should point at, as an ABSOLUTE path
|
|
52
40
|
* @param linkKind override the host's link kind (tests only)
|
|
53
41
|
*/
|
|
54
42
|
export declare function dirLinkTarget(linkPath: string, absTarget: string, linkKind?: DirLinkKind): string;
|
|
55
43
|
/**
|
|
56
44
|
* Link `linkPath` → `absTarget`, choosing symlink-vs-junction and the target
|
|
57
|
-
* spelling for the host.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* describes, and it would only misbehave on Windows, so it is rejected here on
|
|
61
|
-
* every platform rather than left to surface on the one host nobody tests on.
|
|
45
|
+
* spelling for the host. A relative `absTarget` is gotcha 1 and would only misbehave
|
|
46
|
+
* on Windows, so it is rejected on every platform rather than left to surface on the
|
|
47
|
+
* one host nobody tests on.
|
|
62
48
|
*/
|
|
63
49
|
export declare function linkDirSync(linkPath: string, absTarget: string): void;
|
|
64
50
|
/** Promise-returning twin of {@link linkDirSync}. */
|
|
@@ -66,21 +52,16 @@ export declare function linkDir(linkPath: string, absTarget: string): Promise<vo
|
|
|
66
52
|
/**
|
|
67
53
|
* Recreate an EXISTING link at a new path, preserving what it points at.
|
|
68
54
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* directory
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* Node will not do for you — see gotcha 1 above); anything else stays a plain
|
|
78
|
-
* file symlink, which Windows allows unprivileged.
|
|
79
|
-
*
|
|
80
|
-
* A dangling source link keeps the verbatim target: it points nowhere either
|
|
81
|
-
* way, and guessing a kind for it would turn a faithful copy into a fabrication.
|
|
55
|
+
* Cannot reuse {@link linkDirSync}: the source link's target is whatever it
|
|
56
|
+
* already is — frequently relative, deliberately so — while a junction demands an
|
|
57
|
+
* absolute one, so copying verbatim is right on POSIX and wrong on Windows the
|
|
58
|
+
* moment the target is a directory. The target's KIND therefore decides: a link to
|
|
59
|
+
* a directory becomes a junction with the target resolved against the source
|
|
60
|
+
* link's own directory (gotcha 1); anything else stays a plain file symlink, which
|
|
61
|
+
* Windows allows unprivileged. A dangling source link keeps the verbatim target —
|
|
62
|
+
* guessing a kind for it would turn a faithful copy into a fabrication.
|
|
82
63
|
*
|
|
83
|
-
* Primitive fs ops only
|
|
84
|
-
*
|
|
64
|
+
* Primitive fs ops only: this runs under the GJS facade as well as Node, hence no
|
|
65
|
+
* `fs.cp`/`opendir`.
|
|
85
66
|
*/
|
|
86
67
|
export declare function replicateLinkSync(srcLink: string, dstLink: string): void;
|
package/lib/utils/dir-link.js
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
// Creating a link to a DIRECTORY, portably.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// Could not resolve showcase "canvas2d-fireworks": EPERM: operation not
|
|
12
|
-
// permitted, symlink '…\<prepare-dir>' -> '…\pkg.tmp-…'
|
|
13
|
-
//
|
|
14
|
-
// on a plain `npx @gjsify/cli@latest showcase …`. The knowledge existed in the
|
|
15
|
-
// repo and the bug shipped anyway, which is the argument for a single callable
|
|
16
|
-
// definition rather than a convention.
|
|
3
|
+
// Windows cannot create a directory *symlink* without elevation or Developer Mode
|
|
4
|
+
// (`EPERM`), but any user can create an NTFS **junction**, which is why npm, yarn
|
|
5
|
+
// and pnpm all use junctions for directory links. Encoded as a local constant and
|
|
6
|
+
// shared nowhere, the next site got it wrong: `dlx-cache.ts` linked with `'dir'` and
|
|
7
|
+
// every non-elevated Windows user hit `EPERM: operation not permitted, symlink` on a
|
|
8
|
+
// plain `npx @gjsify/cli@latest showcase …`. Hence one callable definition rather
|
|
9
|
+
// than a convention.
|
|
17
10
|
//
|
|
18
11
|
// THE TWO GOTCHAS, both load-bearing:
|
|
19
12
|
//
|
|
@@ -25,19 +18,16 @@
|
|
|
25
18
|
// tree: a relative symlink survives the whole tree being moved or mounted
|
|
26
19
|
// elsewhere. So the target spelling is per-platform, not one string.
|
|
27
20
|
//
|
|
28
|
-
// Autodetection is
|
|
29
|
-
//
|
|
30
|
-
// failing case, not the safe default.
|
|
21
|
+
// Autodetection is no substitute on Windows: with the type argument omitted Node
|
|
22
|
+
// picks `'file'` or `'dir'`, never `'junction'`.
|
|
31
23
|
import { readlinkSync, statSync, symlinkSync } from 'node:fs';
|
|
32
24
|
import { symlink } from 'node:fs/promises';
|
|
33
25
|
import { dirname, relative, resolve } from 'node:path';
|
|
34
26
|
/** The link kind directory links use on this platform. */
|
|
35
27
|
export const DIR_LINK_KIND = process.platform === 'win32' ? 'junction' : 'symlink';
|
|
36
28
|
/**
|
|
37
|
-
* The link type a directory link must pass to `fs.symlink
|
|
38
|
-
*
|
|
39
|
-
* `undefined` on POSIX is deliberate: Node's default is correct there, and
|
|
40
|
-
* passing a type would be noise. Derived from {@link DIR_LINK_KIND} so the
|
|
29
|
+
* The link type a directory link must pass to `fs.symlink` — `undefined` on POSIX,
|
|
30
|
+
* where Node's default is correct. Derived from {@link DIR_LINK_KIND} so the
|
|
41
31
|
* platform decision has exactly one source.
|
|
42
32
|
*/
|
|
43
33
|
export const DIR_LINK_TYPE = DIR_LINK_KIND === 'junction' ? 'junction' : undefined;
|
|
@@ -50,23 +40,17 @@ export const dirLinksAreJunctions = () => DIR_LINK_KIND === 'junction';
|
|
|
50
40
|
* on POSIX so the tree stays movable (gotcha 2).
|
|
51
41
|
*
|
|
52
42
|
* `linkKind` is a PARAMETER, defaulting to the host's, for the same reason
|
|
53
|
-
* `detect-native-packages.ts` takes `platform`/`arch`: the Windows branch is
|
|
54
|
-
*
|
|
55
|
-
* tests. A branch nobody can exercise is how the `'dir'` bug survived to a
|
|
56
|
-
* release — and see {@link DirLinkKind} for why the POSIX side had to be named
|
|
57
|
-
* rather than spelled `undefined` before that promise actually held.
|
|
43
|
+
* `detect-native-packages.ts` takes `platform`/`arch`: the Windows branch is then
|
|
44
|
+
* unit-testable from a Linux host, the only place anyone runs the tests.
|
|
58
45
|
*
|
|
59
|
-
* BOTH paths must be in the SAME canonical space
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* path between them walks out of the real tree. `realpathSync` whichever side
|
|
67
|
-
* is not already canonical before calling this.
|
|
46
|
+
* BOTH paths must be in the SAME canonical space. The POSIX target is relative to
|
|
47
|
+
* the link's own directory, but the kernel resolves it from the link's REAL
|
|
48
|
+
* directory, so mixing the two yields a link pointing nowhere. macOS is where this
|
|
49
|
+
* bites: `os.tmpdir()` is `/var/folders/…` and `/var` is a symlink to
|
|
50
|
+
* `/private/var`, so a `linkPath` from `tmpdir()` and an `absTarget` that went
|
|
51
|
+
* through `realpathSync` disagree and the relative path between them walks out of
|
|
52
|
+
* the real tree. `realpathSync` whichever side is not already canonical.
|
|
68
53
|
*
|
|
69
|
-
* @param linkPath where the link itself will live
|
|
70
54
|
* @param absTarget the directory it should point at, as an ABSOLUTE path
|
|
71
55
|
* @param linkKind override the host's link kind (tests only)
|
|
72
56
|
*/
|
|
@@ -75,11 +59,9 @@ export function dirLinkTarget(linkPath, absTarget, linkKind = DIR_LINK_KIND) {
|
|
|
75
59
|
}
|
|
76
60
|
/**
|
|
77
61
|
* Link `linkPath` → `absTarget`, choosing symlink-vs-junction and the target
|
|
78
|
-
* spelling for the host.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* describes, and it would only misbehave on Windows, so it is rejected here on
|
|
82
|
-
* every platform rather than left to surface on the one host nobody tests on.
|
|
62
|
+
* spelling for the host. A relative `absTarget` is gotcha 1 and would only misbehave
|
|
63
|
+
* on Windows, so it is rejected on every platform rather than left to surface on the
|
|
64
|
+
* one host nobody tests on.
|
|
83
65
|
*/
|
|
84
66
|
export function linkDirSync(linkPath, absTarget) {
|
|
85
67
|
assertAbsolute(absTarget);
|
|
@@ -93,22 +75,17 @@ export async function linkDir(linkPath, absTarget) {
|
|
|
93
75
|
/**
|
|
94
76
|
* Recreate an EXISTING link at a new path, preserving what it points at.
|
|
95
77
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* directory
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* Node will not do for you — see gotcha 1 above); anything else stays a plain
|
|
105
|
-
* file symlink, which Windows allows unprivileged.
|
|
106
|
-
*
|
|
107
|
-
* A dangling source link keeps the verbatim target: it points nowhere either
|
|
108
|
-
* way, and guessing a kind for it would turn a faithful copy into a fabrication.
|
|
78
|
+
* Cannot reuse {@link linkDirSync}: the source link's target is whatever it
|
|
79
|
+
* already is — frequently relative, deliberately so — while a junction demands an
|
|
80
|
+
* absolute one, so copying verbatim is right on POSIX and wrong on Windows the
|
|
81
|
+
* moment the target is a directory. The target's KIND therefore decides: a link to
|
|
82
|
+
* a directory becomes a junction with the target resolved against the source
|
|
83
|
+
* link's own directory (gotcha 1); anything else stays a plain file symlink, which
|
|
84
|
+
* Windows allows unprivileged. A dangling source link keeps the verbatim target —
|
|
85
|
+
* guessing a kind for it would turn a faithful copy into a fabrication.
|
|
109
86
|
*
|
|
110
|
-
* Primitive fs ops only
|
|
111
|
-
*
|
|
87
|
+
* Primitive fs ops only: this runs under the GJS facade as well as Node, hence no
|
|
88
|
+
* `fs.cp`/`opendir`.
|
|
112
89
|
*/
|
|
113
90
|
export function replicateLinkSync(srcLink, dstLink) {
|
|
114
91
|
const rawTarget = readlinkSync(srcLink);
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
export interface ShowcaseInfo {
|
|
2
|
-
/** Short name,
|
|
2
|
+
/** Short name, the `gjsify showcase <name>` argument. */
|
|
3
3
|
name: string;
|
|
4
|
-
/** Full npm package name, e.g. "@gjsify/example-dom-three-postprocessing-pixel" */
|
|
4
|
+
/** Full npm package name, e.g. "@gjsify/example-dom-three-postprocessing-pixel". */
|
|
5
5
|
packageName: string;
|
|
6
|
-
/**
|
|
6
|
+
/** `"dom"` or `"node"`. */
|
|
7
7
|
category: string;
|
|
8
|
-
/** Description for the list view */
|
|
9
8
|
description: string;
|
|
10
9
|
}
|
|
11
10
|
/**
|
|
12
|
-
* Read the curated showcase list from `showcases.json
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Read the curated showcase list from `showcases.json`, sorted by category then
|
|
12
|
+
* name. A missing or unparseable manifest yields `[]`, letting `gjsify showcase`
|
|
13
|
+
* print its empty-state message instead of failing.
|
|
15
14
|
*/
|
|
16
15
|
export declare function discoverShowcases(): ShowcaseInfo[];
|
|
17
16
|
/** Find a single showcase by short name. */
|
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
// Static discovery of showcase packages from `showcases.json`.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// one. Static manifest decouples both: the CLI reads the manifest at runtime,
|
|
7
|
-
// `gjsify showcase <name>` delegates to `gjsify dlx <package>`.
|
|
3
|
+
// A manifest read at runtime, not CLI `dependencies`: a showcase must not have to
|
|
4
|
+
// be a direct CLI dependency, or every new one grows the CLI tarball and needs a
|
|
5
|
+
// CLI rebuild to publish. `gjsify showcase <name>` delegates to `gjsify dlx`.
|
|
8
6
|
import { existsSync, readFileSync } from 'node:fs';
|
|
9
7
|
import { dirname, join } from 'node:path';
|
|
10
8
|
import { fileURLToPath } from 'node:url';
|
|
11
9
|
function manifestPath() {
|
|
12
10
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
// `showcases.json`
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// level up (`../showcases.json`), not two — without this the gjs CLI
|
|
19
|
-
// finds no manifest and `showcase` wrongly reports "No showcases found".
|
|
11
|
+
// `showcases.json` sits at the CLI package root, but its depth below this
|
|
12
|
+
// module depends on how the CLI was loaded: two levels from
|
|
13
|
+
// `lib/utils/discover-showcases.js` (Node/Bun/Deno), one from the committed
|
|
14
|
+
// `dist/cli.gjs.mjs` bundle. Try both, or the gjs CLI finds no manifest and
|
|
15
|
+
// `showcase` wrongly reports that there are none.
|
|
20
16
|
const candidates = [join(here, '..', '..', 'showcases.json'), join(here, '..', 'showcases.json')];
|
|
21
17
|
return candidates.find((c) => existsSync(c)) ?? candidates[0];
|
|
22
18
|
}
|
|
23
19
|
/**
|
|
24
|
-
* Read the curated showcase list from `showcases.json
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* Read the curated showcase list from `showcases.json`, sorted by category then
|
|
21
|
+
* name. A missing or unparseable manifest yields `[]`, letting `gjsify showcase`
|
|
22
|
+
* print its empty-state message instead of failing.
|
|
27
23
|
*/
|
|
28
24
|
export function discoverShowcases() {
|
|
29
25
|
const path = manifestPath();
|
package/lib/utils/dlx-cache.d.ts
CHANGED
|
@@ -11,9 +11,8 @@ export declare function cacheDirFor(cacheKey: string): string;
|
|
|
11
11
|
/** A fresh prepare directory under the per-key cache, named timestamp-pid. */
|
|
12
12
|
export declare function makePrepareDir(cacheDir: string): string;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* has been removed, or has expired.
|
|
14
|
+
* The realpath of `<cacheDir>/pkg` when it is a symlink within its TTL; undefined
|
|
15
|
+
* when the link is missing, is not a symlink, has been removed, or has expired.
|
|
17
16
|
*/
|
|
18
17
|
export declare function getValidCachedPkg(cacheDir: string, maxAgeMinutes?: number): string | undefined;
|
|
19
18
|
/**
|
|
@@ -26,30 +25,25 @@ export declare function getValidCachedPkg(cacheDir: string, maxAgeMinutes?: numb
|
|
|
26
25
|
* Returns the realpath of the new live target. EBUSY/EEXIST indicates a race
|
|
27
26
|
* — a parallel process won, return its realpath.
|
|
28
27
|
*
|
|
29
|
-
* WINDOWS
|
|
28
|
+
* WINDOWS breaks both halves:
|
|
30
29
|
*
|
|
31
|
-
* •
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* permitted, symlink
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* lost", and the function returned the OLD target. That is not a crash but
|
|
42
|
-
* it is worse than one: the cache silently never refreshed. Windows
|
|
43
|
-
* therefore unlinks first and accepts the non-atomic window, which the
|
|
44
|
-
* existing race handling already covers.
|
|
30
|
+
* • A directory SYMLINK needs elevation or Developer Mode, so step 1 goes
|
|
31
|
+
* through {@link linkDirSync} for an NTFS junction — the choice npm and yarn
|
|
32
|
+
* make. A raw `symlinkSync(…, 'dir')` died with `EPERM: operation not
|
|
33
|
+
* permitted, symlink` on a plain `npx @gjsify/cli@latest showcase …`.
|
|
34
|
+
* • Step 2's rename-over-existing is a POSIX guarantee only: `MoveFileEx` will
|
|
35
|
+
* not replace an existing DIRECTORY, and a junction is a directory reparse
|
|
36
|
+
* point. On the second run the rename fails `EPERM`, the catch below reads it
|
|
37
|
+
* as "race lost", and the OLD target comes back — not a crash, but worse: the
|
|
38
|
+
* cache silently never refreshes. Windows therefore unlinks first and accepts
|
|
39
|
+
* a non-atomic window, which the race handling already covers.
|
|
45
40
|
*
|
|
46
|
-
* @param opts.junctions force the Windows link strategy (TESTS ONLY)
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* refreshes; what it cannot prove is `MoveFileEx`/junction syscall behaviour.
|
|
41
|
+
* @param opts.junctions force the Windows link strategy (TESTS ONLY), defaulting to
|
|
42
|
+
* the host's. A parameter for the same reason `dirLinkTarget` takes `linkKind`:
|
|
43
|
+
* nothing in CI runs on Windows, so an injected `true` is the ONLY way the
|
|
44
|
+
* unlink-then-rename ordering is ever EXECUTED. It proves the ORDERING and that a
|
|
45
|
+
* second swap really refreshes; it cannot prove `MoveFileEx`/junction syscall
|
|
46
|
+
* behaviour.
|
|
53
47
|
*/
|
|
54
48
|
export declare function symlinkSwap(cacheDir: string, prepareDir: string, opts?: {
|
|
55
49
|
junctions?: boolean;
|
|
@@ -57,23 +51,20 @@ export declare function symlinkSwap(cacheDir: string, prepareDir: string, opts?:
|
|
|
57
51
|
/**
|
|
58
52
|
* Remove `<cacheDir>/<oldPrepareDir>` siblings older than `maxAgeMinutes`.
|
|
59
53
|
*
|
|
60
|
-
* Every `gjsify dlx` miss creates a fresh prepare dir and swaps the `pkg` link
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* per miss makes that the largest thing gjsify writes to a user's cache.
|
|
54
|
+
* Every `gjsify dlx` miss creates a fresh prepare dir and swaps the `pkg` link onto
|
|
55
|
+
* it, leaving the previous one unreferenced — a full npm tree per miss, the largest
|
|
56
|
+
* thing gjsify writes to a user's cache.
|
|
64
57
|
*
|
|
65
58
|
* Three rules keep it from deleting something it should not:
|
|
66
59
|
*
|
|
67
60
|
* 1. **Only names `makePrepareDir` could have produced** (`<hex>-<hex>`). The
|
|
68
|
-
* cache dir also holds `pkg` and, briefly, `pkg.tmp-<ts>-<pid>` links from
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* 2. **Never the LIVE target**, whatever its age
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* race (a parallel dlx removing the same dir) or a read-only cache must not
|
|
76
|
-
* fail the command the user actually ran.
|
|
61
|
+
* cache dir also holds `pkg` and, briefly, `pkg.tmp-<ts>-<pid>` links from a
|
|
62
|
+
* concurrent swap; the name-shape guard is what makes "delete the rest" safe
|
|
63
|
+
* against anything a future version puts here.
|
|
64
|
+
* 2. **Never the LIVE target**, whatever its age — a still-current entry ages
|
|
65
|
+
* past any TTL precisely because it is used on every run.
|
|
66
|
+
* 3. **Never throws.** Best-effort housekeeping: a lost race against a parallel
|
|
67
|
+
* dlx, or a read-only cache, must not fail the command the user ran.
|
|
77
68
|
*/
|
|
78
69
|
export declare function cleanupStalePrepareDirs(cacheDir: string, maxAgeMinutes?: number): void;
|
|
79
70
|
/** Resolve absolute path to the installed package's directory inside cache. */
|
package/lib/utils/dlx-cache.js
CHANGED
|
@@ -50,9 +50,8 @@ export function makePrepareDir(cacheDir) {
|
|
|
50
50
|
return dir;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* has been removed, or has expired.
|
|
53
|
+
* The realpath of `<cacheDir>/pkg` when it is a symlink within its TTL; undefined
|
|
54
|
+
* when the link is missing, is not a symlink, has been removed, or has expired.
|
|
56
55
|
*/
|
|
57
56
|
export function getValidCachedPkg(cacheDir, maxAgeMinutes = DEFAULT_TTL_MIN) {
|
|
58
57
|
const linkPath = join(cacheDir, 'pkg');
|
|
@@ -87,52 +86,46 @@ export function getValidCachedPkg(cacheDir, maxAgeMinutes = DEFAULT_TTL_MIN) {
|
|
|
87
86
|
* Returns the realpath of the new live target. EBUSY/EEXIST indicates a race
|
|
88
87
|
* — a parallel process won, return its realpath.
|
|
89
88
|
*
|
|
90
|
-
* WINDOWS
|
|
89
|
+
* WINDOWS breaks both halves:
|
|
91
90
|
*
|
|
92
|
-
* •
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* permitted, symlink
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* lost", and the function returned the OLD target. That is not a crash but
|
|
103
|
-
* it is worse than one: the cache silently never refreshed. Windows
|
|
104
|
-
* therefore unlinks first and accepts the non-atomic window, which the
|
|
105
|
-
* existing race handling already covers.
|
|
91
|
+
* • A directory SYMLINK needs elevation or Developer Mode, so step 1 goes
|
|
92
|
+
* through {@link linkDirSync} for an NTFS junction — the choice npm and yarn
|
|
93
|
+
* make. A raw `symlinkSync(…, 'dir')` died with `EPERM: operation not
|
|
94
|
+
* permitted, symlink` on a plain `npx @gjsify/cli@latest showcase …`.
|
|
95
|
+
* • Step 2's rename-over-existing is a POSIX guarantee only: `MoveFileEx` will
|
|
96
|
+
* not replace an existing DIRECTORY, and a junction is a directory reparse
|
|
97
|
+
* point. On the second run the rename fails `EPERM`, the catch below reads it
|
|
98
|
+
* as "race lost", and the OLD target comes back — not a crash, but worse: the
|
|
99
|
+
* cache silently never refreshes. Windows therefore unlinks first and accepts
|
|
100
|
+
* a non-atomic window, which the race handling already covers.
|
|
106
101
|
*
|
|
107
|
-
* @param opts.junctions force the Windows link strategy (TESTS ONLY)
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* refreshes; what it cannot prove is `MoveFileEx`/junction syscall behaviour.
|
|
102
|
+
* @param opts.junctions force the Windows link strategy (TESTS ONLY), defaulting to
|
|
103
|
+
* the host's. A parameter for the same reason `dirLinkTarget` takes `linkKind`:
|
|
104
|
+
* nothing in CI runs on Windows, so an injected `true` is the ONLY way the
|
|
105
|
+
* unlink-then-rename ordering is ever EXECUTED. It proves the ORDERING and that a
|
|
106
|
+
* second swap really refreshes; it cannot prove `MoveFileEx`/junction syscall
|
|
107
|
+
* behaviour.
|
|
114
108
|
*/
|
|
115
109
|
export function symlinkSwap(cacheDir, prepareDir, opts = {}) {
|
|
116
110
|
const junctions = opts.junctions ?? dirLinksAreJunctions();
|
|
117
111
|
const linkPath = join(cacheDir, 'pkg');
|
|
118
112
|
const tmpName = `pkg.tmp-${Date.now().toString(16)}-${process.pid.toString(16)}`;
|
|
119
113
|
const tmpLink = join(cacheDir, tmpName);
|
|
120
|
-
// If
|
|
114
|
+
// If the tmp link cannot be created, give up (the error propagates).
|
|
121
115
|
linkDirSync(tmpLink, prepareDir);
|
|
122
116
|
try {
|
|
123
117
|
if (junctions) {
|
|
124
|
-
// Windows: no rename-over-a-directory. Removing first opens a window
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
118
|
+
// Windows: no rename-over-a-directory. Removing first opens a window in
|
|
119
|
+
// which `pkg` is absent; a concurrent reader treats that as a miss and
|
|
120
|
+
// prepares its own — correct, just wasteful. `force:true` keeps a
|
|
121
|
+
// first-ever swap (nothing to remove) from throwing.
|
|
128
122
|
//
|
|
129
123
|
// maxRetries/retryDelay for the same reason `install-backend-native.ts`
|
|
130
|
-
// wraps
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
// does not refresh. Free on POSIX, where the first attempt succeeds.
|
|
124
|
+
// wraps deletes in `rmWithRetry`: on Windows a delete fails EBUSY/EPERM
|
|
125
|
+
// while ANY process holds a handle (an antivirus scan, a concurrent
|
|
126
|
+
// reader that just realpath'd this link) and then stays PENDING, so
|
|
127
|
+
// without the retry a transient handle takes the "race lost" branch and
|
|
128
|
+
// the cache silently does not refresh. Free on POSIX.
|
|
136
129
|
rmSync(linkPath, { force: true, recursive: false, maxRetries: 10, retryDelay: 100 });
|
|
137
130
|
}
|
|
138
131
|
renameSync(tmpLink, linkPath);
|
|
@@ -140,21 +133,21 @@ export function symlinkSwap(cacheDir, prepareDir, opts = {}) {
|
|
|
140
133
|
catch (err) {
|
|
141
134
|
const code = err.code;
|
|
142
135
|
if (code === 'EBUSY' || code === 'EPERM' || code === 'EEXIST' || code === 'EACCES') {
|
|
143
|
-
// Race lost — clean up our tmp and use whoever won. force:true is
|
|
144
|
-
//
|
|
145
|
-
//
|
|
136
|
+
// Race lost — clean up our tmp and use whoever won. `force:true` is the
|
|
137
|
+
// non-throwing "remove if still there"; any other failure on our own
|
|
138
|
+
// pid-unique link is real and should surface.
|
|
146
139
|
rmSync(tmpLink, { force: true });
|
|
147
140
|
try {
|
|
148
141
|
return realpathSync(linkPath);
|
|
149
142
|
}
|
|
150
143
|
catch {
|
|
151
|
-
// Windows only,
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
144
|
+
// Windows only, because the junction path unlinks before it renames:
|
|
145
|
+
// "race lost" can also mean the winner is INSIDE that non-atomic
|
|
146
|
+
// window, so `pkg` momentarily does not exist and this would throw
|
|
147
|
+
// ENOENT out to a caller that reports it as `Could not resolve
|
|
148
|
+
// showcase "<name>"`. Our own prepareDir is a complete, correctly
|
|
149
|
+
// installed tree — a truthful answer for THIS run, and the next run
|
|
150
|
+
// reads whichever link won.
|
|
158
151
|
return realpathSync(prepareDir);
|
|
159
152
|
}
|
|
160
153
|
}
|
|
@@ -165,28 +158,25 @@ export function symlinkSwap(cacheDir, prepareDir, opts = {}) {
|
|
|
165
158
|
/**
|
|
166
159
|
* Remove `<cacheDir>/<oldPrepareDir>` siblings older than `maxAgeMinutes`.
|
|
167
160
|
*
|
|
168
|
-
* Every `gjsify dlx` miss creates a fresh prepare dir and swaps the `pkg` link
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* per miss makes that the largest thing gjsify writes to a user's cache.
|
|
161
|
+
* Every `gjsify dlx` miss creates a fresh prepare dir and swaps the `pkg` link onto
|
|
162
|
+
* it, leaving the previous one unreferenced — a full npm tree per miss, the largest
|
|
163
|
+
* thing gjsify writes to a user's cache.
|
|
172
164
|
*
|
|
173
165
|
* Three rules keep it from deleting something it should not:
|
|
174
166
|
*
|
|
175
167
|
* 1. **Only names `makePrepareDir` could have produced** (`<hex>-<hex>`). The
|
|
176
|
-
* cache dir also holds `pkg` and, briefly, `pkg.tmp-<ts>-<pid>` links from
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* 2. **Never the LIVE target**, whatever its age
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* race (a parallel dlx removing the same dir) or a read-only cache must not
|
|
184
|
-
* fail the command the user actually ran.
|
|
168
|
+
* cache dir also holds `pkg` and, briefly, `pkg.tmp-<ts>-<pid>` links from a
|
|
169
|
+
* concurrent swap; the name-shape guard is what makes "delete the rest" safe
|
|
170
|
+
* against anything a future version puts here.
|
|
171
|
+
* 2. **Never the LIVE target**, whatever its age — a still-current entry ages
|
|
172
|
+
* past any TTL precisely because it is used on every run.
|
|
173
|
+
* 3. **Never throws.** Best-effort housekeeping: a lost race against a parallel
|
|
174
|
+
* dlx, or a read-only cache, must not fail the command the user ran.
|
|
185
175
|
*/
|
|
186
176
|
export function cleanupStalePrepareDirs(cacheDir, maxAgeMinutes = DEFAULT_TTL_MIN) {
|
|
187
177
|
const cutoff = Date.now() - maxAgeMinutes * ONE_MINUTE_MS;
|
|
188
|
-
//
|
|
189
|
-
//
|
|
178
|
+
// An unreadable or absent `pkg` link means nothing is live yet, so every prepare
|
|
179
|
+
// dir is a candidate.
|
|
190
180
|
let live;
|
|
191
181
|
try {
|
|
192
182
|
live = realpathSync(join(cacheDir, 'pkg'));
|
|
@@ -213,8 +203,8 @@ export function cleanupStalePrepareDirs(cacheDir, maxAgeMinutes = DEFAULT_TTL_MI
|
|
|
213
203
|
rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
214
204
|
}
|
|
215
205
|
catch {
|
|
216
|
-
// Lost a race with another dlx, or the entry vanished between
|
|
217
|
-
//
|
|
206
|
+
// Lost a race with another dlx, or the entry vanished between readdir and
|
|
207
|
+
// stat. Both mean someone else handled it.
|
|
218
208
|
}
|
|
219
209
|
}
|
|
220
210
|
}
|
|
@@ -3,8 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Safe to call from any context, including an async callback where a bare
|
|
5
5
|
* `process.exit()` would be dropped under GJS. Does not return.
|
|
6
|
-
*
|
|
7
|
-
* `process.exitCode` is set FIRST so that a host which somehow reaches natural
|
|
8
|
-
* shutdown anyway still reports the right code.
|
|
9
6
|
*/
|
|
10
7
|
export declare function forceExit(code: number): never;
|