@gjsify/cli 0.4.20 → 0.4.22
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 +164 -163
- package/lib/commands/check.d.ts +6 -2
- package/lib/commands/check.js +169 -59
- package/lib/commands/dlx.js +18 -3
- package/lib/commands/fix.js +2 -2
- package/lib/commands/flatpak/init.js +21 -7
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/install.d.ts +1 -0
- package/lib/commands/install.js +31 -15
- package/lib/commands/pack.d.ts +16 -0
- package/lib/commands/pack.js +32 -5
- package/lib/commands/publish.d.ts +1 -0
- package/lib/commands/publish.js +54 -4
- package/lib/commands/system-check.d.ts +6 -0
- package/lib/commands/system-check.js +72 -0
- package/lib/index.js +22 -1
- package/lib/types/config-data.d.ts +7 -0
- package/lib/utils/check-system-deps.js +24 -0
- package/lib/utils/install-backend-native.d.ts +8 -0
- package/lib/utils/install-backend-native.js +20 -6
- package/lib/utils/run-lifecycle-script.d.ts +14 -0
- package/lib/utils/run-lifecycle-script.js +74 -0
- package/package.json +98 -15
package/lib/commands/publish.js
CHANGED
|
@@ -57,6 +57,11 @@ export const publishCommand = {
|
|
|
57
57
|
description: 'Treat "version already published" as success — covers both classic 409 Conflict and the npm OIDC-path 403 Forbidden + `"previously published"` body shape. Matches yarn `--tolerate-republish`.',
|
|
58
58
|
type: 'boolean',
|
|
59
59
|
default: false,
|
|
60
|
+
})
|
|
61
|
+
.option('tolerate-untrusted-new', {
|
|
62
|
+
description: 'Skip (exit 0) when OIDC token exchange returns `package not found` AND no fallback token is configured — i.e. a never-before-published `@scope/<name>` whose Trusted Publisher entry hasn\'t been set up on npmjs.com yet. Without this flag, one un-bootstrapped new package breaks the entire serialized `gjsify foreach publish` loop. Pair with `--tolerate-republish` in CI release workflows so a fresh-merged package gracefully skips its first CI publish, leaving the manual-bootstrap step to a maintainer (see AGENTS.md "New @gjsify/* package: first-publish + Trusted Publisher bootstrap").',
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
default: false,
|
|
60
65
|
})
|
|
61
66
|
.option('provenance', {
|
|
62
67
|
description: 'Pass-through flag — recorded in the payload but no signing happens (gjsify doesn\'t ship a sigstore signer yet).',
|
|
@@ -91,6 +96,7 @@ export const publishCommand = {
|
|
|
91
96
|
const tag = args.tag ?? 'latest';
|
|
92
97
|
const access = args.access;
|
|
93
98
|
const tolerate = args['tolerate-republish'] === true;
|
|
99
|
+
const tolerateUntrustedNew = args['tolerate-untrusted-new'] === true;
|
|
94
100
|
const provenance = args.provenance === true;
|
|
95
101
|
const dryRun = args['dry-run'] === true;
|
|
96
102
|
const checkTrustedOnly = args['check-trusted'] === true;
|
|
@@ -143,8 +149,17 @@ export const publishCommand = {
|
|
|
143
149
|
return;
|
|
144
150
|
}
|
|
145
151
|
}
|
|
146
|
-
// 1. Pack the workspace (rewrites workspace:^, computes integrity)
|
|
147
|
-
|
|
152
|
+
// 1. Pack the workspace (rewrites workspace:^, computes integrity).
|
|
153
|
+
// Lifecycle scripts: `prepublishOnly` (publish-specific) runs before
|
|
154
|
+
// `prepack`. Matches `npm publish` semantics — packages that gate
|
|
155
|
+
// release-time validation on `prepublishOnly` (typecheck, smoke
|
|
156
|
+
// tests, version-tagging) get exactly that, and packages whose
|
|
157
|
+
// `prepack` generates build artifacts (process-templates,
|
|
158
|
+
// codegen, …) get those artifacts into the tarball.
|
|
159
|
+
const packOpts = {
|
|
160
|
+
dryRun: true,
|
|
161
|
+
lifecycleScripts: ['prepublishOnly', 'prepack'],
|
|
162
|
+
};
|
|
148
163
|
const packed = await packWorkspace(wsDir, packOpts);
|
|
149
164
|
// We need the raw bytes — re-run with destination=null and capture.
|
|
150
165
|
// packWorkspace returns metadata only; for the bytes we re-pack into
|
|
@@ -243,6 +258,34 @@ export const publishCommand = {
|
|
|
243
258
|
}
|
|
244
259
|
}
|
|
245
260
|
catch (err) {
|
|
261
|
+
// Detect the "never-before-published @scope/pkg" shape:
|
|
262
|
+
// npm's OIDC exchange returns 404 with body
|
|
263
|
+
// {"message":"OIDC token exchange error - package not found"}
|
|
264
|
+
// for any package that has no Trusted Publisher entry (which
|
|
265
|
+
// includes every package that doesn't exist on npm yet —
|
|
266
|
+
// see AGENTS.md "New @gjsify/* package: first-publish +
|
|
267
|
+
// Trusted Publisher bootstrap"). Skip such a package when
|
|
268
|
+
// --tolerate-untrusted-new is set so one un-bootstrapped
|
|
269
|
+
// package doesn't break the entire serialized publish loop.
|
|
270
|
+
const isUntrustedNewPackage = err instanceof OidcExchangeError &&
|
|
271
|
+
err.status === 404 &&
|
|
272
|
+
/package not found/i.test(err.body);
|
|
273
|
+
if (isUntrustedNewPackage && tolerateUntrustedNew) {
|
|
274
|
+
const headerMsg = `${packed.name}@${packed.version} (skipped — no Trusted Publisher on npm, see AGENTS.md "New @gjsify/* package: first-publish + Trusted Publisher bootstrap")`;
|
|
275
|
+
if (args.json) {
|
|
276
|
+
process.stdout.write(`${JSON.stringify({
|
|
277
|
+
ok: true,
|
|
278
|
+
action: 'skipped-untrusted-new',
|
|
279
|
+
name: packed.name,
|
|
280
|
+
version: packed.version,
|
|
281
|
+
reason: 'no-trusted-publisher',
|
|
282
|
+
}, null, 2)}\n`);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
process.stdout.write(`~ ${headerMsg}\n`);
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
246
289
|
if (trustedFlag === true) {
|
|
247
290
|
// Explicit --trusted: bail with a clear error.
|
|
248
291
|
handleOidcFailure(err, packed.name, args.json === true);
|
|
@@ -317,9 +360,16 @@ export const publishCommand = {
|
|
|
317
360
|
};
|
|
318
361
|
async function packWorkspaceToBytes(wsDir) {
|
|
319
362
|
// Cheap re-run that writes to a tempdir, then read back. Avoids
|
|
320
|
-
// duplicating the file-walking + tar-building logic here.
|
|
363
|
+
// duplicating the file-walking + tar-building logic here. Lifecycle
|
|
364
|
+
// scripts are already run by the outer publish flow's first pack
|
|
365
|
+
// call — passing `[]` here skips re-running them (idempotent for
|
|
366
|
+
// most projects but a needless cost otherwise).
|
|
321
367
|
const tmp = `/tmp/gjsify-publish-${process.pid}-${Date.now()}`;
|
|
322
|
-
const res = await packWorkspace(wsDir, {
|
|
368
|
+
const res = await packWorkspace(wsDir, {
|
|
369
|
+
destination: tmp,
|
|
370
|
+
dryRun: false,
|
|
371
|
+
lifecycleScripts: [],
|
|
372
|
+
});
|
|
323
373
|
if (!res.absolutePath)
|
|
324
374
|
throw new Error('gjsify publish: pack did not produce a file');
|
|
325
375
|
const bytes = new Uint8Array(readFileSync(res.absolutePath));
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { runAllChecks, detectPackageManager, buildInstallCommand } from '../utils/check-system-deps.js';
|
|
2
|
+
export const systemCheckCommand = {
|
|
3
|
+
command: 'system-check',
|
|
4
|
+
description: 'Check that required system dependencies (GJS, GTK4, libsoup3, …) are installed. Optional dependencies are detected only when their @gjsify/* package is in your project. (Previously called `gjsify check`; the bare name now runs TypeScript checks across the workspace — see `gjsify check --help`.)',
|
|
5
|
+
builder: (yargs) => {
|
|
6
|
+
return yargs
|
|
7
|
+
.option('json', {
|
|
8
|
+
description: 'Output results as JSON',
|
|
9
|
+
type: 'boolean',
|
|
10
|
+
default: false,
|
|
11
|
+
});
|
|
12
|
+
},
|
|
13
|
+
handler: async (args) => {
|
|
14
|
+
const results = runAllChecks(process.cwd());
|
|
15
|
+
const pm = detectPackageManager();
|
|
16
|
+
const missingRequired = results.filter(r => !r.found && r.severity === 'required');
|
|
17
|
+
const missingOptional = results.filter(r => !r.found && r.severity === 'optional');
|
|
18
|
+
const allMissing = [...missingRequired, ...missingOptional];
|
|
19
|
+
if (args.json) {
|
|
20
|
+
console.log(JSON.stringify({ packageManager: pm, deps: results }, null, 2));
|
|
21
|
+
// Only required deps influence the exit code.
|
|
22
|
+
process.exit(missingRequired.length > 0 ? 1 : 0);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.log('System dependency check\n');
|
|
26
|
+
const required = results.filter(r => r.severity === 'required');
|
|
27
|
+
const optional = results.filter(r => r.severity === 'optional');
|
|
28
|
+
if (required.length > 0) {
|
|
29
|
+
console.log('Required:');
|
|
30
|
+
for (const dep of required) {
|
|
31
|
+
const icon = dep.found ? '✓' : '✗';
|
|
32
|
+
const ver = dep.version ? ` (${dep.version})` : '';
|
|
33
|
+
console.log(` ${icon} ${dep.name}${ver}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (optional.length > 0) {
|
|
37
|
+
console.log('\nOptional:');
|
|
38
|
+
for (const dep of optional) {
|
|
39
|
+
// ⚠ for missing-but-needed-by-installed-packages, ○ for missing-but-not-needed (shouldn't appear in conditional mode)
|
|
40
|
+
const icon = dep.found ? '✓' : '⚠';
|
|
41
|
+
const ver = dep.version ? ` (${dep.version})` : '';
|
|
42
|
+
const requiredBy = dep.requiredBy && dep.requiredBy.length > 0
|
|
43
|
+
? ` — needed by ${dep.requiredBy.join(', ')}`
|
|
44
|
+
: '';
|
|
45
|
+
console.log(` ${icon} ${dep.name}${ver}${requiredBy}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.log(`\nPackage manager: ${pm}`);
|
|
49
|
+
if (allMissing.length === 0) {
|
|
50
|
+
console.log('\nAll dependencies found.');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (missingRequired.length > 0) {
|
|
54
|
+
console.log(`\nMissing required: ${missingRequired.map(d => d.name).join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
if (missingOptional.length > 0) {
|
|
57
|
+
console.log(`Missing optional: ${missingOptional.map(d => d.name).join(', ')}`);
|
|
58
|
+
}
|
|
59
|
+
const cmd = buildInstallCommand(pm, allMissing);
|
|
60
|
+
if (cmd) {
|
|
61
|
+
console.log(`\nTo install:\n ${cmd}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log('\nNo install command available for your package manager. Install manually.');
|
|
65
|
+
}
|
|
66
|
+
// Exit non-zero ONLY if a required dependency is missing.
|
|
67
|
+
// Optional deps that are missing but needed by an installed @gjsify/*
|
|
68
|
+
// package generate a warning but keep exit code 0 — the user can still
|
|
69
|
+
// build/run code paths that don't touch the optional library.
|
|
70
|
+
process.exit(missingRequired.length > 0 ? 1 : 0);
|
|
71
|
+
},
|
|
72
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
2
5
|
import yargs from 'yargs';
|
|
3
6
|
import { hideBin } from 'yargs/helpers';
|
|
4
|
-
import { buildCommand as build, testCommand as test, runCommand as run, infoCommand as info, checkCommand as check, showcaseCommand as showcase, createCommand as create, gresourceCommand as gresource, gettextCommand as gettext, gsettingsCommand as gsettings, flatpakCommand as flatpak, dlxCommand as dlx, installCommand as install, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, uninstallCommand as uninstall, formatCommand as format, lintCommand as lint, fixCommand as fix, upgradeCommand as upgrade, barrelsCommand as barrels, } from './commands/index.js';
|
|
7
|
+
import { buildCommand as build, testCommand as test, runCommand as run, infoCommand as info, systemCheckCommand as systemCheck, checkCommand as check, showcaseCommand as showcase, createCommand as create, gresourceCommand as gresource, gettextCommand as gettext, gsettingsCommand as gsettings, flatpakCommand as flatpak, dlxCommand as dlx, installCommand as install, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, uninstallCommand as uninstall, formatCommand as format, lintCommand as lint, fixCommand as fix, upgradeCommand as upgrade, barrelsCommand as barrels, } from './commands/index.js';
|
|
5
8
|
import { APP_NAME } from './constants.js';
|
|
9
|
+
// Read the version from package.json adjacent to the bundle. yargs's
|
|
10
|
+
// auto-version-discovery (its `pkg-up`-driven default) doesn't reach
|
|
11
|
+
// through the bundled `dist/cli.gjs.mjs` path on GJS — falls back to
|
|
12
|
+
// "unknown". Both layouts are covered:
|
|
13
|
+
// - dev (tsx, `yarn workspace`): src/index.ts → ../package.json
|
|
14
|
+
// - bundled (install -g): dist/cli.gjs.mjs → ../package.json
|
|
15
|
+
function readBundleVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
19
|
+
return typeof pkg.version === 'string' ? pkg.version : 'unknown';
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return 'unknown';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
6
25
|
// `parseAsync()` instead of `.argv` so the top-level await keeps the
|
|
7
26
|
// process alive until command handlers complete. Under Node this is
|
|
8
27
|
// cosmetic — the event loop holds the process up — but under GJS the
|
|
@@ -11,6 +30,7 @@ import { APP_NAME } from './constants.js';
|
|
|
11
30
|
const cli = yargs(hideBin(process.argv));
|
|
12
31
|
await cli
|
|
13
32
|
.scriptName(APP_NAME)
|
|
33
|
+
.version(readBundleVersion())
|
|
14
34
|
.strict()
|
|
15
35
|
// Use the full terminal width for help. yargs's default caps at 80
|
|
16
36
|
// (`Math.min(80, process.stdout.columns)`); we explicitly opt into
|
|
@@ -28,6 +48,7 @@ await cli
|
|
|
28
48
|
.command(run.command, run.description, run.builder, run.handler)
|
|
29
49
|
.command(dlx.command, dlx.description, dlx.builder, dlx.handler)
|
|
30
50
|
.command(info.command, info.description, info.builder, info.handler)
|
|
51
|
+
.command(systemCheck.command, systemCheck.description, systemCheck.builder, systemCheck.handler)
|
|
31
52
|
.command(check.command, check.description, check.builder, check.handler)
|
|
32
53
|
.command(showcase.command, showcase.description, showcase.builder, showcase.handler)
|
|
33
54
|
.command(gresource.command, gresource.description, gresource.builder, gresource.handler)
|
|
@@ -253,6 +253,13 @@ export interface ConfigDataFlatpak {
|
|
|
253
253
|
finishArgs?: string[];
|
|
254
254
|
/** Extra Flatpak modules prepended before the app's own meson/simple module (e.g. `blueprint-compiler` build). */
|
|
255
255
|
extraModules?: unknown[];
|
|
256
|
+
/**
|
|
257
|
+
* Full replacement for the manifest's `modules` array. When set, neither
|
|
258
|
+
* `extraModules` nor the meson default get added — the array is used
|
|
259
|
+
* verbatim. Right shape for CLI tools that ship a pre-built bundle and
|
|
260
|
+
* install via shell commands (`buildsystem: simple`) instead of meson.
|
|
261
|
+
*/
|
|
262
|
+
modules?: unknown[];
|
|
256
263
|
/** Cleanup glob patterns applied to the final manifest, e.g. `['/include', '/lib/pkgconfig']`. */
|
|
257
264
|
cleanup?: string[];
|
|
258
265
|
/** Source-of-truth lockfile for `gjsify flatpak deps` — `yarn.lock` or `package-lock.json`. */
|
|
@@ -81,6 +81,14 @@ const OPTIONAL_DEPS = {
|
|
|
81
81
|
pangocairo: { id: 'pangocairo', name: 'PangoCairo', pkgName: 'pangocairo' },
|
|
82
82
|
webkitgtk: { id: 'webkitgtk', name: 'WebKitGTK', pkgName: 'webkitgtk-6.0' },
|
|
83
83
|
cairo: { id: 'cairo', name: 'Cairo', pkgName: 'cairo' },
|
|
84
|
+
// Build-time deps for @gjsify/*-native Vala prebuilds. End-users with
|
|
85
|
+
// installed prebuilds don't need the -devel package — only contributors
|
|
86
|
+
// rebuilding from source via `yarn build:prebuilds`. We still surface
|
|
87
|
+
// them in the optional set so the "missing" diagnostic catches build-
|
|
88
|
+
// time failures with an actionable install-hint instead of a meson
|
|
89
|
+
// `Run-time dependency X found: NO` error mid-build.
|
|
90
|
+
gnutls: { id: 'gnutls', name: 'GnuTLS', pkgName: 'gnutls' },
|
|
91
|
+
nghttp2: { id: 'nghttp2', name: 'libnghttp2', pkgName: 'libnghttp2' },
|
|
84
92
|
};
|
|
85
93
|
/**
|
|
86
94
|
* Map of @gjsify/* package name → ids of OPTIONAL_DEPS this package needs.
|
|
@@ -101,6 +109,12 @@ const PACKAGE_DEPS = {
|
|
|
101
109
|
// runOptionalChecks. Mapping it here so its presence in the project's
|
|
102
110
|
// dep tree triggers the check.
|
|
103
111
|
'@gjsify/webgl': ['gwebgl'],
|
|
112
|
+
// Native Vala bridges with `dependency('gnutls')` / `dependency('libnghttp2')`
|
|
113
|
+
// in their meson.build. Optional because the shipped prebuild covers the
|
|
114
|
+
// common-arch user path; only contributors rebuilding from source hit the
|
|
115
|
+
// build-time dep.
|
|
116
|
+
'@gjsify/tls-native': ['gnutls'],
|
|
117
|
+
'@gjsify/http2-native': ['nghttp2'],
|
|
104
118
|
// @gjsify/event-bridge only needs gtk4/gdk which are already in the
|
|
105
119
|
// required set, so it doesn't need an optional entry.
|
|
106
120
|
};
|
|
@@ -278,6 +292,8 @@ const PM_PACKAGES = {
|
|
|
278
292
|
pango: 'libpango1.0-dev',
|
|
279
293
|
pangocairo: 'libpango1.0-dev',
|
|
280
294
|
cairo: 'libcairo2-dev',
|
|
295
|
+
gnutls: 'libgnutls28-dev',
|
|
296
|
+
nghttp2: 'libnghttp2-dev',
|
|
281
297
|
},
|
|
282
298
|
dnf: {
|
|
283
299
|
gjs: 'gjs',
|
|
@@ -296,6 +312,8 @@ const PM_PACKAGES = {
|
|
|
296
312
|
pango: 'pango-devel',
|
|
297
313
|
pangocairo: 'pango-devel',
|
|
298
314
|
cairo: 'cairo-devel',
|
|
315
|
+
gnutls: 'gnutls-devel',
|
|
316
|
+
nghttp2: 'libnghttp2-devel',
|
|
299
317
|
},
|
|
300
318
|
pacman: {
|
|
301
319
|
gjs: 'gjs',
|
|
@@ -314,6 +332,8 @@ const PM_PACKAGES = {
|
|
|
314
332
|
pango: 'pango',
|
|
315
333
|
pangocairo: 'pango',
|
|
316
334
|
cairo: 'cairo',
|
|
335
|
+
gnutls: 'gnutls',
|
|
336
|
+
nghttp2: 'libnghttp2',
|
|
317
337
|
},
|
|
318
338
|
zypper: {
|
|
319
339
|
gjs: 'gjs',
|
|
@@ -332,6 +352,8 @@ const PM_PACKAGES = {
|
|
|
332
352
|
pango: 'pango-devel',
|
|
333
353
|
pangocairo: 'pango-devel',
|
|
334
354
|
cairo: 'cairo-devel',
|
|
355
|
+
gnutls: 'libgnutls-devel',
|
|
356
|
+
nghttp2: 'libnghttp2-devel',
|
|
335
357
|
},
|
|
336
358
|
apk: {
|
|
337
359
|
gjs: 'gjs',
|
|
@@ -350,6 +372,8 @@ const PM_PACKAGES = {
|
|
|
350
372
|
pango: 'pango-dev',
|
|
351
373
|
pangocairo: 'pango-dev',
|
|
352
374
|
cairo: 'cairo-dev',
|
|
375
|
+
gnutls: 'gnutls-dev',
|
|
376
|
+
nghttp2: 'nghttp2-dev',
|
|
353
377
|
},
|
|
354
378
|
unknown: {},
|
|
355
379
|
};
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { type Packument } from "@gjsify/npm-registry";
|
|
1
2
|
import type { InstallOptions } from "./install-backend.ts";
|
|
3
|
+
interface ParsedSpec {
|
|
4
|
+
name: string;
|
|
5
|
+
range: string;
|
|
6
|
+
}
|
|
2
7
|
export interface InstalledTopLevel {
|
|
3
8
|
name: string;
|
|
4
9
|
version: string;
|
|
5
10
|
}
|
|
6
11
|
export declare function installPackagesNative(opts: InstallOptions): Promise<InstalledTopLevel[]>;
|
|
12
|
+
export declare function parseSpec(raw: string): ParsedSpec;
|
|
13
|
+
export declare function pickVersion(packument: Packument, range: string): string | null;
|
|
14
|
+
export {};
|
|
@@ -384,22 +384,36 @@ function describeLockfileDrift(lockfile, specs) {
|
|
|
384
384
|
lines.push(` - ${removed.sort().join("\n - ")}`);
|
|
385
385
|
return lines.join("\n");
|
|
386
386
|
}
|
|
387
|
-
function
|
|
387
|
+
// Exported for unit-testing — keep the function name + signature
|
|
388
|
+
// stable, the install-backend itself still calls it via the local
|
|
389
|
+
// binding below. Internal API.
|
|
390
|
+
export function parseSpec(raw) {
|
|
391
|
+
// Bare names without an explicit `@version` resolve to the `latest`
|
|
392
|
+
// dist-tag. This matches npm CLI behaviour (`npm install foo` →
|
|
393
|
+
// foo@latest) and — crucially — picks up prereleases when the
|
|
394
|
+
// publisher has tagged them as `latest`. Using semver `*` here
|
|
395
|
+
// would silently exclude any version with a `-` (rc, beta, alpha,
|
|
396
|
+
// …) suffix per semver §9 ("Pre-release versions have a lower
|
|
397
|
+
// precedence than the associated normal version"); ts-for-gir
|
|
398
|
+
// shipped only prereleases (4.0.0-rc.17 is the `latest` tag, no
|
|
399
|
+
// stable 4.x yet) and `*` was selecting the abandoned 3.3.0
|
|
400
|
+
// instead.
|
|
388
401
|
if (raw.startsWith("@")) {
|
|
389
402
|
const slash = raw.indexOf("/");
|
|
390
403
|
if (slash < 0)
|
|
391
404
|
throw new Error(`Invalid spec (scoped name without slash): ${raw}`);
|
|
392
405
|
const at = raw.indexOf("@", slash);
|
|
393
406
|
if (at < 0)
|
|
394
|
-
return { name: raw, range: "
|
|
395
|
-
return { name: raw.slice(0, at), range: raw.slice(at + 1) || "
|
|
407
|
+
return { name: raw, range: "latest" };
|
|
408
|
+
return { name: raw.slice(0, at), range: raw.slice(at + 1) || "latest" };
|
|
396
409
|
}
|
|
397
410
|
const at = raw.indexOf("@");
|
|
398
411
|
if (at < 0)
|
|
399
|
-
return { name: raw, range: "
|
|
400
|
-
return { name: raw.slice(0, at), range: raw.slice(at + 1) || "
|
|
412
|
+
return { name: raw, range: "latest" };
|
|
413
|
+
return { name: raw.slice(0, at), range: raw.slice(at + 1) || "latest" };
|
|
401
414
|
}
|
|
402
|
-
|
|
415
|
+
// Exported for unit-testing. Internal API.
|
|
416
|
+
export function pickVersion(packument, range) {
|
|
403
417
|
// dist-tag fast path: `latest`, `next`, ...
|
|
404
418
|
if (packument["dist-tags"][range])
|
|
405
419
|
return packument["dist-tags"][range];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface RunLifecycleScriptOptions {
|
|
2
|
+
/** When true, do not throw on missing scripts — return `false` instead. */
|
|
3
|
+
optional?: boolean;
|
|
4
|
+
/** Stdio inheritance. Defaults to `'inherit'` so output goes to the parent. */
|
|
5
|
+
stdio?: 'inherit' | 'pipe' | 'ignore';
|
|
6
|
+
/** Extra environment variables layered on top of the defaults. */
|
|
7
|
+
env?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run a lifecycle script defined in `pkg.scripts[name]` from `wsDir`.
|
|
11
|
+
* Returns `true` if the script existed and exited 0. Returns `false` if
|
|
12
|
+
* `optional: true` and the script is missing. Throws on non-zero exit.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runLifecycleScript(wsDir: string, pkg: Record<string, unknown>, name: string, opts?: RunLifecycleScriptOptions): Promise<boolean>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Run an npm-style lifecycle script (`prepack`, `prepare`, `prepublishOnly`,
|
|
2
|
+
// `postpack`, `postpublish`, …) from inside a `gjsify pack` / `gjsify
|
|
3
|
+
// publish` flow.
|
|
4
|
+
//
|
|
5
|
+
// Why this exists separately from `gjsify run`: `gjsify run <script>` ends
|
|
6
|
+
// with `process.exit(<code>)` so it can be used as a CLI entrypoint that
|
|
7
|
+
// behaves like `yarn run` / `npm run`. That's wrong for the embedded use
|
|
8
|
+
// case where pack/publish needs the lifecycle script to finish, then keep
|
|
9
|
+
// running its own logic. This helper resolves to a Promise that settles
|
|
10
|
+
// when the child exits — no process.exit, no GLib-mainloop intermingling
|
|
11
|
+
// from `ensureMainLoop`.
|
|
12
|
+
//
|
|
13
|
+
// Matches yarn / npm script semantics:
|
|
14
|
+
// - `shell: true` so `&&` / `|` / env-var refs work
|
|
15
|
+
// - PATH prepended with `<wsDir>/node_modules/.bin` + monorepo-root bin
|
|
16
|
+
// - `npm_lifecycle_event` / `npm_package_name` / `npm_package_version`
|
|
17
|
+
// env vars set
|
|
18
|
+
// - FORCE_COLOR=1 default unless caller overrides
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { delimiter, join } from 'node:path';
|
|
21
|
+
import { findWorkspaceRoot } from './workspace-root.js';
|
|
22
|
+
/**
|
|
23
|
+
* Run a lifecycle script defined in `pkg.scripts[name]` from `wsDir`.
|
|
24
|
+
* Returns `true` if the script existed and exited 0. Returns `false` if
|
|
25
|
+
* `optional: true` and the script is missing. Throws on non-zero exit.
|
|
26
|
+
*/
|
|
27
|
+
export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
|
|
28
|
+
const scripts = pkg.scripts ?? {};
|
|
29
|
+
const literal = scripts[name];
|
|
30
|
+
if (typeof literal !== 'string') {
|
|
31
|
+
if (opts.optional !== false)
|
|
32
|
+
return false;
|
|
33
|
+
throw new Error(`gjsify lifecycle-script: no "${name}" in ${wsDir}/package.json`);
|
|
34
|
+
}
|
|
35
|
+
const monorepoRoot = findWorkspaceRoot(wsDir);
|
|
36
|
+
const binDirs = [join(wsDir, 'node_modules', '.bin')];
|
|
37
|
+
if (monorepoRoot && monorepoRoot !== wsDir) {
|
|
38
|
+
binDirs.push(join(monorepoRoot, 'node_modules', '.bin'));
|
|
39
|
+
}
|
|
40
|
+
// Match yarn / npm color-forcing default — see runScript() in run.ts
|
|
41
|
+
// for the full reasoning. Without this, lifecycle scripts that call
|
|
42
|
+
// tools like biome / esbuild / tsc lose ANSI color in piped contexts
|
|
43
|
+
// (CI logs, redirected output) because `process.stdout.isTTY` is
|
|
44
|
+
// false for the spawned child.
|
|
45
|
+
const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined
|
|
46
|
+
? {}
|
|
47
|
+
: { FORCE_COLOR: '1' };
|
|
48
|
+
const env = {
|
|
49
|
+
...process.env,
|
|
50
|
+
...colorEnv,
|
|
51
|
+
PATH: [...binDirs, process.env.PATH ?? ''].filter(Boolean).join(delimiter),
|
|
52
|
+
npm_lifecycle_event: name,
|
|
53
|
+
npm_package_name: pkg.name ?? '',
|
|
54
|
+
npm_package_version: pkg.version ?? '',
|
|
55
|
+
...(opts.env ?? {}),
|
|
56
|
+
};
|
|
57
|
+
await new Promise((resolveOk, reject) => {
|
|
58
|
+
const child = spawn(literal, [], {
|
|
59
|
+
cwd: wsDir,
|
|
60
|
+
env: env,
|
|
61
|
+
stdio: opts.stdio ?? 'inherit',
|
|
62
|
+
shell: true,
|
|
63
|
+
});
|
|
64
|
+
child.on('close', (code) => {
|
|
65
|
+
if (code === 0)
|
|
66
|
+
resolveOk();
|
|
67
|
+
else {
|
|
68
|
+
reject(new Error(`gjsify lifecycle-script: "${name}" in ${wsDir} exited with code ${code}`));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
child.on('error', reject);
|
|
72
|
+
});
|
|
73
|
+
return true;
|
|
74
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.22",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -12,6 +12,89 @@
|
|
|
12
12
|
"gjsify": {
|
|
13
13
|
"bin": {
|
|
14
14
|
"gjsify": "./dist/cli.gjs.mjs"
|
|
15
|
+
},
|
|
16
|
+
"flatpak": {
|
|
17
|
+
"appId": "io.github.gjsify.Cli",
|
|
18
|
+
"kind": "cli",
|
|
19
|
+
"name": "gjsify",
|
|
20
|
+
"runtime": "gnome",
|
|
21
|
+
"runtimeVersion": "50",
|
|
22
|
+
"sdkExtensions": [
|
|
23
|
+
"org.freedesktop.Sdk.Extension.node24"
|
|
24
|
+
],
|
|
25
|
+
"appendPath": [
|
|
26
|
+
"/usr/lib/sdk/node24/bin"
|
|
27
|
+
],
|
|
28
|
+
"command": "gjsify",
|
|
29
|
+
"finishArgs": [
|
|
30
|
+
"--share=network",
|
|
31
|
+
"--filesystem=host"
|
|
32
|
+
],
|
|
33
|
+
"developer": {
|
|
34
|
+
"id": "io.github.gjsify",
|
|
35
|
+
"name": "gjsify contributors",
|
|
36
|
+
"email": "pascal@artandcode.studio"
|
|
37
|
+
},
|
|
38
|
+
"summary": "Node.js + Web APIs for GJS — bundle, install, lint, format, flatpak-pack GJS apps",
|
|
39
|
+
"description": [
|
|
40
|
+
{
|
|
41
|
+
"p": "gjsify is the build-and-tooling CLI for the gjsify project — a polyfill family + bundler that lets you write GNOME apps in Node-shaped TypeScript and run them on GJS (the GNOME JavaScript runtime) without Node.js at runtime."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"p": "Use this Flatpak when you want gjsify available on any modern Linux distro without installing Node.js or a system-wide npm setup. The CLI ships its own GJS bundle and a pinned Node 24 SDK extension for the subcommands (`gjsify build`, `gjsify lint`, `gjsify format`) that still call out to Node tooling internally."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"ul": [
|
|
48
|
+
{
|
|
49
|
+
"item": "`gjsify build` — bundle a TypeScript entry into a single GJS / Node / browser file via Rolldown"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"item": "`gjsify install` — Node-free npm install with a lockfile (XDG-aware, native backend)"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"item": "`gjsify dlx <pkg>` — fetch and run any npm package's GJS bundle without persisting it"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"item": "`gjsify lint` / `gjsify format` — Biome-powered linter + formatter"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"item": "`gjsify flatpak {init,build,check,deps,ci}` — pack a GJS app or CLI into a Flatpak end-to-end"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"item": "`gjsify gresource` — bundle GResource XML files for GTK apps"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"item": "`gjsify showcase` — run the bundled demo programs (Excalibur, three.js, WebRTC, …)"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"license": {
|
|
73
|
+
"metadata": "CC0-1.0",
|
|
74
|
+
"project": "MIT"
|
|
75
|
+
},
|
|
76
|
+
"categories": [
|
|
77
|
+
"Development"
|
|
78
|
+
],
|
|
79
|
+
"homepageUrl": "https://gjsify.github.io/gjsify/",
|
|
80
|
+
"vcsBrowserUrl": "https://github.com/gjsify/gjsify",
|
|
81
|
+
"issueTrackerUrl": "https://github.com/gjsify/gjsify/issues",
|
|
82
|
+
"modules": [
|
|
83
|
+
{
|
|
84
|
+
"name": "gjsify-cli",
|
|
85
|
+
"buildsystem": "simple",
|
|
86
|
+
"build-commands": [
|
|
87
|
+
"install -Dm755 dist/cli.gjs.mjs /app/share/gjsify/cli.gjs.mjs",
|
|
88
|
+
"install -Dm755 launcher.sh /app/bin/gjsify"
|
|
89
|
+
],
|
|
90
|
+
"sources": [
|
|
91
|
+
{
|
|
92
|
+
"type": "dir",
|
|
93
|
+
"path": "."
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
15
98
|
}
|
|
16
99
|
},
|
|
17
100
|
"files": [
|
|
@@ -37,18 +120,18 @@
|
|
|
37
120
|
"cli"
|
|
38
121
|
],
|
|
39
122
|
"dependencies": {
|
|
40
|
-
"@gjsify/buffer": "^0.4.
|
|
41
|
-
"@gjsify/create-app": "^0.4.
|
|
42
|
-
"@gjsify/node-globals": "^0.4.
|
|
43
|
-
"@gjsify/node-polyfills": "^0.4.
|
|
44
|
-
"@gjsify/npm-registry": "^0.4.
|
|
45
|
-
"@gjsify/resolve-npm": "^0.4.
|
|
46
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.4.
|
|
47
|
-
"@gjsify/rolldown-plugin-pnp": "^0.4.
|
|
48
|
-
"@gjsify/semver": "^0.4.
|
|
49
|
-
"@gjsify/tar": "^0.4.
|
|
50
|
-
"@gjsify/web-polyfills": "^0.4.
|
|
51
|
-
"@gjsify/workspace": "^0.4.
|
|
123
|
+
"@gjsify/buffer": "^0.4.22",
|
|
124
|
+
"@gjsify/create-app": "^0.4.22",
|
|
125
|
+
"@gjsify/node-globals": "^0.4.22",
|
|
126
|
+
"@gjsify/node-polyfills": "^0.4.22",
|
|
127
|
+
"@gjsify/npm-registry": "^0.4.22",
|
|
128
|
+
"@gjsify/resolve-npm": "^0.4.22",
|
|
129
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.4.22",
|
|
130
|
+
"@gjsify/rolldown-plugin-pnp": "^0.4.22",
|
|
131
|
+
"@gjsify/semver": "^0.4.22",
|
|
132
|
+
"@gjsify/tar": "^0.4.22",
|
|
133
|
+
"@gjsify/web-polyfills": "^0.4.22",
|
|
134
|
+
"@gjsify/workspace": "^0.4.22",
|
|
52
135
|
"cosmiconfig": "^9.0.1",
|
|
53
136
|
"get-tsconfig": "^4.14.0",
|
|
54
137
|
"pkg-types": "^2.3.1",
|
|
@@ -56,12 +139,12 @@
|
|
|
56
139
|
"yargs": "^18.0.0"
|
|
57
140
|
},
|
|
58
141
|
"devDependencies": {
|
|
59
|
-
"@gjsify/unit": "^0.4.
|
|
142
|
+
"@gjsify/unit": "^0.4.22",
|
|
60
143
|
"@types/yargs": "^17.0.35",
|
|
61
144
|
"typescript": "^6.0.3"
|
|
62
145
|
},
|
|
63
146
|
"peerDependencies": {
|
|
64
|
-
"@gjsify/rolldown-native": "^0.4.
|
|
147
|
+
"@gjsify/rolldown-native": "^0.4.22"
|
|
65
148
|
},
|
|
66
149
|
"peerDependenciesMeta": {
|
|
67
150
|
"@gjsify/rolldown-native": {
|