@gjsify/cli 0.51.1 → 0.53.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 +539 -493
- package/lib/actions/build.d.ts +22 -0
- package/lib/actions/build.js +37 -9
- package/lib/bundler-pick.d.ts +37 -1
- package/lib/bundler-pick.js +108 -17
- package/lib/cli-app.js +7 -2
- 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/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/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/foreach.js +5 -97
- package/lib/commands/format.js +26 -6
- package/lib/commands/index.d.ts +4 -0
- package/lib/commands/index.js +4 -0
- package/lib/commands/install.d.ts +2 -2
- package/lib/commands/install.js +81 -5
- 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/self-update.js +2 -1
- package/lib/commands/ship.js +19 -1
- 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 +2 -2
- 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 +5 -19
- package/lib/utils/check-system-deps.js +31 -94
- package/lib/utils/dev-link.d.ts +225 -0
- package/lib/utils/dev-link.js +684 -0
- 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/hang-watchdog.d.ts +95 -0
- package/lib/utils/hang-watchdog.js +194 -0
- package/lib/utils/install-backend-native.d.ts +33 -1
- package/lib/utils/install-backend-native.js +184 -20
- package/lib/utils/install-extraneous.d.ts +90 -0
- package/lib/utils/install-extraneous.js +120 -0
- package/lib/utils/install-lock.js +4 -25
- package/lib/utils/install-tarball-cache.d.ts +37 -9
- package/lib/utils/install-tarball-cache.js +59 -14
- 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/normalize-bundler-options.js +11 -0
- package/lib/utils/oxc-resolve.js +42 -3
- 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/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.d.ts +15 -1
- package/lib/utils/run-gjs.js +98 -13
- 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/gzip.js +1 -1
- 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 +197 -0
- package/lib/utils/ship/icons.js +399 -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/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 +36 -0
- package/lib/utils/win32-command.d.ts +17 -0
- package/lib/utils/win32-command.js +21 -6
- package/package.json +20 -20
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
20
20
|
import { dirname, join, resolve } from 'node:path';
|
|
21
21
|
import { Buffer } from 'node:buffer';
|
|
22
|
+
import { FLATPAK_SOURCES_FILE } from './utils.js';
|
|
22
23
|
/** Checksum algorithms flatpak-builder accepts on a `file` source. */
|
|
23
24
|
const FLATPAK_ALGOS = new Set(['sha256', 'sha512']);
|
|
24
25
|
/** Parse an SRI integrity (`sha512-<base64>`) into `{ algorithm, hex }`. Mirrors
|
|
@@ -219,7 +220,7 @@ export const flatpakSourcesCommand = {
|
|
|
219
220
|
.option('out', {
|
|
220
221
|
description: 'Output JSON sources file',
|
|
221
222
|
type: 'string',
|
|
222
|
-
default:
|
|
223
|
+
default: FLATPAK_SOURCES_FILE,
|
|
223
224
|
normalize: true,
|
|
224
225
|
})
|
|
225
226
|
.option('cache-root', {
|
|
@@ -277,7 +278,7 @@ export const flatpakSourcesCommand = {
|
|
|
277
278
|
}
|
|
278
279
|
// Stable order (by url) so the generated file diffs cleanly across runs.
|
|
279
280
|
const sources = [...byHex.values()].sort((a, b) => (a.url < b.url ? -1 : a.url > b.url ? 1 : 0));
|
|
280
|
-
const out = resolve(cwd, args.out ??
|
|
281
|
+
const out = resolve(cwd, args.out ?? FLATPAK_SOURCES_FILE);
|
|
281
282
|
mkdirSync(dirname(out), { recursive: true });
|
|
282
283
|
writeFileSync(out, JSON.stringify(sources, null, 2) + '\n');
|
|
283
284
|
console.log(`[gjsify flatpak sources] wrote ${out} — ${sources.length} tarball source(s) from ${type} lockfile` +
|
|
@@ -285,7 +286,7 @@ export const flatpakSourcesCommand = {
|
|
|
285
286
|
(skippedSri ? `, ${skippedSri} without a usable integrity skipped` : '') +
|
|
286
287
|
(skippedAlgo ? `, ${skippedAlgo} non-sha256/512 skipped` : ''));
|
|
287
288
|
if (args.printModule) {
|
|
288
|
-
console.error(buildModuleSnippet(args.out ??
|
|
289
|
+
console.error(buildModuleSnippet(args.out ?? FLATPAK_SOURCES_FILE, cacheRoot));
|
|
289
290
|
}
|
|
290
291
|
},
|
|
291
292
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Command } from '../../types/index.js';
|
|
2
2
|
interface SyncFlathubOptions {
|
|
3
3
|
version?: string;
|
|
4
|
+
sourcesFile?: string;
|
|
4
5
|
appId?: string;
|
|
5
6
|
flathubRepo?: string;
|
|
6
7
|
commit?: string;
|
|
@@ -22,5 +23,6 @@ export declare function editManifest(original: string, args: {
|
|
|
22
23
|
tag: string;
|
|
23
24
|
commit: string;
|
|
24
25
|
sourceIndex?: number;
|
|
26
|
+
sourcesFile?: string;
|
|
25
27
|
}): string;
|
|
26
28
|
export {};
|
|
@@ -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/foreach.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// matching yarn's interactive flow. Exit code is non-zero if any child
|
|
10
10
|
// process failed; first failure's stderr is forwarded.
|
|
11
11
|
import { spawnToCompletion } from '../utils/spawn.js';
|
|
12
|
-
import {
|
|
12
|
+
import { collectDescendants } from '../utils/process-table.js';
|
|
13
13
|
import { cpus } from 'node:os';
|
|
14
14
|
import { affectedClosure, buildDependencyGraph, discoverWorkspaces, filterWorkspaces, topologicalSort, } from '@gjsify/workspace';
|
|
15
15
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
@@ -34,101 +34,6 @@ const KILL_GRACE_MS = 5_000;
|
|
|
34
34
|
// killed child whose grandchildren inherit (and keep open) the stdio pipe
|
|
35
35
|
// never emits 'close', so we must not wait on it unboundedly.
|
|
36
36
|
const DRAIN_DEADLINE_MS = 15_000;
|
|
37
|
-
// Read the direct child PIDs of `pid` from /proc/<pid>/task/*/children
|
|
38
|
-
// (Linux, CONFIG_PROC_CHILDREN — standard on every mainstream kernel). When
|
|
39
|
-
// the per-task children files are unavailable, fall back to one full
|
|
40
|
-
// /proc/*/stat scan building a ppid→children map. Both paths use only
|
|
41
|
-
// `node:fs` reads, so they behave identically under Node and GJS
|
|
42
|
-
// (@gjsify/fs) — unlike a process-group kill, which GJS cannot issue
|
|
43
|
-
// (`@gjsify/process.kill` shells out `kill <sig> <pid>` where a negative
|
|
44
|
-
// PID parses as an option, and Gio.Subprocess has no group-signal API).
|
|
45
|
-
function readDirectChildren(pid) {
|
|
46
|
-
let taskIds;
|
|
47
|
-
try {
|
|
48
|
-
taskIds = readdirSync(`/proc/${pid}/task`);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
// Process already gone (common) or /proc unavailable.
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const kids = [];
|
|
55
|
-
let readAny = false;
|
|
56
|
-
for (const tid of taskIds) {
|
|
57
|
-
try {
|
|
58
|
-
const data = readFileSync(`/proc/${pid}/task/${tid}/children`, 'utf-8');
|
|
59
|
-
readAny = true;
|
|
60
|
-
for (const tok of data.trim().split(/\s+/)) {
|
|
61
|
-
const n = Number(tok);
|
|
62
|
-
if (Number.isInteger(n) && n > 0)
|
|
63
|
-
kids.push(n);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// Thread vanished mid-walk, or kernel lacks CONFIG_PROC_CHILDREN.
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return readAny ? kids : null;
|
|
71
|
-
}
|
|
72
|
-
// Fallback: scan every /proc/<pid>/stat once and build ppid → children.
|
|
73
|
-
// O(#processes) small reads — fine for the rare kill path.
|
|
74
|
-
function buildPpidMap() {
|
|
75
|
-
const map = new Map();
|
|
76
|
-
let entries = [];
|
|
77
|
-
try {
|
|
78
|
-
entries = readdirSync('/proc');
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return map;
|
|
82
|
-
}
|
|
83
|
-
for (const entry of entries) {
|
|
84
|
-
const pid = Number(entry);
|
|
85
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
86
|
-
continue;
|
|
87
|
-
try {
|
|
88
|
-
const stat = readFileSync(`/proc/${pid}/stat`, 'utf-8');
|
|
89
|
-
// comm (field 2) may contain spaces/parens — parse after last ')'.
|
|
90
|
-
const rest = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
91
|
-
const ppid = Number(rest[1]);
|
|
92
|
-
if (!Number.isInteger(ppid) || ppid <= 0)
|
|
93
|
-
continue;
|
|
94
|
-
const siblings = map.get(ppid);
|
|
95
|
-
if (siblings)
|
|
96
|
-
siblings.push(pid);
|
|
97
|
-
else
|
|
98
|
-
map.set(ppid, [pid]);
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
// Process exited between readdir and read — fine.
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return map;
|
|
105
|
-
}
|
|
106
|
-
// Snapshot every transitive descendant of `pid`, breadth-first (so
|
|
107
|
-
// reversing the list yields deepest-first order for signalling).
|
|
108
|
-
function collectDescendants(pid) {
|
|
109
|
-
const result = [];
|
|
110
|
-
const seen = new Set([pid]);
|
|
111
|
-
const queue = [pid];
|
|
112
|
-
let ppidMap = null;
|
|
113
|
-
while (queue.length > 0) {
|
|
114
|
-
const cur = queue.shift();
|
|
115
|
-
let kids = readDirectChildren(cur);
|
|
116
|
-
if (kids === null) {
|
|
117
|
-
// children files unreadable — use (and lazily build) the
|
|
118
|
-
// full-scan fallback for this and subsequent levels.
|
|
119
|
-
ppidMap ??= buildPpidMap();
|
|
120
|
-
kids = ppidMap.get(cur) ?? [];
|
|
121
|
-
}
|
|
122
|
-
for (const kid of kids) {
|
|
123
|
-
if (seen.has(kid))
|
|
124
|
-
continue;
|
|
125
|
-
seen.add(kid);
|
|
126
|
-
result.push(kid);
|
|
127
|
-
queue.push(kid);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return result;
|
|
131
|
-
}
|
|
132
37
|
// Signal an already-collected descendant set deepest-first, then the direct
|
|
133
38
|
// child itself. Snapshot-then-signal (not signal-while-walking) so killing
|
|
134
39
|
// the parent can't orphan grandchildren before we enumerated them. The
|
|
@@ -157,7 +62,10 @@ function killActiveChildren() {
|
|
|
157
62
|
// The direct child is `npm run <script>` — killing only it leaves the
|
|
158
63
|
// npm-spawned shell → gjs/node build grandchildren alive (observed: an
|
|
159
64
|
// orphaned gjs bundler at 100% CPU for 19+ min after a fail-fast kill).
|
|
160
|
-
// So terminate the whole process TREE of each child
|
|
65
|
+
// So terminate the whole process TREE of each child — walked by
|
|
66
|
+
// `utils/process-table.ts` (procfs on Linux, the OS process list elsewhere).
|
|
67
|
+
// A process-group kill would be simpler and GJS cannot issue one:
|
|
68
|
+
// Gio.Subprocess has no group-signal API.
|
|
161
69
|
const termSnapshots = new Map();
|
|
162
70
|
for (const child of activeChildren) {
|
|
163
71
|
const descendants = child.pid ? collectDescendants(child.pid) : [];
|
package/lib/commands/format.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
// optionalDependency, keeping the Node-free runtime promise for the bundled
|
|
8
8
|
// CLI intact (the formatter runs in dev/CI, not in shipped GJS apps).
|
|
9
9
|
//
|
|
10
|
-
// oxfmt formats JS/TS
|
|
11
|
-
//
|
|
12
|
-
//
|
|
10
|
+
// oxfmt formats JS/TS, JSON, TOML, CSS and GraphQL in both builds; the
|
|
11
|
+
// languages it hands to its Prettier host (Markdown, HTML, Vue, YAML, …) are
|
|
12
|
+
// napi-only — the native GJS build drops them, which the skip scan below
|
|
13
|
+
// reports.
|
|
13
14
|
//
|
|
14
15
|
// A BARE `gjsify format` WRITES. `--check` is the read-only CI mode and
|
|
15
16
|
// `--no-write` the read-only local one; there is no flagless report mode.
|
|
@@ -24,11 +25,12 @@
|
|
|
24
25
|
// tuned for GJS/GNOME projects (4-space, single-quote, printWidth 120,
|
|
25
26
|
// trailing-comma all, semicolons always, arrow parens always).
|
|
26
27
|
import { existsSync, writeFileSync } from 'node:fs';
|
|
27
|
-
import { resolve } from 'node:path';
|
|
28
|
-
import { OxcNotFoundError, findOxfmtConfig, loadOxfmtTemplate, loadOxlintTemplate, printOxcNotFound, runOxfmt, setOxcExitCode, } from '../utils/oxc-resolve.js';
|
|
28
|
+
import { relative, resolve } from 'node:path';
|
|
29
|
+
import { OxcNotFoundError, findOxfmtConfig, loadOxfmtTemplate, loadOxlintTemplate, printOxcNotFound, runOxfmt, setOxcExitCode, shouldUseNativeOxfmt, } from '../utils/oxc-resolve.js';
|
|
30
|
+
import { scanForNativeSkips } from '../utils/native-skip-scan.js';
|
|
29
31
|
export const formatCommand = {
|
|
30
32
|
command: 'format [paths..]',
|
|
31
|
-
description: 'Format
|
|
33
|
+
description: 'Format source files via oxfmt (Markdown/YAML/HTML/Vue need the napi build).',
|
|
32
34
|
builder: (yargs) => {
|
|
33
35
|
return yargs
|
|
34
36
|
.positional('paths', {
|
|
@@ -90,6 +92,24 @@ export const formatCommand = {
|
|
|
90
92
|
if (configPath)
|
|
91
93
|
oxfmtArgs.push('--config', resolve(configPath));
|
|
92
94
|
oxfmtArgs.push(...paths);
|
|
95
|
+
// Under GJS the native oxfmt formats everything but the files napi oxfmt
|
|
96
|
+
// hands to its Prettier host (Markdown, HTML, Vue, YAML, …), and it drops
|
|
97
|
+
// those without a word. `--check` FAILS on them, so a green check that
|
|
98
|
+
// checked nothing is impossible — asked about exactly the files oxfmt
|
|
99
|
+
// itself would walk with this config, never `node_modules` or ignored ones.
|
|
100
|
+
if (args.check && (await shouldUseNativeOxfmt())) {
|
|
101
|
+
const scan = scanForNativeSkips(paths, cwd, { configPath });
|
|
102
|
+
if (scan.skipped.length > 0) {
|
|
103
|
+
const list = scan.skipped.map((f) => ` ${relative(cwd, f) || f}`).join('\n');
|
|
104
|
+
console.error(`[gjsify format] ERROR: ${scan.skipped.length} file(s) skipped — ` +
|
|
105
|
+
`the native oxfmt (GJS) has no Prettier host for them (Markdown/HTML/Vue/YAML/…):\n` +
|
|
106
|
+
`${list}\n` +
|
|
107
|
+
` The format check is INCOMPLETE. Run \`gjsify format --check\` under Node to check these ` +
|
|
108
|
+
`files, or add them to \`ignorePatterns\`.`);
|
|
109
|
+
setOxcExitCode(1);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
93
113
|
try {
|
|
94
114
|
const code = await runOxfmt(oxfmtArgs, { cwd, verbose: args.verbose });
|
|
95
115
|
setOxcExitCode(code);
|
package/lib/commands/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './build.js';
|
|
2
2
|
export * from './clear.js';
|
|
3
3
|
export * from './copy.js';
|
|
4
|
+
export * from './env.js';
|
|
5
|
+
export * from './exec.js';
|
|
4
6
|
export * from './test.js';
|
|
5
7
|
export * from './run.js';
|
|
6
8
|
export * from './info.js';
|
|
@@ -15,6 +17,7 @@ export { flatpakCommand } from './flatpak/index.js';
|
|
|
15
17
|
export * from './ship.js';
|
|
16
18
|
export * from './dlx.js';
|
|
17
19
|
export * from './install.js';
|
|
20
|
+
export * from './link.js';
|
|
18
21
|
export * from './foreach.js';
|
|
19
22
|
export * from './workspace.js';
|
|
20
23
|
export * from './pack.js';
|
|
@@ -39,3 +42,4 @@ export * from './storybook.js';
|
|
|
39
42
|
export * from './dev.js';
|
|
40
43
|
export * from './debug.js';
|
|
41
44
|
export * from './browse.js';
|
|
45
|
+
export { webextCommand } from './webext/index.js';
|
package/lib/commands/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './build.js';
|
|
2
2
|
export * from './clear.js';
|
|
3
3
|
export * from './copy.js';
|
|
4
|
+
export * from './env.js';
|
|
5
|
+
export * from './exec.js';
|
|
4
6
|
export * from './test.js';
|
|
5
7
|
export * from './run.js';
|
|
6
8
|
export * from './info.js';
|
|
@@ -15,6 +17,7 @@ export { flatpakCommand } from './flatpak/index.js';
|
|
|
15
17
|
export * from './ship.js';
|
|
16
18
|
export * from './dlx.js';
|
|
17
19
|
export * from './install.js';
|
|
20
|
+
export * from './link.js';
|
|
18
21
|
export * from './foreach.js';
|
|
19
22
|
export * from './workspace.js';
|
|
20
23
|
export * from './pack.js';
|
|
@@ -39,3 +42,4 @@ export * from './storybook.js';
|
|
|
39
42
|
export * from './dev.js';
|
|
40
43
|
export * from './debug.js';
|
|
41
44
|
export * from './browse.js';
|
|
45
|
+
export { webextCommand } from './webext/index.js';
|
|
@@ -83,8 +83,8 @@ export interface EnsureProjectGjsEngineDeps {
|
|
|
83
83
|
*
|
|
84
84
|
* The engine is an OPTIONAL PEER of `@gjsify/cli` on purpose — a plain
|
|
85
85
|
* `npm install @gjsify/cli` on Node must not fetch prebuilds — but nobody resolves
|
|
86
|
-
* it for a project: npm 7+ skips optional peers and the native backend
|
|
87
|
-
*
|
|
86
|
+
* it for a project: npm 7+ skips optional peers and so does the native backend
|
|
87
|
+
* (it installs REQUIRED peers only). Under GJS there is no npm `rolldown`
|
|
88
88
|
* fallback, so `gjsify build` had nothing to load.
|
|
89
89
|
*
|
|
90
90
|
* Four conditions, and (b) is the one worth reading:
|