@gjsify/cli 0.51.1 → 0.52.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 +505 -490
- package/lib/cli-app.js +3 -1
- package/lib/commands/affected-classify.d.ts +7 -1
- package/lib/commands/affected-classify.js +66 -9
- package/lib/commands/affected-script-refs.d.ts +20 -0
- package/lib/commands/affected-script-refs.js +218 -0
- package/lib/commands/affected.js +24 -2
- package/lib/commands/flatpak/diff.js +8 -2
- package/lib/commands/flatpak/init.js +4 -1
- package/lib/commands/flatpak/sources.js +4 -3
- package/lib/commands/flatpak/sync-flathub.d.ts +2 -0
- package/lib/commands/flatpak/sync-flathub.js +149 -7
- package/lib/commands/flatpak/utils.d.ts +10 -0
- package/lib/commands/flatpak/utils.js +10 -0
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/install.js +76 -0
- package/lib/commands/link.d.ts +13 -0
- package/lib/commands/link.js +243 -0
- package/lib/commands/onboard.js +18 -2
- package/lib/commands/publish.d.ts +34 -0
- package/lib/commands/publish.js +153 -8
- package/lib/commands/ship.js +19 -1
- package/lib/types/config-data.d.ts +1 -1
- package/lib/utils/check-system-deps.d.ts +1 -17
- package/lib/utils/check-system-deps.js +10 -68
- package/lib/utils/dev-link.d.ts +222 -0
- package/lib/utils/dev-link.js +632 -0
- package/lib/utils/hang-watchdog.d.ts +95 -0
- package/lib/utils/hang-watchdog.js +194 -0
- package/lib/utils/install-backend-native.d.ts +14 -0
- package/lib/utils/install-backend-native.js +91 -13
- package/lib/utils/install-extraneous.d.ts +90 -0
- package/lib/utils/install-extraneous.js +120 -0
- package/lib/utils/install-tarball-cache.d.ts +37 -9
- package/lib/utils/install-tarball-cache.js +59 -14
- package/lib/utils/normalize-bundler-options.js +11 -0
- package/lib/utils/publish-pins.d.ts +38 -0
- package/lib/utils/publish-pins.js +47 -0
- package/lib/utils/publish-readback.d.ts +103 -17
- package/lib/utils/publish-readback.js +326 -47
- package/lib/utils/run-gjs.js +82 -6
- package/lib/utils/ship/icns.d.ts +14 -0
- package/lib/utils/ship/icns.js +83 -0
- package/lib/utils/ship/ico.d.ts +25 -0
- package/lib/utils/ship/ico.js +120 -0
- package/lib/utils/ship/icons.d.ts +191 -0
- package/lib/utils/ship/icons.js +398 -0
- package/lib/utils/ship/launcher.js +16 -1
- package/lib/utils/ship/layout.d.ts +32 -0
- package/lib/utils/ship/layout.js +112 -38
- package/lib/utils/ship/msi.d.ts +1 -1
- package/lib/utils/ship/msi.js +30 -5
- package/lib/utils/ship/pe-launcher.d.ts +18 -0
- package/lib/utils/ship/pe-launcher.js +125 -4
- package/lib/utils/ship/plan.d.ts +1 -34
- package/lib/utils/ship/plan.js +7 -56
- package/lib/utils/ship/plist.d.ts +20 -10
- package/lib/utils/ship/plist.js +29 -12
- package/lib/utils/ship/settings.js +3 -2
- package/package.json +20 -20
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
// 3. Resolve commit SHA via `git rev-list -n 1 <version>`
|
|
12
12
|
// 4. Clone (or update) the flathub tracking-repo into $XDG_CACHE_HOME
|
|
13
13
|
// 5. Surgically edit the manifest: modules[].sources[].{tag,commit}
|
|
14
|
-
// plus x-checker-data block (inject if missing)
|
|
14
|
+
// plus x-checker-data block (inject if missing), and carry over the
|
|
15
|
+
// offline source list (see `offline sources` below)
|
|
15
16
|
// 6. Create a branch, commit the changes
|
|
16
17
|
// 7. (unless --no-pr) push + open a PR via `gh pr create`
|
|
17
18
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -21,7 +22,7 @@ import { execFile } from 'node:child_process';
|
|
|
21
22
|
import { describeExit, spawnToCompletion } from '../../utils/spawn.js';
|
|
22
23
|
import { promisify } from 'node:util';
|
|
23
24
|
import { Config } from '../../config.js';
|
|
24
|
-
import { readPackageJson } from './utils.js';
|
|
25
|
+
import { FLATPAK_SOURCES_FILE, readPackageJson } from './utils.js';
|
|
25
26
|
const execFileAsync = promisify(execFile);
|
|
26
27
|
export const flatpakSyncFlathubCommand = {
|
|
27
28
|
command: 'sync-flathub',
|
|
@@ -51,6 +52,12 @@ export const flatpakSyncFlathubCommand = {
|
|
|
51
52
|
.option('branch', {
|
|
52
53
|
description: 'Branch name in the flathub-repo. Default: `update-to-<version>`.',
|
|
53
54
|
type: 'string',
|
|
55
|
+
})
|
|
56
|
+
.option('sources-file', {
|
|
57
|
+
description: 'Generated offline tarball list to carry into the Flathub repo and name in the manifest. ' +
|
|
58
|
+
`Default: \`${FLATPAK_SOURCES_FILE}\`. Skipped when the tag does not carry it.`,
|
|
59
|
+
type: 'string',
|
|
60
|
+
default: FLATPAK_SOURCES_FILE,
|
|
54
61
|
})
|
|
55
62
|
.option('source-index', {
|
|
56
63
|
description: 'Index into modules[0].sources[] to update (when manifest has multiple sources). Default: first `type: git` source.',
|
|
@@ -105,13 +112,33 @@ export const flatpakSyncFlathubCommand = {
|
|
|
105
112
|
if (!existsSync(manifestPath)) {
|
|
106
113
|
throw new Error(`[gjsify flatpak sync-flathub] Flathub manifest not found at ${manifestPath} — wrong appId / wrong flathub-repo?`);
|
|
107
114
|
}
|
|
115
|
+
// Read AT THE TAG, not from the working tree: the manifest is about to pin
|
|
116
|
+
// that tag, and a list taken from an edited checkout would describe a tree
|
|
117
|
+
// nobody can get. Absent is the ordinary case for an app that installs
|
|
118
|
+
// online or vendors its dependencies, so it is a skip and not an error.
|
|
119
|
+
const sourcesFile = args.sourcesFile ?? FLATPAK_SOURCES_FILE;
|
|
120
|
+
const offline = await readSourceListAtTag(cwd, version, sourcesFile, args.verbose);
|
|
121
|
+
let offlineCopied = false;
|
|
122
|
+
if (offline !== null) {
|
|
123
|
+
const dest = join(cloneDir, sourcesFile);
|
|
124
|
+
offlineCopied = !existsSync(dest) || readFileSync(dest, 'utf-8') !== offline;
|
|
125
|
+
if (offlineCopied) {
|
|
126
|
+
writeFileSync(dest, offline, 'utf-8');
|
|
127
|
+
console.log(`[gjsify flatpak sync-flathub] ${sourcesFile} copied from ${version}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
108
130
|
const original = readFileSync(manifestPath, 'utf-8');
|
|
109
131
|
const updated = editManifest(original, {
|
|
110
132
|
tag: version,
|
|
111
133
|
commit: commitSha,
|
|
112
134
|
sourceIndex: args.sourceIndex,
|
|
135
|
+
sourcesFile: offline !== null ? sourcesFile : undefined,
|
|
113
136
|
});
|
|
114
|
-
|
|
137
|
+
// NOT `updated === original` alone: the tarball list changes whenever a
|
|
138
|
+
// dependency does, so a release can need a PR with the pin unmoved. That
|
|
139
|
+
// test used to be the only one, and it would have returned "nothing to do"
|
|
140
|
+
// over a Flathub repo carrying a stale list.
|
|
141
|
+
if (updated === original && !offlineCopied) {
|
|
115
142
|
console.log(`[gjsify flatpak sync-flathub] manifest already at ${version} — nothing to do.`);
|
|
116
143
|
return;
|
|
117
144
|
}
|
|
@@ -130,6 +157,88 @@ export const flatpakSyncFlathubCommand = {
|
|
|
130
157
|
},
|
|
131
158
|
};
|
|
132
159
|
// ─── Internal helpers ────────────────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* The generated source list as of `tag`, or null when the tag does not carry one.
|
|
162
|
+
*
|
|
163
|
+
* `git show <tag>:<path>` rather than reading the working tree, so the file that
|
|
164
|
+
* travels to Flathub is the one the pinned commit actually contains.
|
|
165
|
+
*
|
|
166
|
+
* WHAT COMES BACK IS CHECKED, not just whether the command succeeded. A missing
|
|
167
|
+
* path makes real `git show` exit non-zero, so the obvious version of this
|
|
168
|
+
* function looks complete — and it treats an EMPTY answer as "the file exists
|
|
169
|
+
* and is empty", which then writes an empty file into the Flathub repo and
|
|
170
|
+
* names it in the manifest. That is strictly worse than skipping: the build
|
|
171
|
+
* still cannot install, and now the manifest says it can. Caught by the
|
|
172
|
+
* sync-flathub E2E, whose git stub answers every unknown subcommand with exit 0
|
|
173
|
+
* and no output.
|
|
174
|
+
*
|
|
175
|
+
* A blank answer is a skip. Content that is not a non-empty array of sources is
|
|
176
|
+
* an ERROR, because this file has exactly one producer (`gjsify flatpak
|
|
177
|
+
* sources`) and anything else means something upstream is broken.
|
|
178
|
+
*/
|
|
179
|
+
async function readSourceListAtTag(cwd, tag, path, verbose) {
|
|
180
|
+
let raw;
|
|
181
|
+
try {
|
|
182
|
+
const { stdout } = await execFileAsync('git', ['show', `${tag}:${path}`], { cwd, maxBuffer: 64 * 1024 * 1024 });
|
|
183
|
+
raw = stdout;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
await assertGitMeantAbsent(cwd, tag, path, error);
|
|
187
|
+
if (verbose)
|
|
188
|
+
console.log(`[gjsify flatpak sync-flathub] ${path} not present at ${tag} — skipping`);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
if (raw.trim() === '') {
|
|
192
|
+
if (verbose)
|
|
193
|
+
console.log(`[gjsify flatpak sync-flathub] ${path} is empty at ${tag} — skipping`);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
let parsed;
|
|
197
|
+
try {
|
|
198
|
+
parsed = JSON.parse(raw);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
throw new Error(`[gjsify flatpak sync-flathub] ${path} at ${tag} is not JSON: ` +
|
|
202
|
+
(error instanceof Error ? error.message : String(error)));
|
|
203
|
+
}
|
|
204
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
205
|
+
throw new Error(`[gjsify flatpak sync-flathub] ${path} at ${tag} is not a non-empty array of flatpak sources`);
|
|
206
|
+
}
|
|
207
|
+
return raw;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Throw unless a failed `git show <tag>:<path>` really did mean "that tag does
|
|
211
|
+
* not carry that file".
|
|
212
|
+
*
|
|
213
|
+
* MEASURED, and it is why the failure cannot be read on its own: path absent,
|
|
214
|
+
* unknown tag and not-a-git-repository are ALL exit 128, and git translates the
|
|
215
|
+
* message (`Pfad … existiert nicht in …` on a German host), so neither the
|
|
216
|
+
* status nor the text separates them. Taken for absence, a tag nobody fetched
|
|
217
|
+
* moves the pin and leaves the Flathub repo's old list in place — the failure
|
|
218
|
+
* this file exists to prevent, now silent. `--commit <sha>` is what puts it in
|
|
219
|
+
* reach: it is the one path on which nothing else resolves the tag.
|
|
220
|
+
*
|
|
221
|
+
* The tag is therefore asked about separately, and a SHA is believed rather
|
|
222
|
+
* than an exit code — the same lesson as the read above, since a `git` that
|
|
223
|
+
* answers 0 with nothing must not pass for a tag that exists.
|
|
224
|
+
*/
|
|
225
|
+
async function assertGitMeantAbsent(cwd, tag, path, error) {
|
|
226
|
+
const code = error?.code;
|
|
227
|
+
if (typeof code !== 'number') {
|
|
228
|
+
// Not git's verdict at all: ENOENT is no `git` on PATH, and
|
|
229
|
+
// ERR_CHILD_PROCESS_STDIO_MAXBUFFER a list past the cap set above.
|
|
230
|
+
throw new Error(`[gjsify flatpak sync-flathub] could not run \`git show ${tag}:${path}\` — ` +
|
|
231
|
+
(error instanceof Error ? error.message : String(error)));
|
|
232
|
+
}
|
|
233
|
+
// `--quiet` exits 1 rather than printing for a name it cannot resolve.
|
|
234
|
+
const sha = await execFileAsync('git', ['rev-parse', '--verify', '--quiet', `${tag}^{commit}`], { cwd })
|
|
235
|
+
.then(({ stdout }) => stdout.trim())
|
|
236
|
+
.catch(() => '');
|
|
237
|
+
if (!sha) {
|
|
238
|
+
throw new Error(`[gjsify flatpak sync-flathub] ${tag} does not resolve in ${cwd}, so whether it carries ${path} is unanswered. ` +
|
|
239
|
+
'Run `git fetch --tags`, pass --version <an existing tag>, or run from the app checkout.');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
133
242
|
async function resolveLatestTag(cwd, verbose) {
|
|
134
243
|
try {
|
|
135
244
|
const { stdout } = await execFileAsync('git', ['describe', '--tags', '--abbrev=0'], { cwd });
|
|
@@ -258,14 +367,17 @@ export function editManifest(original, args) {
|
|
|
258
367
|
if (sources.length === 0) {
|
|
259
368
|
throw new Error('[gjsify flatpak sync-flathub] modules[0] has no sources');
|
|
260
369
|
}
|
|
261
|
-
|
|
370
|
+
const isObject = (s) => typeof s === 'object';
|
|
371
|
+
let idx = args.sourceIndex ?? sources.findIndex((s) => isObject(s) && s.type === 'git');
|
|
262
372
|
if (idx < 0 || idx >= sources.length) {
|
|
263
373
|
throw new Error(`[gjsify flatpak sync-flathub] no git source found in modules[0].sources (use --source-index <n>)`);
|
|
264
374
|
}
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
267
|
-
|
|
375
|
+
const entry = sources[idx];
|
|
376
|
+
if (!isObject(entry) || entry.type !== 'git') {
|
|
377
|
+
const what = isObject(entry) ? `type is "${entry.type}"` : `is the file reference "${entry}"`;
|
|
378
|
+
throw new Error(`[gjsify flatpak sync-flathub] modules[0].sources[${idx}] ${what}, expected a git source`);
|
|
268
379
|
}
|
|
380
|
+
const source = entry;
|
|
269
381
|
source.tag = args.tag;
|
|
270
382
|
source.commit = args.commit;
|
|
271
383
|
if (!source['x-checker-data']) {
|
|
@@ -275,12 +387,42 @@ export function editManifest(original, args) {
|
|
|
275
387
|
'version-scheme': 'semantic',
|
|
276
388
|
};
|
|
277
389
|
}
|
|
390
|
+
// THE OFFLINE TARBALL LIST, which is the half of a Flathub bump that is easy
|
|
391
|
+
// to forget because nothing points at it. Flathub builds with the network
|
|
392
|
+
// unshared, so an app whose install reads a generated source list cannot
|
|
393
|
+
// build from the git checkout alone; the list has to live in the Flathub repo
|
|
394
|
+
// and be named here. Repointing tag and commit alone produces a manifest that
|
|
395
|
+
// looks correct and cannot build, and the error it eventually gives is
|
|
396
|
+
// whatever the app's build system says when its install fails.
|
|
397
|
+
//
|
|
398
|
+
// Measured: Learn6502 0.8.0 was the first release after its vendored
|
|
399
|
+
// dependency cache was dropped, and its Flathub build died on exactly this.
|
|
400
|
+
if (args.sourcesFile && !namesFile(sources, args.sourcesFile)) {
|
|
401
|
+
sources.push(args.sourcesFile);
|
|
402
|
+
}
|
|
278
403
|
// Detect the original indent (2 vs 4 spaces) by inspecting the second
|
|
279
404
|
// line — Flathub manifests are all 2-space in practice, but some
|
|
280
405
|
// older ones might be 4. Preserve original convention.
|
|
281
406
|
const indent = detectIndent(original);
|
|
282
407
|
return JSON.stringify(manifest, null, indent) + (original.endsWith('\n') ? '\n' : '');
|
|
283
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Is `file` already among the module's bare-string sources?
|
|
411
|
+
*
|
|
412
|
+
* Compared with `./` stripped, because flatpak-builder resolves a string source
|
|
413
|
+
* against the manifest's own directory: `./gjsify-sources.json` and
|
|
414
|
+
* `gjsify-sources.json` are one file. Compared as written, a manifest holding
|
|
415
|
+
* the dotted spelling grows a second reference and the same tarballs are
|
|
416
|
+
* spliced in twice — the duplicate `dest`/`dest-filename` pairs `gjsify flatpak
|
|
417
|
+
* sources` dedupes its own output to avoid.
|
|
418
|
+
*/
|
|
419
|
+
function namesFile(sources, file) {
|
|
420
|
+
const want = stripDotSlash(file);
|
|
421
|
+
return sources.some((entry) => typeof entry === 'string' && stripDotSlash(entry) === want);
|
|
422
|
+
}
|
|
423
|
+
function stripDotSlash(ref) {
|
|
424
|
+
return ref.replace(/^\.\//, '');
|
|
425
|
+
}
|
|
284
426
|
function detectIndent(json) {
|
|
285
427
|
const match = json.match(/^\{\n( +)/);
|
|
286
428
|
if (match)
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated offline tarball list, by the name both halves have to agree on.
|
|
3
|
+
*
|
|
4
|
+
* `gjsify flatpak sources` WRITES it; `gjsify flatpak sync-flathub` carries it
|
|
5
|
+
* into the Flathub repo and names it in the manifest. Each spelled the default
|
|
6
|
+
* itself, so changing one would have left the other reading a file nobody
|
|
7
|
+
* writes — and that shows up as a Flathub build which cannot install, four
|
|
8
|
+
* steps from its cause.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FLATPAK_SOURCES_FILE = "gjsify-sources.json";
|
|
1
11
|
/** Read package.json from a directory. Throws a helpful error if missing/invalid. */
|
|
2
12
|
export declare function readPackageJson(dir: string): Record<string, unknown>;
|
|
3
13
|
/** True if a name string looks like a reverse-DNS Flatpak app id. */
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
// header. What is left here is what only these subcommands ask.
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
7
|
import { resolve } from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* The generated offline tarball list, by the name both halves have to agree on.
|
|
10
|
+
*
|
|
11
|
+
* `gjsify flatpak sources` WRITES it; `gjsify flatpak sync-flathub` carries it
|
|
12
|
+
* into the Flathub repo and names it in the manifest. Each spelled the default
|
|
13
|
+
* itself, so changing one would have left the other reading a file nobody
|
|
14
|
+
* writes — and that shows up as a Flathub build which cannot install, four
|
|
15
|
+
* steps from its cause.
|
|
16
|
+
*/
|
|
17
|
+
export const FLATPAK_SOURCES_FILE = 'gjsify-sources.json';
|
|
8
18
|
/** Read package.json from a directory. Throws a helpful error if missing/invalid. */
|
|
9
19
|
export function readPackageJson(dir) {
|
|
10
20
|
const path = resolve(dir, 'package.json');
|
package/lib/commands/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export { flatpakCommand } from './flatpak/index.js';
|
|
|
15
15
|
export * from './ship.js';
|
|
16
16
|
export * from './dlx.js';
|
|
17
17
|
export * from './install.js';
|
|
18
|
+
export * from './link.js';
|
|
18
19
|
export * from './foreach.js';
|
|
19
20
|
export * from './workspace.js';
|
|
20
21
|
export * from './pack.js';
|
package/lib/commands/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export { flatpakCommand } from './flatpak/index.js';
|
|
|
15
15
|
export * from './ship.js';
|
|
16
16
|
export * from './dlx.js';
|
|
17
17
|
export * from './install.js';
|
|
18
|
+
export * from './link.js';
|
|
18
19
|
export * from './foreach.js';
|
|
19
20
|
export * from './workspace.js';
|
|
20
21
|
export * from './pack.js';
|
package/lib/commands/install.js
CHANGED
|
@@ -35,6 +35,7 @@ import { binDirOnPath, BUNDLER_ENGINE_PACKAGE, defaultGlobalLayout, hasBundlerEn
|
|
|
35
35
|
import { resolveHostPlatform } from '../utils/platform-check.js';
|
|
36
36
|
import { pruneAfterInstall } from '../utils/prune-prefix.js';
|
|
37
37
|
import { addDependencyEntry, defaultRangeFromVersion, parseSpec, projectSpecsFromPackageJson, readPackageJson, writePackageJson, } from '../utils/pkg-json-edit.js';
|
|
38
|
+
import { applyDevLinks, assertNoDevLinkUnderImmutable, devLinkRoot, prepareDevLinks, } from '../utils/dev-link.js';
|
|
38
39
|
/**
|
|
39
40
|
* Link type for the workspace↔workspace directory links.
|
|
40
41
|
*
|
|
@@ -157,6 +158,12 @@ export const installCommand = {
|
|
|
157
158
|
'(--immutable forbids rewriting the lockfile). Drop one.');
|
|
158
159
|
return process.exit(1);
|
|
159
160
|
}
|
|
161
|
+
// FAIL CLOSED before anything is resolved or written: an active `gjsify
|
|
162
|
+
// link` override would make a local development tree an input to the one
|
|
163
|
+
// install shape whose whole promise is "exactly what is committed" — CI,
|
|
164
|
+
// the Flatpak build, a release. Throws rather than exits so `index.ts`
|
|
165
|
+
// prints the message that names the override file (utils/dev-link.ts).
|
|
166
|
+
assertNoDevLinkUnderImmutable(process.cwd());
|
|
160
167
|
}
|
|
161
168
|
if (args.global) {
|
|
162
169
|
if (!args.packages || args.packages.length === 0) {
|
|
@@ -426,6 +433,61 @@ function depKindFromArgs(args) {
|
|
|
426
433
|
return 'optionalDependencies';
|
|
427
434
|
return 'dependencies';
|
|
428
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Wire the `gjsify link` development override for this prefix, and SAY SO.
|
|
438
|
+
*
|
|
439
|
+
* Called before the fetch phase for the same reason `wireWorkspaceSymlinks` is:
|
|
440
|
+
* these links point at local source and depend on no download, so writing them
|
|
441
|
+
* first means an install that dies mid-way still leaves a resolvable tree. Called
|
|
442
|
+
* again afterwards as a near no-op safety net.
|
|
443
|
+
*
|
|
444
|
+
* The announcement is not decoration. A linked tree changes what a build compiles
|
|
445
|
+
* without changing any file the consumer commits, so the one place it can be seen
|
|
446
|
+
* is the output of the command that re-applies it — this is exactly what a bare
|
|
447
|
+
* `npm link` does not do, and why the next `npm install` silently undoes it.
|
|
448
|
+
*
|
|
449
|
+
* Returns `null` when no override is present; throws when one names a checkout
|
|
450
|
+
* that is gone, is not a gjsify workspace, or is not BUILT (utils/dev-link.ts).
|
|
451
|
+
*
|
|
452
|
+
* `devLinkRoot` and not `prefix` verbatim: `assertNoDevLinkUnderImmutable` already
|
|
453
|
+
* walks up to the workspace root to decide whether an override is in play, and two
|
|
454
|
+
* answers to "which override governs this install" is one answer too many. Nothing
|
|
455
|
+
* rescued the difference either — the bare `install` path happens to delegate to
|
|
456
|
+
* the workspace root before reaching here, `install <pkg>` does not.
|
|
457
|
+
*/
|
|
458
|
+
function activateDevLinks(prefix, announce) {
|
|
459
|
+
const active = prepareDevLinks(devLinkRoot(prefix));
|
|
460
|
+
if (!active)
|
|
461
|
+
return null;
|
|
462
|
+
const written = applyDevLinks(active.links);
|
|
463
|
+
if (!announce)
|
|
464
|
+
return active;
|
|
465
|
+
console.log(`gjsify install: development link ACTIVE (${active.overridePath}) — ${active.links.length} ` +
|
|
466
|
+
`package(s) come from ${active.checkout}, linked rather than fetched ` +
|
|
467
|
+
`(${written.length} (re)written, ${active.links.length - written.length} already current):`);
|
|
468
|
+
const names = active.links.map((l) => l.name);
|
|
469
|
+
const shown = names.slice(0, 12);
|
|
470
|
+
console.log(` ${shown.join(', ')}${names.length > shown.length ? `, +${names.length - shown.length} more` : ''}`);
|
|
471
|
+
console.log(' `gjsify unlink` restores the registry copies; `gjsify install --immutable` refuses while it exists.');
|
|
472
|
+
return active;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Re-apply the dev links after the install, and SAY SO when it changed anything.
|
|
476
|
+
*
|
|
477
|
+
* `applyDevLinks` returns what it had to (re)write, and both post-install call
|
|
478
|
+
* sites used to throw that away — so an installer that clobbered a link and got it
|
|
479
|
+
* put back looked exactly like one that never touched it. The safety net is
|
|
480
|
+
* supposed to be a no-op; the run where it is not is the one worth a line.
|
|
481
|
+
*/
|
|
482
|
+
function reportRewiredDevLinks(active) {
|
|
483
|
+
if (!active)
|
|
484
|
+
return;
|
|
485
|
+
const rewired = applyDevLinks(active.links);
|
|
486
|
+
if (rewired.length === 0)
|
|
487
|
+
return;
|
|
488
|
+
console.log(`gjsify install: re-applied ${rewired.length} development link(s) the install had replaced: ` +
|
|
489
|
+
`${rewired.map((l) => l.name).join(', ')}`);
|
|
490
|
+
}
|
|
429
491
|
async function projectInstallNative(args, signal) {
|
|
430
492
|
const cwd = process.cwd();
|
|
431
493
|
const pkgPath = join(cwd, 'package.json');
|
|
@@ -497,6 +559,7 @@ async function projectInstallNative(args, signal) {
|
|
|
497
559
|
// re-acquires the same lock re-entrantly.
|
|
498
560
|
const lock = await acquireInstallLock(cwd, { signal });
|
|
499
561
|
try {
|
|
562
|
+
const devLinks = activateDevLinks(cwd, true);
|
|
500
563
|
// Typed as NativeInstallOptions so the native-backend-only `optionalSpecs`
|
|
501
564
|
// is CHECKED here instead of smuggled past an excess-property check;
|
|
502
565
|
// `installPackages` forwards the object to that backend unchanged.
|
|
@@ -518,8 +581,14 @@ async function projectInstallNative(args, signal) {
|
|
|
518
581
|
...optionalDependencyNames(pkg ? [pkg] : []),
|
|
519
582
|
...(args['save-optional'] ? (args.packages ?? []).map((s) => parseSpec(s).name) : []),
|
|
520
583
|
]),
|
|
584
|
+
linkedNames: devLinks?.names,
|
|
521
585
|
};
|
|
522
586
|
const result = await installPackages(nativeOpts);
|
|
587
|
+
// The link survives the install — the point of reading the override at all.
|
|
588
|
+
// The return value is REPORTED, not dropped: a non-empty one means the
|
|
589
|
+
// install disturbed a link that the pre-fetch pass had already wired, which
|
|
590
|
+
// is a fact about the installer, not a routine no-op.
|
|
591
|
+
reportRewiredDevLinks(devLinks);
|
|
523
592
|
// Only the `gjsify install <pkg>...` add-a-dep flow mutates the manifest;
|
|
524
593
|
// the no-args refresh must not.
|
|
525
594
|
if (args.packages && args.packages.length > 0 && pkg) {
|
|
@@ -650,6 +719,10 @@ async function workspaceInstallLocked(cwd, args, signal) {
|
|
|
650
719
|
}
|
|
651
720
|
}
|
|
652
721
|
console.log(`gjsify install: ${workspaces.length} workspace(s), ${externalSpecs.size} external dep spec(s), ${symlinks.length} workspace symlink(s)`);
|
|
722
|
+
// A `gjsify link` override at the workspace ROOT governs the whole install, and
|
|
723
|
+
// is wired here for the same reason the shims and workspace symlinks are: before
|
|
724
|
+
// anything can fail on the network.
|
|
725
|
+
const devLinks = activateDevLinks(cwd, true);
|
|
653
726
|
// EARLY, before the download/extract phase: the shims derive from workspace
|
|
654
727
|
// manifests alone, while the heavy phase that follows can die mid-way (network,
|
|
655
728
|
// timeout, crash). Writing them up front means a failed install can never leave
|
|
@@ -836,6 +909,7 @@ async function workspaceInstallLocked(cwd, args, signal) {
|
|
|
836
909
|
// `types-dev/<lib>` workspace that also exists on npm and is pinned
|
|
837
910
|
// transitively in the lockfile).
|
|
838
911
|
workspaceNames: new Set(byName.keys()),
|
|
912
|
+
linkedNames: devLinks?.names,
|
|
839
913
|
specOrigins: new Map([...specOrigins].map(([k, v]) => [k, [...v]])),
|
|
840
914
|
// Aggregated over EVERY member, because the specs are: a name any member
|
|
841
915
|
// declares as a plain dependency stays required.
|
|
@@ -865,6 +939,7 @@ async function workspaceInstallLocked(cwd, args, signal) {
|
|
|
865
939
|
frozen: args.immutable,
|
|
866
940
|
signal,
|
|
867
941
|
workspaceNames: new Set(byName.keys()),
|
|
942
|
+
linkedNames: devLinks?.names,
|
|
868
943
|
// Same aggregated answer as the root install: these specs are a SUBSET of
|
|
869
944
|
// the root's, so classifying them differently would make one dep optional
|
|
870
945
|
// at the root and required in a member.
|
|
@@ -874,6 +949,7 @@ async function workspaceInstallLocked(cwd, args, signal) {
|
|
|
874
949
|
}
|
|
875
950
|
// A near no-op safety net in case a later step disturbed a link.
|
|
876
951
|
await wireWorkspaceSymlinks();
|
|
952
|
+
reportRewiredDevLinks(devLinks);
|
|
877
953
|
// Now that the tree is materialised, refresh the shims' GJS preamble with the
|
|
878
954
|
// native prebuild dirs that only became discoverable after the install (on a
|
|
879
955
|
// fresh checkout `detectNativePackages` finds nothing before extraction).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LeafCommand } from '../types/index.js';
|
|
2
|
+
interface LinkOptions {
|
|
3
|
+
checkout: string;
|
|
4
|
+
packages?: string[];
|
|
5
|
+
'dry-run'?: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface UnlinkOptions {
|
|
8
|
+
'dry-run'?: boolean;
|
|
9
|
+
install?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const linkCommand: LeafCommand<unknown, LinkOptions>;
|
|
12
|
+
export declare const unlinkCommand: LeafCommand<unknown, UnlinkOptions>;
|
|
13
|
+
export {};
|