@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
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
// On-disk packument metadata cache for `gjsify install`.
|
|
2
2
|
//
|
|
3
3
|
// Sibling to the content-addressable tarball cache (install-tarball-cache.ts).
|
|
4
|
-
// Stores each package's
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// `304 Not Modified` responses
|
|
8
|
-
//
|
|
9
|
-
// (packuments are fetched gzip-compressed and decoded by the fetch layer; see
|
|
10
|
-
// `@gjsify/npm-registry` `fetchPackumentConditional`).
|
|
4
|
+
// Stores each package's packument plus the registry `ETag` it came with, so a
|
|
5
|
+
// re-resolve (lockfile miss — e.g. a dependency range changed) can send a
|
|
6
|
+
// conditional `If-None-Match` and turn the unchanged majority into empty
|
|
7
|
+
// `304 Not Modified` responses, on top of the ~4× gzip transfer for the changed
|
|
8
|
+
// minority (see `@gjsify/npm-registry` `fetchPackumentConditional`).
|
|
11
9
|
//
|
|
12
10
|
// Mirrors pnpm's metadata cache + npm's make-fetch-happen HTTP-cache layer.
|
|
13
11
|
//
|
|
@@ -15,32 +13,26 @@
|
|
|
15
13
|
//
|
|
16
14
|
// $XDG_CACHE_HOME/gjsify/metadata/v3/<shard>/<shape>,<encoded-registry>,<encoded-name>.json
|
|
17
15
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// as the tarball cache's hex sharding). `v2` is a layout version.
|
|
16
|
+
// Keyed by (SHAPE, registry, name) — NOT name alone — so switching the registry
|
|
17
|
+
// for a scope can never serve a packument from the wrong source on a coincidental
|
|
18
|
+
// ETag match. `<shard>` is a 2-hex FNV-1a digest of the key, directory fan-out so
|
|
19
|
+
// the leaf dir never grows unbounded (as with the tarball cache's hex sharding).
|
|
23
20
|
//
|
|
24
|
-
// WHY THE SHAPE IS PART OF THE KEY
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// pacote prefixes its own cache key with `full:`/`corgi:` for precisely this
|
|
33
|
-
// reason. The layout bump (rather than just a longer filename) makes the v1
|
|
34
|
-
// entries — written before the distinction existed and therefore of unknown
|
|
35
|
-
// shape — unreachable instead of ambiguous.
|
|
21
|
+
// WHY THE SHAPE IS PART OF THE KEY: one packument URL serves TWO documents — the
|
|
22
|
+
// abbreviated "corgi" install document and the full one — and only the full one
|
|
23
|
+
// carries a version's `libc` (`@gjsify/npm-registry` `FetchOptions.fullMetadata`).
|
|
24
|
+
// Keyed per (registry, name) alone, a read is answered by whichever shape was cached
|
|
25
|
+
// first, so a musl-only package comes back `libc`-less, is judged compatible on a
|
|
26
|
+
// glibc host, and installs — a wrong answer indistinguishable from a cache hit, with
|
|
27
|
+
// no failure until something dlopens the binary. pacote prefixes its own key with
|
|
28
|
+
// `full:`/`corgi:` for the same reason.
|
|
36
29
|
//
|
|
37
30
|
// Disabled with `GJSIFY_PACKUMENT_CACHE=0` (or `false`). Honours
|
|
38
31
|
// `XDG_CACHE_HOME` like the tarball + dlx caches.
|
|
39
32
|
import { join } from 'node:path';
|
|
40
33
|
import { atomicWrite, gjsifyCacheRoot, readCacheFile } from './install-cache-fs.js';
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// document SHAPE part of the key. Either change alone makes older entries
|
|
34
|
+
// v2 moved the separator off the Win32-reserved `|` (see `packumentCacheKey`); v3
|
|
35
|
+
// made the document SHAPE part of the key. Either change alone makes older entries
|
|
44
36
|
// unreachable rather than merely stale, so a new root retires them instead of
|
|
45
37
|
// leaving ambiguous orphans under the old one.
|
|
46
38
|
const CACHE_LAYOUT_VERSION = 'v3';
|
|
@@ -80,25 +72,21 @@ function encodePart(part) {
|
|
|
80
72
|
* The cache key for a (shape, registry, name) triple — exported so the filename
|
|
81
73
|
* rule below is a PROPERTY a test can assert, not a claim in a comment.
|
|
82
74
|
*
|
|
83
|
-
* THE SEPARATOR IS NOT COSMETIC.
|
|
84
|
-
* `
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* cache
|
|
88
|
-
*
|
|
89
|
-
* it was silently DEAD, and the `If-None-Match` → `304` revalidation that is the
|
|
90
|
-
* entire reason this file exists never engaged once.
|
|
75
|
+
* THE SEPARATOR IS NOT COSMETIC. `|` is a Win32 RESERVED filename character, so
|
|
76
|
+
* while it was the separator every write there failed with `EINVAL` and nothing
|
|
77
|
+
* reported it: `atomicWrite` is best-effort on purpose (a read-only cache volume
|
|
78
|
+
* must not break an install) and `readCacheFile` treats an unreadable path as a
|
|
79
|
+
* MISS. The cache was not broken loudly on Windows, it was silently DEAD, and the
|
|
80
|
+
* `If-None-Match` → `304` revalidation this file exists for never engaged once.
|
|
91
81
|
*
|
|
92
|
-
* `,` is legal on Win32 AND escaped by `encodeURIComponent`, which
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* and it LEADS so that listing a shard groups by shape.
|
|
82
|
+
* `,` is legal on Win32 AND escaped by `encodeURIComponent`, which keeps a flat
|
|
83
|
+
* three-part name injective: an encoded part cannot contain the separator, so
|
|
84
|
+
* exactly one triple maps to any filename. The shape needs no encoding — one of two
|
|
85
|
+
* literals — and LEADS so listing a shard groups by shape.
|
|
97
86
|
*
|
|
98
|
-
* The shard key and the filename
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* of a function whose other half looked correct.
|
|
87
|
+
* The shard key and the filename must derive from the SAME encoded parts; hashing
|
|
88
|
+
* the raw values while naming with the encoded ones is how a reserved character
|
|
89
|
+
* survived review of a function whose other half looked correct.
|
|
102
90
|
*/
|
|
103
91
|
export function packumentCacheKey(registry, name, shape) {
|
|
104
92
|
return `${shape},${encodePart(registry)},${encodePart(name)}`;
|
|
@@ -111,15 +99,15 @@ function pathFor(registry, name, shape) {
|
|
|
111
99
|
return join(cacheRoot(), shardFor(key), `${key}.json`);
|
|
112
100
|
}
|
|
113
101
|
/**
|
|
114
|
-
* Read a cached packument + its ETag for `(registry, name)` in the given
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* re-fetches).
|
|
102
|
+
* Read a cached packument + its ETag for `(registry, name)` in the given document
|
|
103
|
+
* `shape`. Returns `null` on a miss, when the cache is disabled, or when the entry
|
|
104
|
+
* is unreadable/corrupt (treated as a miss — the caller re-fetches).
|
|
118
105
|
*
|
|
119
|
-
* `shape`
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
106
|
+
* The `shape` must be the one the body was fetched under: the two documents are
|
|
107
|
+
* different bodies with independently-versioned ETags behind ONE URL, so reading
|
|
108
|
+
* across shapes returns a `libc`-less body that looks exactly like a hit. The
|
|
109
|
+
* install backend resolves with `'full'`; the `'corgi'` default serves callers that
|
|
110
|
+
* do not escalate.
|
|
123
111
|
*/
|
|
124
112
|
export function getCachedPackument(registry, name, shape = 'corgi') {
|
|
125
113
|
const path = pathFor(registry, name, shape);
|
|
@@ -143,12 +131,11 @@ export function getCachedPackument(registry, name, shape = 'corgi') {
|
|
|
143
131
|
/**
|
|
144
132
|
* Persist a packument + ETag for `(registry, name)` under its document `shape`.
|
|
145
133
|
* Writes to a `<path>.tmp.<pid>` sibling then atomically renames so a concurrent
|
|
146
|
-
* reader never sees a partial write. No-op when the cache is disabled, the ETag
|
|
147
|
-
*
|
|
148
|
-
*
|
|
134
|
+
* reader never sees a partial write. No-op when the cache is disabled, the ETag is
|
|
135
|
+
* empty, or the write fails (a read-only / out-of-disk cache volume must not break
|
|
136
|
+
* the install).
|
|
149
137
|
*
|
|
150
|
-
* `shape`
|
|
151
|
-
* shape valid; it must match the `accept` header the body was fetched with.
|
|
138
|
+
* `shape` must match the `accept` header the body was fetched with.
|
|
152
139
|
*/
|
|
153
140
|
export function putCachedPackument(registry, name, etag, packument, shape = 'corgi') {
|
|
154
141
|
if (!etag)
|
|
@@ -7,19 +7,12 @@ export interface ProgressEvent {
|
|
|
7
7
|
name?: string;
|
|
8
8
|
}
|
|
9
9
|
export interface ProgressReporter {
|
|
10
|
-
/** Called for each step within a phase. */
|
|
11
10
|
update(event: ProgressEvent): void;
|
|
12
|
-
/** Called once when a phase begins; lets the reporter print a header line. */
|
|
13
11
|
beginPhase(phase: ProgressPhase, total: number): void;
|
|
14
|
-
/**
|
|
12
|
+
/** Clears the inline bar and replaces it with a completion line. */
|
|
15
13
|
endPhase(phase: ProgressPhase): void;
|
|
16
14
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Make a progress reporter that auto-targets `process.stderr`.
|
|
19
|
-
* - `enabled=false` → silent.
|
|
20
|
-
* - stderr is not a TTY → fall back to one line per phase (begin + end).
|
|
21
|
-
* - stderr is a TTY → live single-line progress bar (\r-updated, 30fps).
|
|
22
|
-
*/
|
|
15
|
+
/** Pick the reporter for `process.stderr` (or `opts.stream`): silent, plain, or TTY bar. */
|
|
23
16
|
export declare function makeProgressReporter(opts?: {
|
|
24
17
|
enabled?: boolean;
|
|
25
18
|
stream?: NodeJS.WriteStream;
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
// Progress reporter for `gjsify install` + `gjsify dlx`.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// resolving [████████░░░░░░░░] 234/500 typescript@^6.0.3
|
|
7
|
-
//
|
|
8
|
-
// Falls back to a chatty mode (one line per completed step) when stderr is
|
|
9
|
-
// not a TTY, e.g. when piped into a log file or running on CI.
|
|
10
|
-
//
|
|
11
|
-
// Caller plumbing — the install backend calls `report({phase, current, total,
|
|
12
|
-
// name})` after every resolve/download/extract step; the renderer rate-limits
|
|
13
|
-
// to ~30fps so a tight inner loop doesn't drown the terminal.
|
|
1
|
+
// Progress reporter for `gjsify install` + `gjsify dlx`. The backend reports every
|
|
2
|
+
// resolve/download/extract/link step; on TTY stderr this renders one `\r`-overwritten
|
|
3
|
+
// bar, rate-limited to ~30fps so a tight inner loop cannot drown the terminal, and
|
|
4
|
+
// on a non-TTY it degrades to the heartbeat mode described below.
|
|
14
5
|
const PHASE_LABEL = {
|
|
15
6
|
resolve: 'resolving',
|
|
16
7
|
download: 'downloading',
|
|
@@ -22,12 +13,7 @@ const NOOP_REPORTER = {
|
|
|
22
13
|
beginPhase() { },
|
|
23
14
|
endPhase() { },
|
|
24
15
|
};
|
|
25
|
-
/**
|
|
26
|
-
* Make a progress reporter that auto-targets `process.stderr`.
|
|
27
|
-
* - `enabled=false` → silent.
|
|
28
|
-
* - stderr is not a TTY → fall back to one line per phase (begin + end).
|
|
29
|
-
* - stderr is a TTY → live single-line progress bar (\r-updated, 30fps).
|
|
30
|
-
*/
|
|
16
|
+
/** Pick the reporter for `process.stderr` (or `opts.stream`): silent, plain, or TTY bar. */
|
|
31
17
|
export function makeProgressReporter(opts = {}) {
|
|
32
18
|
if (opts.enabled === false)
|
|
33
19
|
return NOOP_REPORTER;
|
|
@@ -38,7 +24,6 @@ export function makeProgressReporter(opts = {}) {
|
|
|
38
24
|
return NOOP_REPORTER;
|
|
39
25
|
return isTty ? makeTtyReporter(stream) : makePlainReporter(stream);
|
|
40
26
|
}
|
|
41
|
-
// ──── TTY reporter — single-line, \r-updated, 30fps rate-limit ────────────
|
|
42
27
|
function makeTtyReporter(stream) {
|
|
43
28
|
let lastRender = 0;
|
|
44
29
|
let lastLine = '';
|
|
@@ -49,7 +34,7 @@ function makeTtyReporter(stream) {
|
|
|
49
34
|
return;
|
|
50
35
|
lastRender = now;
|
|
51
36
|
lastLine = line;
|
|
52
|
-
//
|
|
37
|
+
// `\x1b[2K` = erase entire line.
|
|
53
38
|
stream.write('\r\x1b[2K' + line);
|
|
54
39
|
}
|
|
55
40
|
function clearLine() {
|
|
@@ -58,33 +43,26 @@ function makeTtyReporter(stream) {
|
|
|
58
43
|
}
|
|
59
44
|
return {
|
|
60
45
|
beginPhase(phase, total) {
|
|
61
|
-
//
|
|
46
|
+
// Forced, so the first frame is not eaten by the rate limit.
|
|
62
47
|
render(formatBar(phase, 0, total, undefined, stream.columns ?? 80), true);
|
|
63
48
|
},
|
|
64
49
|
update(ev) {
|
|
65
50
|
render(formatBar(ev.phase, ev.current, ev.total, ev.name, stream.columns ?? 80));
|
|
66
51
|
},
|
|
67
52
|
endPhase(phase) {
|
|
68
|
-
//
|
|
69
|
-
// a definitive "done" after the spinner stops.
|
|
53
|
+
// A definitive "done" line, or the stopped bar reads as a stall.
|
|
70
54
|
const label = PHASE_LABEL[phase];
|
|
71
55
|
clearLine();
|
|
72
56
|
stream.write(`gjsify install: ${label} done\n`);
|
|
73
57
|
},
|
|
74
58
|
};
|
|
75
59
|
}
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// minutes" symptom of a slow install. So we emit a low-frequency heartbeat:
|
|
83
|
-
// at most one line per HEARTBEAT_MS, AND only after at least a few packages of
|
|
84
|
-
// real progress, so a fast warm install (most nodes skipped) stays quiet while
|
|
85
|
-
// a genuinely long phase reports `… N/M (X%)` every few seconds. This mirrors
|
|
86
|
-
// what npm (`--loglevel http`) and pnpm log on non-TTY: progress you can watch
|
|
87
|
-
// without it being one line per file.
|
|
60
|
+
// Non-TTY heartbeat. Per-package lines would flood a log file, but emitting
|
|
61
|
+
// NOTHING between `beginPhase` and `endPhase` makes a long download/extract look
|
|
62
|
+
// frozen — the exact "no further output for 25+ minutes" symptom of a slow install.
|
|
63
|
+
// So: at most one line per HEARTBEAT_MS, and only once `current` has actually
|
|
64
|
+
// advanced, which keeps a fast warm install (most nodes skipped) silent while a
|
|
65
|
+
// genuinely long phase reports `… N/M (X%)` every few seconds.
|
|
88
66
|
const HEARTBEAT_MS = 5_000;
|
|
89
67
|
function makePlainReporter(stream) {
|
|
90
68
|
let lastHeartbeat = 0;
|
|
@@ -111,12 +89,10 @@ function makePlainReporter(stream) {
|
|
|
111
89
|
},
|
|
112
90
|
};
|
|
113
91
|
}
|
|
114
|
-
// ──── Bar formatter ────────────────────────────────────────────────────────
|
|
115
92
|
function formatBar(phase, current, total, name, columns) {
|
|
116
93
|
const label = PHASE_LABEL[phase];
|
|
117
94
|
const ratio = total > 0 ? Math.min(1, current / total) : 0;
|
|
118
95
|
const pct = `${current}/${total}`;
|
|
119
|
-
// Reserve: "gjsify install: " (16) + label (max ~12) + " [" (2) + "] " (2) + pct (~8) + " " + name
|
|
120
96
|
const prefix = `gjsify install: ${label} `;
|
|
121
97
|
const suffix = ` ${pct}` + (name ? ` ${name}` : '');
|
|
122
98
|
const barWidth = Math.max(8, Math.min(40, columns - prefix.length - suffix.length - 4));
|
|
@@ -124,7 +100,6 @@ function formatBar(phase, current, total, name, columns) {
|
|
|
124
100
|
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
125
101
|
let line = `${prefix}[${bar}]${suffix}`;
|
|
126
102
|
if (line.length > columns) {
|
|
127
|
-
// Truncate name if the terminal is narrow.
|
|
128
103
|
const overflow = line.length - columns + 1;
|
|
129
104
|
const truncatedName = name && name.length > overflow ? name.slice(0, name.length - overflow - 1) + '…' : '';
|
|
130
105
|
line = `${prefix}[${bar}] ${pct}${truncatedName ? ' ' + truncatedName : ''}`;
|
|
@@ -1,31 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Read a cached tarball by SRI integrity
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* is left untouched so we don't trip a follow-up writer's atomic rename.
|
|
2
|
+
* Read a cached tarball by SRI integrity — raw bytes on a HIT, `null` otherwise. A
|
|
3
|
+
* read failure (e.g. a partial write from an interrupted run) is a MISS, and the
|
|
4
|
+
* file is left untouched so a follow-up writer's atomic rename is not tripped.
|
|
6
5
|
*/
|
|
7
6
|
export declare function getCachedTarball(integrity: string | undefined): Uint8Array | null;
|
|
8
7
|
/**
|
|
9
|
-
* Persist a tarball to the cache
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - the write fails (e.g. cache root is read-only) — silently degrade
|
|
15
|
-
* so a cache-volume issue doesn't break the install itself.
|
|
8
|
+
* Persist a tarball to the cache, via a `<path>.tmp.<pid>` sibling and an atomic
|
|
9
|
+
* rename so concurrent installs never observe a half-written entry. No-op when
|
|
10
|
+
* `integrity` is missing/malformed, when the destination already exists
|
|
11
|
+
* (content-addressed, so immutable), or when the write fails — a read-only cache
|
|
12
|
+
* volume must not break the install itself.
|
|
16
13
|
*/
|
|
17
14
|
export declare function putCachedTarball(integrity: string | undefined, bytes: Uint8Array): void;
|
|
18
|
-
/**
|
|
19
|
-
* Best-effort cache stats for diagnostics. Returns `null` when the cache
|
|
20
|
-
* root doesn't exist yet (first run).
|
|
21
|
-
*/
|
|
15
|
+
/** The cache root, for diagnostics. Not probed — the directory may not exist yet. */
|
|
22
16
|
export declare function cacheRootForLogging(): string;
|
|
23
17
|
export declare function isCacheHit(integrity: string | undefined): boolean;
|
|
24
18
|
/**
|
|
25
|
-
* Read a tarball from npm's cacache content store by SRI integrity
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* tarball loudly
|
|
19
|
+
* Read a tarball from npm's cacache content store by SRI integrity — raw `.tgz`
|
|
20
|
+
* bytes on a HIT, `null` on a MISS, disabled interop or read failure. Like
|
|
21
|
+
* {@link getCachedTarball} it trusts the content-addressed path rather than
|
|
22
|
+
* re-hashing: cacache verified the bytes on write, and the extractor surfaces a
|
|
23
|
+
* genuinely corrupt tarball loudly.
|
|
30
24
|
*/
|
|
31
25
|
export declare function getForeignCachedTarball(integrity: string | undefined): Uint8Array | null;
|
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
// Content-addressable tarball cache for `gjsify install`.
|
|
2
2
|
//
|
|
3
|
-
// Mirrors pnpm / yarn-berry's store layout: each tarball is stored once on
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// we read the bytes off the local filesystem instead.
|
|
3
|
+
// Mirrors pnpm / yarn-berry's store layout: each tarball is stored once on disk
|
|
4
|
+
// keyed by its SRI integrity hash (`sha512-…`), so a node whose integrity is
|
|
5
|
+
// already cached skips `fetchTarball` and reads the bytes off local disk.
|
|
7
6
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// the second `gjsify install` on the same repo skips every tarball fetch
|
|
13
|
-
// and just goes straight to extract → drops well below 1 minute in practice.
|
|
7
|
+
// Measured on this monorepo (200+ workspaces, 600+ transitive deps): a cold
|
|
8
|
+
// install spends ~20 minutes at 80% CPU, most of it re-downloading tarballs the
|
|
9
|
+
// previous run had already fetched. With the cache the second `gjsify install`
|
|
10
|
+
// goes straight to extract and drops well under a minute.
|
|
14
11
|
//
|
|
15
|
-
// Layout
|
|
16
|
-
// `~/.cache/gjsify/store` if we eventually share a store across projects):
|
|
12
|
+
// Layout, following the pnpm pattern:
|
|
17
13
|
//
|
|
18
14
|
// $XDG_CACHE_HOME/gjsify/tarballs/v1/<hex-prefix-2>/<full-hex>.tgz
|
|
19
15
|
//
|
|
20
|
-
// The 2-byte prefix
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// manifest sidecar) without invalidating the world.
|
|
16
|
+
// The 2-byte prefix shards the directory so the leaf never gets pathologically
|
|
17
|
+
// large (git's loose-object pattern). `v1` is a layout version, so the file shape
|
|
18
|
+
// can change without invalidating the world.
|
|
24
19
|
//
|
|
25
|
-
// SRI integrity
|
|
20
|
+
// SRI integrity → cache key, by hex-encoding the base64 digest:
|
|
26
21
|
//
|
|
27
22
|
// "sha512-AbCd…==" → ("sha512", "ab/cdefg….tgz")
|
|
28
23
|
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// fall through to a no-op cache and download every time.
|
|
24
|
+
// Two integrities with the same hex bytes share an entry, the invariant pnpm relies
|
|
25
|
+
// on too. Tarballs without an integrity hash (older registries) fall through to a
|
|
26
|
+
// no-op cache and download every time.
|
|
33
27
|
import { Buffer } from 'node:buffer';
|
|
34
28
|
import { existsSync, statSync } from 'node:fs';
|
|
35
29
|
import { homedir } from 'node:os';
|
|
@@ -41,10 +35,9 @@ function cacheRoot() {
|
|
|
41
35
|
return gjsifyCacheRoot('tarballs', CACHE_LAYOUT_VERSION);
|
|
42
36
|
}
|
|
43
37
|
/**
|
|
44
|
-
* Parse an SRI integrity string (`sha512-AbCd…=`) into its algorithm + hex
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* derivations below — the base64→hex decode used to be inlined in each.
|
|
38
|
+
* Parse an SRI integrity string (`sha512-AbCd…=`) into its algorithm + hex digest,
|
|
39
|
+
* or `null` for a missing / malformed value (the caller falls back to a fresh
|
|
40
|
+
* download). Shared by the gjsify-store and npm-cacache path derivations below.
|
|
48
41
|
*/
|
|
49
42
|
function parseSri(integrity) {
|
|
50
43
|
if (!integrity)
|
|
@@ -75,10 +68,9 @@ function pathFor(integrity) {
|
|
|
75
68
|
return join(cacheRoot(), sri.algorithm, shard, `${sri.hex}.tgz`);
|
|
76
69
|
}
|
|
77
70
|
/**
|
|
78
|
-
* Read a cached tarball by SRI integrity
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* is left untouched so we don't trip a follow-up writer's atomic rename.
|
|
71
|
+
* Read a cached tarball by SRI integrity — raw bytes on a HIT, `null` otherwise. A
|
|
72
|
+
* read failure (e.g. a partial write from an interrupted run) is a MISS, and the
|
|
73
|
+
* file is left untouched so a follow-up writer's atomic rename is not tripped.
|
|
82
74
|
*/
|
|
83
75
|
export function getCachedTarball(integrity) {
|
|
84
76
|
const path = pathFor(integrity);
|
|
@@ -87,13 +79,11 @@ export function getCachedTarball(integrity) {
|
|
|
87
79
|
return readCacheFile(path);
|
|
88
80
|
}
|
|
89
81
|
/**
|
|
90
|
-
* Persist a tarball to the cache
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* - the write fails (e.g. cache root is read-only) — silently degrade
|
|
96
|
-
* so a cache-volume issue doesn't break the install itself.
|
|
82
|
+
* Persist a tarball to the cache, via a `<path>.tmp.<pid>` sibling and an atomic
|
|
83
|
+
* rename so concurrent installs never observe a half-written entry. No-op when
|
|
84
|
+
* `integrity` is missing/malformed, when the destination already exists
|
|
85
|
+
* (content-addressed, so immutable), or when the write fails — a read-only cache
|
|
86
|
+
* volume must not break the install itself.
|
|
97
87
|
*/
|
|
98
88
|
export function putCachedTarball(integrity, bytes) {
|
|
99
89
|
const path = pathFor(integrity);
|
|
@@ -104,10 +94,7 @@ export function putCachedTarball(integrity, bytes) {
|
|
|
104
94
|
return;
|
|
105
95
|
atomicWrite(path, bytes);
|
|
106
96
|
}
|
|
107
|
-
/**
|
|
108
|
-
* Best-effort cache stats for diagnostics. Returns `null` when the cache
|
|
109
|
-
* root doesn't exist yet (first run).
|
|
110
|
-
*/
|
|
97
|
+
/** The cache root, for diagnostics. Not probed — the directory may not exist yet. */
|
|
111
98
|
export function cacheRootForLogging() {
|
|
112
99
|
return cacheRoot();
|
|
113
100
|
}
|
|
@@ -123,26 +110,21 @@ export function isCacheHit(integrity) {
|
|
|
123
110
|
return false;
|
|
124
111
|
}
|
|
125
112
|
}
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
113
|
// Foreign cache interop — read npm's cacache content store.
|
|
128
114
|
//
|
|
129
|
-
// npm
|
|
130
|
-
//
|
|
131
|
-
//
|
|
115
|
+
// npm keys its content-addressable store on the SAME SRI integrity as ours and
|
|
116
|
+
// holds the raw `.tgz` bytes verbatim, so anyone who has run `npm install` already
|
|
117
|
+
// has these tarballs on disk and reading them turns a cold `gjsify install` into a
|
|
118
|
+
// near-warm one with no network round-trip. Layout (cacache `content-v2`):
|
|
132
119
|
//
|
|
133
120
|
// <npm-cache>/_cacache/content-v2/<algo>/<hex[0:2]>/<hex[2:4]>/<hex[4:]>
|
|
134
121
|
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
// `.tgz` extension. So anyone who has run `npm install` before already has
|
|
138
|
-
// these tarballs on disk; reading them turns a cold `gjsify install` into a
|
|
139
|
-
// near-warm one without a single network round-trip.
|
|
122
|
+
// Identical derivation to our own `pathFor`, with a different root and no `.tgz`
|
|
123
|
+
// extension.
|
|
140
124
|
//
|
|
141
|
-
// pnpm/yarn/bun stores are deliberately NOT read
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
// tarball-by-integrity lookup the way npm's cacache does.
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
125
|
+
// pnpm/yarn/bun stores are deliberately NOT read: pnpm and bun store *unpacked*
|
|
126
|
+
// per-file content (no tarball to hand the extractor) and yarn berry stores zips
|
|
127
|
+
// under its own non-SRI key, so none map to a tarball-by-integrity lookup.
|
|
146
128
|
/**
|
|
147
129
|
* Resolve npm's `content-v2` directory, honouring `GJSIFY_NPM_CACHE`
|
|
148
130
|
* (full path to a `_cacache` dir; `0`/`false`/empty disables the interop),
|
|
@@ -175,11 +157,11 @@ function npmCachePathFor(integrity) {
|
|
|
175
157
|
return join(contentDir, sri.algorithm, sri.hex.slice(0, 2), sri.hex.slice(2, 4), sri.hex.slice(4));
|
|
176
158
|
}
|
|
177
159
|
/**
|
|
178
|
-
* Read a tarball from npm's cacache content store by SRI integrity
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* tarball loudly
|
|
160
|
+
* Read a tarball from npm's cacache content store by SRI integrity — raw `.tgz`
|
|
161
|
+
* bytes on a HIT, `null` on a MISS, disabled interop or read failure. Like
|
|
162
|
+
* {@link getCachedTarball} it trusts the content-addressed path rather than
|
|
163
|
+
* re-hashing: cacache verified the bytes on write, and the extractor surfaces a
|
|
164
|
+
* genuinely corrupt tarball loudly.
|
|
183
165
|
*/
|
|
184
166
|
export function getForeignCachedTarball(integrity) {
|
|
185
167
|
const path = npmCachePathFor(integrity);
|
|
@@ -1,30 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The literal (non-glob) base directories the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - `src/index.ts` → `<cwd>/src`
|
|
7
|
-
* - `src/**` + `/*.ts` → `<cwd>/src`
|
|
8
|
-
* - `src` → `<cwd>/src`
|
|
2
|
+
* The literal (non-glob) base directories the input source files live in, derived from the
|
|
3
|
+
* bundler `input` patterns: a file entry maps to its containing dir, a glob is reduced to the
|
|
4
|
+
* literal prefix before the first wildcard (`src/index.ts`, `src` and `src/**` + `/*.ts` all
|
|
5
|
+
* give `<cwd>/src`).
|
|
9
6
|
*
|
|
10
|
-
* The project root
|
|
11
|
-
*
|
|
12
|
-
* not the `src/`-subdir footgun this guard targets.
|
|
7
|
+
* The project root is intentionally excluded — a flat library keeping its source at the root and
|
|
8
|
+
* emitting there is an explicit layout, not the `src/`-subdir footgun this guard targets.
|
|
13
9
|
*/
|
|
14
10
|
export declare function inputSourceDirs(input: unknown, cwd: string): string[];
|
|
15
11
|
/**
|
|
16
|
-
* `true` when
|
|
17
|
-
*
|
|
18
|
-
* equals, or is nested under, any input source directory.
|
|
19
|
-
*
|
|
20
|
-
* isOutdirInsideSource('src', ['<cwd>/src']) → true (leak)
|
|
21
|
-
* isOutdirInsideSource('src/nest', ['<cwd>/src']) → true (leak)
|
|
22
|
-
* isOutdirInsideSource('dist/esm', ['<cwd>/src']) → false (safe)
|
|
23
|
-
* isOutdirInsideSource('lib', ['<cwd>/src']) → false (safe)
|
|
12
|
+
* `true` when library output written to `outdir` would land inside (or on top of) the input
|
|
13
|
+
* source tree — i.e. `outdir` equals, or is nested under, any input source directory.
|
|
24
14
|
*/
|
|
25
15
|
export declare function isOutdirInsideSource(outdir: string, sourceDirs: readonly string[], cwd: string): boolean;
|
|
26
|
-
/**
|
|
27
|
-
* The error thrown when a `--library` build would emit into its own source
|
|
28
|
-
* tree. Names both remedies so the fix is actionable at the point of failure.
|
|
29
|
-
*/
|
|
16
|
+
/** The error thrown when a `--library` build would emit into its own source tree. */
|
|
30
17
|
export declare function libraryOutputLeakError(outdir: string): Error;
|