@gjsify/cli 0.52.0 → 0.54.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 +351 -305
- package/lib/actions/build.d.ts +22 -0
- package/lib/actions/build.js +37 -9
- package/lib/bundler-pick.d.ts +72 -1
- package/lib/bundler-pick.js +175 -17
- package/lib/cli-app.js +7 -2
- package/lib/commands/blueprint/index.d.ts +4 -0
- package/lib/commands/blueprint/index.js +15 -0
- package/lib/commands/blueprint/types.d.ts +8 -0
- package/lib/commands/blueprint/types.js +124 -0
- package/lib/commands/devtools.d.ts +21 -0
- package/lib/commands/devtools.js +146 -0
- package/lib/commands/env.d.ts +31 -0
- package/lib/commands/env.js +114 -0
- package/lib/commands/exec.d.ts +10 -0
- package/lib/commands/exec.js +208 -0
- package/lib/commands/foreach.js +5 -97
- package/lib/commands/format.js +26 -6
- package/lib/commands/index.d.ts +5 -0
- package/lib/commands/index.js +5 -0
- package/lib/commands/install.d.ts +2 -2
- package/lib/commands/install.js +5 -5
- package/lib/commands/self-update.js +2 -1
- package/lib/commands/ship.js +51 -8
- package/lib/commands/test.d.ts +18 -9
- package/lib/commands/test.js +56 -11
- package/lib/commands/tsc.js +4 -4
- package/lib/commands/upgrade.d.ts +15 -0
- package/lib/commands/upgrade.js +76 -2
- package/lib/commands/webext/index.d.ts +2 -0
- package/lib/commands/webext/index.js +15 -0
- package/lib/commands/webext/subcommands.d.ts +16 -0
- package/lib/commands/webext/subcommands.js +204 -0
- package/lib/types/config-data.d.ts +1 -1
- package/lib/utils/bin-shim.d.ts +8 -2
- package/lib/utils/bin-shim.js +6 -5
- package/lib/utils/bundle-inputs.d.ts +84 -0
- package/lib/utils/bundle-inputs.js +268 -0
- package/lib/utils/check-system-deps.d.ts +4 -2
- package/lib/utils/check-system-deps.js +21 -26
- package/lib/utils/dev-link.d.ts +9 -6
- package/lib/utils/dev-link.js +71 -19
- package/lib/utils/exec-bin.d.ts +137 -0
- package/lib/utils/exec-bin.js +321 -0
- package/lib/utils/gi-runtime-paths.js +3 -2
- package/lib/utils/gitignore.d.ts +15 -0
- package/lib/utils/gitignore.js +178 -0
- package/lib/utils/gjsify-shim.d.ts +47 -0
- package/lib/utils/gjsify-shim.js +97 -8
- package/lib/utils/install-backend-native.d.ts +19 -1
- package/lib/utils/install-backend-native.js +93 -7
- package/lib/utils/install-lock.js +4 -25
- package/lib/utils/native-skip-scan.d.ts +20 -0
- package/lib/utils/native-skip-scan.js +343 -0
- package/lib/utils/node-script.js +1 -1
- package/lib/utils/oxc-resolve.js +46 -5
- package/lib/utils/process-table.d.ts +22 -0
- package/lib/utils/process-table.js +188 -0
- package/lib/utils/prune-prefix.js +1 -1
- package/lib/utils/run-gjs.d.ts +15 -1
- package/lib/utils/run-gjs.js +16 -7
- package/lib/utils/run-node.d.ts +2 -0
- package/lib/utils/run-node.js +2 -1
- package/lib/utils/runtimes.js +14 -13
- package/lib/utils/ship/app-runtime.d.ts +7 -5
- package/lib/utils/ship/app-runtime.js +11 -8
- package/lib/utils/ship/appimage.d.ts +9 -0
- package/lib/utils/ship/appimage.js +2 -1
- package/lib/utils/ship/deb.d.ts +10 -0
- package/lib/utils/ship/deb.js +7 -0
- package/lib/utils/ship/depends.d.ts +37 -3
- package/lib/utils/ship/depends.js +21 -9
- package/lib/utils/ship/gi-namespaces.d.ts +16 -0
- package/lib/utils/ship/gi-namespaces.js +64 -1
- package/lib/utils/ship/gzip.js +1 -1
- package/lib/utils/ship/icons.d.ts +6 -0
- package/lib/utils/ship/icons.js +6 -5
- package/lib/utils/ship/launcher.js +2 -2
- package/lib/utils/ship/layout.js +25 -2
- package/lib/utils/ship/rpm.d.ts +5 -0
- package/lib/utils/ship/rpm.js +42 -0
- package/lib/utils/ship/settings.js +6 -0
- package/lib/utils/ship/signing.d.ts +7 -2
- package/lib/utils/ship/signing.js +59 -32
- package/lib/utils/ship/stage-manifest.d.ts +14 -0
- package/lib/utils/ship/stage-manifest.js +7 -0
- package/lib/utils/spawn.d.ts +13 -0
- package/lib/utils/spawn.js +10 -5
- package/lib/utils/watch-loop.d.ts +15 -1
- package/lib/utils/watch-loop.js +9 -5
- package/lib/utils/webext/build.d.ts +42 -0
- package/lib/utils/webext/build.js +120 -0
- package/lib/utils/webext/config.d.ts +72 -0
- package/lib/utils/webext/config.js +234 -0
- package/lib/utils/webext/icons.d.ts +24 -0
- package/lib/utils/webext/icons.js +83 -0
- package/lib/utils/webext/launch.d.ts +32 -0
- package/lib/utils/webext/launch.js +77 -0
- package/lib/utils/webext/manifest.d.ts +53 -0
- package/lib/utils/webext/manifest.js +219 -0
- package/lib/utils/webext/pages.d.ts +26 -0
- package/lib/utils/webext/pages.js +61 -0
- package/lib/utils/webext/targets.d.ts +13 -0
- package/lib/utils/webext/targets.js +48 -0
- package/lib/utils/win32-command.d.ts +17 -0
- package/lib/utils/win32-command.js +21 -6
- package/package.json +20 -19
package/lib/commands/upgrade.js
CHANGED
|
@@ -36,7 +36,7 @@ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
|
36
36
|
import { join, resolve } from 'node:path';
|
|
37
37
|
import { homedir } from 'node:os';
|
|
38
38
|
import { createInterface } from 'node:readline/promises';
|
|
39
|
-
import { parse } from '@gjsify/semver';
|
|
39
|
+
import { parse, satisfies } from '@gjsify/semver';
|
|
40
40
|
import { DEFAULT_REGISTRY, fetchPackument, parseNpmrc } from '@gjsify/npm-registry';
|
|
41
41
|
import { discoverWorkspaces, filterWorkspaces } from '@gjsify/workspace';
|
|
42
42
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
@@ -325,7 +325,81 @@ export function reportInexactRanges(groups) {
|
|
|
325
325
|
}
|
|
326
326
|
return total;
|
|
327
327
|
}
|
|
328
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Check that every lockfile resolution satisfies the ranges declared for it.
|
|
330
|
+
*
|
|
331
|
+
* `gjsify upgrade --check` compares declared ranges against each other across
|
|
332
|
+
* workspaces. It does NOT compare the resolution against any of them, so a
|
|
333
|
+
* lockfile entry that violates every declaration in the repo passes silently.
|
|
334
|
+
*
|
|
335
|
+
* This check reads the lockfile, resolves each entry's version, and verifies
|
|
336
|
+
* it satisfies at least one declared range for that dependency.
|
|
337
|
+
*
|
|
338
|
+
* @param groups dependency groups with declared ranges
|
|
339
|
+
* @param cwd working directory
|
|
340
|
+
* @returns number of violations
|
|
341
|
+
*/
|
|
342
|
+
export function checkLockfileRangeViolations(groups, cwd) {
|
|
343
|
+
const lockPath = join(cwd, 'gjsify-lock.json');
|
|
344
|
+
if (!existsSync(lockPath))
|
|
345
|
+
return 0;
|
|
346
|
+
let lock;
|
|
347
|
+
try {
|
|
348
|
+
lock = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return 0;
|
|
352
|
+
}
|
|
353
|
+
if (!lock.packages)
|
|
354
|
+
return 0;
|
|
355
|
+
// Build a map: dep name -> set of declared ranges
|
|
356
|
+
const declaredRanges = new Map();
|
|
357
|
+
for (const g of groups) {
|
|
358
|
+
const ranges = declaredRanges.get(g.name) ?? new Set();
|
|
359
|
+
for (const occ of g.occurrences) {
|
|
360
|
+
ranges.add(occ.currentRange);
|
|
361
|
+
}
|
|
362
|
+
declaredRanges.set(g.name, ranges);
|
|
363
|
+
}
|
|
364
|
+
let violations = 0;
|
|
365
|
+
for (const [key, entry] of Object.entries(lock.packages)) {
|
|
366
|
+
if (!entry.version)
|
|
367
|
+
continue;
|
|
368
|
+
// Only check direct dependencies — the single-segment "node_modules/<name>" form.
|
|
369
|
+
// A deep transitive resolution (e.g. "node_modules/a/node_modules/b") is pinned by
|
|
370
|
+
// its parent, not by the root's declared range, and must not be checked against it.
|
|
371
|
+
const parts = key.split('node_modules/');
|
|
372
|
+
if (parts.length !== 2)
|
|
373
|
+
continue;
|
|
374
|
+
const depName = parts[1];
|
|
375
|
+
const ranges = declaredRanges.get(depName);
|
|
376
|
+
if (!ranges || ranges.size === 0)
|
|
377
|
+
continue;
|
|
378
|
+
// Check if the resolved version satisfies at least one declared range
|
|
379
|
+
let satisfied = false;
|
|
380
|
+
for (const range of ranges) {
|
|
381
|
+
try {
|
|
382
|
+
if (entry.version && satisfies(entry.version, range)) {
|
|
383
|
+
satisfied = true;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
// Invalid range, skip
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (!satisfied) {
|
|
392
|
+
console.error(` ${depName}: lockfile has ${entry.version}, but declared ranges are ${[...ranges].join(', ')}`);
|
|
393
|
+
violations++;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (violations > 0) {
|
|
397
|
+
console.error(`gjsify upgrade --check: FAIL. ${violations} lockfile entr${violations === 1 ? 'y' : 'ies'} violate declared ranges:\n`);
|
|
398
|
+
}
|
|
399
|
+
return violations;
|
|
400
|
+
}
|
|
401
|
+
function runCheckMode(groups, exact = false, cwd = process.cwd()) {
|
|
402
|
+
const lockfileViolations = checkLockfileRangeViolations(groups, cwd);
|
|
329
403
|
const inexact = exact ? reportInexactRanges(groups) : 0;
|
|
330
404
|
const inconsistencies = findInconsistencies(groups);
|
|
331
405
|
if (inconsistencies.length === 0 && inexact > 0) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// `gjsify webext` — yargs subcommand-group dispatcher (ADR 0077).
|
|
2
|
+
//
|
|
3
|
+
// Wires {build, zip, dev}; each is a self-contained `Command<>` in `subcommands.ts`,
|
|
4
|
+
// the same composition `flatpak/index.ts` uses.
|
|
5
|
+
import { webextBuildCommand, webextDevCommand, webextZipCommand } from './subcommands.js';
|
|
6
|
+
export const webextCommand = {
|
|
7
|
+
command: 'webext <subcommand>',
|
|
8
|
+
description: 'Browser extensions: build, zip and develop one WebExtension for Chrome, Edge, Firefox, Opera and Safari.',
|
|
9
|
+
builder: (yargs) => yargs
|
|
10
|
+
.command(webextBuildCommand.command, webextBuildCommand.description, webextBuildCommand.builder, webextBuildCommand.handler)
|
|
11
|
+
.command(webextZipCommand.command, webextZipCommand.description, webextZipCommand.builder, webextZipCommand.handler)
|
|
12
|
+
.command(webextDevCommand.command, webextDevCommand.description, webextDevCommand.builder, webextDevCommand.handler)
|
|
13
|
+
.demandCommand(1)
|
|
14
|
+
.strict(),
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
interface WebextArgs {
|
|
3
|
+
target?: string[];
|
|
4
|
+
outDir?: string;
|
|
5
|
+
define: string[];
|
|
6
|
+
mode?: string;
|
|
7
|
+
launch: boolean;
|
|
8
|
+
browserBinary?: string;
|
|
9
|
+
profile?: string;
|
|
10
|
+
headless: boolean;
|
|
11
|
+
debounce: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const webextBuildCommand: Command<unknown, WebextArgs>;
|
|
14
|
+
export declare const webextZipCommand: Command<unknown, WebextArgs>;
|
|
15
|
+
export declare const webextDevCommand: Command<unknown, WebextArgs>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// `gjsify webext build|zip|dev` — browser extensions as a project shape
|
|
2
|
+
// (ADR 0077). The command owns the folder around the bundles: one per target,
|
|
3
|
+
// with a manifest composed for that target, pages, icons, public files and
|
|
4
|
+
// locales. The bundles themselves are ordinary `gjsify build --app browser`
|
|
5
|
+
// runs, re-entered in-process like `gjsify dev` does.
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
8
|
+
import { buildWebext } from '../../utils/webext/build.js';
|
|
9
|
+
import { resolveWebextConfig, selectTargets } from '../../utils/webext/config.js';
|
|
10
|
+
import { defaultDevProfile, resolveWebExt, WEB_EXT_MISSING, webExtRunArgs, } from '../../utils/webext/launch.js';
|
|
11
|
+
function loadConfig(cwd) {
|
|
12
|
+
const manifest = join(cwd, 'package.json');
|
|
13
|
+
if (!existsSync(manifest))
|
|
14
|
+
throw new Error(`gjsify webext: no package.json in ${cwd}.`);
|
|
15
|
+
return resolveWebextConfig(JSON.parse(readFileSync(manifest, 'utf8')), cwd);
|
|
16
|
+
}
|
|
17
|
+
/** `--define K=V` entries on top of the config's map, the way `gjsify build` spells them. */
|
|
18
|
+
function effectiveDefines(config, flags) {
|
|
19
|
+
const out = { ...config.define };
|
|
20
|
+
for (const flag of flags) {
|
|
21
|
+
const at = flag.indexOf('=');
|
|
22
|
+
if (at <= 0)
|
|
23
|
+
throw new Error(`gjsify webext: --define "${flag}" is not KEY=VALUE.`);
|
|
24
|
+
out[flag.slice(0, at)] = flag.slice(at + 1);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
/** One bundle, through the ordinary build command in this process. */
|
|
29
|
+
async function bundleInProcess(request) {
|
|
30
|
+
const { runCli } = await import('../../cli-app.js');
|
|
31
|
+
const argv = ['build', request.entry, '--app', 'browser', '--format', request.format, '--outfile', request.outfile];
|
|
32
|
+
for (const [key, value] of Object.entries(request.define))
|
|
33
|
+
argv.push('--define', `${key}=${value}`);
|
|
34
|
+
if (!request.minify)
|
|
35
|
+
argv.push('--no-minify');
|
|
36
|
+
await runCli(argv);
|
|
37
|
+
}
|
|
38
|
+
const targetOption = {
|
|
39
|
+
description: 'Build only these targets (default: every target in gjsify.webext.targets).',
|
|
40
|
+
type: 'string',
|
|
41
|
+
array: true,
|
|
42
|
+
};
|
|
43
|
+
const outDirOption = {
|
|
44
|
+
description: 'Output directory (default: gjsify.webext.outDir, else .output).',
|
|
45
|
+
type: 'string',
|
|
46
|
+
};
|
|
47
|
+
const defineOption = {
|
|
48
|
+
description: 'Compile-time constant for every bundle, KEY=VALUE (repeatable); wins over gjsify.webext.define.',
|
|
49
|
+
type: 'string',
|
|
50
|
+
array: true,
|
|
51
|
+
default: [],
|
|
52
|
+
};
|
|
53
|
+
async function runBuild(args, zip) {
|
|
54
|
+
const cwd = process.cwd();
|
|
55
|
+
const config = loadConfig(cwd);
|
|
56
|
+
const mode = args.mode === 'development' ? 'development' : 'production';
|
|
57
|
+
await buildWebext({
|
|
58
|
+
config,
|
|
59
|
+
targets: selectTargets(config, args.target),
|
|
60
|
+
outDir: args.outDir ? resolve(cwd, args.outDir) : config.outDir,
|
|
61
|
+
mode,
|
|
62
|
+
define: effectiveDefines(config, args.define),
|
|
63
|
+
inPlace: false,
|
|
64
|
+
zip,
|
|
65
|
+
bundle: bundleInProcess,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export const webextBuildCommand = {
|
|
69
|
+
command: 'build',
|
|
70
|
+
description: 'Build one extension folder per target: bundles, pages, icons, locales and the manifest.',
|
|
71
|
+
builder: (yargs) => yargs
|
|
72
|
+
.option('target', targetOption)
|
|
73
|
+
.option('out-dir', outDirOption)
|
|
74
|
+
.option('define', defineOption)
|
|
75
|
+
.option('mode', {
|
|
76
|
+
description: 'Passed to the manifest as ctx.mode; development also skips minification.',
|
|
77
|
+
type: 'string',
|
|
78
|
+
choices: ['production', 'development'],
|
|
79
|
+
default: 'production',
|
|
80
|
+
}),
|
|
81
|
+
handler: (args) => runBuild(args, false),
|
|
82
|
+
};
|
|
83
|
+
export const webextZipCommand = {
|
|
84
|
+
command: 'zip',
|
|
85
|
+
description: 'Production build, then one store-ready <name>-<version>-<target>.zip per target.',
|
|
86
|
+
builder: (yargs) => yargs.option('target', targetOption).option('out-dir', outDirOption).option('define', defineOption),
|
|
87
|
+
handler: (args) => runBuild(args, true),
|
|
88
|
+
};
|
|
89
|
+
/** The dev target when none is asked for: the first Firefox target, since web-ext's Firefox driver needs no extra binary. */
|
|
90
|
+
function devTarget(config, requested) {
|
|
91
|
+
if (requested && requested.length > 1)
|
|
92
|
+
throw new Error('gjsify webext dev: pass one --target.');
|
|
93
|
+
const [chosen] = selectTargets(config, requested);
|
|
94
|
+
if (requested && requested.length === 1)
|
|
95
|
+
return chosen;
|
|
96
|
+
return config.targets.find((t) => t.browser === 'firefox') ?? chosen;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* What the loop watches: every top-level directory except `node_modules`,
|
|
100
|
+
* dot-directories and the output, recursively; the project root itself only
|
|
101
|
+
* for its own entries (`manifest.ts`, `package.json`); and `watch` extras.
|
|
102
|
+
*/
|
|
103
|
+
function devWatchTargets(config, outDir) {
|
|
104
|
+
const skip = (name) => name === 'node_modules' || name.startsWith('.');
|
|
105
|
+
const out = [{ path: config.root, recursive: false }];
|
|
106
|
+
for (const entry of readdirSync(config.root, { withFileTypes: true })) {
|
|
107
|
+
const full = join(config.root, entry.name);
|
|
108
|
+
if (!entry.isDirectory() || skip(entry.name) || full === outDir || outDir.startsWith(full + sep))
|
|
109
|
+
continue;
|
|
110
|
+
out.push({ path: full, recursive: true });
|
|
111
|
+
}
|
|
112
|
+
for (const extra of config.watch)
|
|
113
|
+
out.push({ path: extra, recursive: true });
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
export const webextDevCommand = {
|
|
117
|
+
command: 'dev',
|
|
118
|
+
description: 'Build one target in development mode, launch it in a browser via web-ext, and rebuild in place on change.',
|
|
119
|
+
builder: (yargs) => yargs
|
|
120
|
+
.option('target', {
|
|
121
|
+
...targetOption,
|
|
122
|
+
description: 'The target to run (default: the first Firefox target).',
|
|
123
|
+
})
|
|
124
|
+
.option('out-dir', { description: 'Output directory (default: <outDir>-dev).', type: 'string' })
|
|
125
|
+
.option('define', defineOption)
|
|
126
|
+
.option('launch', {
|
|
127
|
+
description: 'Start a browser with the extension (--no-launch only rebuilds).',
|
|
128
|
+
type: 'boolean',
|
|
129
|
+
default: true,
|
|
130
|
+
})
|
|
131
|
+
.option('browser-binary', { description: 'Browser executable web-ext should start.', type: 'string' })
|
|
132
|
+
.option('profile', {
|
|
133
|
+
description: 'Browser profile directory (default: $XDG_CACHE_HOME/gjsify/webext/<name>/<target>).',
|
|
134
|
+
type: 'string',
|
|
135
|
+
})
|
|
136
|
+
.option('headless', { description: 'Start the browser without a window.', type: 'boolean', default: false })
|
|
137
|
+
.option('debounce', { description: 'Quiet window in ms before a rebuild.', type: 'number', default: 200 }),
|
|
138
|
+
handler: async (args) => {
|
|
139
|
+
const cwd = process.cwd();
|
|
140
|
+
const config = loadConfig(cwd);
|
|
141
|
+
const target = devTarget(config, args.target);
|
|
142
|
+
const outDir = args.outDir ? resolve(cwd, args.outDir) : `${config.outDir}-dev`;
|
|
143
|
+
const define = effectiveDefines(config, args.define);
|
|
144
|
+
const sourceDir = join(outDir, target.id);
|
|
145
|
+
let webExt = null;
|
|
146
|
+
if (args.launch) {
|
|
147
|
+
webExt = resolveWebExt(cwd);
|
|
148
|
+
if (webExt === null)
|
|
149
|
+
throw new Error(WEB_EXT_MISSING);
|
|
150
|
+
}
|
|
151
|
+
let browser = null;
|
|
152
|
+
const stopBrowser = () => {
|
|
153
|
+
browser?.kill();
|
|
154
|
+
};
|
|
155
|
+
// Registered BEFORE the loop's own handlers, which end in process.exit.
|
|
156
|
+
process.on('SIGINT', stopBrowser);
|
|
157
|
+
process.on('SIGTERM', stopBrowser);
|
|
158
|
+
const launch = async () => {
|
|
159
|
+
if (webExt === null || browser !== null)
|
|
160
|
+
return;
|
|
161
|
+
const profile = args.profile ? resolve(cwd, args.profile) : defaultDevProfile(config.name, target);
|
|
162
|
+
// web-ext creates the profile directory but not its parent (ENOENT on a fresh machine).
|
|
163
|
+
mkdirSync(dirname(profile), { recursive: true });
|
|
164
|
+
const argv = webExtRunArgs({
|
|
165
|
+
sourceDir,
|
|
166
|
+
target,
|
|
167
|
+
profile,
|
|
168
|
+
browserBinary: args.browserBinary,
|
|
169
|
+
headless: args.headless,
|
|
170
|
+
});
|
|
171
|
+
console.log(`[webext] launching ${target.id} with profile ${profile}`);
|
|
172
|
+
const { spawnSupervised } = await import('../../utils/watch-loop.js');
|
|
173
|
+
browser = await spawnSupervised(webExt.cmd, argv, webExt.env);
|
|
174
|
+
browser.on('exit', (code) => {
|
|
175
|
+
console.log(`[webext] browser closed (${code ?? 0}) — stopping`);
|
|
176
|
+
process.exit(0);
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
const { runWatchLoop } = await import('../../utils/watch-loop.js');
|
|
180
|
+
await runWatchLoop({
|
|
181
|
+
dir: cwd,
|
|
182
|
+
dirLabel: '.',
|
|
183
|
+
label: 'webext',
|
|
184
|
+
watch: devWatchTargets(config, outDir),
|
|
185
|
+
ignore: (changed) => changed === outDir || changed.startsWith(outDir + sep),
|
|
186
|
+
build: async () => {
|
|
187
|
+
await buildWebext({
|
|
188
|
+
config,
|
|
189
|
+
targets: [target],
|
|
190
|
+
outDir,
|
|
191
|
+
mode: 'development',
|
|
192
|
+
define,
|
|
193
|
+
inPlace: true,
|
|
194
|
+
zip: false,
|
|
195
|
+
bundle: bundleInProcess,
|
|
196
|
+
});
|
|
197
|
+
// After the first SUCCESSFUL build: web-ext on a folder with no manifest.json exits at once.
|
|
198
|
+
await launch();
|
|
199
|
+
},
|
|
200
|
+
spawnChild: null,
|
|
201
|
+
debounceMs: args.debounce,
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
};
|
|
@@ -743,7 +743,7 @@ export interface ShipFlatpakOptions {
|
|
|
743
743
|
* here (ADR 0024 § 8; the window is `utils/ship/flatpak-config.ts`). They are
|
|
744
744
|
* NOT marked `@deprecated`: for the subcommands in this group they are still
|
|
745
745
|
* the only spelling, and those commands have not moved under `ship` yet
|
|
746
|
-
* (`status/open-todos.md`). Nothing else in this block is deprecated at all —
|
|
746
|
+
* (`status/open-todos/README.md`). Nothing else in this block is deprecated at all —
|
|
747
747
|
* the {@link AppMetadata} half is a designed alias, not a legacy one.
|
|
748
748
|
*/
|
|
749
749
|
export interface ConfigDataFlatpak extends AppMetadata {
|
package/lib/utils/bin-shim.d.ts
CHANGED
|
@@ -138,7 +138,7 @@ export declare function buildLauncherShims(opts: {
|
|
|
138
138
|
* in shell would be a knowingly partial THIRD copy of a two-copy rule, in the
|
|
139
139
|
* one language nothing here type-checks. The REMAINING gap — a host GI stack
|
|
140
140
|
* under a prefix dyld never defaulted to, `/opt/homebrew/lib` on Apple silicon —
|
|
141
|
-
* stays in `status/open-todos.md`, to be closed by teaching such a launcher to
|
|
141
|
+
* stays in `status/open-todos/README.md`, to be closed by teaching such a launcher to
|
|
142
142
|
* defer to the CLI rather than to re-derive.
|
|
143
143
|
*
|
|
144
144
|
* @param scanRoot Directory whose `node_modules` is globbed at launch time.
|
|
@@ -178,7 +178,7 @@ export declare function buildNativeEnvPreamble(scanRoot: string, bakedDirs?: rea
|
|
|
178
178
|
*
|
|
179
179
|
* NOT the host's GI libdirs: `/opt/homebrew/lib` was never in dyld's default
|
|
180
180
|
* either, so an ARM Mac has that gap with or without gjsify —
|
|
181
|
-
* `status/open-todos.md`.
|
|
181
|
+
* `status/open-todos/README.md`.
|
|
182
182
|
*/
|
|
183
183
|
export declare function dyldFallbackPreamble(platform: string): string;
|
|
184
184
|
/** POSIX `sh` single-quoting for a launcher's target path. */
|
|
@@ -263,6 +263,12 @@ export declare function buildShLauncher(targetAbs: string, opts: {
|
|
|
263
263
|
envPreamble?: string;
|
|
264
264
|
isGjsBundle: boolean;
|
|
265
265
|
nodeFallbackAbs?: string;
|
|
266
|
+
/**
|
|
267
|
+
* The gjs to exec — `GJS_CONSOLE` for the CLI's self-shim. NAMED rather
|
|
268
|
+
* than pathed by default, so a missing interpreter fails the same way on
|
|
269
|
+
* every OS.
|
|
270
|
+
*/
|
|
271
|
+
gjs?: string;
|
|
266
272
|
}): string;
|
|
267
273
|
/** True when a bin target is a GJS-runnable bundle rather than a Node script. */
|
|
268
274
|
export declare function isGjsBundlePath(target: string): boolean;
|
package/lib/utils/bin-shim.js
CHANGED
|
@@ -349,7 +349,7 @@ function shQuote(s) {
|
|
|
349
349
|
* in shell would be a knowingly partial THIRD copy of a two-copy rule, in the
|
|
350
350
|
* one language nothing here type-checks. The REMAINING gap — a host GI stack
|
|
351
351
|
* under a prefix dyld never defaulted to, `/opt/homebrew/lib` on Apple silicon —
|
|
352
|
-
* stays in `status/open-todos.md`, to be closed by teaching such a launcher to
|
|
352
|
+
* stays in `status/open-todos/README.md`, to be closed by teaching such a launcher to
|
|
353
353
|
* defer to the CLI rather than to re-derive.
|
|
354
354
|
*
|
|
355
355
|
* @param scanRoot Directory whose `node_modules` is globbed at launch time.
|
|
@@ -449,7 +449,7 @@ export function buildNativeEnvPreamble(scanRoot, bakedDirs = [], target = {}) {
|
|
|
449
449
|
*
|
|
450
450
|
* NOT the host's GI libdirs: `/opt/homebrew/lib` was never in dyld's default
|
|
451
451
|
* either, so an ARM Mac has that gap with or without gjsify —
|
|
452
|
-
* `status/open-todos.md`.
|
|
452
|
+
* `status/open-todos/README.md`.
|
|
453
453
|
*/
|
|
454
454
|
export function dyldFallbackPreamble(platform) {
|
|
455
455
|
if (platform !== 'darwin')
|
|
@@ -556,15 +556,16 @@ export function pickBinMap(pkgName, pkgJson) {
|
|
|
556
556
|
*/
|
|
557
557
|
export function buildShLauncher(targetAbs, opts) {
|
|
558
558
|
const preamble = opts.isGjsBundle ? (opts.envPreamble ?? '') : '';
|
|
559
|
+
const gjs = opts.gjs === undefined || opts.gjs === 'gjs' ? 'gjs' : shQuote(opts.gjs);
|
|
559
560
|
if (opts.isGjsBundle && opts.nodeFallbackAbs !== undefined) {
|
|
560
561
|
// The preamble only ever configures the GI search path, so it stays
|
|
561
562
|
// inside the gjs branch — exporting it around a Node exec would be
|
|
562
563
|
// inert at best and misleading to anyone reading the shim.
|
|
563
|
-
return (`#!/bin/sh\nif command -v gjs >/dev/null 2>&1; then\n${preamble}` +
|
|
564
|
-
`exec gjs -m ${shQuote(targetAbs)} "$@"\nfi\n` +
|
|
564
|
+
return (`#!/bin/sh\nif command -v ${gjs} >/dev/null 2>&1; then\n${preamble}` +
|
|
565
|
+
`exec ${gjs} -m ${shQuote(targetAbs)} "$@"\nfi\n` +
|
|
565
566
|
`exec node ${shQuote(opts.nodeFallbackAbs)} "$@"\n`);
|
|
566
567
|
}
|
|
567
|
-
const exec = opts.isGjsBundle ? `exec gjs -m ${shQuote(targetAbs)} "$@"` : `exec ${shQuote(targetAbs)} "$@"`;
|
|
568
|
+
const exec = opts.isGjsBundle ? `exec ${gjs} -m ${shQuote(targetAbs)} "$@"` : `exec ${shQuote(targetAbs)} "$@"`;
|
|
568
569
|
return `#!/bin/sh\n${preamble}${exec}\n`;
|
|
569
570
|
}
|
|
570
571
|
/** True when a bin target is a GJS-runnable bundle rather than a Node script. */
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { RolldownPluginOption } from 'rolldown';
|
|
2
|
+
/**
|
|
3
|
+
* Bumped when the shape changes; a manifest of any other version is ignored.
|
|
4
|
+
*
|
|
5
|
+
* v2 adds `watchList`. A v1 manifest says nothing about whether the engine that
|
|
6
|
+
* wrote it could report a watch list at all, and the safe reading of "unknown"
|
|
7
|
+
* is "do not trust it": see {@link readBundleInputs}.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BUNDLE_INPUTS_VERSION = 2;
|
|
10
|
+
/** What a manifest says, once parsed. */
|
|
11
|
+
export interface BundleInputs {
|
|
12
|
+
inputs: string[];
|
|
13
|
+
/** `false` = this bundle's build could not see plugin-read inputs at all. */
|
|
14
|
+
watchList: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** The manifest that belongs to `outfile`. */
|
|
17
|
+
export declare function bundleInputsPath(outfile: string): string;
|
|
18
|
+
export interface BundleInputsRecorder {
|
|
19
|
+
/** Pass to the bundler as a plugin; it observes, it never transforms. */
|
|
20
|
+
plugin: RolldownPluginOption;
|
|
21
|
+
/**
|
|
22
|
+
* The build's own watch list — what the engine reports, which is its module
|
|
23
|
+
* set PLUS everything a plugin declared with `this.addWatchFile`. That is
|
|
24
|
+
* the only account of a file the bundler never turned into a module: the
|
|
25
|
+
* CSS plugin follows a stylesheet's `@import` chain with its own `readFile`,
|
|
26
|
+
* and no graph mentions the imported file.
|
|
27
|
+
*/
|
|
28
|
+
addWatchFiles(watch: {
|
|
29
|
+
files: readonly string[];
|
|
30
|
+
reportedByEngine: boolean;
|
|
31
|
+
}): void;
|
|
32
|
+
/** The recorded inputs so far: sorted, deduplicated, existing files only. */
|
|
33
|
+
inputs(): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Persist the manifest beside `outfile`, so the NEXT freshness check can
|
|
36
|
+
* judge against the same set. Never throws: a manifest that cannot be
|
|
37
|
+
* written must not fail a test run — it degrades the check to the package
|
|
38
|
+
* walk, and says so.
|
|
39
|
+
*/
|
|
40
|
+
write(): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A plugin that records every module the build transforms, plus the writer for
|
|
44
|
+
* the manifest.
|
|
45
|
+
*
|
|
46
|
+
* `transform` rather than a graph API (`getModuleIds`/`getModuleInfo`) because
|
|
47
|
+
* it is the one hook both engines run and the native bridge translates
|
|
48
|
+
* (`bundler-pick.ts` picks it verbatim). Every module that reaches the
|
|
49
|
+
* transform stage was LOADED from disk, so the observed set is what the build
|
|
50
|
+
* read — the modules it then tree-shook away are still inputs of the next
|
|
51
|
+
* build, which is the direction that errs safely.
|
|
52
|
+
*
|
|
53
|
+
* The observation is KEPT alongside {@link BundleInputsRecorder.addWatchFiles}
|
|
54
|
+
* rather than replaced by it, and neither engine is the reason: the native
|
|
55
|
+
* engine has no watch list to hand over (`BundleResult` carries warnings and
|
|
56
|
+
* output only, and its JS context had no `addWatchFile` at all until
|
|
57
|
+
* `rolldown-native` grew one), so on GJS the observed set is the only account
|
|
58
|
+
* of the modules; on npm the watch list is a superset of it. Union: the cost of
|
|
59
|
+
* a redundant path is a `stat`, the cost of a missing one is a stale green.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createBundleInputsRecorder(outfile: string): BundleInputsRecorder;
|
|
62
|
+
/**
|
|
63
|
+
* The inputs recorded for `outfile` by the build that wrote it, or `null` when
|
|
64
|
+
* there is none to trust — never written, unreadable, or written by another
|
|
65
|
+
* version of this shape. `null` is the fallback the caller must handle, not an
|
|
66
|
+
* error: a bundle built by an older CLI, or copied in from elsewhere, has no
|
|
67
|
+
* manifest and is judged by the package walk alone.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readBundleInputs(outfile: string): BundleInputs | null;
|
|
70
|
+
/**
|
|
71
|
+
* Newest mtime (ms) among recorded inputs, or 0 for an empty set.
|
|
72
|
+
*
|
|
73
|
+
* A recorded input that has since VANISHED counts as CHANGED, exactly as the
|
|
74
|
+
* package walk counts one that vanished mid-walk: an input we cannot read is
|
|
75
|
+
* not evidence that the artifact is current. A workspace-package source deleted
|
|
76
|
+
* while the bundle still imports it is precisely that case, and skipping it
|
|
77
|
+
* would rerun a bundle built from a file that no longer exists.
|
|
78
|
+
*
|
|
79
|
+
* Safe only because the toolchain's own scratch is never RECORDED
|
|
80
|
+
* ({@link isToolchainScratch}): the globals-inject stub and the cached plugin
|
|
81
|
+
* bundles are written and deleted between builds, and treating their absence as
|
|
82
|
+
* a change would rebuild on every run, forever.
|
|
83
|
+
*/
|
|
84
|
+
export declare function newestBundleInputMtimeMs(inputs: readonly string[]): number;
|