@gjsify/cli 0.4.43 → 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 +24 -21
- 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/package.json +16 -16
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
|
+
}
|
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": {
|