@gjsify/cli 0.4.42 → 0.4.44
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/README.md +3 -1
- package/dist/cli.gjs.mjs +25 -22
- package/lib/bundler-pick.js +28 -10
- package/lib/commands/affected.js +32 -24
- package/lib/commands/flatpak/index.d.ts +2 -1
- package/lib/commands/flatpak/index.js +4 -2
- package/lib/commands/flatpak/sources.d.ts +11 -0
- package/lib/commands/flatpak/sources.js +318 -0
- package/lib/commands/self-update.d.ts +1 -0
- package/lib/commands/self-update.js +35 -10
- package/package.json +16 -16
package/lib/bundler-pick.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
// Phase D-2.B.5b
|
|
1
|
+
// Runtime bundler pick (Phase D-2.B.5b → D-3.1 runtime-aware default).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// `@gjsify/rolldown-native` Vala/Rust prebuild via
|
|
3
|
+
// The engine is chosen by `shouldUseNative()` per host runtime:
|
|
4
|
+
// - Node: npm `rolldown(opts).write(opts.output)`.
|
|
5
|
+
// - GJS: `@gjsify/rolldown-native` (the Vala/Rust prebuild) via
|
|
6
|
+
// `bundleWithPlugins()`. npm `rolldown` is a Rust N-API addon
|
|
7
|
+
// that CANNOT load under GJS, so the native engine is the GJS
|
|
8
|
+
// DEFAULT — no env var needed (this is the Node-free build
|
|
9
|
+
// chain's bundler). This writes the BundleOutput to disk
|
|
10
|
+
// (`runNativeBundle`), replicating npm rolldown's `.write()`
|
|
11
|
+
// incl. nested chunk/asset subdirectories.
|
|
6
12
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// different code path.
|
|
13
|
+
// `GJSIFY_BUNDLER=native|npm` is an explicit override: `native` forces
|
|
14
|
+
// the native engine and throws if its prebuild isn't loadable for the
|
|
15
|
+
// running architecture (instead of silently switching engines); `npm`
|
|
16
|
+
// forces the npm crate (Node only).
|
|
12
17
|
//
|
|
13
18
|
// Plugin shape conversion: rolldown plugins may declare hooks either
|
|
14
19
|
// as bare functions (`load(id)`) or as `{filter, handler}` objects.
|
|
@@ -264,10 +269,23 @@ async function runNativeBundle(finalOpts) {
|
|
|
264
269
|
if (outDir)
|
|
265
270
|
await fs.mkdir(outDir, { recursive: true });
|
|
266
271
|
for (const item of result.output) {
|
|
272
|
+
// A single `--outfile` build collapses to that exact path; otherwise
|
|
273
|
+
// each chunk/asset lands at `outDir/<fileName>`. Nested fileNames
|
|
274
|
+
// (e.g. `_virtual/_rolldown/runtime.js` from a `--library` /
|
|
275
|
+
// code-split build) live in subdirectories npm rolldown's `.write()`
|
|
276
|
+
// creates implicitly — replicate that here, or `writeFile` throws
|
|
277
|
+
// ENOENT on the missing parent (regression: `--library` under the
|
|
278
|
+
// native GJS engine). Also write text assets for `.write()` parity.
|
|
279
|
+
const target = outputCfg.file && item.type === 'chunk' && result.output.length === 1
|
|
280
|
+
? outputCfg.file
|
|
281
|
+
: path.join(outDir, item.fileName);
|
|
282
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
267
283
|
if (item.type === 'chunk') {
|
|
268
|
-
const target = outputCfg.file && result.output.length === 1 ? outputCfg.file : path.join(outDir, item.fileName);
|
|
269
284
|
await fs.writeFile(target, item.code, 'utf8');
|
|
270
285
|
}
|
|
286
|
+
else if (item.sourceText !== undefined) {
|
|
287
|
+
await fs.writeFile(target, item.sourceText, 'utf8');
|
|
288
|
+
}
|
|
271
289
|
}
|
|
272
290
|
// Synthesize the RolldownOutput shape downstream code touches.
|
|
273
291
|
return synthRolldownOutput(result);
|
package/lib/commands/affected.js
CHANGED
|
@@ -10,15 +10,19 @@
|
|
|
10
10
|
// Classifier table (first-match wins) handles the cases that aren't a
|
|
11
11
|
// straightforward "file lives in workspace X":
|
|
12
12
|
//
|
|
13
|
-
// 1.
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
13
|
+
// 1. IGNORE — pure-docs / *.md / website / refs/ submodule / unrelated
|
|
14
|
+
// workflow files / LICENSE / .gitignore. Discarded FIRST, before every
|
|
15
|
+
// other rule: an ignored file is never build-relevant, not even inside
|
|
16
|
+
// a GLOBAL_TRIGGERS directory (e.g. `packages/infra/cli/README.md` must
|
|
17
|
+
// not force a full run just because `packages/infra/cli/` is a global
|
|
18
|
+
// trigger). Ignore wins over global.
|
|
19
19
|
//
|
|
20
|
-
// 2.
|
|
21
|
-
//
|
|
20
|
+
// 2. GLOBAL_TRIGGERS — a non-ignored change touching infra the classifier
|
|
21
|
+
// itself depends on (`@gjsify/workspace`, `@gjsify/cli`, the bundler
|
|
22
|
+
// plugins, the lockfile, root tsconfig / package.json, this very
|
|
23
|
+
// workflow file). Emits `global=true` → CI must run the full suite. We
|
|
24
|
+
// can't trust the closure when the algorithm that computes it just
|
|
25
|
+
// changed.
|
|
22
26
|
//
|
|
23
27
|
// 3. TEST_ONLY — every changed file under ONE workspace is a spec
|
|
24
28
|
// file, e2e fixture, or integration test. Seed = that workspace
|
|
@@ -133,22 +137,11 @@ function classifyAndExpand(workspaces, rootDir, changedFiles) {
|
|
|
133
137
|
skipAll: true,
|
|
134
138
|
};
|
|
135
139
|
}
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
global: true,
|
|
142
|
-
reason: `global-trigger ${re.source} matched ${f}`,
|
|
143
|
-
workspaces: workspaces.map((w) => w.name),
|
|
144
|
-
runE2E: true,
|
|
145
|
-
runIntegration: true,
|
|
146
|
-
skipAll: false,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
// Drop ignored files; collect the remainder.
|
|
140
|
+
// Drop ignored files FIRST — before the global-trigger check. An ignored
|
|
141
|
+
// file (docs, *.md, LICENSE, .gitignore, …) is never build-relevant, not
|
|
142
|
+
// even inside a GLOBAL_TRIGGERS directory: `packages/infra/cli/README.md`
|
|
143
|
+
// must NOT force a full run just because `packages/infra/cli/` is a global
|
|
144
|
+
// trigger. Ignore wins over global.
|
|
152
145
|
const remaining = [];
|
|
153
146
|
for (const f of files) {
|
|
154
147
|
if (IGNORE.some((re) => re.test(f)))
|
|
@@ -165,6 +158,21 @@ function classifyAndExpand(workspaces, rootDir, changedFiles) {
|
|
|
165
158
|
skipAll: true,
|
|
166
159
|
};
|
|
167
160
|
}
|
|
161
|
+
// Global triggers short-circuit — checked on the non-ignored remainder.
|
|
162
|
+
for (const f of remaining) {
|
|
163
|
+
for (const re of GLOBAL_TRIGGERS) {
|
|
164
|
+
if (re.test(f)) {
|
|
165
|
+
return {
|
|
166
|
+
global: true,
|
|
167
|
+
reason: `global-trigger ${re.source} matched ${f}`,
|
|
168
|
+
workspaces: workspaces.map((w) => w.name),
|
|
169
|
+
runE2E: true,
|
|
170
|
+
runIntegration: true,
|
|
171
|
+
skipAll: false,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
168
176
|
// Map files → workspaces. Files outside any workspace stay in `unmatched`.
|
|
169
177
|
const { matched, unmatched } = workspacesForChangedFiles(workspaces, rootDir, remaining);
|
|
170
178
|
// Unmatched-but-not-ignored files are suspicious enough to fall back to
|
|
@@ -2,10 +2,11 @@ import type { Command } from '../../types/index.js';
|
|
|
2
2
|
import { flatpakInitCommand } from './init.js';
|
|
3
3
|
import { flatpakBuildCommand } from './build.js';
|
|
4
4
|
import { flatpakDepsCommand } from './deps.js';
|
|
5
|
+
import { flatpakSourcesCommand } from './sources.js';
|
|
5
6
|
import { flatpakCiCommand } from './ci.js';
|
|
6
7
|
import { flatpakCheckCommand } from './check.js';
|
|
7
8
|
import { flatpakSyncFlathubCommand } from './sync-flathub.js';
|
|
8
9
|
import { flatpakDiffCommand } from './diff.js';
|
|
9
10
|
import { flatpakReleaseCommand } from './release.js';
|
|
10
11
|
export declare const flatpakCommand: Command;
|
|
11
|
-
export { flatpakInitCommand, flatpakBuildCommand, flatpakDepsCommand, flatpakCiCommand, flatpakCheckCommand, flatpakSyncFlathubCommand, flatpakDiffCommand, flatpakReleaseCommand, };
|
|
12
|
+
export { flatpakInitCommand, flatpakBuildCommand, flatpakDepsCommand, flatpakSourcesCommand, flatpakCiCommand, flatpakCheckCommand, flatpakSyncFlathubCommand, flatpakDiffCommand, flatpakReleaseCommand, };
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { flatpakInitCommand } from './init.js';
|
|
7
7
|
import { flatpakBuildCommand } from './build.js';
|
|
8
8
|
import { flatpakDepsCommand } from './deps.js';
|
|
9
|
+
import { flatpakSourcesCommand } from './sources.js';
|
|
9
10
|
import { flatpakCiCommand } from './ci.js';
|
|
10
11
|
import { flatpakCheckCommand } from './check.js';
|
|
11
12
|
import { flatpakSyncFlathubCommand } from './sync-flathub.js';
|
|
@@ -13,12 +14,13 @@ import { flatpakDiffCommand } from './diff.js';
|
|
|
13
14
|
import { flatpakReleaseCommand } from './release.js';
|
|
14
15
|
export const flatpakCommand = {
|
|
15
16
|
command: 'flatpak <subcommand>',
|
|
16
|
-
description: 'Flatpak toolchain: init/build/deps/ci/check/sync-flathub/diff/release subcommands for shipping GJS apps and CLIs as Flatpaks.',
|
|
17
|
+
description: 'Flatpak toolchain: init/build/deps/sources/ci/check/sync-flathub/diff/release subcommands for shipping GJS apps and CLIs as Flatpaks.',
|
|
17
18
|
builder: (yargs) => {
|
|
18
19
|
return yargs
|
|
19
20
|
.command(flatpakInitCommand.command, flatpakInitCommand.description, flatpakInitCommand.builder, flatpakInitCommand.handler)
|
|
20
21
|
.command(flatpakBuildCommand.command, flatpakBuildCommand.description, flatpakBuildCommand.builder, flatpakBuildCommand.handler)
|
|
21
22
|
.command(flatpakDepsCommand.command, flatpakDepsCommand.description, flatpakDepsCommand.builder, flatpakDepsCommand.handler)
|
|
23
|
+
.command(flatpakSourcesCommand.command, flatpakSourcesCommand.description, flatpakSourcesCommand.builder, flatpakSourcesCommand.handler)
|
|
22
24
|
.command(flatpakCiCommand.command, flatpakCiCommand.description, flatpakCiCommand.builder, flatpakCiCommand.handler)
|
|
23
25
|
.command(flatpakCheckCommand.command, flatpakCheckCommand.description, flatpakCheckCommand.builder, flatpakCheckCommand.handler)
|
|
24
26
|
.command(flatpakSyncFlathubCommand.command, flatpakSyncFlathubCommand.description, flatpakSyncFlathubCommand.builder, flatpakSyncFlathubCommand.handler)
|
|
@@ -28,4 +30,4 @@ export const flatpakCommand = {
|
|
|
28
30
|
.strict();
|
|
29
31
|
},
|
|
30
32
|
};
|
|
31
|
-
export { flatpakInitCommand, flatpakBuildCommand, flatpakDepsCommand, flatpakCiCommand, flatpakCheckCommand, flatpakSyncFlathubCommand, flatpakDiffCommand, flatpakReleaseCommand, };
|
|
33
|
+
export { flatpakInitCommand, flatpakBuildCommand, flatpakDepsCommand, flatpakSourcesCommand, flatpakCiCommand, flatpakCheckCommand, flatpakSyncFlathubCommand, flatpakDiffCommand, flatpakReleaseCommand, };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
type LockfileType = 'gjsify' | 'npm' | 'yarn' | 'pnpm';
|
|
3
|
+
interface FlatpakSourcesOptions {
|
|
4
|
+
lockfile?: string;
|
|
5
|
+
type?: LockfileType;
|
|
6
|
+
out?: string;
|
|
7
|
+
cacheRoot?: string;
|
|
8
|
+
printModule?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const flatpakSourcesCommand: Command<unknown, FlatpakSourcesOptions>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// `gjsify flatpak sources` — turn a lockfile into a flatpak-builder `sources`
|
|
2
|
+
// array so a Flathub build (which forbids network access) can vendor every npm
|
|
3
|
+
// tarball offline.
|
|
4
|
+
//
|
|
5
|
+
// Auto-detects the lockfile format — gjsify's own `gjsify-lock.json`, npm's
|
|
6
|
+
// `package-lock.json`, classic `yarn.lock`, or pnpm's `pnpm-lock.yaml` — so it
|
|
7
|
+
// is a Node-free, dependency-free replacement for the Python
|
|
8
|
+
// `flatpak-node-generator` that `gjsify flatpak deps` wraps (one tool, no pipx).
|
|
9
|
+
//
|
|
10
|
+
// Each locked tarball becomes a flatpak `file` source that downloads straight
|
|
11
|
+
// into gjsify's content-addressed tarball cache layout:
|
|
12
|
+
//
|
|
13
|
+
// $XDG_CACHE_HOME/gjsify/tarballs/v1/<algo>/<hex[0:2]>/<full-hex>.tgz
|
|
14
|
+
//
|
|
15
|
+
// (the exact path `install-tarball-cache.ts` computes from the SRI integrity).
|
|
16
|
+
// flatpak-builder fetches them in its network-allowed download phase; the build
|
|
17
|
+
// itself then runs `gjsify install --immutable` fully offline, finding every
|
|
18
|
+
// tarball in the pre-populated cache.
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
20
|
+
import { dirname, join, resolve } from 'node:path';
|
|
21
|
+
import { Buffer } from 'node:buffer';
|
|
22
|
+
/** Checksum algorithms flatpak-builder accepts on a `file` source. */
|
|
23
|
+
const FLATPAK_ALGOS = new Set(['sha256', 'sha512']);
|
|
24
|
+
/** Parse an SRI integrity (`sha512-<base64>`) into `{ algorithm, hex }`. Mirrors
|
|
25
|
+
* `install-tarball-cache.ts` so the emitted cache paths match byte-for-byte. */
|
|
26
|
+
function parseSri(integrity) {
|
|
27
|
+
if (!integrity)
|
|
28
|
+
return null;
|
|
29
|
+
const dash = integrity.indexOf('-');
|
|
30
|
+
if (dash <= 0 || dash === integrity.length - 1)
|
|
31
|
+
return null;
|
|
32
|
+
const algorithm = integrity.slice(0, dash);
|
|
33
|
+
const b64 = integrity.slice(dash + 1).replace(/=+$/, '');
|
|
34
|
+
let hex;
|
|
35
|
+
try {
|
|
36
|
+
hex = Buffer.from(b64, 'base64').toString('hex');
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (hex.length < 4)
|
|
42
|
+
return null;
|
|
43
|
+
return { algorithm, hex };
|
|
44
|
+
}
|
|
45
|
+
/** gjsify-lock.json + package-lock.json share npm's v2/v3 `packages` shape:
|
|
46
|
+
* a map keyed by install path, each with `resolved` + `integrity`. Falls back
|
|
47
|
+
* to npm's v1 nested `dependencies` tree. */
|
|
48
|
+
function parseNpmLike(json) {
|
|
49
|
+
const out = [];
|
|
50
|
+
const root = json;
|
|
51
|
+
const packages = root.packages;
|
|
52
|
+
if (packages && typeof packages === 'object') {
|
|
53
|
+
for (const entry of Object.values(packages)) {
|
|
54
|
+
if (entry?.resolved && entry.integrity)
|
|
55
|
+
out.push({ url: entry.resolved, integrity: entry.integrity });
|
|
56
|
+
}
|
|
57
|
+
if (out.length > 0)
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
// npm lockfileVersion 1: nested `dependencies` tree.
|
|
61
|
+
if (root.dependencies && typeof root.dependencies === 'object')
|
|
62
|
+
collectV1Deps(root.dependencies, out);
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
function collectV1Deps(deps, out) {
|
|
66
|
+
for (const node of Object.values(deps)) {
|
|
67
|
+
if (node?.resolved && node.integrity)
|
|
68
|
+
out.push({ url: node.resolved, integrity: node.integrity });
|
|
69
|
+
if (node?.dependencies)
|
|
70
|
+
collectV1Deps(node.dependencies, out);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Classic (v1) `yarn.lock`: blank-line-separated blocks, each with a
|
|
74
|
+
* `resolved "<url>#<sha1>"` and (yarn ≥1.x) an `integrity sha512-…` line.
|
|
75
|
+
* Yarn Berry (v2+) is rejected — its `checksum` is a zip-cache hash, not a
|
|
76
|
+
* tarball SRI, so the tarball can't be verified from the lockfile. */
|
|
77
|
+
function parseYarnClassic(text) {
|
|
78
|
+
if (/^__metadata:/m.test(text)) {
|
|
79
|
+
throw new Error('gjsify flatpak sources: Yarn Berry (v2+) yarn.lock is not supported — its `checksum` is ' +
|
|
80
|
+
'a cache hash, not a tarball SRI. Run `gjsify install` to produce a gjsify-lock.json, ' +
|
|
81
|
+
'or use a classic (v1) yarn.lock.');
|
|
82
|
+
}
|
|
83
|
+
const out = [];
|
|
84
|
+
let noIntegrity = 0;
|
|
85
|
+
for (const block of text.split(/\n\s*\n/)) {
|
|
86
|
+
const rm = block.match(/^\s+resolved\s+"([^"]+)"/m);
|
|
87
|
+
if (!rm)
|
|
88
|
+
continue;
|
|
89
|
+
const url = rm[1].replace(/#.*$/, ''); // strip the trailing #<sha1> fragment
|
|
90
|
+
const im = block.match(/^\s+integrity\s+(\S+)/m);
|
|
91
|
+
if (!im) {
|
|
92
|
+
noIntegrity++;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
out.push({ url, integrity: im[1] });
|
|
96
|
+
}
|
|
97
|
+
if (noIntegrity > 0) {
|
|
98
|
+
console.warn(`[gjsify flatpak sources] ${noIntegrity} yarn.lock entr(y/ies) had no integrity (pre-1.x format) ` +
|
|
99
|
+
'and were skipped — re-lock with a modern yarn or use `gjsify install`.');
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/** pnpm-lock.yaml: each `packages:` entry's `resolution.integrity` is the
|
|
104
|
+
* tarball SRI; the URL is `resolution.tarball` or reconstructed from the key.
|
|
105
|
+
* Handles both v9 (`name@version`) and v5/6 (`/name/version`) key shapes.
|
|
106
|
+
*
|
|
107
|
+
* A small targeted scanner instead of a full YAML parser: pnpm always writes
|
|
108
|
+
* the resolution inline (`resolution: {integrity: …}`), so this needs no extra
|
|
109
|
+
* dependency and stays bundle-light. It reads ONLY the top-level `packages:`
|
|
110
|
+
* block (not `snapshots:`/`importers:`), keys at exactly 2-space indent. */
|
|
111
|
+
function parsePnpm(text) {
|
|
112
|
+
const out = [];
|
|
113
|
+
let inPackages = false;
|
|
114
|
+
let currentKey = null;
|
|
115
|
+
for (const raw of text.split('\n')) {
|
|
116
|
+
const line = raw.replace(/\r$/, '');
|
|
117
|
+
if (/^packages:\s*$/.test(line)) {
|
|
118
|
+
inPackages = true;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// A non-indented, non-comment line ends the `packages:` section.
|
|
122
|
+
if (inPackages && /^[^\s#]/.test(line))
|
|
123
|
+
inPackages = false;
|
|
124
|
+
if (!inPackages)
|
|
125
|
+
continue;
|
|
126
|
+
// Package key: exactly 2-space indent, quoted or bare, ending in ':'.
|
|
127
|
+
const keyM = line.match(/^ {2}(?:'([^']+)'|"([^"]+)"|([^\s#][^:]*)):\s*$/);
|
|
128
|
+
if (keyM) {
|
|
129
|
+
currentKey = keyM[1] ?? keyM[2] ?? keyM[3] ?? null;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// Inline resolution: `resolution: {integrity: sha…, tarball: …}`.
|
|
133
|
+
const resM = line.match(/resolution:\s*\{([^}]*)\}/);
|
|
134
|
+
if (resM && currentKey) {
|
|
135
|
+
const intM = resM[1].match(/integrity:\s*(sha\d+-[A-Za-z0-9+/=]+)/);
|
|
136
|
+
if (!intM)
|
|
137
|
+
continue;
|
|
138
|
+
const tarM = resM[1].match(/tarball:\s*([^\s,}]+)/);
|
|
139
|
+
const url = tarM ? tarM[1] : reconstructNpmTarballUrl(currentKey);
|
|
140
|
+
if (url)
|
|
141
|
+
out.push({ url, integrity: intM[1] });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
/** Reconstruct the canonical npm tarball URL from a pnpm package key. Returns
|
|
147
|
+
* null for non-registry refs (aliases, git, …). */
|
|
148
|
+
function reconstructNpmTarballUrl(key) {
|
|
149
|
+
let name;
|
|
150
|
+
let version;
|
|
151
|
+
if (key.startsWith('/')) {
|
|
152
|
+
// v5/6: /name/version or /@scope/name/version, optional `_peer` suffix.
|
|
153
|
+
const k = key.slice(1).replace(/_.*$/, '');
|
|
154
|
+
const idx = k.lastIndexOf('/');
|
|
155
|
+
if (idx <= 0)
|
|
156
|
+
return null;
|
|
157
|
+
name = k.slice(0, idx);
|
|
158
|
+
version = k.slice(idx + 1);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// v9: name@version, optional `(peer@x)` suffixes.
|
|
162
|
+
const k = key.replace(/\(.*\)$/, '');
|
|
163
|
+
const at = k.lastIndexOf('@');
|
|
164
|
+
if (at <= 0)
|
|
165
|
+
return null;
|
|
166
|
+
name = k.slice(0, at);
|
|
167
|
+
version = k.slice(at + 1);
|
|
168
|
+
}
|
|
169
|
+
// Only plain registry semver versions — skip aliases / protocols.
|
|
170
|
+
if (!name || !version || /[@:/]/.test(version))
|
|
171
|
+
return null;
|
|
172
|
+
const basename = name.slice(name.lastIndexOf('/') + 1);
|
|
173
|
+
return `https://registry.npmjs.org/${name}/-/${basename}-${version}.tgz`;
|
|
174
|
+
}
|
|
175
|
+
function detectType(file) {
|
|
176
|
+
const base = file.toLowerCase();
|
|
177
|
+
if (base.endsWith('pnpm-lock.yaml'))
|
|
178
|
+
return 'pnpm';
|
|
179
|
+
if (base.endsWith('yarn.lock'))
|
|
180
|
+
return 'yarn';
|
|
181
|
+
if (base.endsWith('package-lock.json'))
|
|
182
|
+
return 'npm';
|
|
183
|
+
return 'gjsify';
|
|
184
|
+
}
|
|
185
|
+
function defaultLockfile(cwd) {
|
|
186
|
+
for (const f of ['gjsify-lock.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml']) {
|
|
187
|
+
if (existsSync(join(cwd, f)))
|
|
188
|
+
return f;
|
|
189
|
+
}
|
|
190
|
+
return 'gjsify-lock.json'; // surfaces a clear "not found" later
|
|
191
|
+
}
|
|
192
|
+
function parseLockfile(path, type) {
|
|
193
|
+
const text = readFileSync(path, 'utf-8');
|
|
194
|
+
if (type === 'yarn')
|
|
195
|
+
return parseYarnClassic(text);
|
|
196
|
+
if (type === 'pnpm')
|
|
197
|
+
return parsePnpm(text);
|
|
198
|
+
let json;
|
|
199
|
+
try {
|
|
200
|
+
json = JSON.parse(text);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
throw new Error(`gjsify flatpak sources: ${path} is not valid JSON: ${err.message}`);
|
|
204
|
+
}
|
|
205
|
+
return parseNpmLike(json);
|
|
206
|
+
}
|
|
207
|
+
// --- command ---------------------------------------------------------------
|
|
208
|
+
export const flatpakSourcesCommand = {
|
|
209
|
+
command: 'sources',
|
|
210
|
+
description: 'Generate an offline flatpak-builder `sources` array from any lockfile (gjsify-lock.json / package-lock.json / yarn.lock / pnpm-lock.yaml) for a no-network Flathub build.',
|
|
211
|
+
builder: (yargs) => {
|
|
212
|
+
return yargs
|
|
213
|
+
.option('lockfile', {
|
|
214
|
+
description: 'Path to the lockfile. Default: the first of gjsify-lock.json / package-lock.json / yarn.lock / pnpm-lock.yaml found in cwd.',
|
|
215
|
+
type: 'string',
|
|
216
|
+
normalize: true,
|
|
217
|
+
})
|
|
218
|
+
.option('type', {
|
|
219
|
+
description: 'Lockfile format. Default: detected from the filename.',
|
|
220
|
+
choices: ['gjsify', 'npm', 'yarn', 'pnpm'],
|
|
221
|
+
})
|
|
222
|
+
.option('out', {
|
|
223
|
+
description: 'Output JSON sources file',
|
|
224
|
+
type: 'string',
|
|
225
|
+
default: 'gjsify-sources.json',
|
|
226
|
+
normalize: true,
|
|
227
|
+
})
|
|
228
|
+
.option('cache-root', {
|
|
229
|
+
description: 'Relative dir the tarballs download into; point XDG_CACHE_HOME here in the build. ' +
|
|
230
|
+
'Tarballs land at <cache-root>/gjsify/tarballs/v1/<algo>/<shard>/<hex>.tgz.',
|
|
231
|
+
type: 'string',
|
|
232
|
+
default: 'flatpak-gjsify-cache',
|
|
233
|
+
})
|
|
234
|
+
.option('print-module', {
|
|
235
|
+
description: 'Also print a ready-to-paste flatpak manifest module snippet to stderr',
|
|
236
|
+
type: 'boolean',
|
|
237
|
+
default: false,
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
handler: async (args) => {
|
|
241
|
+
const cwd = process.cwd();
|
|
242
|
+
const lockfile = resolve(cwd, args.lockfile ?? defaultLockfile(cwd));
|
|
243
|
+
if (!existsSync(lockfile)) {
|
|
244
|
+
throw new Error(`gjsify flatpak sources: lockfile ${lockfile} not found. ` +
|
|
245
|
+
'Run `gjsify install` (or npm/yarn/pnpm) first, or pass --lockfile.');
|
|
246
|
+
}
|
|
247
|
+
const type = args.type ?? detectType(lockfile);
|
|
248
|
+
const tarballs = parseLockfile(lockfile, type);
|
|
249
|
+
const cacheRoot = args.cacheRoot ?? 'flatpak-gjsify-cache';
|
|
250
|
+
// Dedupe by content hash — the same tarball is locked at many install
|
|
251
|
+
// paths; flatpak rejects duplicate dest/filename pairs.
|
|
252
|
+
const byHex = new Map();
|
|
253
|
+
let skippedSri = 0;
|
|
254
|
+
let skippedAlgo = 0;
|
|
255
|
+
for (const { url, integrity } of tarballs) {
|
|
256
|
+
const sri = parseSri(integrity);
|
|
257
|
+
if (!sri) {
|
|
258
|
+
skippedSri++;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!FLATPAK_ALGOS.has(sri.algorithm)) {
|
|
262
|
+
skippedAlgo++;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const key = `${sri.algorithm}:${sri.hex}`;
|
|
266
|
+
if (byHex.has(key))
|
|
267
|
+
continue;
|
|
268
|
+
const source = {
|
|
269
|
+
type: 'file',
|
|
270
|
+
url,
|
|
271
|
+
dest: `${cacheRoot}/gjsify/tarballs/v1/${sri.algorithm}/${sri.hex.slice(0, 2)}`,
|
|
272
|
+
'dest-filename': `${sri.hex}.tgz`,
|
|
273
|
+
};
|
|
274
|
+
// FLATPAK_ALGOS gates this to exactly sha256 / sha512.
|
|
275
|
+
if (sri.algorithm === 'sha512')
|
|
276
|
+
source.sha512 = sri.hex;
|
|
277
|
+
else
|
|
278
|
+
source.sha256 = sri.hex;
|
|
279
|
+
byHex.set(key, source);
|
|
280
|
+
}
|
|
281
|
+
// Stable order (by url) so the generated file diffs cleanly across runs.
|
|
282
|
+
const sources = [...byHex.values()].sort((a, b) => (a.url < b.url ? -1 : a.url > b.url ? 1 : 0));
|
|
283
|
+
const out = resolve(cwd, args.out ?? 'gjsify-sources.json');
|
|
284
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
285
|
+
writeFileSync(out, JSON.stringify(sources, null, 2) + '\n');
|
|
286
|
+
console.log(`[gjsify flatpak sources] wrote ${out} — ${sources.length} tarball source(s) from ${type} lockfile` +
|
|
287
|
+
` (${tarballs.length} locked entr${tarballs.length === 1 ? 'y' : 'ies'})` +
|
|
288
|
+
(skippedSri ? `, ${skippedSri} without a usable integrity skipped` : '') +
|
|
289
|
+
(skippedAlgo ? `, ${skippedAlgo} non-sha256/512 skipped` : ''));
|
|
290
|
+
if (args.printModule) {
|
|
291
|
+
console.error(buildModuleSnippet(args.out ?? 'gjsify-sources.json', cacheRoot));
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
/** A copy-pasteable flatpak manifest module that consumes the generated
|
|
296
|
+
* sources file and runs a fully offline `gjsify install`. */
|
|
297
|
+
function buildModuleSnippet(sourcesFile, cacheRoot) {
|
|
298
|
+
return [
|
|
299
|
+
'',
|
|
300
|
+
'# Add the generated sources to your app module and point XDG_CACHE_HOME',
|
|
301
|
+
'# at the vendored cache before running gjsify offline. Example module:',
|
|
302
|
+
'#',
|
|
303
|
+
'# {',
|
|
304
|
+
'# "name": "my-app",',
|
|
305
|
+
'# "buildsystem": "simple",',
|
|
306
|
+
'# "build-commands": [',
|
|
307
|
+
`# "export XDG_CACHE_HOME=\\"$(pwd)/${cacheRoot}\\"",`,
|
|
308
|
+
'# "gjsify install --immutable",',
|
|
309
|
+
'# "gjsify build src/main.ts --app gjs --outfile /app/bin/my-app"',
|
|
310
|
+
'# ],',
|
|
311
|
+
'# "sources": [',
|
|
312
|
+
'# { "type": "dir", "path": "." },',
|
|
313
|
+
`# "${sourcesFile}"`,
|
|
314
|
+
'# ]',
|
|
315
|
+
'# }',
|
|
316
|
+
'',
|
|
317
|
+
].join('\n');
|
|
318
|
+
}
|
|
@@ -36,6 +36,13 @@ export const selfUpdateCommand = {
|
|
|
36
36
|
description: 'npm dist-tag or pinned version to install (e.g. `latest`, `next`, `0.5.0`).',
|
|
37
37
|
type: 'string',
|
|
38
38
|
default: 'latest',
|
|
39
|
+
})
|
|
40
|
+
.option('skip-deps', {
|
|
41
|
+
description: 'Update only the @gjsify/cli bundle itself; do not pull its runtime dependencies ' +
|
|
42
|
+
'(rolldown, lightningcss, @gjsify/tsc, the native gi:// bridges, …). Faster, but can ' +
|
|
43
|
+
'leave the on-disk runtime deps stale relative to the new bundle.',
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
default: false,
|
|
39
46
|
}),
|
|
40
47
|
handler: async (args) => {
|
|
41
48
|
const layout = defaultGlobalLayout();
|
|
@@ -80,21 +87,33 @@ export const selfUpdateCommand = {
|
|
|
80
87
|
process.exit(1);
|
|
81
88
|
return;
|
|
82
89
|
}
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
// published `
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
90
|
+
// `@gjsify/cli` ships two runtimes from one package: the Node `bin`
|
|
91
|
+
// (`lib/index.js`, which imports its deps from `node_modules`) and the
|
|
92
|
+
// self-contained GJS bundle (`dist/cli.gjs.mjs`, which inlines them).
|
|
93
|
+
// The published `dependencies` therefore must cover everything `lib/**`
|
|
94
|
+
// resolves at runtime PLUS the pieces only the GJS runtime loads from
|
|
95
|
+
// disk (the `gi://` native typelib bridges via the launcher's
|
|
96
|
+
// GI_TYPELIB_PATH, `rolldown`/`lightningcss` native addons, the
|
|
97
|
+
// separate `@gjsify/tsc` bundle, `@gjsify/create-app`'s disk templates).
|
|
98
|
+
//
|
|
99
|
+
// By DEFAULT self-update now resolves that full production `dependencies`
|
|
100
|
+
// tree (`skipDeps: false`) so the on-disk runtime deps move in lockstep
|
|
101
|
+
// with the freshly-installed bundle instead of skewing behind it. This is
|
|
102
|
+
// safe today because every `@gjsify/*` package — including the
|
|
103
|
+
// `@gjsify/v8` that once 404'd → 406 and forced `skipDeps: true`
|
|
104
|
+
// unconditionally — is now published to the public registry.
|
|
105
|
+
//
|
|
106
|
+
// `--skip-deps` restores the old fast path: fetch only the top-level
|
|
107
|
+
// `@gjsify/cli` tarball and re-link the bin, leaving runtime deps untouched.
|
|
108
|
+
const pullDeps = !args.skipDeps;
|
|
109
|
+
const depNote = pullDeps ? ' + runtime dependencies' : ' (bundle only)';
|
|
110
|
+
console.log(`Installing ${PACKAGE_NAME}@${target}${depNote} ...`);
|
|
92
111
|
try {
|
|
93
112
|
await installPackages({
|
|
94
113
|
prefix: layout.prefix,
|
|
95
114
|
specs: [`${PACKAGE_NAME}@${target}`],
|
|
96
115
|
verbose: false,
|
|
97
|
-
skipDeps:
|
|
116
|
+
skipDeps: !pullDeps,
|
|
98
117
|
});
|
|
99
118
|
}
|
|
100
119
|
catch (err) {
|
|
@@ -114,6 +133,12 @@ export const selfUpdateCommand = {
|
|
|
114
133
|
}
|
|
115
134
|
}
|
|
116
135
|
console.log(`\nUpdated ${PACKAGE_NAME} to v${target}.`);
|
|
136
|
+
if (pullDeps) {
|
|
137
|
+
console.log('Runtime dependencies were resolved alongside the bundle.');
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.log('Bundle-only update (--skip-deps): on-disk runtime dependencies were left unchanged.');
|
|
141
|
+
}
|
|
117
142
|
},
|
|
118
143
|
};
|
|
119
144
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.44",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -120,19 +120,19 @@
|
|
|
120
120
|
"cli"
|
|
121
121
|
],
|
|
122
122
|
"dependencies": {
|
|
123
|
-
"@gjsify/buffer": "^0.4.
|
|
124
|
-
"@gjsify/create-app": "^0.4.
|
|
125
|
-
"@gjsify/node-globals": "^0.4.
|
|
126
|
-
"@gjsify/node-polyfills": "^0.4.
|
|
127
|
-
"@gjsify/npm-registry": "^0.4.
|
|
128
|
-
"@gjsify/resolve-npm": "^0.4.
|
|
129
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.4.
|
|
130
|
-
"@gjsify/rolldown-plugin-pnp": "^0.4.
|
|
131
|
-
"@gjsify/semver": "^0.4.
|
|
132
|
-
"@gjsify/tar": "^0.4.
|
|
133
|
-
"@gjsify/tsc": "^0.4.
|
|
134
|
-
"@gjsify/web-polyfills": "^0.4.
|
|
135
|
-
"@gjsify/workspace": "^0.4.
|
|
123
|
+
"@gjsify/buffer": "^0.4.44",
|
|
124
|
+
"@gjsify/create-app": "^0.4.44",
|
|
125
|
+
"@gjsify/node-globals": "^0.4.44",
|
|
126
|
+
"@gjsify/node-polyfills": "^0.4.44",
|
|
127
|
+
"@gjsify/npm-registry": "^0.4.44",
|
|
128
|
+
"@gjsify/resolve-npm": "^0.4.44",
|
|
129
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.4.44",
|
|
130
|
+
"@gjsify/rolldown-plugin-pnp": "^0.4.44",
|
|
131
|
+
"@gjsify/semver": "^0.4.44",
|
|
132
|
+
"@gjsify/tar": "^0.4.44",
|
|
133
|
+
"@gjsify/tsc": "^0.4.44",
|
|
134
|
+
"@gjsify/web-polyfills": "^0.4.44",
|
|
135
|
+
"@gjsify/workspace": "^0.4.44",
|
|
136
136
|
"cosmiconfig": "^9.0.1",
|
|
137
137
|
"get-tsconfig": "^4.14.0",
|
|
138
138
|
"pkg-types": "^2.3.1",
|
|
@@ -140,12 +140,12 @@
|
|
|
140
140
|
"yargs": "^18.0.0"
|
|
141
141
|
},
|
|
142
142
|
"devDependencies": {
|
|
143
|
-
"@gjsify/unit": "^0.4.
|
|
143
|
+
"@gjsify/unit": "^0.4.44",
|
|
144
144
|
"@types/yargs": "^17.0.35",
|
|
145
145
|
"typescript": "^6.0.3"
|
|
146
146
|
},
|
|
147
147
|
"peerDependencies": {
|
|
148
|
-
"@gjsify/rolldown-native": "^0.4.
|
|
148
|
+
"@gjsify/rolldown-native": "^0.4.44"
|
|
149
149
|
},
|
|
150
150
|
"peerDependenciesMeta": {
|
|
151
151
|
"@gjsify/rolldown-native": {
|