@gjsify/cli 0.24.2 → 0.26.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 +189 -187
- package/lib/actions/build.d.ts +13 -0
- package/lib/actions/build.js +57 -11
- package/lib/cli-app.js +7 -10
- package/lib/commands/affected.js +14 -2
- package/lib/commands/check.js +25 -8
- package/lib/commands/create.js +3 -1
- package/lib/commands/debug.js +3 -1
- package/lib/commands/flatpak/check.js +4 -3
- package/lib/commands/flatpak/diff.js +3 -1
- package/lib/commands/flatpak/sources.js +4 -7
- package/lib/commands/foreach.js +25 -7
- package/lib/commands/generate-installer.js +2 -4
- package/lib/commands/install.js +15 -7
- package/lib/commands/login.js +3 -1
- package/lib/commands/onboard.js +3 -6
- package/lib/commands/publish.js +8 -11
- package/lib/commands/self-update.js +4 -8
- package/lib/commands/showcase.d.ts +16 -0
- package/lib/commands/showcase.js +57 -26
- package/lib/commands/storybook.js +16 -2
- package/lib/commands/system-check.js +1 -2
- package/lib/commands/test.js +6 -2
- package/lib/commands/trust.js +12 -7
- package/lib/commands/tsc.js +3 -1
- package/lib/commands/uninstall.js +1 -2
- package/lib/commands/whoami.js +6 -3
- package/lib/utils/dlx-cache.js +4 -5
- package/lib/utils/install-backend-native.js +1 -1
- package/lib/utils/install-lock.js +5 -7
- package/lib/utils/install-tarball-cache.js +5 -8
- package/lib/utils/node-bundle-guard.d.ts +16 -0
- package/lib/utils/node-bundle-guard.js +69 -0
- package/lib/utils/oxc-resolve.js +1 -1
- package/lib/utils/prompt.js +6 -1
- package/lib/utils/runtimes.d.ts +18 -0
- package/lib/utils/runtimes.js +20 -0
- package/lib/utils/stdin.js +6 -7
- package/package.json +17 -17
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import type { Command } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pinned invocations for the three runners that cache an unpinned bin.
|
|
4
|
+
*
|
|
5
|
+
* The deno line carries TWO flags because deno stacks two independent refusals,
|
|
6
|
+
* and dropping either one leaves the hint broken in exactly the window where it
|
|
7
|
+
* is reached most: `--reload` busts the `DENO_DIR` copy, and `--min-dep-age 0`
|
|
8
|
+
* waives deno's supply-chain policy of refusing any version published within
|
|
9
|
+
* the last 24 h (`minimumDependencyAge`, default `24h`). Without the second
|
|
10
|
+
* flag `@latest` silently resolves to the newest release OLDER THAN A DAY — so
|
|
11
|
+
* a user who hits a bug, is told to run `@latest`, and does, gets the SAME
|
|
12
|
+
* pre-fix binary back and a stale error message with it. Measured twice on one
|
|
13
|
+
* day against 0.25.1 (minutes old) falling back to 0.24.1. `@latest` is kept
|
|
14
|
+
* rather than a baked version so the hint cannot go stale itself; the age
|
|
15
|
+
* waiver is what makes `@latest` mean latest.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PIN_HINT: string;
|
|
2
18
|
interface ShowcaseOptions {
|
|
3
19
|
name?: string;
|
|
4
20
|
json: boolean;
|
package/lib/commands/showcase.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ensurePkgDir } from './dlx.js';
|
|
|
7
7
|
import { parseSpec } from '../utils/parse-spec.js';
|
|
8
8
|
import { resolveNodeEntry } from '../utils/resolve-gjs-entry.js';
|
|
9
9
|
import { runRuntimeBundle } from '../utils/run-node.js';
|
|
10
|
-
import { EXAMPLE_RUNTIMES, isExampleRuntime, readDeclaredRuntimes, checkRuntimeSupported, defaultExampleRuntime, } from '../utils/runtimes.js';
|
|
10
|
+
import { EXAMPLE_RUNTIMES, isExampleRuntime, readDeclaredRuntimes, checkRuntimeSupported, defaultExampleRuntime, requiresGjsSystemDeps, } from '../utils/runtimes.js';
|
|
11
11
|
function readCliVersion() {
|
|
12
12
|
try {
|
|
13
13
|
const pkgUrl = new URL('../../package.json', import.meta.url);
|
|
@@ -34,11 +34,25 @@ function readCliVersion() {
|
|
|
34
34
|
function runningAs(cliVersion) {
|
|
35
35
|
return cliVersion ? `[gjsify ${cliVersion}] ` : '';
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Pinned invocations for the three runners that cache an unpinned bin.
|
|
39
|
+
*
|
|
40
|
+
* The deno line carries TWO flags because deno stacks two independent refusals,
|
|
41
|
+
* and dropping either one leaves the hint broken in exactly the window where it
|
|
42
|
+
* is reached most: `--reload` busts the `DENO_DIR` copy, and `--min-dep-age 0`
|
|
43
|
+
* waives deno's supply-chain policy of refusing any version published within
|
|
44
|
+
* the last 24 h (`minimumDependencyAge`, default `24h`). Without the second
|
|
45
|
+
* flag `@latest` silently resolves to the newest release OLDER THAN A DAY — so
|
|
46
|
+
* a user who hits a bug, is told to run `@latest`, and does, gets the SAME
|
|
47
|
+
* pre-fix binary back and a stale error message with it. Measured twice on one
|
|
48
|
+
* day against 0.25.1 (minutes old) falling back to 0.24.1. `@latest` is kept
|
|
49
|
+
* rather than a baked version so the hint cannot go stale itself; the age
|
|
50
|
+
* waiver is what makes `@latest` mean latest.
|
|
51
|
+
*/
|
|
52
|
+
export const PIN_HINT = 'A package runner reuses a CACHED copy of an unpinned bin. Force the current one:\n' +
|
|
39
53
|
' npx @gjsify/cli@latest showcase <name>\n' +
|
|
40
54
|
' bunx @gjsify/cli@latest showcase <name>\n' +
|
|
41
|
-
' deno run -A --reload npm:@gjsify/cli@latest showcase <name>';
|
|
55
|
+
' deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase <name>';
|
|
42
56
|
export const showcaseCommand = {
|
|
43
57
|
command: 'showcase [name]',
|
|
44
58
|
description: 'List or run curated gjsify showcase applications.',
|
|
@@ -119,33 +133,39 @@ export const showcaseCommand = {
|
|
|
119
133
|
// `m_should_exit` core dump. Returning halts the handler here.
|
|
120
134
|
return process.exit(1);
|
|
121
135
|
}
|
|
136
|
+
// Resolve the runtime BEFORE the system-dependency gate: that gate is a
|
|
137
|
+
// question ABOUT the runtime, and asking it first gated every showcase
|
|
138
|
+
// on a gjs binary (see `requiresGjsSystemDeps`).
|
|
139
|
+
const runtime = (args.runtime ?? defaultExampleRuntime());
|
|
140
|
+
if (!isExampleRuntime(runtime)) {
|
|
141
|
+
console.error(`Unknown --runtime "${runtime}" (expected: ${EXAMPLE_RUNTIMES.join(', ')}).`);
|
|
142
|
+
// `return` — bare `process.exit` falls through under GJS (see above).
|
|
143
|
+
return process.exit(1);
|
|
144
|
+
}
|
|
122
145
|
// System dependency check before delegating — only system libs (gjs,
|
|
123
|
-
// gtk4, …)
|
|
146
|
+
// gtk4, …), and only for a runtime that consumes the `--app gjs`
|
|
147
|
+
// bundle. The showcase's npm deps (incl. `@gjsify/webgl` with the
|
|
124
148
|
// gwebgl Vala prebuild) are fetched by `gjsify dlx` into the npm
|
|
125
149
|
// cache, and `runGjsBundle()` picks the prebuild up from the bundle
|
|
126
150
|
// dir via `detectNativePackages()`. Pre-flight-checking npm deps
|
|
127
151
|
// here would fail for `npx @gjsify/cli showcase` (no project
|
|
128
152
|
// node_modules, CLI doesn't dep on the showcase libs).
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
if (requiresGjsSystemDeps(runtime)) {
|
|
154
|
+
const results = runMinimalChecks();
|
|
155
|
+
const missingHard = results.filter((r) => !r.found && r.severity === 'required');
|
|
156
|
+
if (missingHard.length > 0) {
|
|
157
|
+
console.error('Missing system dependencies:\n');
|
|
158
|
+
for (const dep of missingHard) {
|
|
159
|
+
console.error(` ✗ ${dep.name}`);
|
|
160
|
+
}
|
|
161
|
+
const pm = detectPackageManager();
|
|
162
|
+
const cmd = buildInstallCommand(pm, missingHard);
|
|
163
|
+
if (cmd) {
|
|
164
|
+
console.error(`\nInstall with:\n ${cmd}`);
|
|
165
|
+
}
|
|
166
|
+
// `return` — bare `process.exit` falls through under GJS (see above).
|
|
167
|
+
return process.exit(1);
|
|
140
168
|
}
|
|
141
|
-
// `return` — bare `process.exit` falls through under GJS (see above).
|
|
142
|
-
return process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
const runtime = (args.runtime ?? defaultExampleRuntime());
|
|
145
|
-
if (!isExampleRuntime(runtime)) {
|
|
146
|
-
console.error(`Unknown --runtime "${runtime}" (expected: ${EXAMPLE_RUNTIMES.join(', ')}).`);
|
|
147
|
-
// `return` — bare `process.exit` falls through under GJS (see above).
|
|
148
|
-
return process.exit(1);
|
|
149
169
|
}
|
|
150
170
|
const cliVersion = readCliVersion();
|
|
151
171
|
// Non-gjs runtime: resolve the showcase's `--app node` bundle and run it
|
|
@@ -266,6 +286,17 @@ async function runShowcaseOnRuntime(showcase, runtime, cliVersion) {
|
|
|
266
286
|
return process.exit(1);
|
|
267
287
|
}
|
|
268
288
|
console.log(`Running showcase: ${showcase.name} ${runningAs(cliVersion)}on ${runtime} (${entry})\n`);
|
|
269
|
-
// Terminal call — exit on success.
|
|
270
|
-
|
|
289
|
+
// Terminal call — exit on success. Failures are reported here rather than
|
|
290
|
+
// thrown: an exception escaping the handler makes yargs dump the command's
|
|
291
|
+
// full `--help` above the message, which buries it.
|
|
292
|
+
try {
|
|
293
|
+
await runRuntimeBundle(runtime, entry, [], { exitOnSuccess: true });
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
console.error(`${err.message}\n\n` +
|
|
297
|
+
` The showcase and the \`@gjsify/node-gi\` bridge it needs are both installed by THIS CLI\n` +
|
|
298
|
+
` (gjsify ${cliVersion ?? '(unknown)'}) into its own cache, so a missing bridge points at the CLI\n` +
|
|
299
|
+
` rather than at anything in your project.\n\n ${PIN_HINT}`);
|
|
300
|
+
return process.exit(1);
|
|
301
|
+
}
|
|
271
302
|
}
|
|
@@ -136,8 +136,22 @@ export const storybookCommand = {
|
|
|
136
136
|
};
|
|
137
137
|
const initialCount = writeEntry();
|
|
138
138
|
if (initialCount === 0) {
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// `return process.exit()`: a bare `process.exit()` does NOT halt
|
|
140
|
+
// synchronously under GJS (no atexit, the GLib loop is still
|
|
141
|
+
// armed), so execution fell through to the "Found 0 story file(s)"
|
|
142
|
+
// log and on into the build — and the deferred exit never carried
|
|
143
|
+
// its code, so `gjsify storybook` in a directory with no stories
|
|
144
|
+
// printed BOTH messages and exited 0. Measured: exit 1 under bun,
|
|
145
|
+
// exit 0 under gjs, same command. Same trap as `showcase.ts`.
|
|
146
|
+
console.error(`No *.story.ts files found under ${relative(cwd, storiesDir) || storiesDir}\n\n` +
|
|
147
|
+
` \`gjsify storybook\` renders the stories of the CURRENT project: it discovers\n` +
|
|
148
|
+
` \`*.story.ts\` files and launches a component browser over them. Point it at a\n` +
|
|
149
|
+
` different directory with --stories <dir> (or \`gjsify.storybook.stories\` in\n` +
|
|
150
|
+
` package.json) if yours live elsewhere.\n\n` +
|
|
151
|
+
` To SEE a storybook without authoring one first, run the curated Libadwaita\n` +
|
|
152
|
+
` component browser — it is a showcase, so it needs no project at all:\n\n` +
|
|
153
|
+
` gjsify showcase adwaita-storybook`);
|
|
154
|
+
return process.exit(1);
|
|
141
155
|
}
|
|
142
156
|
console.log(`Found ${initialCount} story file(s) under ${relative(cwd, storiesDir) || '.'} (runtime: ${runtime})`);
|
|
143
157
|
// In-process build — runtime-agnostic (Node uses npm rolldown, GJS uses
|
|
@@ -18,8 +18,7 @@ export const systemCheckCommand = {
|
|
|
18
18
|
if (args.json) {
|
|
19
19
|
console.log(JSON.stringify({ packageManager: pm, deps: results }, null, 2));
|
|
20
20
|
// Only required deps influence the exit code.
|
|
21
|
-
process.exit(missingRequired.length > 0 ? 1 : 0);
|
|
22
|
-
return;
|
|
21
|
+
return process.exit(missingRequired.length > 0 ? 1 : 0);
|
|
23
22
|
}
|
|
24
23
|
console.log('System dependency check\n');
|
|
25
24
|
const required = results.filter((r) => r.severity === 'required');
|
package/lib/commands/test.js
CHANGED
|
@@ -60,7 +60,9 @@ export const testCommand = {
|
|
|
60
60
|
console.error(`[gjsify test] no test entry at ${relative(cwd, entry)} — ` +
|
|
61
61
|
`add an \`src/test.mts\` that aggregates your \`@gjsify/unit\` suites, ` +
|
|
62
62
|
`or set \`gjsify.test.entry\` in package.json.`);
|
|
63
|
-
process.exit(
|
|
63
|
+
// `return` — a bare `process.exit()` is deferred under GJS and the
|
|
64
|
+
// handler would try to build the missing entry anyway.
|
|
65
|
+
return process.exit(1);
|
|
64
66
|
}
|
|
65
67
|
const requested = args.runtime === 'gjs'
|
|
66
68
|
? ['gjs']
|
|
@@ -124,7 +126,9 @@ export const testCommand = {
|
|
|
124
126
|
console.log(`[gjsify test] ${summary}`);
|
|
125
127
|
const anyFailed = results.some((r) => !r.ok);
|
|
126
128
|
if (anyFailed) {
|
|
127
|
-
|
|
129
|
+
// `return` — the deferred GJS exit otherwise fell through to the
|
|
130
|
+
// success `process.exit(0)` below, clobbering the failure code.
|
|
131
|
+
return process.exit(1);
|
|
128
132
|
}
|
|
129
133
|
// Explicit success exit: under GJS the spawn-armed main loop would
|
|
130
134
|
// otherwise park this process after the summary. (runGjsBundle
|
package/lib/commands/trust.js
CHANGED
|
@@ -79,7 +79,9 @@ export const trustCommand = {
|
|
|
79
79
|
const npmrc = await loadNpmrc(cwd);
|
|
80
80
|
if (!hasAnyCredential(npmrc)) {
|
|
81
81
|
console.error('gjsify trust: no npm token configured. Run `gjsify login` first.');
|
|
82
|
-
process.exit(
|
|
82
|
+
// `return` — a bare `process.exit()` is deferred under GJS and the
|
|
83
|
+
// handler would keep going without credentials.
|
|
84
|
+
return process.exit(1);
|
|
83
85
|
}
|
|
84
86
|
// Pre-flight: a present-but-DEAD token would otherwise surface as a
|
|
85
87
|
// confusing per-package `401 "You must be logged in to publish
|
|
@@ -97,13 +99,13 @@ export const trustCommand = {
|
|
|
97
99
|
console.error(`gjsify trust: npm auth check failed (${msg}).\n` +
|
|
98
100
|
'Your ~/.npmrc token is dead or revoked — refresh it with `npm login`, then re-run.\n' +
|
|
99
101
|
'(`gjsify trust` uses the ~/.npmrc bearer token directly — unlike `npm trust`, which logs in via the browser.)');
|
|
100
|
-
process.exit(1);
|
|
101
|
-
return;
|
|
102
|
+
return process.exit(1);
|
|
102
103
|
}
|
|
103
104
|
if (!who.username) {
|
|
104
105
|
console.error('gjsify trust: your npm token appears dead or revoked (registry returned an empty whoami).\n' +
|
|
105
106
|
'Refresh it with `npm login`, then re-run.');
|
|
106
|
-
|
|
107
|
+
// `return` — see the deferred-exit note on the guard above.
|
|
108
|
+
return process.exit(1);
|
|
107
109
|
}
|
|
108
110
|
console.log(`gjsify trust: authenticated as ${who.username}`);
|
|
109
111
|
}
|
|
@@ -117,11 +119,13 @@ export const trustCommand = {
|
|
|
117
119
|
if (!repository) {
|
|
118
120
|
console.error('gjsify trust: could not determine the GitHub repository.\n' +
|
|
119
121
|
'Pass --repository owner/repo (no `origin` remote to infer from).');
|
|
120
|
-
|
|
122
|
+
// `return` — see the deferred-exit note on the guard above.
|
|
123
|
+
return process.exit(1);
|
|
121
124
|
}
|
|
122
125
|
if (!validateRepository(repository)) {
|
|
123
126
|
console.error(`gjsify trust: --repository must be "owner/repo", got "${repository}".`);
|
|
124
|
-
|
|
127
|
+
// `return` — see the deferred-exit note on the guard above.
|
|
128
|
+
return process.exit(1);
|
|
125
129
|
}
|
|
126
130
|
const workflow = normalizeWorkflowFile(args.workflow);
|
|
127
131
|
const environment = args.environment;
|
|
@@ -148,7 +152,8 @@ export const trustCommand = {
|
|
|
148
152
|
}).filter((ws) => ws.name);
|
|
149
153
|
if (selected.length === 0) {
|
|
150
154
|
console.error('gjsify trust: no publishable packages matched.');
|
|
151
|
-
|
|
155
|
+
// `return` — see the deferred-exit note on the guard above.
|
|
156
|
+
return process.exit(1);
|
|
152
157
|
}
|
|
153
158
|
console.log(`gjsify trust: ${selected.length} package(s) | repo=${repository} workflow=${workflow}`);
|
|
154
159
|
if (dryRun)
|
package/lib/commands/tsc.js
CHANGED
|
@@ -104,7 +104,9 @@ export const tscCommand = {
|
|
|
104
104
|
if (!bundlePath && !nodeTscPath) {
|
|
105
105
|
console.error('gjsify tsc: neither @gjsify/tsc (GJS bundle) nor npm `typescript` (Node) is installed.');
|
|
106
106
|
console.error(' Install with: gjsify install --save-dev @gjsify/tsc (or add `typescript`).');
|
|
107
|
-
process.exit(
|
|
107
|
+
// `return` — a bare `process.exit()` is deferred under GJS and the
|
|
108
|
+
// handler would try to spawn a compiler that is not there.
|
|
109
|
+
return process.exit(1);
|
|
108
110
|
}
|
|
109
111
|
// Mirror `gjsify run`'s native-env composition so any future
|
|
110
112
|
// typescript-the-bundle code path that reaches a native bridge
|
|
@@ -46,8 +46,7 @@ export const uninstallCommand = {
|
|
|
46
46
|
if (!args.global) {
|
|
47
47
|
console.error('gjsify uninstall currently only supports --global. ' +
|
|
48
48
|
'For project-local removal, edit package.json + re-run `gjsify install`.');
|
|
49
|
-
process.exit(1);
|
|
50
|
-
return;
|
|
49
|
+
return process.exit(1);
|
|
51
50
|
}
|
|
52
51
|
const layout = defaultGlobalLayout();
|
|
53
52
|
const dryRun = args['dry-run'] ?? false;
|
package/lib/commands/whoami.js
CHANGED
|
@@ -69,7 +69,9 @@ export const whoamiCommand = {
|
|
|
69
69
|
'Then verify: gjsify whoami',
|
|
70
70
|
].join('\n') + '\n');
|
|
71
71
|
}
|
|
72
|
-
process.exit(
|
|
72
|
+
// `return` — a bare `process.exit()` is deferred under GJS and the
|
|
73
|
+
// handler would probe the registry without a token.
|
|
74
|
+
return process.exit(1);
|
|
73
75
|
}
|
|
74
76
|
let result;
|
|
75
77
|
try {
|
|
@@ -85,8 +87,9 @@ export const whoamiCommand = {
|
|
|
85
87
|
process.stderr.write(`gjsify whoami: ${message}\n`);
|
|
86
88
|
process.stderr.write(`Registry: ${registryClean}\n`);
|
|
87
89
|
}
|
|
88
|
-
process.exit
|
|
89
|
-
|
|
90
|
+
// `return process.exit` also keeps the type narrower happy: the
|
|
91
|
+
// never-typed call marks this branch as terminated.
|
|
92
|
+
return process.exit(1);
|
|
90
93
|
}
|
|
91
94
|
if (result.username && result.username.length > 0) {
|
|
92
95
|
// Branch 1 — live token.
|
package/lib/utils/dlx-cache.js
CHANGED
|
@@ -92,11 +92,10 @@ export function symlinkSwap(cacheDir, prepareDir) {
|
|
|
92
92
|
catch (err) {
|
|
93
93
|
const code = err.code;
|
|
94
94
|
if (code === 'EBUSY' || code === 'EPERM' || code === 'EEXIST') {
|
|
95
|
-
// Race lost — clean up our tmp and use whoever won.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
catch { }
|
|
95
|
+
// Race lost — clean up our tmp and use whoever won. force:true is
|
|
96
|
+
// the non-throwing spelling of "remove if still there"; any other
|
|
97
|
+
// failure on our own pid-unique link is real and should surface.
|
|
98
|
+
rmSync(tmpLink, { force: true });
|
|
100
99
|
return realpathSync(linkPath);
|
|
101
100
|
}
|
|
102
101
|
throw err;
|
|
@@ -458,7 +458,7 @@ specOrigins) {
|
|
|
458
458
|
*
|
|
459
459
|
* This makes the current single-root-slot behavior honest instead of silent;
|
|
460
460
|
* the real fix — a per-workspace dedup pass that gives each conflicting
|
|
461
|
-
* requester its own nested copy — is Phase D.8 (see
|
|
461
|
+
* requester its own nested copy — is Phase D.8 (see status/open-todos.md).
|
|
462
462
|
*/
|
|
463
463
|
function emitTopLevelConflictWarnings(topLevelRanges, root) {
|
|
464
464
|
for (const [name, ranges] of topLevelRanges) {
|
|
@@ -84,13 +84,11 @@ function lockingDisabled() {
|
|
|
84
84
|
* we cannot prove abandoned; the mtime staleness budget still applies).
|
|
85
85
|
*/
|
|
86
86
|
function isPidAlive(pid) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
/* fall through to the signal probe */
|
|
93
|
-
}
|
|
87
|
+
// `existsSync` never throws by contract: Node returns false on any error,
|
|
88
|
+
// and the GJS shim is a typeof guard + Gio `query_exists` (both without a
|
|
89
|
+
// throw path in the GIR). Off Linux the `if` is simply false.
|
|
90
|
+
if (existsSync('/proc/self'))
|
|
91
|
+
return existsSync(`/proc/${pid}`);
|
|
94
92
|
try {
|
|
95
93
|
process.kill(pid, 0);
|
|
96
94
|
return true;
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
// same hex bytes share the same cache entry; that is the invariant pnpm
|
|
31
31
|
// relies on too. Tarballs without an integrity hash (older registries)
|
|
32
32
|
// fall through to a no-op cache and download every time.
|
|
33
|
+
import { Buffer } from 'node:buffer';
|
|
33
34
|
import { existsSync, statSync } from 'node:fs';
|
|
34
35
|
import { homedir } from 'node:os';
|
|
35
36
|
import { join } from 'node:path';
|
|
@@ -53,14 +54,10 @@ function parseSri(integrity) {
|
|
|
53
54
|
return null;
|
|
54
55
|
const algorithm = integrity.slice(0, dashIdx);
|
|
55
56
|
const b64 = integrity.slice(dashIdx + 1).replace(/=+$/, '');
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
57
|
+
// Base64 decoding is lenient on every runtime (Node skips invalid chars,
|
|
58
|
+
// @gjsify/buffer's table decode maps them to 0): malformed input yields
|
|
59
|
+
// short/garbage output — caught by the length check below — never a throw.
|
|
60
|
+
const hex = Buffer.from(b64, 'base64').toString('hex');
|
|
64
61
|
if (hex.length < 4)
|
|
65
62
|
return null;
|
|
66
63
|
return { algorithm, hex };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GJS ambient globals referenced as BARE identifiers in `code`, sorted.
|
|
3
|
+
*
|
|
4
|
+
* Empty for a bundle that only uses the `globalThis.imports` isomorphic-guard
|
|
5
|
+
* shape — that form is a runtime probe, not a dependency, and the detector
|
|
6
|
+
* ignores it for exactly that reason.
|
|
7
|
+
*/
|
|
8
|
+
export declare function findUnshimmedGjsGlobals(code: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Throw when a `--app node` bundle references GJS ambient globals but was built
|
|
11
|
+
* without the `@gjsify/node-gi/globals` shim.
|
|
12
|
+
*
|
|
13
|
+
* @param code the emitted bundle source
|
|
14
|
+
* @param outfile path of the bundle, for the error message
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertNodeBundleGlobalsShimmed(code: string, outfile: string): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Build-time guard: a `--app node` bundle that USES the GJS ambient globals
|
|
2
|
+
// must also have been given `@gjsify/node-gi/globals`.
|
|
3
|
+
//
|
|
4
|
+
// `print`/`printerr`/`log`/`logError`/`ARGV`/`imports` exist implicitly under
|
|
5
|
+
// gjs and nowhere else. On the node target they are seeded by the side-effect
|
|
6
|
+
// shim `@gjsify/node-gi/globals`, which the CLI injects when — and only when —
|
|
7
|
+
// `detectNodeGiGlobals` finds one of them in an analysis bundle. Injecting it
|
|
8
|
+
// unconditionally is not an option: it eagerly loads the native node-gi addon,
|
|
9
|
+
// so a cross-platform package's node bundle would stop loading on plain Node.
|
|
10
|
+
//
|
|
11
|
+
// That makes the decision a PREDICTION about an artifact that does not exist
|
|
12
|
+
// yet, and a prediction can be wrong. It was: the analysis pass used to run
|
|
13
|
+
// before the explicit-`--globals` register stub was resolved, and that stub is
|
|
14
|
+
// one of the two signals `isGjsSourceBuild()` reads — so the analysis bundle
|
|
15
|
+
// routed `/register` subpaths to `@gjsify/empty` and emptied `@girs/*` while the
|
|
16
|
+
// REAL build resolved them for real. Every ambient global reachable only through
|
|
17
|
+
// a register module was therefore invisible to the detector. The build exited 0
|
|
18
|
+
// and the emitted bundle died at its first canvas op with
|
|
19
|
+
//
|
|
20
|
+
// ReferenceError: imports is not defined
|
|
21
|
+
// at CanvasRenderingContext2D._toDataURL
|
|
22
|
+
//
|
|
23
|
+
// on node AND bun (the excalibur-jelly-jumper showcase), while gjs — where the
|
|
24
|
+
// globals are ambient — ran fine. Nothing between `gjsify build --app node` and
|
|
25
|
+
// a user running the bundle ever looked at the artifact.
|
|
26
|
+
//
|
|
27
|
+
// So this is a POST-condition on what was actually emitted: whatever the
|
|
28
|
+
// detector predicted, check it against the bundle. It is deliberately NOT a
|
|
29
|
+
// second detector — it runs the SAME `detectGjsAmbientGlobals` over the SAME
|
|
30
|
+
// kind of input (bundled, tree-shaken output, parsed by acorn, bare identifiers
|
|
31
|
+
// only). Two passes over one oracle that must agree; when they don't, the graph
|
|
32
|
+
// they saw differed, which is a bug in the build pipeline, not in user code.
|
|
33
|
+
//
|
|
34
|
+
// Scope: only when the CLI decided NOT to inject (`--globals auto`, no shim).
|
|
35
|
+
// `--globals none` opts out of the whole mechanism explicitly and is skipped.
|
|
36
|
+
import { detectGjsAmbientGlobals } from '@gjsify/rolldown-plugin-gjsify/globals';
|
|
37
|
+
/**
|
|
38
|
+
* The GJS ambient globals referenced as BARE identifiers in `code`, sorted.
|
|
39
|
+
*
|
|
40
|
+
* Empty for a bundle that only uses the `globalThis.imports` isomorphic-guard
|
|
41
|
+
* shape — that form is a runtime probe, not a dependency, and the detector
|
|
42
|
+
* ignores it for exactly that reason.
|
|
43
|
+
*/
|
|
44
|
+
export function findUnshimmedGjsGlobals(code) {
|
|
45
|
+
return [...detectGjsAmbientGlobals(code)].sort();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Throw when a `--app node` bundle references GJS ambient globals but was built
|
|
49
|
+
* without the `@gjsify/node-gi/globals` shim.
|
|
50
|
+
*
|
|
51
|
+
* @param code the emitted bundle source
|
|
52
|
+
* @param outfile path of the bundle, for the error message
|
|
53
|
+
*/
|
|
54
|
+
export function assertNodeBundleGlobalsShimmed(code, outfile) {
|
|
55
|
+
const offenders = findUnshimmedGjsGlobals(code);
|
|
56
|
+
if (offenders.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
throw new Error(`gjsify build --app node: ${outfile} references ${offenders.length} GJS ambient global(s) that ` +
|
|
59
|
+
'nothing installs on node/bun/deno, so the bundle throws `ReferenceError: <name> is not defined` ' +
|
|
60
|
+
'the first time that code runs:\n' +
|
|
61
|
+
offenders.map((s) => ` - ${s}`).join('\n') +
|
|
62
|
+
'\n\nThe `@gjsify/node-gi/globals` shim seeds them, but it is injected only when the pre-build ' +
|
|
63
|
+
'detection pass finds them — and that pass did not. The two passes therefore disagreed about the ' +
|
|
64
|
+
'module graph. Either the detection ran against a different graph than the one emitted (check that ' +
|
|
65
|
+
'anything feeding `isGjsSourceBuild()` — the `--globals` register stub, `nodeGiGlobalsInject` — is ' +
|
|
66
|
+
'settled BEFORE `detectNodeGiGlobals`), or the offending package should stop reaching for the ' +
|
|
67
|
+
'legacy `imports` object: `gi://<Ns>` and the bare `system`/`gettext`/`cairo` built-ins resolve on ' +
|
|
68
|
+
'both runtimes, `imports` does not.');
|
|
69
|
+
}
|
package/lib/utils/oxc-resolve.js
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
// `gjsify/register-class-order` rule wired via `.oxlintrc.json` jsPlugins)
|
|
46
46
|
// lives in the Node launcher, so a native oxlint bridge could only run the
|
|
47
47
|
// Rust rule subset. Lint stays Node-spawned until that trade-off is decided
|
|
48
|
-
// (tracked in
|
|
48
|
+
// (tracked in status/open-todos.md).
|
|
49
49
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
50
50
|
import { join, resolve } from 'node:path';
|
|
51
51
|
import { createRequire } from 'node:module';
|
package/lib/utils/prompt.js
CHANGED
|
@@ -82,7 +82,12 @@ async function runRawSession(fn) {
|
|
|
82
82
|
out.write(r.echo);
|
|
83
83
|
if (r.interrupt) {
|
|
84
84
|
cleanup();
|
|
85
|
-
process.exit(
|
|
85
|
+
// `return` — a bare `process.exit()` is deferred under GJS (no
|
|
86
|
+
// atexit), so the loop kept consuming the rest of the chunk
|
|
87
|
+
// after Ctrl-C and could still resolve the pending prompt.
|
|
88
|
+
// Returning stops input handling here; the deferred exit then
|
|
89
|
+
// carries 130 on the way out.
|
|
90
|
+
return process.exit(130);
|
|
86
91
|
}
|
|
87
92
|
if (r.done) {
|
|
88
93
|
const { resolve } = pending;
|
package/lib/utils/runtimes.d.ts
CHANGED
|
@@ -26,6 +26,24 @@ export declare const RUNTIMES: Record<ExampleRuntime, RuntimeSpec>;
|
|
|
26
26
|
export declare function isExampleRuntime(value: string): value is ExampleRuntime;
|
|
27
27
|
/** The `gjsify build --app <target>` a runtime consumes (`gjs`, else `node`). */
|
|
28
28
|
export declare function buildAppForRuntime(runtime: ExampleRuntime): 'gjs' | 'node';
|
|
29
|
+
/**
|
|
30
|
+
* Does running an example/showcase on `runtime` require the GJS SYSTEM
|
|
31
|
+
* dependencies — i.e. a `gjs` binary on PATH?
|
|
32
|
+
*
|
|
33
|
+
* DERIVED from `buildApp`, deliberately not a second table: "consumes the
|
|
34
|
+
* `--app gjs` bundle" and "needs a gjs interpreter" are the same fact, and a
|
|
35
|
+
* hand-written second list drifts the first time a runtime is added.
|
|
36
|
+
*
|
|
37
|
+
* The caller must resolve the runtime BEFORE gating on it. `gjsify showcase`
|
|
38
|
+
* called `runMinimalChecks()` — which marks `gjs` `required` — three statements
|
|
39
|
+
* ABOVE the line resolving `args.runtime`, so `--runtime node` aborted with
|
|
40
|
+
* "Missing system dependencies: ✗ GJS" before reaching the `runtime !== 'gjs'`
|
|
41
|
+
* branch that never touches gjs. Every showcase was unreachable on a host
|
|
42
|
+
* without gjs (Windows, plain Node/bun/deno) — including the default path,
|
|
43
|
+
* since `defaultExampleRuntime()` falls back to the host runtime for exactly
|
|
44
|
+
* those hosts.
|
|
45
|
+
*/
|
|
46
|
+
export declare function requiresGjsSystemDeps(runtime: ExampleRuntime): boolean;
|
|
29
47
|
/** Whether `<probe> --version` succeeds (i.e. the runtime binary is on PATH). */
|
|
30
48
|
export declare function isRuntimeOnPath(probe: string): boolean;
|
|
31
49
|
/**
|
package/lib/utils/runtimes.js
CHANGED
|
@@ -41,6 +41,26 @@ export function isExampleRuntime(value) {
|
|
|
41
41
|
export function buildAppForRuntime(runtime) {
|
|
42
42
|
return RUNTIMES[runtime].buildApp;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Does running an example/showcase on `runtime` require the GJS SYSTEM
|
|
46
|
+
* dependencies — i.e. a `gjs` binary on PATH?
|
|
47
|
+
*
|
|
48
|
+
* DERIVED from `buildApp`, deliberately not a second table: "consumes the
|
|
49
|
+
* `--app gjs` bundle" and "needs a gjs interpreter" are the same fact, and a
|
|
50
|
+
* hand-written second list drifts the first time a runtime is added.
|
|
51
|
+
*
|
|
52
|
+
* The caller must resolve the runtime BEFORE gating on it. `gjsify showcase`
|
|
53
|
+
* called `runMinimalChecks()` — which marks `gjs` `required` — three statements
|
|
54
|
+
* ABOVE the line resolving `args.runtime`, so `--runtime node` aborted with
|
|
55
|
+
* "Missing system dependencies: ✗ GJS" before reaching the `runtime !== 'gjs'`
|
|
56
|
+
* branch that never touches gjs. Every showcase was unreachable on a host
|
|
57
|
+
* without gjs (Windows, plain Node/bun/deno) — including the default path,
|
|
58
|
+
* since `defaultExampleRuntime()` falls back to the host runtime for exactly
|
|
59
|
+
* those hosts.
|
|
60
|
+
*/
|
|
61
|
+
export function requiresGjsSystemDeps(runtime) {
|
|
62
|
+
return buildAppForRuntime(runtime) === 'gjs';
|
|
63
|
+
}
|
|
44
64
|
/** Whether `<probe> --version` succeeds (i.e. the runtime binary is on PATH). */
|
|
45
65
|
export function isRuntimeOnPath(probe) {
|
|
46
66
|
try {
|
package/lib/utils/stdin.js
CHANGED
|
@@ -75,14 +75,13 @@ function readStdinTextGioFile() {
|
|
|
75
75
|
throw new Error('Gio.File.load_contents("/dev/stdin") returned false');
|
|
76
76
|
return new TextDecoder().decode(contents);
|
|
77
77
|
}
|
|
78
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* The GJS `imports.gi` namespace, or `undefined` when not running on GJS.
|
|
80
|
+
* Same optional-chained probe as `isGjs()` — off GJS `imports` is simply
|
|
81
|
+
* absent and the read yields `undefined`; there is no throw path.
|
|
82
|
+
*/
|
|
79
83
|
function giNamespace() {
|
|
80
|
-
|
|
81
|
-
return globalThis.imports?.gi;
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
84
|
+
return globalThis.imports?.gi;
|
|
86
85
|
}
|
|
87
86
|
/**
|
|
88
87
|
* Read the whole of stdin as UTF-8. Works on Node and under the GJS bundle.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -130,19 +130,19 @@
|
|
|
130
130
|
"cli"
|
|
131
131
|
],
|
|
132
132
|
"dependencies": {
|
|
133
|
-
"@gjsify/buffer": "^0.
|
|
134
|
-
"@gjsify/create-app": "^0.
|
|
135
|
-
"@gjsify/node-globals": "^0.
|
|
136
|
-
"@gjsify/node-polyfills": "^0.
|
|
137
|
-
"@gjsify/npm-registry": "^0.
|
|
138
|
-
"@gjsify/resolve-npm": "^0.
|
|
139
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
140
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
141
|
-
"@gjsify/semver": "^0.
|
|
142
|
-
"@gjsify/tar": "^0.
|
|
143
|
-
"@gjsify/tsc": "^0.
|
|
144
|
-
"@gjsify/web-polyfills": "^0.
|
|
145
|
-
"@gjsify/workspace": "^0.
|
|
133
|
+
"@gjsify/buffer": "^0.26.0",
|
|
134
|
+
"@gjsify/create-app": "^0.26.0",
|
|
135
|
+
"@gjsify/node-globals": "^0.26.0",
|
|
136
|
+
"@gjsify/node-polyfills": "^0.26.0",
|
|
137
|
+
"@gjsify/npm-registry": "^0.26.0",
|
|
138
|
+
"@gjsify/resolve-npm": "^0.26.0",
|
|
139
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.26.0",
|
|
140
|
+
"@gjsify/rolldown-plugin-pnp": "^0.26.0",
|
|
141
|
+
"@gjsify/semver": "^0.26.0",
|
|
142
|
+
"@gjsify/tar": "^0.26.0",
|
|
143
|
+
"@gjsify/tsc": "^0.26.0",
|
|
144
|
+
"@gjsify/web-polyfills": "^0.26.0",
|
|
145
|
+
"@gjsify/workspace": "^0.26.0",
|
|
146
146
|
"cosmiconfig": "^9.0.2",
|
|
147
147
|
"get-tsconfig": "^4.14.0",
|
|
148
148
|
"pkg-types": "^2.3.1",
|
|
@@ -150,13 +150,13 @@
|
|
|
150
150
|
"yargs": "^18.0.0"
|
|
151
151
|
},
|
|
152
152
|
"devDependencies": {
|
|
153
|
-
"@gjsify/unit": "^0.
|
|
153
|
+
"@gjsify/unit": "^0.26.0",
|
|
154
154
|
"@types/yargs": "^17.0.35",
|
|
155
155
|
"typescript": "^6.0.3"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@gjsify/oxfmt-native": "^0.
|
|
159
|
-
"@gjsify/rolldown-native": "^0.
|
|
158
|
+
"@gjsify/oxfmt-native": "^0.26.0",
|
|
159
|
+
"@gjsify/rolldown-native": "^0.26.0"
|
|
160
160
|
},
|
|
161
161
|
"peerDependenciesMeta": {
|
|
162
162
|
"@gjsify/oxfmt-native": {
|