@gjsify/cli 0.38.1 → 0.40.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 +212 -195
- package/lib/cli-app.js +11 -25
- package/lib/commands/affected-classify.d.ts +16 -0
- package/lib/commands/affected-classify.js +304 -0
- package/lib/commands/affected.js +2 -291
- package/lib/commands/flatpak/check.js +34 -43
- package/lib/commands/flatpak/ci.js +1 -1
- package/lib/commands/flatpak/deps.js +10 -19
- package/lib/commands/flatpak/diff.js +1 -1
- package/lib/commands/flatpak/init.js +1 -1
- package/lib/commands/flatpak/release.js +7 -11
- package/lib/commands/flatpak/scaffold.d.ts +2 -4
- package/lib/commands/flatpak/scaffold.js +25 -298
- package/lib/commands/flatpak/sync-flathub.js +18 -36
- package/lib/commands/foreach.js +34 -20
- package/lib/commands/gettext.js +7 -4
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/ship.d.ts +11 -0
- package/lib/commands/ship.js +217 -0
- package/lib/config.d.ts +12 -0
- package/lib/config.js +15 -0
- package/lib/types/config-data.d.ts +136 -46
- package/lib/utils/app-metadata.d.ts +51 -0
- package/lib/utils/app-metadata.js +338 -0
- package/lib/utils/cli-runtime-closure.d.ts +23 -0
- package/lib/utils/cli-runtime-closure.js +274 -0
- package/lib/utils/publish-headers.d.ts +13 -0
- package/lib/utils/publish-headers.js +30 -4
- package/lib/utils/ship/ar.d.ts +21 -0
- package/lib/utils/ship/ar.js +57 -0
- package/lib/utils/ship/bytes.d.ts +4 -0
- package/lib/utils/ship/bytes.js +23 -0
- package/lib/utils/ship/cpio.d.ts +22 -0
- package/lib/utils/ship/cpio.js +93 -0
- package/lib/utils/ship/deb.d.ts +24 -0
- package/lib/utils/ship/deb.js +118 -0
- package/lib/utils/ship/depends.d.ts +62 -0
- package/lib/utils/ship/depends.js +202 -0
- package/lib/utils/ship/discover.d.ts +21 -0
- package/lib/utils/ship/discover.js +129 -0
- package/lib/utils/ship/formats.d.ts +5 -0
- package/lib/utils/ship/formats.js +83 -0
- package/lib/utils/ship/gi-namespaces.d.ts +7 -0
- package/lib/utils/ship/gi-namespaces.js +49 -0
- package/lib/utils/ship/gzip.d.ts +2 -0
- package/lib/utils/ship/gzip.js +21 -0
- package/lib/utils/ship/launcher.d.ts +7 -0
- package/lib/utils/ship/launcher.js +37 -0
- package/lib/utils/ship/payload.d.ts +30 -0
- package/lib/utils/ship/payload.js +59 -0
- package/lib/utils/ship/plan.d.ts +34 -0
- package/lib/utils/ship/plan.js +167 -0
- package/lib/utils/ship/rpm-header.d.ts +29 -0
- package/lib/utils/ship/rpm-header.js +170 -0
- package/lib/utils/ship/rpm.d.ts +12 -0
- package/lib/utils/ship/rpm.js +377 -0
- package/lib/utils/ship/scripts.d.ts +31 -0
- package/lib/utils/ship/scripts.js +76 -0
- package/lib/utils/ship/settings.d.ts +60 -0
- package/lib/utils/ship/settings.js +184 -0
- package/lib/utils/ship/stage-writer.d.ts +17 -0
- package/lib/utils/ship/stage-writer.js +49 -0
- package/lib/utils/ship/types.d.ts +114 -0
- package/lib/utils/ship/types.js +10 -0
- package/lib/utils/ship/version.d.ts +17 -0
- package/lib/utils/ship/version.js +55 -0
- package/lib/utils/spawn.d.ts +11 -1
- package/lib/utils/spawn.js +28 -4
- package/package.json +19 -18
- /package/lib/templates/{flatpak → app}/desktop.tmpl +0 -0
package/lib/commands/affected.js
CHANGED
|
@@ -56,7 +56,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
56
56
|
// every spec and fires only inside a GitHub Actions step.
|
|
57
57
|
import { appendFileSync } from 'node:fs';
|
|
58
58
|
import { readStdinLines } from '../utils/stdin.js';
|
|
59
|
-
import { discoverWorkspaces
|
|
59
|
+
import { discoverWorkspaces } from '@gjsify/workspace';
|
|
60
|
+
import { classifyAndExpand } from './affected-classify.js';
|
|
60
61
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
61
62
|
export const affectedCommand = {
|
|
62
63
|
command: 'affected',
|
|
@@ -95,296 +96,6 @@ export const affectedCommand = {
|
|
|
95
96
|
emit(args.format, result);
|
|
96
97
|
},
|
|
97
98
|
};
|
|
98
|
-
/** Patterns that force a full run. First-match wins; order is intentional. */
|
|
99
|
-
const GLOBAL_TRIGGERS = [
|
|
100
|
-
// The classifier itself + everything in its plumbing.
|
|
101
|
-
/^packages\/infra\/workspace\//,
|
|
102
|
-
/^packages\/infra\/cli\//,
|
|
103
|
-
/^packages\/infra\/rolldown-plugin-gjsify\//,
|
|
104
|
-
/^packages\/infra\/resolve-npm\//,
|
|
105
|
-
// The test framework every spec imports, as a *devDependency* — which the
|
|
106
|
-
// prod-deps-only closure below does not walk, so a change to it would yield a
|
|
107
|
-
// near-empty closure and silently skip every downstream test, while a bug in a
|
|
108
|
-
// matcher can break assertions anywhere.
|
|
109
|
-
/^packages\/gjs\/unit\//,
|
|
110
|
-
// The composite action every CI job sets itself up with: a change alters the
|
|
111
|
-
// environment of the whole matrix. Listed EXPLICITLY — it previously forced a
|
|
112
|
-
// full run only by falling through to the "unmatched files" path, which is luck
|
|
113
|
-
// rather than intent.
|
|
114
|
-
/^\.github\/actions\//,
|
|
115
|
-
// Cross-cutting dep + lockfile + root config.
|
|
116
|
-
/^gjsify-lock\.json$/,
|
|
117
|
-
/^package\.json$/,
|
|
118
|
-
/^tsconfig[^/]*\.json$/,
|
|
119
|
-
// The workflow file itself: a job-shape change is invisible until the workflow
|
|
120
|
-
// re-runs, so a path-filtered job cannot safely apply the new shape to an
|
|
121
|
-
// in-flight PR.
|
|
122
|
-
/^\.github\/workflows\/main\.yml$/,
|
|
123
|
-
/^scripts\/audit-runtimes\.mjs$/,
|
|
124
|
-
];
|
|
125
|
-
/**
|
|
126
|
-
* Inputs OWNED BY ANOTHER WORKFLOW — build-relevant, just not to `main.yml`.
|
|
127
|
-
*
|
|
128
|
-
* Adding a regex here is a claim with two halves and BOTH must hold: `main.yml`
|
|
129
|
-
* must not read the path, and some other workflow must have it in its own
|
|
130
|
-
* `paths:` filter so a break still reds a PR. The test is never "is this file
|
|
131
|
-
* unimportant" — none of these are.
|
|
132
|
-
*/
|
|
133
|
-
const OTHER_WORKFLOW_INPUTS = [
|
|
134
|
-
// Each sibling workflow's own definition. `main.yml` is deliberately absent: it
|
|
135
|
-
// is a GLOBAL_TRIGGER instead.
|
|
136
|
-
/^\.github\/workflows\/(deploy-docs|commitlint|release|release-cut|audit-runtimes|prebuilds|node-gi|napi|cli-cross-platform|build-ci-image|cancel-pr-runs)\.yml$/,
|
|
137
|
-
// `prebuilds.yml`'s toolchain (#838): the QEMU-emulated build script and the
|
|
138
|
-
// classifier deciding which native packages a prebuild run rebuilds. It is in
|
|
139
|
-
// both of that workflow's `paths:` filters; `main.yml` builds no prebuild and
|
|
140
|
-
// runs no emulated leg.
|
|
141
|
-
/^\.github\/prebuild-toolchain\//,
|
|
142
|
-
// The REPO-SCOPED manifest-conformance rules (#847), gated by
|
|
143
|
-
// `audit-runtimes.yml`, which runs on EVERY pull_request with no `paths:` filter;
|
|
144
|
-
// `prebuilds.yml` also treats it as a shared input (#843).
|
|
145
|
-
//
|
|
146
|
-
// The distinction is real, not a technicality: `main.yml` DOES run
|
|
147
|
-
// manifest-conformance code, but reaches it through
|
|
148
|
-
// `packages/infra/manifest-conformance/` — the PACKAGE, a normal workspace that
|
|
149
|
-
// maps and seeds like any other. Only the `scripts/`-side repo-scoped half is
|
|
150
|
-
// ignored, and that is precisely the half `main.yml` does not load.
|
|
151
|
-
/^scripts\/manifest-conformance\//,
|
|
152
|
-
// The authored status data (ADR 0016) + its generator. Same two halves:
|
|
153
|
-
// `main.yml` never runs `status:generate` and never opens `status/`, while
|
|
154
|
-
// `audit-runtimes.yml` runs the `status-data` rule over it on every PR.
|
|
155
|
-
//
|
|
156
|
-
// Naming the directory makes the `.md` case intentional rather than incidental:
|
|
157
|
-
// `status/*.md` already fell into IGNORE via the generic `/\.md$/i`, so the gap
|
|
158
|
-
// showed up only on `status/status.json`, which matched nothing, landed in
|
|
159
|
-
// `unmatched`, and paid for a FULL matrix run over a file `main.yml` cannot read.
|
|
160
|
-
/^status\//,
|
|
161
|
-
/^scripts\/generate-status\.mjs$/,
|
|
162
|
-
];
|
|
163
|
-
/** Patterns that contribute no seed and don't force a full run. */
|
|
164
|
-
const IGNORE = [
|
|
165
|
-
/\.md$/i,
|
|
166
|
-
/^refs\//,
|
|
167
|
-
/^website\//,
|
|
168
|
-
/^docs\//,
|
|
169
|
-
...OTHER_WORKFLOW_INPUTS,
|
|
170
|
-
// Neither `@gjsify/node-gi` (the Node-native GI engine) nor `@gjsify/napi` (the
|
|
171
|
-
// N-API host over SpiderMonkey) is a gjsify workspace member: the GJS-first
|
|
172
|
-
// install/foreach tooling cannot build their node-gyp addons or Vala+C++/meson
|
|
173
|
-
// shim, so `main.yml` neither builds nor tests them and their own workflows are
|
|
174
|
-
// the source of truth. Without the carve-outs their files map to no workspace,
|
|
175
|
-
// land in `unmatched`, and force a full run on every node-gi/napi PR.
|
|
176
|
-
/^packages\/node-gi\//,
|
|
177
|
-
/^packages\/napi\//,
|
|
178
|
-
// Flatpak build/distribution tooling (SDK-extension manifest + metainfo). No
|
|
179
|
-
// package-test consumers; its own `tests/e2e/flatpak-sdk-extension` runs on
|
|
180
|
-
// `tests/e2e/**` and global triggers. Worth revisiting: ignoring `.githooks/`
|
|
181
|
-
// for the same reason is what let a change walk past the suite guarding it (see
|
|
182
|
-
// SCRIPT_COUPLINGS), and IGNORE wins over every other rule.
|
|
183
|
-
/^flatpak\//,
|
|
184
|
-
/^LICENSE/,
|
|
185
|
-
/^\.gitignore$/,
|
|
186
|
-
/^\.gjsify-[^/]*\.md$/,
|
|
187
|
-
/^STATUS\.md$/,
|
|
188
|
-
/^CHANGELOG\.md$/,
|
|
189
|
-
/^AGENTS\.md$/,
|
|
190
|
-
/^CLAUDE\.md$/,
|
|
191
|
-
/^README\.md$/,
|
|
192
|
-
];
|
|
193
|
-
/** Patterns that suggest a test-only change. */
|
|
194
|
-
const TEST_PATHS = [/\.spec\.[mc]?[tj]sx?$/, /^tests\/(e2e|integration)\//];
|
|
195
|
-
const SCRIPT_COUPLINGS = [
|
|
196
|
-
{
|
|
197
|
-
// `.githooks/pre-commit` is guarded by an e2e suite and reachable from no
|
|
198
|
-
// workspace at all — not source, not a dependency, its consumer is git. So a
|
|
199
|
-
// change to it selected NOTHING and the e2e tier stayed off (that turns on
|
|
200
|
-
// for a `tests/e2e/**` touch): #1095 edited the hook's command line, merged
|
|
201
|
-
// green, and turned `main` red on the push run. Same shape #1028 noted —
|
|
202
|
-
// whoever changes the GUARDED thing walks past the guard, because the guard's
|
|
203
|
-
// trigger names the TEST's path instead of the thing under test.
|
|
204
|
-
//
|
|
205
|
-
// No extra seeds: there is no workspace to rebuild. The tier is the point.
|
|
206
|
-
match: /^\.githooks\//,
|
|
207
|
-
seeds: [],
|
|
208
|
-
runE2E: true,
|
|
209
|
-
why: 'tests/e2e/git-hooks-cli-bundle-staleness drives .githooks/pre-commit',
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
// `templates/*` ARE workspaces, so a template change already seeds its own
|
|
213
|
-
// `@gjsify/template-<name>` — not the consumer that matters. That consumer is
|
|
214
|
-
// `@gjsify/create-app`, unreachable twice over: it lives at
|
|
215
|
-
// `packages/infra/create-gjsify/`, so no directory-name search finds it, and
|
|
216
|
-
// it pulls the templates in through its own `scripts/process-template.mjs`
|
|
217
|
-
// build step (which resolves each `workspace:^` specifier and copies the
|
|
218
|
-
// result into `dist-templates/`), so there is no dependency edge either.
|
|
219
|
-
// `dist-templates/` is a build-output cache candidate, so once stale it
|
|
220
|
-
// stays stale.
|
|
221
|
-
match: /^templates\//,
|
|
222
|
-
seeds: ['@gjsify/create-app'],
|
|
223
|
-
// `tests/e2e/create-app` is the ONLY thing that would notice — it scaffolds a
|
|
224
|
-
// project out of `dist-templates/` and builds it. Without this a
|
|
225
|
-
// templates-only PR got neither the regenerated templates nor that suite;
|
|
226
|
-
// #853 passed only by also touching root `package.json`.
|
|
227
|
-
runE2E: true,
|
|
228
|
-
why: '@gjsify/create-app build → node scripts/process-template.mjs reads templates/',
|
|
229
|
-
},
|
|
230
|
-
];
|
|
231
|
-
function classifyAndExpand(workspaces, changedFiles) {
|
|
232
|
-
const files = changedFiles.map((f) => f.replace(/\\/g, '/')).filter((f) => f.length > 0);
|
|
233
|
-
if (files.length === 0) {
|
|
234
|
-
return {
|
|
235
|
-
global: false,
|
|
236
|
-
reason: 'empty-diff',
|
|
237
|
-
workspaces: [],
|
|
238
|
-
runE2E: false,
|
|
239
|
-
runIntegration: false,
|
|
240
|
-
skipAll: true,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
// Ignored files go FIRST, before the global-trigger check: ignore wins over
|
|
244
|
-
// global, so `packages/infra/cli/README.md` must not force a full run.
|
|
245
|
-
const remaining = [];
|
|
246
|
-
for (const f of files) {
|
|
247
|
-
if (IGNORE.some((re) => re.test(f)))
|
|
248
|
-
continue;
|
|
249
|
-
remaining.push(f);
|
|
250
|
-
}
|
|
251
|
-
if (remaining.length === 0) {
|
|
252
|
-
return {
|
|
253
|
-
global: false,
|
|
254
|
-
reason: 'ignored-only',
|
|
255
|
-
workspaces: [],
|
|
256
|
-
runE2E: false,
|
|
257
|
-
runIntegration: false,
|
|
258
|
-
skipAll: true,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
// Global triggers short-circuit — checked on the non-ignored remainder.
|
|
262
|
-
for (const f of remaining) {
|
|
263
|
-
for (const re of GLOBAL_TRIGGERS) {
|
|
264
|
-
if (re.test(f)) {
|
|
265
|
-
return {
|
|
266
|
-
global: true,
|
|
267
|
-
reason: `global-trigger ${re.source} matched ${f}`,
|
|
268
|
-
workspaces: workspaces.map((w) => w.name),
|
|
269
|
-
runE2E: true,
|
|
270
|
-
runIntegration: true,
|
|
271
|
-
skipAll: false,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
const { matched, unmatched } = workspacesForChangedFiles(workspaces, remaining);
|
|
277
|
-
// Seeds no graph edge can reach. BEFORE the `unmatched` bail-out, so the table
|
|
278
|
-
// also works for a coupled directory that is not itself a workspace — such a
|
|
279
|
-
// file would otherwise force a full run and never consult the declared seeds.
|
|
280
|
-
const knownNames = new Set(workspaces.map((w) => w.name));
|
|
281
|
-
const couplingSeeds = new Set();
|
|
282
|
-
const couplingAccounted = new Set();
|
|
283
|
-
let couplingRunE2E = false;
|
|
284
|
-
for (const c of SCRIPT_COUPLINGS) {
|
|
285
|
-
const hits = remaining.filter((f) => c.match.test(f));
|
|
286
|
-
if (hits.length === 0)
|
|
287
|
-
continue;
|
|
288
|
-
for (const h of hits)
|
|
289
|
-
couplingAccounted.add(h);
|
|
290
|
-
for (const s of c.seeds) {
|
|
291
|
-
// A declared seed that is not a workspace means the table drifted from the
|
|
292
|
-
// tree (renamed, moved, removed). Fail towards the full run and SAY SO: a
|
|
293
|
-
// missing rebuild here is invisible, so this must never degrade quietly
|
|
294
|
-
// back into the bug the table exists to prevent.
|
|
295
|
-
if (!knownNames.has(s)) {
|
|
296
|
-
return {
|
|
297
|
-
global: true,
|
|
298
|
-
reason: `script-coupling seed ${s} is not a workspace (${c.match.source} → ${c.why}); ` +
|
|
299
|
-
`SCRIPT_COUPLINGS in commands/affected.ts is stale`,
|
|
300
|
-
workspaces: workspaces.map((w) => w.name),
|
|
301
|
-
runE2E: true,
|
|
302
|
-
runIntegration: true,
|
|
303
|
-
skipAll: false,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
couplingSeeds.add(s);
|
|
307
|
-
}
|
|
308
|
-
if (c.runE2E)
|
|
309
|
-
couplingRunE2E = true;
|
|
310
|
-
}
|
|
311
|
-
// Unmatched-but-not-ignored (a new top-level dotfile, an uncarved `scripts/`
|
|
312
|
-
// file) falls back to the full run. A file a coupling claimed does not count.
|
|
313
|
-
const stillUnmatched = unmatched.filter((f) => !couplingAccounted.has(f));
|
|
314
|
-
if (stillUnmatched.length > 0) {
|
|
315
|
-
return {
|
|
316
|
-
global: true,
|
|
317
|
-
reason: `unmatched files (${stillUnmatched.length}): ${stillUnmatched.slice(0, 3).join(', ')}${stillUnmatched.length > 3 ? '…' : ''}`,
|
|
318
|
-
workspaces: workspaces.map((w) => w.name),
|
|
319
|
-
runE2E: true,
|
|
320
|
-
runIntegration: true,
|
|
321
|
-
skipAll: false,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
for (const s of couplingSeeds)
|
|
325
|
-
matched.add(s);
|
|
326
|
-
// Every remaining file is a spec / e2e / integration path under ONE workspace, so
|
|
327
|
-
// skip the closure: test code has no downstream consumers. A coupling disables
|
|
328
|
-
// this — its extra seeds exist precisely because a consumer DOES care.
|
|
329
|
-
const testOnly = couplingSeeds.size === 0 && remaining.every((f) => TEST_PATHS.some((re) => re.test(f)));
|
|
330
|
-
if (testOnly && matched.size === 1) {
|
|
331
|
-
const only = [...matched][0];
|
|
332
|
-
// An e2e- or integration-only change still needs its own job.
|
|
333
|
-
const touchedE2E = remaining.some((f) => f.startsWith('tests/e2e/'));
|
|
334
|
-
const touchedIntegration = remaining.some((f) => f.startsWith('tests/integration/'));
|
|
335
|
-
return {
|
|
336
|
-
global: false,
|
|
337
|
-
reason: `test-only (${remaining.length} file(s) in ${only})`,
|
|
338
|
-
workspaces: [only],
|
|
339
|
-
runE2E: touchedE2E,
|
|
340
|
-
runIntegration: touchedIntegration || isIntegrationWorkspace(only),
|
|
341
|
-
skipAll: false,
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
// PRODUCTION dependencies only. The closure answers "whose tests must re-run
|
|
345
|
-
// because a package they depend on changed", which is a RUNTIME relationship;
|
|
346
|
-
// walking devDependency edges conflated it with the build/test toolchain, so ANY
|
|
347
|
-
// single-package change fanned out to ~210 of 221 workspaces and selective CI
|
|
348
|
-
// bought almost nothing. Prod-only collapses a typical seed to a handful (sqlite
|
|
349
|
-
// 210→4, fetch 210→34) while keeping every real runtime dependent.
|
|
350
|
-
//
|
|
351
|
-
// Safe against under-selection: no `*.spec.ts` imports a CROSS-PACKAGE sibling
|
|
352
|
-
// via a devDependency (the only such imports are self-imports, covered by the
|
|
353
|
-
// package being its own seed), `@gjsify/unit` is a GLOBAL_TRIGGER above, and
|
|
354
|
-
// every push-to-main plus the nightly cron still run the FULL suite.
|
|
355
|
-
const reverse = buildReverseDependencyGraph(workspaces, { includeDev: false });
|
|
356
|
-
const closure = affectedClosure(reverse, [...matched]);
|
|
357
|
-
// `includeDev: true`, deliberately broader than the reverse closure: integration
|
|
358
|
-
// packages declare the pillars they exercise as `dependencies`, but the wider
|
|
359
|
-
// walk tolerates a devDep-declared edge too. Over-running an integration suite is
|
|
360
|
-
// cheap; missing one is not.
|
|
361
|
-
const forward = buildDependencyGraph(workspaces, { includeDev: true });
|
|
362
|
-
let runIntegration = false;
|
|
363
|
-
for (const [from, deps] of forward.edges) {
|
|
364
|
-
if (!isIntegrationWorkspace(from))
|
|
365
|
-
continue;
|
|
366
|
-
for (const dep of deps) {
|
|
367
|
-
if (closure.has(dep)) {
|
|
368
|
-
closure.add(from);
|
|
369
|
-
runIntegration = true;
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const runE2E = remaining.some((f) => f.startsWith('tests/e2e/')) || couplingRunE2E;
|
|
375
|
-
return {
|
|
376
|
-
global: false,
|
|
377
|
-
reason: `closure (${closure.size} ws from ${matched.size} seed(s))` +
|
|
378
|
-
(couplingSeeds.size > 0 ? `, ${couplingSeeds.size} via script-coupling` : ''),
|
|
379
|
-
workspaces: [...closure].sort(),
|
|
380
|
-
runE2E,
|
|
381
|
-
runIntegration,
|
|
382
|
-
skipAll: false,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
function isIntegrationWorkspace(name) {
|
|
386
|
-
return name.startsWith('@gjsify/integration-');
|
|
387
|
-
}
|
|
388
99
|
function runGitDiff(cwd, base, head) {
|
|
389
100
|
// `base...head` lists changed paths on `head` relative to the MERGE-BASE, which
|
|
390
101
|
// matches what a GitHub PR diff shows and survives stacked PRs without picking up
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// `flatpak install -y flathub org.flatpak.Builder`.
|
|
12
12
|
import { existsSync, readdirSync } from 'node:fs';
|
|
13
13
|
import { resolve, join } from 'node:path';
|
|
14
|
-
import {
|
|
14
|
+
import { spawnToCompletion } from '../../utils/spawn.js';
|
|
15
15
|
import { readPackageJson, looksLikeAppId } from './utils.js';
|
|
16
16
|
import { Config } from '../../config.js';
|
|
17
17
|
export const flatpakCheckCommand = {
|
|
@@ -52,7 +52,7 @@ export const flatpakCheckCommand = {
|
|
|
52
52
|
},
|
|
53
53
|
handler: async (args) => {
|
|
54
54
|
const cfg = new Config();
|
|
55
|
-
const configData = await cfg.
|
|
55
|
+
const configData = await cfg.forCommand().catch(() => ({}));
|
|
56
56
|
const flatpak = configData.flatpak ?? {};
|
|
57
57
|
const cwd = process.cwd();
|
|
58
58
|
const appId = resolveAppId(args.manifest, flatpak, cwd);
|
|
@@ -123,48 +123,39 @@ function resolveManifestPath(explicit, appId, cwd) {
|
|
|
123
123
|
throw new Error('gjsify flatpak check: no manifest path given and could not auto-detect. ' +
|
|
124
124
|
'Pass the manifest as a positional argument.');
|
|
125
125
|
}
|
|
126
|
-
function runLinter(bin, args, verbose) {
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
async function runLinter(bin, args, verbose) {
|
|
127
|
+
// `completion: 'return'` — the handler's SUCCESS path ends with a `console.log`
|
|
128
|
+
// and returns, so under GJS an async spawn leaves `ensureMainLoop`'s loop armed
|
|
129
|
+
// and the command parks at 0 % CPU after every check has passed. The failure
|
|
130
|
+
// path exits, which is why the hang would have shown up only on a clean run.
|
|
131
|
+
let result;
|
|
132
|
+
try {
|
|
133
|
+
result = await spawnToCompletion(bin, args, {
|
|
134
|
+
completion: 'return',
|
|
135
|
+
stdio: verbose ? 'inherit' : 'capture',
|
|
130
136
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
child.stderr?.on('data', (c) => {
|
|
140
|
-
stderr += c;
|
|
141
|
-
});
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
const e = err;
|
|
140
|
+
if (e.code === 'ENOENT') {
|
|
141
|
+
console.error(`[gjsify flatpak check] ${bin} not found in PATH.\n` +
|
|
142
|
+
`Install via:\n` +
|
|
143
|
+
` flatpak install -y flathub org.flatpak.Builder\n` +
|
|
144
|
+
` alias ${bin}="flatpak run --command=${bin} org.flatpak.Builder"`);
|
|
142
145
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const ok = code === 0;
|
|
158
|
-
const tag = ok ? 'OK' : 'FAIL';
|
|
159
|
-
console.log(`[gjsify flatpak check] ${tag}: ${bin} ${args.join(' ')}`);
|
|
160
|
-
if (!ok && !verbose) {
|
|
161
|
-
if (stdout.trim())
|
|
162
|
-
console.log(stdout.trimEnd());
|
|
163
|
-
if (stderr.trim())
|
|
164
|
-
console.error(stderr.trimEnd());
|
|
165
|
-
}
|
|
166
|
-
resolveP(ok);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
146
|
+
else {
|
|
147
|
+
console.error(`[gjsify flatpak check] ${bin} failed to spawn: ${e.message}`);
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const ok = result.code === 0;
|
|
152
|
+
console.log(`[gjsify flatpak check] ${ok ? 'OK' : 'FAIL'}: ${bin} ${args.join(' ')}`);
|
|
153
|
+
if (!ok && !verbose) {
|
|
154
|
+
if (result.stdout?.trim())
|
|
155
|
+
console.log(result.stdout.trimEnd());
|
|
156
|
+
if (result.stderr?.trim())
|
|
157
|
+
console.error(result.stderr.trimEnd());
|
|
158
|
+
}
|
|
159
|
+
return ok;
|
|
169
160
|
}
|
|
170
161
|
void join;
|
|
@@ -58,7 +58,7 @@ export const flatpakCiCommand = {
|
|
|
58
58
|
handler: async (args) => {
|
|
59
59
|
const cwd = process.cwd();
|
|
60
60
|
const cfg = new Config();
|
|
61
|
-
const configData = await cfg.
|
|
61
|
+
const configData = await cfg.forCommand().catch(() => ({}));
|
|
62
62
|
const flatpak = configData.flatpak ?? {};
|
|
63
63
|
const pkg = readPackageJson(cwd);
|
|
64
64
|
const appId = flatpak.appId ?? (looksLikeAppId(pkg.name) ? pkg.name : undefined);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// https://github.com/flatpak/flatpak-builder-tools — installed via
|
|
8
8
|
// `pipx install flatpak-node-generator` or `pip install --user
|
|
9
9
|
// flatpak-node-generator`.
|
|
10
|
-
import {
|
|
10
|
+
import { describeExit, spawnToCompletion } from '../../utils/spawn.js';
|
|
11
11
|
import { existsSync } from 'node:fs';
|
|
12
12
|
import { dirname, resolve } from 'node:path';
|
|
13
13
|
import { mkdirSync } from 'node:fs';
|
|
@@ -64,25 +64,16 @@ export const flatpakDepsCommand = {
|
|
|
64
64
|
if (args.verbose) {
|
|
65
65
|
console.log(`[gjsify flatpak deps] flatpak-node-generator ${cmdArgs.join(' ')}`);
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
'(see https://github.com/flatpak/flatpak-builder-tools/tree/master/node).'));
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
rej(err);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
child.on('exit', (code) => {
|
|
80
|
-
if (code === 0)
|
|
81
|
-
res();
|
|
82
|
-
else
|
|
83
|
-
rej(new Error(`gjsify flatpak deps: flatpak-node-generator exited with status ${code}`));
|
|
84
|
-
});
|
|
67
|
+
// `completion: 'return'` — this handler returns; see `utils/spawn.ts`.
|
|
68
|
+
const result = await spawnToCompletion('flatpak-node-generator', cmdArgs, {
|
|
69
|
+
completion: 'return',
|
|
70
|
+
notFound: () => new Error('gjsify flatpak deps: flatpak-node-generator not found. ' +
|
|
71
|
+
'Install via `pipx install flatpak-node-generator` ' +
|
|
72
|
+
'(see https://github.com/flatpak/flatpak-builder-tools/tree/master/node).'),
|
|
85
73
|
});
|
|
74
|
+
if (result.code !== 0) {
|
|
75
|
+
throw new Error(`gjsify flatpak deps: flatpak-node-generator exited with ${describeExit(result)}`);
|
|
76
|
+
}
|
|
86
77
|
console.log(`[gjsify flatpak deps] wrote ${out}`);
|
|
87
78
|
},
|
|
88
79
|
};
|
|
@@ -60,7 +60,7 @@ export const flatpakDiffCommand = {
|
|
|
60
60
|
handler: async (args) => {
|
|
61
61
|
const cwd = process.cwd();
|
|
62
62
|
const cfg = new Config();
|
|
63
|
-
const configData = await cfg.
|
|
63
|
+
const configData = await cfg.forCommand().catch(() => ({}));
|
|
64
64
|
const flatpak = configData.flatpak ?? {};
|
|
65
65
|
const appId = args.appId ?? flatpak.appId ?? readPackageJson(cwd).name;
|
|
66
66
|
if (!appId) {
|
|
@@ -97,7 +97,7 @@ export const flatpakInitCommand = {
|
|
|
97
97
|
},
|
|
98
98
|
handler: async (args) => {
|
|
99
99
|
const cfg = new Config();
|
|
100
|
-
const configData = await cfg.
|
|
100
|
+
const configData = await cfg.forCommand().catch(() => ({}));
|
|
101
101
|
const flatpak = configData.flatpak ?? {};
|
|
102
102
|
const cwd = process.cwd();
|
|
103
103
|
const pkg = readPackageJson(cwd);
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
// behaviour to running the sub-commands directly. The orchestrator
|
|
14
14
|
// stops at the first failure and reports which step blocked, with a
|
|
15
15
|
// clear command the user can re-run by hand.
|
|
16
|
-
import { execFile
|
|
16
|
+
import { execFile } from 'node:child_process';
|
|
17
|
+
import { describeExit, spawnToCompletion } from '../../utils/spawn.js';
|
|
17
18
|
import { promisify } from 'node:util';
|
|
18
19
|
import { fileURLToPath } from 'node:url';
|
|
19
20
|
import { resolve, dirname } from 'node:path';
|
|
@@ -139,14 +140,9 @@ function resolveCliEntry() {
|
|
|
139
140
|
}
|
|
140
141
|
/** Spawn `node <args>` and reject on non-zero exit. */
|
|
141
142
|
async function runNode(args, cwd) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
resolvePromise();
|
|
148
|
-
else
|
|
149
|
-
reject(new Error(`sub-command exited with code ${code}: node ${args.join(' ')}`));
|
|
150
|
-
});
|
|
151
|
-
});
|
|
143
|
+
// `completion: 'return'` — the handler returns; see `utils/spawn.ts`.
|
|
144
|
+
const result = await spawnToCompletion('node', args, { completion: 'return', cwd });
|
|
145
|
+
if (result.code !== 0) {
|
|
146
|
+
throw new Error(`sub-command exited with ${describeExit(result)}: node ${args.join(' ')}`);
|
|
147
|
+
}
|
|
152
148
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ConfigDataFlatpak } from '../../types/config-data.js';
|
|
2
|
+
import { type MissingFieldError } from '../../utils/app-metadata.js';
|
|
3
|
+
export type { MissingFieldError };
|
|
2
4
|
export interface ScaffoldInputs {
|
|
3
5
|
appId: string;
|
|
4
6
|
name: string;
|
|
@@ -6,10 +8,6 @@ export interface ScaffoldInputs {
|
|
|
6
8
|
kind: 'app' | 'cli';
|
|
7
9
|
flatpak: ConfigDataFlatpak;
|
|
8
10
|
}
|
|
9
|
-
export interface MissingFieldError {
|
|
10
|
-
field: string;
|
|
11
|
-
hint: string;
|
|
12
|
-
}
|
|
13
11
|
/**
|
|
14
12
|
* Validate that the config has the minimum set of fields required for
|
|
15
13
|
* MetaInfo XML rendering. Returns the list of missing fields with
|