@gjsify/cli 0.41.0 → 0.43.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/README.md +79 -33
- package/dist/cli.gjs.mjs +1068 -405
- package/lib/actions/build.d.ts +20 -0
- package/lib/actions/build.js +118 -15
- package/lib/bundler-pick.js +10 -2
- package/lib/cli-app.js +2 -1
- package/lib/commands/browse.js +1 -1
- package/lib/commands/debug.js +1 -1
- package/lib/commands/dev.d.ts +13 -0
- package/lib/commands/dev.js +132 -0
- package/lib/commands/dlx.js +1 -1
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/run.js +38 -85
- package/lib/commands/ship.d.ts +2 -0
- package/lib/commands/ship.js +285 -88
- package/lib/commands/showcase.js +1 -1
- package/lib/commands/storybook.js +17 -74
- package/lib/commands/test.js +12 -16
- package/lib/commands/tsc.js +96 -45
- package/lib/config.js +4 -0
- package/lib/types/config-data.d.ts +59 -0
- package/lib/utils/app-metadata.js +27 -6
- package/lib/utils/check-system-deps.js +49 -7
- package/lib/utils/daemon-command.d.ts +7 -0
- package/lib/utils/daemon-command.js +30 -0
- package/lib/utils/dev-plan.d.ts +52 -0
- package/lib/utils/dev-plan.js +117 -0
- package/lib/utils/gi-runtime-paths.d.ts +27 -0
- package/lib/utils/gi-runtime-paths.js +84 -0
- package/lib/utils/gi-typelib.d.ts +49 -0
- package/lib/utils/gi-typelib.js +160 -0
- package/lib/utils/gjs-bundle-guard.d.ts +57 -3
- package/lib/utils/gjs-bundle-guard.js +122 -13
- package/lib/utils/jsx-config.d.ts +45 -0
- package/lib/utils/jsx-config.js +98 -0
- package/lib/utils/node-script.d.ts +4 -1
- package/lib/utils/node-script.js +27 -3
- package/lib/utils/npm-oidc.js +8 -5
- package/lib/utils/onboard-probe.d.ts +23 -0
- package/lib/utils/onboard-probe.js +65 -0
- package/lib/utils/run-gjs.d.ts +12 -1
- package/lib/utils/run-gjs.js +24 -31
- package/lib/utils/run-node.d.ts +9 -4
- package/lib/utils/run-node.js +34 -41
- package/lib/utils/ship/deb.d.ts +3 -3
- package/lib/utils/ship/deb.js +6 -2
- package/lib/utils/ship/depends.d.ts +14 -6
- package/lib/utils/ship/depends.js +45 -4
- package/lib/utils/ship/discover.js +178 -7
- package/lib/utils/ship/formats.js +9 -1
- package/lib/utils/ship/launcher.js +27 -4
- package/lib/utils/ship/mime.d.ts +19 -0
- package/lib/utils/ship/mime.js +89 -0
- package/lib/utils/ship/payload.d.ts +111 -0
- package/lib/utils/ship/payload.js +174 -0
- package/lib/utils/ship/plan.d.ts +30 -1
- package/lib/utils/ship/plan.js +74 -2
- package/lib/utils/ship/rpm.d.ts +3 -3
- package/lib/utils/ship/rpm.js +20 -3
- package/lib/utils/ship/scripts.d.ts +14 -4
- package/lib/utils/ship/scripts.js +26 -8
- package/lib/utils/ship/settings.d.ts +8 -1
- package/lib/utils/ship/settings.js +26 -1
- package/lib/utils/ship/stage-manifest.d.ts +154 -0
- package/lib/utils/ship/stage-manifest.js +408 -0
- package/lib/utils/ship/stage-writer.d.ts +30 -3
- package/lib/utils/ship/stage-writer.js +76 -9
- package/lib/utils/ship/types.d.ts +110 -18
- package/lib/utils/ship/types.js +5 -0
- package/lib/utils/simple-command.d.ts +16 -0
- package/lib/utils/simple-command.js +77 -0
- package/lib/utils/spawn.d.ts +7 -1
- package/lib/utils/spawn.js +34 -7
- package/lib/utils/system-gi.d.ts +24 -0
- package/lib/utils/system-gi.js +14 -3
- package/lib/utils/watch-loop.d.ts +91 -0
- package/lib/utils/watch-loop.js +207 -0
- package/package.json +19 -18
- package/lib/templates/app/desktop.tmpl +0 -10
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// The watch → rebuild → relaunch loop, shared by `gjsify storybook --watch` and
|
|
2
|
+
// `gjsify dev`.
|
|
3
|
+
//
|
|
4
|
+
// It lives here rather than in either command because it is the whole Node-free
|
|
5
|
+
// inner development cycle and `gjsify build --watch` cannot be it: rolldown's
|
|
6
|
+
// watcher API is npm-engine-only (`bundler-pick.ts` refuses under
|
|
7
|
+
// `@gjsify/rolldown-native`), so the one path that watches is the one running on
|
|
8
|
+
// the host that does not need it. This loop asks the bundler for no watcher at
|
|
9
|
+
// all — `fs.watch` reports the change and the rebuild is an in-process re-entry
|
|
10
|
+
// of the ordinary build command, which is runtime-agnostic.
|
|
11
|
+
import { watch } from 'node:fs';
|
|
12
|
+
import { dirname, resolve, sep } from 'node:path';
|
|
13
|
+
import { holdMainLoop } from '@gjsify/utils/main-loop';
|
|
14
|
+
import { markDaemonCommand } from './daemon-command.js';
|
|
15
|
+
import { spawnToCompletion } from './spawn.js';
|
|
16
|
+
import { RUNTIMES } from './runtimes.js';
|
|
17
|
+
/**
|
|
18
|
+
* Start a child the caller SUPERVISES rather than waits for.
|
|
19
|
+
*
|
|
20
|
+
* `completion: 'daemon'` is the third row of the teardown table: a watch loop
|
|
21
|
+
* neither exits nor unwinds, so the armed GJS main loop is not a leak here —
|
|
22
|
+
* it is what keeps the session alive. The handle arrives synchronously through
|
|
23
|
+
* `onSpawn`; the completion promise is deliberately never awaited (it settles
|
|
24
|
+
* when the child is killed for the next rebuild), only its REJECTION is routed
|
|
25
|
+
* out, so a missing interpreter still surfaces as a rebuild failure.
|
|
26
|
+
*/
|
|
27
|
+
export function spawnSupervised(cmd, cmdArgs, env) {
|
|
28
|
+
return new Promise((resolveChild, reject) => {
|
|
29
|
+
void spawnToCompletion(cmd, cmdArgs, { completion: 'daemon', env, onSpawn: resolveChild }).catch(reject);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Launch a built bundle as a supervised child on `runtime`, wired the way
|
|
34
|
+
* `gjsify run` would launch it for real: `gjs -m` for the `--app gjs` bundle,
|
|
35
|
+
* the `--app node` bundle on node/bun/deno, and in BOTH cases the native
|
|
36
|
+
* typelib/library environment for any `@gjsify` native package the bundle
|
|
37
|
+
* reaches. The env is not optional on the gjs arm either — a watch loop that
|
|
38
|
+
* skipped it would run an app that `gjsify run` can start and this cannot.
|
|
39
|
+
*
|
|
40
|
+
* `subject` names what is being run in the node-gi diagnostic ("the storybook",
|
|
41
|
+
* "this project").
|
|
42
|
+
*/
|
|
43
|
+
export async function spawnBundleSupervised(runtime, bundlePath, cwd, subject) {
|
|
44
|
+
if (runtime !== 'gjs') {
|
|
45
|
+
// Only a bundle that actually reaches gi:// needs the reverse bridge;
|
|
46
|
+
// the bundler keeps `@gjsify/node-gi` external, so its presence in the
|
|
47
|
+
// output is the question. A plain Node app must run without it.
|
|
48
|
+
const { bundleRequiresNodeGi, resolveNodeGiForBundle } = await import('./run-node.js');
|
|
49
|
+
// Same resolution base as `runRuntimeBundle`: the bundle's own dir first
|
|
50
|
+
// (that is where the runtime resolves the external specifier from), cwd
|
|
51
|
+
// as the fallback.
|
|
52
|
+
if (bundleRequiresNodeGi(bundlePath) && !resolveNodeGiForBundle(bundlePath, cwd)) {
|
|
53
|
+
throw new Error(`Cannot run ${subject} on ${runtime}: \`@gjsify/node-gi\` is not installed. ` +
|
|
54
|
+
`Add @gjsify/node-gi as a dependency to run ${subject} on ${runtime}.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const { computeNativeEnvForBundle } = await import('./run-gjs.js');
|
|
58
|
+
const { env: nativeEnv } = computeNativeEnvForBundle(bundlePath, cwd);
|
|
59
|
+
const env = { ...process.env, ...nativeEnv };
|
|
60
|
+
if (runtime === 'gjs') {
|
|
61
|
+
return spawnSupervised('gjs', ['-m', bundlePath], env);
|
|
62
|
+
}
|
|
63
|
+
// `node` uses the resolved node binary (under a GJS-hosted CLI
|
|
64
|
+
// `process.execPath` is gjs); bun/deno launch via the shared spec (deno's
|
|
65
|
+
// `--node-modules-dir=manual`, etc.).
|
|
66
|
+
if (runtime === 'node') {
|
|
67
|
+
const { nodeBinary } = await import('./run-node.js');
|
|
68
|
+
return spawnSupervised(nodeBinary(), [bundlePath], env);
|
|
69
|
+
}
|
|
70
|
+
const [cmd, launchArgs] = RUNTIMES[runtime].launch(bundlePath);
|
|
71
|
+
return spawnSupervised(cmd, launchArgs, env);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Whether a watcher event is the loop's OWN output rather than a source edit.
|
|
75
|
+
*
|
|
76
|
+
* Without this the loop feeds itself whenever the bundle lands inside the
|
|
77
|
+
* watched tree, which is the DEFAULT arrangement rather than an exotic one: the
|
|
78
|
+
* watch dir defaults to the entry point's directory, so `gjsify dev index.ts` on
|
|
79
|
+
* a project building to `dist/` watches the project root and every build it runs
|
|
80
|
+
* is a change under it. Measured before this filter: one edit to `index.ts`
|
|
81
|
+
* started a rebuild every ~5 s that never stopped.
|
|
82
|
+
*
|
|
83
|
+
* Two shapes, because a build writes more than the file it is named after:
|
|
84
|
+
*
|
|
85
|
+
* - the bundle itself and anything sharing its stem (`out.mjs.map`), which the
|
|
86
|
+
* bundler writes beside it in the same pass;
|
|
87
|
+
* - the whole output DIRECTORY — but only when it is not the watched directory
|
|
88
|
+
* itself. `--outfile dist/out.mjs` under a watch dir of `.` puts chunks and
|
|
89
|
+
* assets there too; an `--outfile` written straight into the watched dir has
|
|
90
|
+
* no directory to exclude that would not also exclude the sources.
|
|
91
|
+
*
|
|
92
|
+
* `filename` is what `fs.watch` reports, relative to `dir` — `null` on the
|
|
93
|
+
* platforms that cannot name the changed path, where the honest answer is "no
|
|
94
|
+
* idea", i.e. rebuild.
|
|
95
|
+
*
|
|
96
|
+
* BOTH operands are resolved before they are compared. Resolving only the
|
|
97
|
+
* changed path is right on POSIX for as long as every caller happens to pass an
|
|
98
|
+
* already-absolute `output`, and silently wrong the moment one does not: on
|
|
99
|
+
* win32 `resolve()` also rewrites the separators and picks up the drive, so the
|
|
100
|
+
* two sides stop being the same spelling of the same file and every self-write
|
|
101
|
+
* reads as a source edit — which is the feedback loop this filter exists to
|
|
102
|
+
* stop. Caught by the win32 leg; on Linux all five cases passed.
|
|
103
|
+
*/
|
|
104
|
+
export function isSelfWrite(dir, output, filename) {
|
|
105
|
+
if (output === undefined || filename === null)
|
|
106
|
+
return false;
|
|
107
|
+
const changed = resolve(dir, filename);
|
|
108
|
+
const out = resolve(dir, output);
|
|
109
|
+
if (changed === out || changed.startsWith(`${out}.`))
|
|
110
|
+
return true;
|
|
111
|
+
const outDir = dirname(out);
|
|
112
|
+
if (outDir === resolve(dir))
|
|
113
|
+
return false;
|
|
114
|
+
return changed === outDir || changed.startsWith(`${outDir}${sep}`);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Run the loop: build + launch once, then rebuild + relaunch on every change
|
|
118
|
+
* under `dir`. Returns once the watcher is armed; the process is then held open
|
|
119
|
+
* — by the watcher handle on Node, by the GLib main loop on GJS — so there is
|
|
120
|
+
* nothing left to await. That the RESOLUTION is not completion is the one thing
|
|
121
|
+
* a caller has to know, which is why it is also declared through
|
|
122
|
+
* `markDaemonCommand()` for the callers that cannot see this signature.
|
|
123
|
+
*
|
|
124
|
+
* A build failure is reported and the loop KEEPS WATCHING: the next save is the
|
|
125
|
+
* fix, and a dev loop that dies on a syntax error is a dev loop nobody uses.
|
|
126
|
+
*/
|
|
127
|
+
export async function runWatchLoop(opts) {
|
|
128
|
+
const { dir, label, prepare, build, spawnChild, output, debounceMs = 200 } = opts;
|
|
129
|
+
// Say so BEFORE the first build: `gjsify run`'s in-process dispatch acts on
|
|
130
|
+
// this the moment the handler resolves, and a first build that throws still
|
|
131
|
+
// leaves the loop watching.
|
|
132
|
+
markDaemonCommand();
|
|
133
|
+
let child = null;
|
|
134
|
+
let rebuilding = false;
|
|
135
|
+
let pending = false;
|
|
136
|
+
const rebuild = async () => {
|
|
137
|
+
if (rebuilding) {
|
|
138
|
+
pending = true;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
rebuilding = true;
|
|
142
|
+
try {
|
|
143
|
+
if (prepare)
|
|
144
|
+
await prepare();
|
|
145
|
+
if (child) {
|
|
146
|
+
child.kill();
|
|
147
|
+
child = null;
|
|
148
|
+
}
|
|
149
|
+
await build();
|
|
150
|
+
if (spawnChild) {
|
|
151
|
+
child = await spawnChild();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
console.error(`[${label}] rebuild failed: ${err.message}`);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
rebuilding = false;
|
|
159
|
+
if (pending) {
|
|
160
|
+
pending = false;
|
|
161
|
+
void rebuild();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
await rebuild();
|
|
166
|
+
let debounce = null;
|
|
167
|
+
const watcher = watch(dir, { recursive: true }, (_event, filename) => {
|
|
168
|
+
if (isSelfWrite(dir, output, typeof filename === 'string' ? filename : null))
|
|
169
|
+
return;
|
|
170
|
+
if (debounce)
|
|
171
|
+
clearTimeout(debounce);
|
|
172
|
+
debounce = setTimeout(() => void rebuild(), debounceMs);
|
|
173
|
+
});
|
|
174
|
+
// Under GJS nothing else keeps us here. An `fs.watch` handle is a Gio file
|
|
175
|
+
// monitor, not a Node handle count, and the supervised spawn's own
|
|
176
|
+
// `ensureMainLoop()` declines to arm anything: it is reached from a
|
|
177
|
+
// continuation that GJS resumed from inside the main-context spin it runs
|
|
178
|
+
// while our entry module's top-level await is pending, where
|
|
179
|
+
// `GLib.main_depth()` is 1. So the loop used to print "watching …" and exit
|
|
180
|
+
// 0 on the very host `gjsify dev` exists for, leaving the app orphaned.
|
|
181
|
+
// `holdMainLoop()` is the opt-in that arms it anyway; `shutdown()` below is
|
|
182
|
+
// what takes it back down.
|
|
183
|
+
holdMainLoop();
|
|
184
|
+
// Ctrl+C is how this command ENDS, so the supervised child has to be taken
|
|
185
|
+
// down with it: the loop spawns the app in the CLI's own process group, and
|
|
186
|
+
// a SIGINT delivered to the CLI alone leaves a live GTK window behind with
|
|
187
|
+
// nothing watching it. Registering a handler also removes Node's default
|
|
188
|
+
// terminate, so the exit has to be explicit — and under GJS it is required
|
|
189
|
+
// anyway, since the loop held above is what is keeping us alive. Delivery
|
|
190
|
+
// there is `@gjsify/process` arming a `GLibUnix.signal_add()` source on the
|
|
191
|
+
// first listener, which dispatches on that same held loop; before it existed
|
|
192
|
+
// these two lines registered listeners nothing ever emitted.
|
|
193
|
+
let stopping = false;
|
|
194
|
+
const shutdown = () => {
|
|
195
|
+
if (stopping)
|
|
196
|
+
return;
|
|
197
|
+
stopping = true;
|
|
198
|
+
if (debounce)
|
|
199
|
+
clearTimeout(debounce);
|
|
200
|
+
child?.kill();
|
|
201
|
+
watcher.close();
|
|
202
|
+
process.exit(0);
|
|
203
|
+
};
|
|
204
|
+
process.on('SIGINT', shutdown);
|
|
205
|
+
process.on('SIGTERM', shutdown);
|
|
206
|
+
console.log(`[${label}] watching ${opts.dirLabel ?? dir} — press Ctrl+C to stop`);
|
|
207
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -142,20 +142,21 @@
|
|
|
142
142
|
"cli"
|
|
143
143
|
],
|
|
144
144
|
"dependencies": {
|
|
145
|
-
"@gjsify/buffer": "^0.
|
|
146
|
-
"@gjsify/create-app": "^0.
|
|
147
|
-
"@gjsify/node-globals": "^0.
|
|
148
|
-
"@gjsify/node-polyfills": "^0.
|
|
149
|
-
"@gjsify/npm-registry": "^0.
|
|
150
|
-
"@gjsify/resolve-npm": "^0.
|
|
151
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
152
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
153
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
154
|
-
"@gjsify/semver": "^0.
|
|
155
|
-
"@gjsify/tar": "^0.
|
|
156
|
-
"@gjsify/tsc": "^0.
|
|
157
|
-
"@gjsify/
|
|
158
|
-
"@gjsify/
|
|
145
|
+
"@gjsify/buffer": "^0.43.0",
|
|
146
|
+
"@gjsify/create-app": "^0.43.0",
|
|
147
|
+
"@gjsify/node-globals": "^0.43.0",
|
|
148
|
+
"@gjsify/node-polyfills": "^0.43.0",
|
|
149
|
+
"@gjsify/npm-registry": "^0.43.0",
|
|
150
|
+
"@gjsify/resolve-npm": "^0.43.0",
|
|
151
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.43.0",
|
|
152
|
+
"@gjsify/vite-plugin-blueprint": "^0.43.0",
|
|
153
|
+
"@gjsify/rolldown-plugin-pnp": "^0.43.0",
|
|
154
|
+
"@gjsify/semver": "^0.43.0",
|
|
155
|
+
"@gjsify/tar": "^0.43.0",
|
|
156
|
+
"@gjsify/tsc": "^0.43.0",
|
|
157
|
+
"@gjsify/utils": "^0.43.0",
|
|
158
|
+
"@gjsify/web-polyfills": "^0.43.0",
|
|
159
|
+
"@gjsify/workspace": "^0.43.0",
|
|
159
160
|
"acorn": "^8.17.0",
|
|
160
161
|
"cosmiconfig": "^9.0.2",
|
|
161
162
|
"get-tsconfig": "^4.14.0",
|
|
@@ -164,13 +165,13 @@
|
|
|
164
165
|
"yargs": "^18.0.0"
|
|
165
166
|
},
|
|
166
167
|
"devDependencies": {
|
|
167
|
-
"@gjsify/unit": "^0.
|
|
168
|
+
"@gjsify/unit": "^0.43.0",
|
|
168
169
|
"@types/yargs": "^17.0.35",
|
|
169
170
|
"typescript": "^6.0.3"
|
|
170
171
|
},
|
|
171
172
|
"peerDependencies": {
|
|
172
|
-
"@gjsify/oxfmt-native": "^0.
|
|
173
|
-
"@gjsify/rolldown-native": "^0.
|
|
173
|
+
"@gjsify/oxfmt-native": "^0.43.0",
|
|
174
|
+
"@gjsify/rolldown-native": "^0.43.0"
|
|
174
175
|
},
|
|
175
176
|
"peerDependenciesMeta": {
|
|
176
177
|
"@gjsify/oxfmt-native": {
|