@gjsify/cli 0.53.0 → 0.55.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 +266 -249
- package/lib/bundler-pick.d.ts +35 -0
- package/lib/bundler-pick.js +74 -7
- package/lib/cli-app.js +3 -1
- package/lib/commands/blueprint/index.d.ts +4 -0
- package/lib/commands/blueprint/index.js +15 -0
- package/lib/commands/blueprint/types.d.ts +8 -0
- package/lib/commands/blueprint/types.js +148 -0
- package/lib/commands/devtools.d.ts +21 -0
- package/lib/commands/devtools.js +146 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/ship.js +56 -8
- package/lib/commands/webext/index.js +1 -1
- package/lib/types/config-data.d.ts +5 -0
- package/lib/utils/oxc-resolve.js +9 -7
- package/lib/utils/ship/app-runtime.d.ts +7 -5
- package/lib/utils/ship/app-runtime.js +11 -8
- package/lib/utils/ship/appimage.d.ts +9 -0
- package/lib/utils/ship/appimage.js +2 -1
- package/lib/utils/ship/deb.d.ts +10 -0
- package/lib/utils/ship/deb.js +7 -0
- package/lib/utils/ship/depends.d.ts +37 -3
- package/lib/utils/ship/depends.js +21 -9
- package/lib/utils/ship/flatpak-config.d.ts +16 -0
- package/lib/utils/ship/flatpak-config.js +26 -0
- package/lib/utils/ship/formats.js +10 -7
- package/lib/utils/ship/gi-namespaces.d.ts +16 -0
- package/lib/utils/ship/gi-namespaces.js +64 -1
- package/lib/utils/ship/launcher.js +2 -2
- package/lib/utils/ship/layout.d.ts +7 -6
- package/lib/utils/ship/layout.js +42 -16
- package/lib/utils/ship/rpm.d.ts +5 -0
- package/lib/utils/ship/rpm.js +42 -0
- package/lib/utils/ship/settings.js +6 -0
- package/lib/utils/ship/signing.d.ts +7 -2
- package/lib/utils/ship/signing.js +59 -32
- package/lib/utils/ship/stage-manifest.d.ts +14 -0
- package/lib/utils/ship/stage-manifest.js +7 -0
- package/lib/utils/ship/types.d.ts +2 -0
- package/lib/utils/webext/targets.d.ts +1 -1
- package/lib/utils/webext/targets.js +20 -8
- package/package.json +20 -19
package/lib/bundler-pick.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RolldownOutput, InputOptions, RolldownWatcher } from 'rolldown';
|
|
2
2
|
import type { BundlerOptions } from './types/index.js';
|
|
3
|
+
import { type ResolveNpmPackageOptions } from './utils/resolve-npm-package.js';
|
|
3
4
|
export interface NativePluginContext {
|
|
4
5
|
resolve(specifier: string, importer?: string, opts?: {
|
|
5
6
|
skipSelf?: boolean;
|
|
@@ -113,6 +114,40 @@ export interface RunBundleOptions {
|
|
|
113
114
|
* `rolldown(opts).write(opts.output)` flow used directly in build.ts.
|
|
114
115
|
*/
|
|
115
116
|
export declare function runBundle(finalOpts: BundlerOptions, options?: RunBundleOptions): Promise<RolldownOutput>;
|
|
117
|
+
/**
|
|
118
|
+
* The file `@gjsify/utils/native-library` resolves to, or THROWS when no anchor
|
|
119
|
+
* reaches it.
|
|
120
|
+
*
|
|
121
|
+
* `resolveNpmPackage` first, the same resolver — and the same `?? createRequire`
|
|
122
|
+
* tail — the ENGINE loader below uses, for the same reason: ONE
|
|
123
|
+
* `createRequire(import.meta.url)` anchor walks exactly one node_modules chain,
|
|
124
|
+
* the one rooted at the BUNDLE. v0.53.0's release `publish-napi` job runs the
|
|
125
|
+
* published bundle from `~/.cache/gjsify/bootstrap/`, which is in no workspace's
|
|
126
|
+
* chain, so the bundle-only anchor found nothing — and since #1901 moved
|
|
127
|
+
* `probeNativeLibrary()` onto `tryLoadNative()`'s SUCCESS path, that is not a
|
|
128
|
+
* degraded diagnostic but NO ENGINE: every `gjsify build` under GJS died with
|
|
129
|
+
* "no usable bundler engine", reported by a diagnostic that resolves the same
|
|
130
|
+
* specifier and so could not name the cause. MEASURED on the release job's own
|
|
131
|
+
* shape: with `@gjsify/utils` FULLY BUILT, anchoring only at the bundle still
|
|
132
|
+
* fails with `Cannot find module "@gjsify/utils/native-library" - not found in
|
|
133
|
+
* any node_modules directory`; anchoring at cwd resolves it. `main.yml` never
|
|
134
|
+
* saw this because it exports `XDG_CACHE_HOME=$PWD/.gjsify-cache` and therefore
|
|
135
|
+
* caches the bundle INSIDE the workspace, where the bundle-only anchor happens
|
|
136
|
+
* to work.
|
|
137
|
+
*
|
|
138
|
+
* Two independent defects, so both are fixed where they live: this anchor, and
|
|
139
|
+
* the `lib/esm` emit it then needs on a cold tree (`@gjsify/utils build:esm`
|
|
140
|
+
* before the first bundler clause, rule 5 of
|
|
141
|
+
* `scripts/check-build-infra-order.mjs`).
|
|
142
|
+
*
|
|
143
|
+
* SCOPE, since it decides what this unblocks: the release job runs `build:infra`
|
|
144
|
+
* with the PUBLISHED bundle, which is fixed code this change cannot reach — so on
|
|
145
|
+
* its own it does NOT unblock v0.53.0. What does is the `XDG_CACHE_HOME` line in
|
|
146
|
+
* release.yml's `publish-napi` job, which puts that bundle inside the workspace
|
|
147
|
+
* where ANY anchor finds this tree. This fix is what stops the next release from
|
|
148
|
+
* depending on that line.
|
|
149
|
+
*/
|
|
150
|
+
export declare function resolveNativeLibraryModule(opts?: ResolveNpmPackageOptions): string;
|
|
116
151
|
export declare function shouldUseNative(): Promise<boolean>;
|
|
117
152
|
/**
|
|
118
153
|
* Walk parent directories from `startDir` (and optionally a second anchor
|
package/lib/bundler-pick.js
CHANGED
|
@@ -318,6 +318,43 @@ let _utilsCore = null;
|
|
|
318
318
|
* measurement is reported as one.
|
|
319
319
|
*/
|
|
320
320
|
let _utilsCoreError = null;
|
|
321
|
+
/**
|
|
322
|
+
* The file `@gjsify/utils/native-library` resolves to, or THROWS when no anchor
|
|
323
|
+
* reaches it.
|
|
324
|
+
*
|
|
325
|
+
* `resolveNpmPackage` first, the same resolver — and the same `?? createRequire`
|
|
326
|
+
* tail — the ENGINE loader below uses, for the same reason: ONE
|
|
327
|
+
* `createRequire(import.meta.url)` anchor walks exactly one node_modules chain,
|
|
328
|
+
* the one rooted at the BUNDLE. v0.53.0's release `publish-napi` job runs the
|
|
329
|
+
* published bundle from `~/.cache/gjsify/bootstrap/`, which is in no workspace's
|
|
330
|
+
* chain, so the bundle-only anchor found nothing — and since #1901 moved
|
|
331
|
+
* `probeNativeLibrary()` onto `tryLoadNative()`'s SUCCESS path, that is not a
|
|
332
|
+
* degraded diagnostic but NO ENGINE: every `gjsify build` under GJS died with
|
|
333
|
+
* "no usable bundler engine", reported by a diagnostic that resolves the same
|
|
334
|
+
* specifier and so could not name the cause. MEASURED on the release job's own
|
|
335
|
+
* shape: with `@gjsify/utils` FULLY BUILT, anchoring only at the bundle still
|
|
336
|
+
* fails with `Cannot find module "@gjsify/utils/native-library" - not found in
|
|
337
|
+
* any node_modules directory`; anchoring at cwd resolves it. `main.yml` never
|
|
338
|
+
* saw this because it exports `XDG_CACHE_HOME=$PWD/.gjsify-cache` and therefore
|
|
339
|
+
* caches the bundle INSIDE the workspace, where the bundle-only anchor happens
|
|
340
|
+
* to work.
|
|
341
|
+
*
|
|
342
|
+
* Two independent defects, so both are fixed where they live: this anchor, and
|
|
343
|
+
* the `lib/esm` emit it then needs on a cold tree (`@gjsify/utils build:esm`
|
|
344
|
+
* before the first bundler clause, rule 5 of
|
|
345
|
+
* `scripts/check-build-infra-order.mjs`).
|
|
346
|
+
*
|
|
347
|
+
* SCOPE, since it decides what this unblocks: the release job runs `build:infra`
|
|
348
|
+
* with the PUBLISHED bundle, which is fixed code this change cannot reach — so on
|
|
349
|
+
* its own it does NOT unblock v0.53.0. What does is the `XDG_CACHE_HOME` line in
|
|
350
|
+
* release.yml's `publish-napi` job, which puts that bundle inside the workspace
|
|
351
|
+
* where ANY anchor finds this tree. This fix is what stops the next release from
|
|
352
|
+
* depending on that line.
|
|
353
|
+
*/
|
|
354
|
+
export function resolveNativeLibraryModule(opts = {}) {
|
|
355
|
+
const specifier = '@gjsify/utils/native-library';
|
|
356
|
+
return resolveNpmPackage(specifier, opts) ?? createRequire(opts.bundleUrl ?? import.meta.url).resolve(specifier);
|
|
357
|
+
}
|
|
321
358
|
async function utilsCore() {
|
|
322
359
|
if (_utilsCore === null) {
|
|
323
360
|
// `./native-library`, not `./core`: `core` re-exports `main-loop`, which
|
|
@@ -325,7 +362,18 @@ async function utilsCore() {
|
|
|
325
362
|
// so loading `core` off disk would give the process a SECOND copy of that
|
|
326
363
|
// singleton — inert today because only pure probe functions are read
|
|
327
364
|
// through this edge, and a trap the first time that stops being true.
|
|
328
|
-
|
|
365
|
+
//
|
|
366
|
+
// Off disk, so the resolution's ANCHORS matter — see
|
|
367
|
+
// `resolveNativeLibraryModule()` — and on a cold tree the WORKSPACE copy
|
|
368
|
+
// it finds is itself a build output, which is what
|
|
369
|
+
// `@gjsify/utils build:esm` orders (rule 5 of
|
|
370
|
+
// `scripts/check-build-infra-order.mjs`). Two independent preconditions,
|
|
371
|
+
// and v0.53.0's release `publish-napi` job needed both: a bundle-only
|
|
372
|
+
// anchor resolved nothing when the bundle was the published
|
|
373
|
+
// `cli.gjs.mjs` — one loose file in `<cache>/gjsify/bootstrap/`, no
|
|
374
|
+
// `node_modules` above it. So a throw here is "not measured" (see
|
|
375
|
+
// `tryLoadNative`), never "no engine".
|
|
376
|
+
const href = pathToFileURL(resolveNativeLibraryModule({ bundleUrl: import.meta.url })).href;
|
|
329
377
|
_utilsCore = (await import(/* @vite-ignore */ href));
|
|
330
378
|
}
|
|
331
379
|
return _utilsCore;
|
|
@@ -464,18 +512,37 @@ async function tryLoadNative() {
|
|
|
464
512
|
// past `diagnoseNativeEngine()`, which then never runs.
|
|
465
513
|
// Separated from the engine's own failure so that "the engine is
|
|
466
514
|
// absent" and "the measurement could not run" stay distinguishable
|
|
467
|
-
// at the one place that reports them
|
|
468
|
-
|
|
515
|
+
// at the one place that reports them — and only a library the probe
|
|
516
|
+
// MEASURED as broken vetoes the engine. A probe that could not be
|
|
517
|
+
// LOADED does not: the engine's own load already answered twice
|
|
518
|
+
// over (the module imported, `hasNativeRolldown()` resolved its
|
|
519
|
+
// typelib), and under GJS there is no second engine to fall back to
|
|
520
|
+
// — the npm `rolldown` crate is a napi binary — so `null` here
|
|
521
|
+
// turned "nothing was measured" into "there is no bundler".
|
|
522
|
+
//
|
|
523
|
+
// What reaches that verdict is not exotic. `utilsCore()` resolves
|
|
524
|
+
// `@gjsify/utils/native-library` from THIS bundle's own directory,
|
|
525
|
+
// while every other resolution in this function goes through
|
|
526
|
+
// `resolveNpmPackage()` with its cwd/workspace anchors; the bundle
|
|
527
|
+
// that runs a cold `build:infra` is the PUBLISHED `cli.gjs.mjs`,
|
|
528
|
+
// which `install.mjs --fetch-only` caches as one loose file in
|
|
529
|
+
// `<cache>/gjsify/bootstrap/` with no `node_modules` beside it. On
|
|
530
|
+
// the release runner the probe therefore resolved nothing and
|
|
531
|
+
// `@gjsify/napi` did not publish (v0.53.0), and building utils'
|
|
532
|
+
// `lib/esm` first changed nothing — measured, which is why the
|
|
533
|
+
// asymmetry is fixed here and no clause was reordered.
|
|
534
|
+
let surface = null;
|
|
469
535
|
try {
|
|
470
536
|
surface = await utilsCore();
|
|
471
537
|
}
|
|
472
538
|
catch (err) {
|
|
473
539
|
_utilsCoreError = err;
|
|
474
|
-
return null;
|
|
475
540
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
541
|
+
if (surface !== null) {
|
|
542
|
+
_nativeLibraryFailure = surface.probeNativeLibrary('GjsifyRolldown');
|
|
543
|
+
if (_nativeLibraryFailure)
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
479
546
|
return mod;
|
|
480
547
|
}
|
|
481
548
|
catch {
|
package/lib/cli-app.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import yargs from 'yargs';
|
|
19
19
|
import { classifyCliFailure } from './cli-fail.js';
|
|
20
20
|
import { cliVersion } from './utils/publish-headers.js';
|
|
21
|
-
import { buildCommand as build, clearCommand as clear, copyCommand as copy, envCommand as env, execCommand as exec, 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, shipCommand as ship, dlxCommand as dlx, installCommand as install, linkCommand as link, unlinkCommand as unlink, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, whoamiCommand as whoami, loginCommand as login, logoutCommand as logout, trustCommand as trust, onboardCommand as onboard, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, pruneCommand as prune, uninstallCommand as uninstall, formatCommand as format, lintCommand as lint, fixCommand as fix, upgradeCommand as upgrade, barrelsCommand as barrels, tscCommand as tsc, affectedCommand as affected, storybookCommand as storybook, devCommand as dev, debugCommand as debug, browseCommand as browse, webextCommand as webext, } from './commands/index.js';
|
|
21
|
+
import { buildCommand as build, clearCommand as clear, copyCommand as copy, envCommand as env, execCommand as exec, 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, shipCommand as ship, dlxCommand as dlx, installCommand as install, linkCommand as link, unlinkCommand as unlink, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, whoamiCommand as whoami, loginCommand as login, logoutCommand as logout, trustCommand as trust, onboardCommand as onboard, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, pruneCommand as prune, uninstallCommand as uninstall, formatCommand as format, lintCommand as lint, fixCommand as fix, upgradeCommand as upgrade, barrelsCommand as barrels, tscCommand as tsc, affectedCommand as affected, storybookCommand as storybook, devCommand as dev, debugCommand as debug, devtoolsCommand as devtools, browseCommand as browse, webextCommand as webext, blueprintCommand as blueprint, } from './commands/index.js';
|
|
22
22
|
import { APP_NAME } from './constants.js';
|
|
23
23
|
import { isBun, isDeno, isNode, gjsSystemVersion } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
24
24
|
import { ensureGjsifyShimOnPath } from './utils/gjsify-shim.js';
|
|
@@ -135,8 +135,10 @@ export async function runCli(argv) {
|
|
|
135
135
|
.command(storybook.command, storybook.description, storybook.builder, storybook.handler)
|
|
136
136
|
.command(dev.command, dev.description, dev.builder, dev.handler)
|
|
137
137
|
.command(debug.command, debug.description, debug.builder, debug.handler)
|
|
138
|
+
.command(devtools.command, devtools.description, devtools.builder, devtools.handler)
|
|
138
139
|
.command(browse.command, browse.description, browse.builder, browse.handler)
|
|
139
140
|
.command(webext.command, webext.description, webext.builder, webext.handler)
|
|
141
|
+
.command(blueprint.command, blueprint.description, blueprint.builder, blueprint.handler)
|
|
140
142
|
.demandCommand(1)
|
|
141
143
|
.epilogue(`Running on ${runtimeLabel()}`)
|
|
142
144
|
.help()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// `gjsify blueprint` — yargs subcommand-group dispatcher, the same shape as `flatpak/`.
|
|
2
|
+
//
|
|
3
|
+
// A directory rather than one file because `check-website-cli-reference.mjs` reads every
|
|
4
|
+
// `command:` in a top-level file as a top-level command; a group's subcommands live beside
|
|
5
|
+
// its `index.ts` and are documented as `#### gjsify blueprint <sub>`.
|
|
6
|
+
import { blueprintTypesCommand } from './types.js';
|
|
7
|
+
export { blueprintTypesCommand };
|
|
8
|
+
export const blueprintCommand = {
|
|
9
|
+
command: 'blueprint <subcommand>',
|
|
10
|
+
description: 'Blueprint (.blp) tooling: generate the TypeScript type sidecars a .blp exports (ADR 0088).',
|
|
11
|
+
builder: (yargs) => yargs
|
|
12
|
+
.command(blueprintTypesCommand.command, blueprintTypesCommand.description, blueprintTypesCommand.builder, blueprintTypesCommand.handler)
|
|
13
|
+
.demandCommand(1)
|
|
14
|
+
.strict(),
|
|
15
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// `gjsify blueprint types [paths..]` — write the `.d.blp.ts` sidecars ADR 0088 § 4 defines.
|
|
2
|
+
//
|
|
3
|
+
// WHY THE CLI HAS THIS AT ALL, when `@gjsify/vite-plugin-blueprint` already writes a sidecar
|
|
4
|
+
// during `load`: a type-check is not a build. `gjsify check` runs `tsc` and nothing else, so a
|
|
5
|
+
// fresh clone — or a `.blp` whose consumer is being WRITTEN, before any bundle exists — has no
|
|
6
|
+
// sidecar unless something outside the bundler can produce one. This is that something, and it
|
|
7
|
+
// is why the command is usable before `gjsify check` rather than only after a build.
|
|
8
|
+
//
|
|
9
|
+
// `--check` IS FOR CONSUMERS OF THE PUBLISHED CLI. In this repository the sidecar gate is
|
|
10
|
+
// `scripts/check-blueprint-sidecars.mjs`, which CI runs with plain `node` beside the other three
|
|
11
|
+
// Blueprint gates — it has to be, because the CI bootstrap is the PREVIOUS RELEASE's bundle
|
|
12
|
+
// (ADR 0002) and cannot run a command this tree just added. A third-party project has no
|
|
13
|
+
// `scripts/` of ours, so without this flag it could generate sidecars and never hold them.
|
|
14
|
+
// The two share the derivation; what is duplicated is the byte comparison.
|
|
15
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
17
|
+
import { emitTypedSidecar, parseBlueprint, sidecarPathFor } from '@gjsify/blueprint';
|
|
18
|
+
import { emitFormatForTree } from '@gjsify/blueprint/oxfmt';
|
|
19
|
+
/** What a walk never descends into: build output, and other projects' sources. */
|
|
20
|
+
const SKIP = new Set(['node_modules', 'dist', 'lib', '.git']);
|
|
21
|
+
/**
|
|
22
|
+
* Every `.blp` under one path, or the path itself when it names one.
|
|
23
|
+
*
|
|
24
|
+
* `statSync` rather than `withFileTypes`, the same choice `utils/build-cache.ts` makes and for
|
|
25
|
+
* the same reason: this command also runs from the GJS bundle, where `@gjsify/fs` is the readdir
|
|
26
|
+
* implementation.
|
|
27
|
+
*/
|
|
28
|
+
function blueprintsUnder(path, found) {
|
|
29
|
+
let entry;
|
|
30
|
+
try {
|
|
31
|
+
entry = statSync(path);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
if (!entry.isDirectory()) {
|
|
37
|
+
if (path.endsWith('.blp'))
|
|
38
|
+
found.push(path);
|
|
39
|
+
return found;
|
|
40
|
+
}
|
|
41
|
+
for (const name of readdirSync(path).sort()) {
|
|
42
|
+
if (SKIP.has(name) || name.startsWith('.'))
|
|
43
|
+
continue;
|
|
44
|
+
blueprintsUnder(join(path, name), found);
|
|
45
|
+
}
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The emitter options for the tree a `.blp` lives in, memoized per directory.
|
|
50
|
+
*
|
|
51
|
+
* ONE directory of the walk is a sidecar's, not the CWD's, and that is the difference between
|
|
52
|
+
* right and right-by-luck in a monorepo: a package with its own `.oxfmtrc` gets that package's
|
|
53
|
+
* width, and running the command from the repo root still writes that package's indent. The walk
|
|
54
|
+
* itself is `emitFormatForTree`'s — the same one the drift gate and the bundler plugin use, so
|
|
55
|
+
* three callers cannot answer "which config does the formatter read" three ways (ADR 0088 §
|
|
56
|
+
* Consequences: the emitter once read `printWidth` and hardcoded a 4-space indent beside it, which
|
|
57
|
+
* is a `tabWidth: 2` consumer's red `oxfmt --check`).
|
|
58
|
+
*
|
|
59
|
+
* Memoized because the walk touches the filesystem once per `.blp` and a tree of a thousand
|
|
60
|
+
* `.blp` files would do that a thousand times; one read per distinct directory, not per file.
|
|
61
|
+
*/
|
|
62
|
+
const formatCache = new Map();
|
|
63
|
+
const formatFor = (dir) => {
|
|
64
|
+
const cached = formatCache.get(dir);
|
|
65
|
+
if (cached !== undefined)
|
|
66
|
+
return cached;
|
|
67
|
+
const format = emitFormatForTree(dir);
|
|
68
|
+
formatCache.set(dir, format);
|
|
69
|
+
return format;
|
|
70
|
+
};
|
|
71
|
+
export const blueprintTypesCommand = {
|
|
72
|
+
command: 'types [paths..]',
|
|
73
|
+
description: 'Generate the `<name>.d.blp.ts` type sidecar for each .blp (ADR 0088), so `import { build }` / ' +
|
|
74
|
+
'`import { GTypeName }` type-check before anything is built. Defaults to the current directory.',
|
|
75
|
+
builder: (yargs) => yargs
|
|
76
|
+
.positional('paths', {
|
|
77
|
+
description: 'Files or directories to scan for .blp (default: the current directory)',
|
|
78
|
+
type: 'string',
|
|
79
|
+
array: true,
|
|
80
|
+
normalize: true,
|
|
81
|
+
default: [],
|
|
82
|
+
})
|
|
83
|
+
.option('check', {
|
|
84
|
+
description: 'Write nothing; exit non-zero when a sidecar is missing or stale',
|
|
85
|
+
type: 'boolean',
|
|
86
|
+
default: false,
|
|
87
|
+
})
|
|
88
|
+
.option('verbose', {
|
|
89
|
+
description: 'Print every file considered, not only the ones that changed',
|
|
90
|
+
type: 'boolean',
|
|
91
|
+
default: false,
|
|
92
|
+
}),
|
|
93
|
+
handler: async (args) => {
|
|
94
|
+
const given = args.paths ?? [];
|
|
95
|
+
const roots = (given.length > 0 ? given : ['.']).map((path) => isAbsolute(path) ? path : resolve(process.cwd(), path));
|
|
96
|
+
const blueprints = [];
|
|
97
|
+
for (const root of roots)
|
|
98
|
+
blueprintsUnder(root, blueprints);
|
|
99
|
+
const outdated = [];
|
|
100
|
+
let written = 0;
|
|
101
|
+
for (const file of blueprints) {
|
|
102
|
+
const sidecar = sidecarPathFor(file);
|
|
103
|
+
// A parse or derive refusal is NOT caught. It names its file and its line (ADR 0053
|
|
104
|
+
// clause 3), which is the whole contract, and swallowing it here would report "0
|
|
105
|
+
// sidecars written" for a `.blp` that cannot compile.
|
|
106
|
+
// `basename` from `node:path`, which is the host's own answer under the GJS bundle as
|
|
107
|
+
// well as under node: `@gjsify/node-path` picks its win32 or posix flavour at module
|
|
108
|
+
// init, and the path here is the user's, on their machine. `@gjsify/blueprint` cannot
|
|
109
|
+
// take the name itself — see `typed-exports.mjs` § `emitTypedSidecar`.
|
|
110
|
+
const ast = parseBlueprint(readFileSync(file, 'utf8'), file);
|
|
111
|
+
const expected = emitTypedSidecar(ast, basename(file), formatFor(dirname(file)));
|
|
112
|
+
let current;
|
|
113
|
+
try {
|
|
114
|
+
current = readFileSync(sidecar, 'utf8');
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
current = undefined;
|
|
118
|
+
}
|
|
119
|
+
if (current === expected) {
|
|
120
|
+
if (args.verbose)
|
|
121
|
+
console.log(`[gjsify blueprint types] current: ${sidecar}`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (args.check) {
|
|
125
|
+
outdated.push(sidecar);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// Only on a difference: an identical rewrite still moves the mtime, which a watcher
|
|
129
|
+
// reads as a change — the same rule `vite-plugin-blueprint`'s own writer follows.
|
|
130
|
+
writeFileSync(sidecar, expected, 'utf8');
|
|
131
|
+
written += 1;
|
|
132
|
+
console.log(`[gjsify blueprint types] wrote ${sidecar}`);
|
|
133
|
+
}
|
|
134
|
+
if (args.check) {
|
|
135
|
+
if (outdated.length === 0) {
|
|
136
|
+
console.log(`[gjsify blueprint types] ${blueprints.length} .blp checked, every sidecar current.`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
for (const sidecar of outdated)
|
|
140
|
+
console.error(`[gjsify blueprint types] missing or stale: ${sidecar}`);
|
|
141
|
+
console.error('[gjsify blueprint types] run without --check to regenerate.');
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
console.log(`[gjsify blueprint types] ${blueprints.length} .blp scanned, ${written} sidecar(s) written, ` +
|
|
146
|
+
`${blueprints.length - written} already current.`);
|
|
147
|
+
},
|
|
148
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
interface DevtoolsCliOptions {
|
|
3
|
+
/** Positional 1: the control-plane operation, e.g. `shot`. */
|
|
4
|
+
operation: string;
|
|
5
|
+
/** Positional 2+: its arguments — the output path for `shot`, the selector for `find`. */
|
|
6
|
+
operands?: string[];
|
|
7
|
+
busName?: string;
|
|
8
|
+
address?: string;
|
|
9
|
+
instance?: string;
|
|
10
|
+
globals?: string;
|
|
11
|
+
out?: string;
|
|
12
|
+
timeout: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `gjsify devtools` builds and runs a tiny client over the `org.gjsify.Devtools` control
|
|
16
|
+
* plane. Subcommands mirror the control-plane methods — `shot`, `find`, `activate`, `key`,
|
|
17
|
+
* `tree`, `property`, `resize`, `status`, `actions`, `toplevels`, `css`, `gsettings`,
|
|
18
|
+
* `instances` — so anything a rig does by hand over `gdbus` has a scriptable spelling.
|
|
19
|
+
*/
|
|
20
|
+
export declare const devtoolsCommand: Command<unknown, DevtoolsCliOptions>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// `gjsify devtools` — drive a running, devtools-enabled gjsify app from a SCRIPT.
|
|
2
|
+
//
|
|
3
|
+
// The peer of `gjsify debug`, not a replacement for it. `debug` speaks MCP over stdio, which
|
|
4
|
+
// is the right shape for an AI agent and the wrong one for a shell script, a Makefile, or a CI
|
|
5
|
+
// step: there is no way to pipe `Screenshot` into a file, because MCP's answer is base64 inside
|
|
6
|
+
// a JSON-RPC frame. So every consumer that wanted a screenshot on disk wrote its own GJS
|
|
7
|
+
// caller to unpack the `ay` variant — and every one of them had to re-decide the same two
|
|
8
|
+
// things this command decides once:
|
|
9
|
+
//
|
|
10
|
+
// · EMPTY BYTES ARE A FAILURE. `Screenshot` answers `ay[0]` when there is no active window
|
|
11
|
+
// or the window was never realised (the deliberate `CaptureBlocker` contract). Writing
|
|
12
|
+
// that produces a 0-byte file named `.png` and a success message.
|
|
13
|
+
// · THE SIZE COMES FROM THE PNG. `ResizeWindow` answers with the size it was ASKED for, so a
|
|
14
|
+
// window that ignored the resize still reports success — measured in a consumer: 1280
|
|
15
|
+
// requested, 1100 in the file, four checks green.
|
|
16
|
+
//
|
|
17
|
+
// Both live in `@gjsify/devtools` (`captureShot`), so a consumer driving the control plane from
|
|
18
|
+
// its own code gets them by importing one function rather than by copying a script.
|
|
19
|
+
//
|
|
20
|
+
// Dispatch is IN-PROCESS like `debug`/`storybook`: the generated entry imports
|
|
21
|
+
// `@gjsify/devtools` + `@gjsify/devtools-mcp` from the CONSUMER's node_modules (so the CLI keeps
|
|
22
|
+
// no dependency on either), is built `--app gjs`, and the RESULT is printed on stdout.
|
|
23
|
+
//
|
|
24
|
+
// WHY A GENERATED BUNDLE AND NOT `gdbus`: the peer transport (macOS/Windows, which have no
|
|
25
|
+
// session bus) is `Gio.DBusServer` + an address file, and `gdbus` cannot speak it. A command
|
|
26
|
+
// that reached the app only over the session bus would be the Linux-only half of a capability
|
|
27
|
+
// the package deliberately made OS-agnostic.
|
|
28
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
29
|
+
import { join, resolve } from 'node:path';
|
|
30
|
+
function readPackage(cwd) {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The generated entry. It takes its options as ARGV rather than baking them in, so one cached
|
|
40
|
+
* bundle serves every invocation — the command-line flags of a rig change far more often than
|
|
41
|
+
* the code does, and a rebuild per flag would make `--timeout` cost a full bundle build.
|
|
42
|
+
*
|
|
43
|
+
* `system.exit(await …)` is load-bearing rather than decorative: 0 done / 1 the app said no /
|
|
44
|
+
* 2 the request itself was wrong is the whole contract a shell rig reads out of `$?`, and an
|
|
45
|
+
* awaited value cannot become a process exit status under GJS. `system.programArgs` are the
|
|
46
|
+
* user arguments — under `gjs -m bundle.js …` the `-m` and the path are `programInvocationName`,
|
|
47
|
+
* not args, so nothing has to be sliced off.
|
|
48
|
+
*/
|
|
49
|
+
function generateEntry() {
|
|
50
|
+
return `// Auto-generated by \`gjsify devtools\` — do not edit by hand.
|
|
51
|
+
import system from 'system';
|
|
52
|
+
|
|
53
|
+
import { runDevtoolsCli } from '@gjsify/devtools-mcp';
|
|
54
|
+
|
|
55
|
+
system.exit(await runDevtoolsCli(system.programArgs));
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `gjsify devtools` builds and runs a tiny client over the `org.gjsify.Devtools` control
|
|
60
|
+
* plane. Subcommands mirror the control-plane methods — `shot`, `find`, `activate`, `key`,
|
|
61
|
+
* `tree`, `property`, `resize`, `status`, `actions`, `toplevels`, `css`, `gsettings`,
|
|
62
|
+
* `instances` — so anything a rig does by hand over `gdbus` has a scriptable spelling.
|
|
63
|
+
*/
|
|
64
|
+
export const devtoolsCommand = {
|
|
65
|
+
command: 'devtools',
|
|
66
|
+
description: 'Drive a running devtools-enabled app from a script: capture a screenshot to a file, find a widget, ' +
|
|
67
|
+
'activate it, send a key. Peer of `gjsify debug`, which speaks MCP for an AI agent.',
|
|
68
|
+
builder: (yargs) => yargs
|
|
69
|
+
.positional('operation', {
|
|
70
|
+
description: 'Control-plane operation: shot | find | activate | key | tree | property | …',
|
|
71
|
+
type: 'string',
|
|
72
|
+
demandOption: true,
|
|
73
|
+
})
|
|
74
|
+
.positional('operands', {
|
|
75
|
+
description: 'Subcommand arguments — e.g. the output path for `shot`, the selector for `find`.',
|
|
76
|
+
type: 'string',
|
|
77
|
+
array: true,
|
|
78
|
+
})
|
|
79
|
+
.option('bus-name', {
|
|
80
|
+
description: "App DBus base name — the app's Gtk application id (default: gjsify.devtools.busNameBase, " +
|
|
81
|
+
'then the storybook app-id). Unlike `gjsify debug` there is no derived fallback: the bus name ' +
|
|
82
|
+
'IS the application id, and guessing it produces a name nothing answers to',
|
|
83
|
+
type: 'string',
|
|
84
|
+
})
|
|
85
|
+
.option('address', {
|
|
86
|
+
description: 'Peer D-Bus address of the app (unix:path=… / nonce-tcp:…) instead of the session bus — for ' +
|
|
87
|
+
'macOS/Windows, which have no session bus. Default: GJSIFY_DEVTOOLS_ADDRESS, then the address ' +
|
|
88
|
+
'file the app publishes, then the session bus',
|
|
89
|
+
type: 'string',
|
|
90
|
+
})
|
|
91
|
+
.option('instance', {
|
|
92
|
+
description: 'App instance label for a multi-instance app (default: GJSIFY_DEVTOOLS_INSTANCE)',
|
|
93
|
+
type: 'string',
|
|
94
|
+
})
|
|
95
|
+
.option('globals', {
|
|
96
|
+
description: 'Value for `gjsify build --globals` (default: auto)',
|
|
97
|
+
type: 'string',
|
|
98
|
+
})
|
|
99
|
+
.option('out', {
|
|
100
|
+
description: 'Output bundle path (default: node_modules/.cache/gjsify-devtools)',
|
|
101
|
+
type: 'string',
|
|
102
|
+
})
|
|
103
|
+
.option('timeout', {
|
|
104
|
+
description: 'Seconds to keep polling GetStatus for the control plane to answer (default: 30). 0 = do not wait',
|
|
105
|
+
type: 'number',
|
|
106
|
+
default: 30,
|
|
107
|
+
}),
|
|
108
|
+
handler: async (args) => {
|
|
109
|
+
const cwd = process.cwd();
|
|
110
|
+
const pkg = readPackage(cwd);
|
|
111
|
+
const config = pkg.gjsify?.devtools ?? {};
|
|
112
|
+
const busNameBase = args.busName ??
|
|
113
|
+
config.busNameBase ??
|
|
114
|
+
pkg.gjsify?.storybook?.applicationId ??
|
|
115
|
+
pkg.gjsify?.browse?.applicationId;
|
|
116
|
+
// Asked for BEFORE a bundle is built: without a bus name the build succeeds and the
|
|
117
|
+
// client then exits 2 having done nothing, which reads like a broken app.
|
|
118
|
+
if (!busNameBase) {
|
|
119
|
+
console.error("gjsify devtools: no bus name — the bus name IS the app's Gtk application id, so it cannot be " +
|
|
120
|
+
'derived from the package name. Pass --bus-name <org.example.App> (the app prints it when it ' +
|
|
121
|
+
'exports the control plane) or set package.json#gjsify.devtools.busNameBase.');
|
|
122
|
+
return process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const cacheDir = join(cwd, 'node_modules', '.cache', 'gjsify-devtools');
|
|
125
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
126
|
+
const entryPath = join(cacheDir, 'entry.ts');
|
|
127
|
+
const outPath = args.out ? resolve(cwd, args.out) : join(cacheDir, 'cli.gjs.mjs');
|
|
128
|
+
const globals = args.globals ?? 'auto';
|
|
129
|
+
writeFileSync(entryPath, generateEntry());
|
|
130
|
+
// stderr for progress — stdout is the RESULT the caller captures.
|
|
131
|
+
console.error(`[gjsify devtools] bus=${busNameBase}${args.address ? ` address=${args.address}` : ''}${args.instance ? ` instance=${args.instance}` : ''}`);
|
|
132
|
+
const { runCli } = await import('../cli-app.js');
|
|
133
|
+
await runCli(['build', entryPath, '--app', 'gjs', '--globals', globals, '--outfile', outPath]);
|
|
134
|
+
// The RESOLVED values are forwarded, never the raw argv: the child parses its own flags
|
|
135
|
+
// and knows nothing about package.json, so forwarding only what was typed would drop the
|
|
136
|
+
// configured bus name on the floor.
|
|
137
|
+
const forwarded = [args.operation, ...(args.operands ?? []), '--bus-name', busNameBase];
|
|
138
|
+
if (args.address)
|
|
139
|
+
forwarded.push('--address', args.address);
|
|
140
|
+
if (args.instance)
|
|
141
|
+
forwarded.push('--instance', args.instance);
|
|
142
|
+
forwarded.push('--timeout', String(args.timeout));
|
|
143
|
+
const { runGjsBundle } = await import('../utils/run-gjs.js');
|
|
144
|
+
await runGjsBundle(outPath, forwarded, { completion: 'exit', exitOnSuccess: true });
|
|
145
|
+
},
|
|
146
|
+
};
|
package/lib/commands/index.d.ts
CHANGED
|
@@ -41,5 +41,7 @@ export * from './affected.js';
|
|
|
41
41
|
export * from './storybook.js';
|
|
42
42
|
export * from './dev.js';
|
|
43
43
|
export * from './debug.js';
|
|
44
|
+
export * from './devtools.js';
|
|
44
45
|
export * from './browse.js';
|
|
45
46
|
export { webextCommand } from './webext/index.js';
|
|
47
|
+
export { blueprintCommand, blueprintTypesCommand } from './blueprint/index.js';
|
package/lib/commands/index.js
CHANGED
|
@@ -41,5 +41,7 @@ export * from './affected.js';
|
|
|
41
41
|
export * from './storybook.js';
|
|
42
42
|
export * from './dev.js';
|
|
43
43
|
export * from './debug.js';
|
|
44
|
+
export * from './devtools.js';
|
|
44
45
|
export * from './browse.js';
|
|
45
46
|
export { webextCommand } from './webext/index.js';
|
|
47
|
+
export { blueprintCommand, blueprintTypesCommand } from './blueprint/index.js';
|