@gjsify/cli 0.9.0 → 0.11.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 +89 -67
- package/lib/cli-app.js +9 -1
- package/lib/commands/browse.d.ts +14 -0
- package/lib/commands/browse.js +93 -0
- package/lib/commands/build.js +6 -1
- package/lib/commands/debug.d.ts +10 -0
- package/lib/commands/debug.js +139 -0
- package/lib/commands/foreach.js +6 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/run.js +9 -0
- package/lib/commands/workspace.js +7 -0
- package/lib/config.js +6 -0
- package/lib/utils/gjsify-shim.d.ts +6 -0
- package/lib/utils/gjsify-shim.js +52 -0
- package/package.json +17 -17
package/lib/cli-app.js
CHANGED
|
@@ -19,9 +19,10 @@ import { readFileSync } from 'node:fs';
|
|
|
19
19
|
import { dirname, join } from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
21
|
import yargs from 'yargs';
|
|
22
|
-
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, whoamiCommand as whoami, loginCommand as login, logoutCommand as logout, trustCommand as trust, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, 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, } from './commands/index.js';
|
|
22
|
+
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, whoamiCommand as whoami, loginCommand as login, logoutCommand as logout, trustCommand as trust, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, 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, debugCommand as debug, browseCommand as browse, } from './commands/index.js';
|
|
23
23
|
import { APP_NAME } from './constants.js';
|
|
24
24
|
import { isNode } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
25
|
+
import { ensureGjsifyShimOnPath } from './utils/gjsify-shim.js';
|
|
25
26
|
// Detect which runtime is executing the CLI (GJS or Node.js).
|
|
26
27
|
// GJS MUST be checked first because @gjsify/process sets
|
|
27
28
|
// `process.versions.node = '20.0.0'` for compatibility — a plain
|
|
@@ -68,6 +69,11 @@ function readBundleVersion() {
|
|
|
68
69
|
* exit before any async work runs.
|
|
69
70
|
*/
|
|
70
71
|
export async function runCli(argv) {
|
|
72
|
+
// Under GJS, make a GJS-runnable `gjsify` available on PATH for child
|
|
73
|
+
// processes (workspace/foreach orchestration + compound `gjsify run`
|
|
74
|
+
// scripts) so node-free builds don't fall back to the Node bin. No-op on
|
|
75
|
+
// Node.
|
|
76
|
+
ensureGjsifyShimOnPath();
|
|
71
77
|
const cli = yargs(argv);
|
|
72
78
|
await cli
|
|
73
79
|
.scriptName(APP_NAME)
|
|
@@ -108,6 +114,8 @@ export async function runCli(argv) {
|
|
|
108
114
|
.command(tsc.command, tsc.description, tsc.builder, tsc.handler)
|
|
109
115
|
.command(affected.command, affected.description, affected.builder, affected.handler)
|
|
110
116
|
.command(storybook.command, storybook.description, storybook.builder, storybook.handler)
|
|
117
|
+
.command(debug.command, debug.description, debug.builder, debug.handler)
|
|
118
|
+
.command(browse.command, browse.description, browse.builder, browse.handler)
|
|
111
119
|
.demandCommand(1)
|
|
112
120
|
.epilogue(`Running on ${runtimeLabel()}`)
|
|
113
121
|
.help()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
interface BrowseCliOptions {
|
|
3
|
+
url?: string;
|
|
4
|
+
appId?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
globals?: string;
|
|
7
|
+
out?: string;
|
|
8
|
+
devtools: boolean;
|
|
9
|
+
buildOnly: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Derive a DBus app id from the package name (mirrored by `gjsify debug`). */
|
|
12
|
+
export declare function deriveBrowserAppId(name?: string): string;
|
|
13
|
+
export declare const browseCommand: Command<unknown, BrowseCliOptions>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// `gjsify browse [url]` — launch the Adwaita web browser from
|
|
2
|
+
// @gjsify/devtools-browser, optionally at a URL. Generates a tiny entry that
|
|
3
|
+
// calls runBrowserDevtools(), builds it (`--app gjs --globals auto,dom`), and
|
|
4
|
+
// launches it. Dispatch is IN-PROCESS (runCli + runGjsBundle), like
|
|
5
|
+
// `gjsify storybook`. `--devtools` sets GJSIFY_DEVTOOLS=1 so @gjsify/devtools
|
|
6
|
+
// wires the org.gjsify.Devtools control plane (driveable via `gjsify debug`).
|
|
7
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { join, resolve } from 'node:path';
|
|
9
|
+
function readPackage(cwd) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Derive a DBus app id from the package name (mirrored by `gjsify debug`). */
|
|
18
|
+
export function deriveBrowserAppId(name) {
|
|
19
|
+
if (!name)
|
|
20
|
+
return 'org.gjsify.Browser';
|
|
21
|
+
const slug = name
|
|
22
|
+
.replace(/^@/, '')
|
|
23
|
+
.replace(/[^a-zA-Z0-9]+/g, '.')
|
|
24
|
+
.replace(/^\.+|\.+$/g, '');
|
|
25
|
+
return `org.gjsify.browser.${slug}`;
|
|
26
|
+
}
|
|
27
|
+
function generateEntry(applicationId, url, title) {
|
|
28
|
+
const titleLine = title ? `\n title: ${JSON.stringify(title)},` : '';
|
|
29
|
+
const urlLine = url ? `\n homeUrl: ${JSON.stringify(url)},` : '';
|
|
30
|
+
return `// Auto-generated by \`gjsify browse\` — do not edit by hand.
|
|
31
|
+
import { runBrowserDevtools } from '@gjsify/devtools-browser';
|
|
32
|
+
|
|
33
|
+
await runBrowserDevtools({
|
|
34
|
+
applicationId: ${JSON.stringify(applicationId)},${titleLine}${urlLine}
|
|
35
|
+
});
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
export const browseCommand = {
|
|
39
|
+
command: 'browse [url]',
|
|
40
|
+
description: 'Launch the Adwaita web browser, optionally pointed at a URL.',
|
|
41
|
+
builder: (yargs) => yargs
|
|
42
|
+
.positional('url', { description: 'Initial URL to load (default: page:welcome)', type: 'string' })
|
|
43
|
+
.option('app-id', {
|
|
44
|
+
description: 'GApplication id (default: gjsify.browse.applicationId or derived from the package name)',
|
|
45
|
+
type: 'string',
|
|
46
|
+
})
|
|
47
|
+
.option('title', { description: 'Window title', type: 'string' })
|
|
48
|
+
.option('globals', {
|
|
49
|
+
description: 'Value for `gjsify build --globals` (default: auto,dom — WebKit/iframe need DOM globals)',
|
|
50
|
+
type: 'string',
|
|
51
|
+
})
|
|
52
|
+
.option('out', {
|
|
53
|
+
description: 'Output bundle path (default: node_modules/.cache/gjsify-browse)',
|
|
54
|
+
type: 'string',
|
|
55
|
+
})
|
|
56
|
+
.option('devtools', {
|
|
57
|
+
description: 'Enable the MCP devtools control plane (sets GJSIFY_DEVTOOLS=1)',
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
default: false,
|
|
60
|
+
})
|
|
61
|
+
.option('build-only', {
|
|
62
|
+
description: 'Build the bundle but do not launch it',
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
default: false,
|
|
65
|
+
}),
|
|
66
|
+
handler: async (args) => {
|
|
67
|
+
const cwd = process.cwd();
|
|
68
|
+
const pkg = readPackage(cwd);
|
|
69
|
+
const config = pkg.gjsify?.browse ?? {};
|
|
70
|
+
const applicationId = args.appId ?? config.applicationId ?? deriveBrowserAppId(pkg.name);
|
|
71
|
+
const title = args.title ?? config.title;
|
|
72
|
+
const globals = args.globals ?? config.globals ?? 'auto,dom';
|
|
73
|
+
const url = args.url;
|
|
74
|
+
const cacheDir = join(cwd, 'node_modules', '.cache', 'gjsify-browse');
|
|
75
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
76
|
+
const entryPath = join(cacheDir, 'entry.ts');
|
|
77
|
+
const outPath = args.out ? resolve(cwd, args.out) : join(cacheDir, 'browse.gjs.mjs');
|
|
78
|
+
writeFileSync(entryPath, generateEntry(applicationId, url, title));
|
|
79
|
+
console.log(`[gjsify browse] app=${applicationId}${url ? ` url=${url}` : ''}${args.devtools ? ' devtools=1' : ''}`);
|
|
80
|
+
const { runCli } = await import('../cli-app.js');
|
|
81
|
+
await runCli(['build', entryPath, '--app', 'gjs', '--globals', globals, '--outfile', outPath]);
|
|
82
|
+
if (args.buildOnly) {
|
|
83
|
+
console.log(`[gjsify browse] built ${outPath}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// runGjsBundle spreads process.env into the child, so set the devtools
|
|
87
|
+
// gate here (RunGjsBundleOptions has no env field).
|
|
88
|
+
if (args.devtools)
|
|
89
|
+
process.env.GJSIFY_DEVTOOLS = '1';
|
|
90
|
+
const { runGjsBundle } = await import('../utils/run-gjs.js');
|
|
91
|
+
await runGjsBundle(outPath, [], { exitOnSuccess: true });
|
|
92
|
+
},
|
|
93
|
+
};
|
package/lib/commands/build.js
CHANGED
|
@@ -99,7 +99,12 @@ export const buildCommand = {
|
|
|
99
99
|
description: "Comma-separated list of global identifiers, 'auto' (default) to detect automatically from the bundled output, or 'none' to disable. The 'auto' token may be combined with explicit identifiers/groups (e.g. 'auto,dom') for cases where the detector cannot statically see a global because it's accessed via indirection. Each identifier is mapped to the corresponding `@gjsify/<pkg>/register` module and injected into the bundle. See the CLI Reference docs for the full list of known identifiers. Only applies to GJS app builds.",
|
|
100
100
|
type: 'string',
|
|
101
101
|
normalize: true,
|
|
102
|
-
default: 'auto'
|
|
102
|
+
// No yargs `default: 'auto'` — a yargs default is
|
|
103
|
+
// indistinguishable from a user-set value and would always
|
|
104
|
+
// clobber `package.json#gjsify.globals` / `.gjsifyrc.*` in
|
|
105
|
+
// config.ts (same footgun as `entryPoints`/`bundler.input`).
|
|
106
|
+
// The 'auto' fallback is applied post-merge in `Config.forBuild`
|
|
107
|
+
// so precedence is: CLI flag > config file > 'auto'.
|
|
103
108
|
})
|
|
104
109
|
.option('shebang', {
|
|
105
110
|
description: 'Prepend a target-appropriate shebang to the output and mark it executable (chmod 755): `#!/usr/bin/env -S gjs -m` for --app gjs, `#!/usr/bin/env node` for --app node. Applies to GJS and Node app builds with a single --outfile. Default: false (use --shebang to enable, or set `shebang: true` in `.gjsifyrc.js`).',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
interface DebugCliOptions {
|
|
3
|
+
busName?: string;
|
|
4
|
+
profile?: string;
|
|
5
|
+
globals?: string;
|
|
6
|
+
out?: string;
|
|
7
|
+
buildOnly: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const debugCommand: Command<unknown, DebugCliOptions>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// `gjsify debug` — launch an MCP↔devtools bridge for a running, devtools-enabled
|
|
2
|
+
// gjsify app (its `org.gjsify.Devtools` DBus control plane). An MCP client (e.g.
|
|
3
|
+
// Claude Code) uses this as its server command; the bridge speaks JSON-RPC on
|
|
4
|
+
// stdio and translates tool calls to the app's DBus interface.
|
|
5
|
+
//
|
|
6
|
+
// Dispatch is IN-PROCESS (runCli + runGjsBundle), like `gjsify storybook`. The
|
|
7
|
+
// generated entry resolves `@gjsify/devtools-mcp` from the CONSUMER's
|
|
8
|
+
// node_modules (so it stays out of the CLI's own deps). The bridge is built
|
|
9
|
+
// `--app gjs --shebang` and launched with stdio inherited, so its JSON-RPC
|
|
10
|
+
// stdout reaches the client untouched.
|
|
11
|
+
//
|
|
12
|
+
// CRITICAL: this command logs ONLY to stderr — stdout is the MCP channel.
|
|
13
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { join, resolve } from 'node:path';
|
|
15
|
+
function readPackage(cwd) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Mirror of `commands/storybook.ts` deriveAppId — the storybook app's DBus id. */
|
|
24
|
+
function deriveStorybookAppId(name) {
|
|
25
|
+
if (!name)
|
|
26
|
+
return 'org.gjsify.Storybook';
|
|
27
|
+
const slug = name
|
|
28
|
+
.replace(/^@/, '')
|
|
29
|
+
.replace(/[^a-zA-Z0-9]+/g, '.')
|
|
30
|
+
.replace(/^\.+|\.+$/g, '');
|
|
31
|
+
return `org.gjsify.storybook.${slug}`;
|
|
32
|
+
}
|
|
33
|
+
/** Mirror of `commands/browse.ts` deriveBrowserAppId — the browser app's DBus id. */
|
|
34
|
+
function deriveBrowserAppId(name) {
|
|
35
|
+
if (!name)
|
|
36
|
+
return 'org.gjsify.Browser';
|
|
37
|
+
const slug = name
|
|
38
|
+
.replace(/^@/, '')
|
|
39
|
+
.replace(/[^a-zA-Z0-9]+/g, '.')
|
|
40
|
+
.replace(/^\.+|\.+$/g, '');
|
|
41
|
+
return `org.gjsify.browser.${slug}`;
|
|
42
|
+
}
|
|
43
|
+
function hasDep(pkg, name) {
|
|
44
|
+
return Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
|
|
45
|
+
}
|
|
46
|
+
function generateEntry(profile, busNameBase, name, version) {
|
|
47
|
+
if (profile === 'storybook') {
|
|
48
|
+
return `// Auto-generated by \`gjsify debug\` — do not edit by hand.
|
|
49
|
+
import { runDevtoolsMcp, storybookProfile } from '@gjsify/devtools-mcp';
|
|
50
|
+
|
|
51
|
+
await runDevtoolsMcp(storybookProfile(${JSON.stringify(busNameBase)}));
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
if (profile === 'browser') {
|
|
55
|
+
return `// Auto-generated by \`gjsify debug\` — do not edit by hand.
|
|
56
|
+
import { runDevtoolsMcp, browserProfile } from '@gjsify/devtools-mcp';
|
|
57
|
+
|
|
58
|
+
await runDevtoolsMcp(browserProfile(${JSON.stringify(busNameBase)}));
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
return `// Auto-generated by \`gjsify debug\` — do not edit by hand.
|
|
62
|
+
import { runDevtoolsMcp } from '@gjsify/devtools-mcp';
|
|
63
|
+
|
|
64
|
+
await runDevtoolsMcp({
|
|
65
|
+
name: ${JSON.stringify(name)},
|
|
66
|
+
version: ${JSON.stringify(version)},
|
|
67
|
+
busNameBase: ${JSON.stringify(busNameBase)},
|
|
68
|
+
});
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
export const debugCommand = {
|
|
72
|
+
command: 'debug',
|
|
73
|
+
description: 'Launch an MCP bridge for a devtools-enabled gjsify app (org.gjsify.Devtools).',
|
|
74
|
+
builder: (yargs) => yargs
|
|
75
|
+
.option('bus-name', {
|
|
76
|
+
description: 'App DBus base name (default: gjsify.devtools.busNameBase, or the storybook app-id)',
|
|
77
|
+
type: 'string',
|
|
78
|
+
})
|
|
79
|
+
.option('profile', {
|
|
80
|
+
description: 'Tool profile: generic | storybook | browser (default: auto — storybook if @gjsify/storybook is a dep, browser if @gjsify/devtools-browser is, else generic)',
|
|
81
|
+
type: 'string',
|
|
82
|
+
})
|
|
83
|
+
.option('globals', {
|
|
84
|
+
description: 'Value for `gjsify build --globals` (default: auto)',
|
|
85
|
+
type: 'string',
|
|
86
|
+
})
|
|
87
|
+
.option('out', {
|
|
88
|
+
description: 'Output bundle path (default: node_modules/.cache/gjsify-debug)',
|
|
89
|
+
type: 'string',
|
|
90
|
+
})
|
|
91
|
+
.option('build-only', {
|
|
92
|
+
description: 'Build the bridge bundle but do not launch it (for a .mcp.json command pointing at the bundle)',
|
|
93
|
+
type: 'boolean',
|
|
94
|
+
default: false,
|
|
95
|
+
}),
|
|
96
|
+
handler: async (args) => {
|
|
97
|
+
const cwd = process.cwd();
|
|
98
|
+
const pkg = readPackage(cwd);
|
|
99
|
+
const config = pkg.gjsify?.devtools ?? {};
|
|
100
|
+
const profile = args.profile ??
|
|
101
|
+
config.profile ??
|
|
102
|
+
(hasDep(pkg, '@gjsify/storybook')
|
|
103
|
+
? 'storybook'
|
|
104
|
+
: hasDep(pkg, '@gjsify/devtools-browser')
|
|
105
|
+
? 'browser'
|
|
106
|
+
: 'generic');
|
|
107
|
+
const busNameBase = args.busName ??
|
|
108
|
+
config.busNameBase ??
|
|
109
|
+
(profile === 'storybook'
|
|
110
|
+
? (pkg.gjsify?.storybook?.applicationId ?? deriveStorybookAppId(pkg.name))
|
|
111
|
+
: profile === 'browser'
|
|
112
|
+
? (pkg.gjsify?.browse?.applicationId ?? deriveBrowserAppId(pkg.name))
|
|
113
|
+
: undefined);
|
|
114
|
+
if (!busNameBase) {
|
|
115
|
+
console.error('gjsify debug: no bus name — set package.json#gjsify.devtools.busNameBase or pass --bus-name <org.example.App>');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const globals = args.globals ?? config.globals ?? 'auto';
|
|
119
|
+
const name = pkg.name ? `${pkg.name}-devtools` : 'gjsify-devtools';
|
|
120
|
+
const version = '0.8.0';
|
|
121
|
+
const cacheDir = join(cwd, 'node_modules', '.cache', 'gjsify-debug');
|
|
122
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
123
|
+
const entryPath = join(cacheDir, 'entry.ts');
|
|
124
|
+
const outPath = args.out ? resolve(cwd, args.out) : join(cacheDir, 'bridge.gjs.mjs');
|
|
125
|
+
writeFileSync(entryPath, generateEntry(profile, busNameBase, name, version));
|
|
126
|
+
// stderr only — stdout is the MCP JSON-RPC channel.
|
|
127
|
+
console.error(`[gjsify debug] profile=${profile} bus=${busNameBase}`);
|
|
128
|
+
// In-process build (runtime-agnostic), then launch with stdio inherited
|
|
129
|
+
// so the bridge's JSON-RPC stdin/stdout are the client's pipes.
|
|
130
|
+
const { runCli } = await import('../cli-app.js');
|
|
131
|
+
await runCli(['build', entryPath, '--app', 'gjs', '--shebang', '--globals', globals, '--outfile', outPath]);
|
|
132
|
+
if (args.buildOnly) {
|
|
133
|
+
console.error(`[gjsify debug] built ${outPath}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const { runGjsBundle } = await import('../utils/run-gjs.js');
|
|
137
|
+
await runGjsBundle(outPath, [], { exitOnSuccess: true });
|
|
138
|
+
},
|
|
139
|
+
};
|
package/lib/commands/foreach.js
CHANGED
|
@@ -14,6 +14,7 @@ import { cpus } from 'node:os';
|
|
|
14
14
|
import { buildDependencyGraph, discoverWorkspaces, filterWorkspaces, topologicalSort, } from '@gjsify/workspace';
|
|
15
15
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
16
16
|
import { prefixLines } from '../utils/prefixed-output.js';
|
|
17
|
+
import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
17
18
|
// Every child spawned by spawnPrefixed registers here so fail-fast can
|
|
18
19
|
// terminate the whole in-flight set instead of waiting on it. On CI a
|
|
19
20
|
// single failed package used to leave foreach awaiting its still-running
|
|
@@ -520,6 +521,11 @@ async function runOne(ws, script, args, prefixOutput, verbose, exec) {
|
|
|
520
521
|
await spawnPrefixed(runner, argv, ws.location, prefixOutput ? `[${ws.name}] ` : null);
|
|
521
522
|
}
|
|
522
523
|
function detectPackageManager() {
|
|
524
|
+
// Under GJS there is no Node — neither `npm` nor `yarn` can run scripts, so
|
|
525
|
+
// the runner is `gjsify` itself via the GJS shim on PATH
|
|
526
|
+
// (`ensureGjsifyShimOnPath`). Makes node-free `gjsify foreach` work.
|
|
527
|
+
if (isGjs())
|
|
528
|
+
return 'gjsify';
|
|
523
529
|
// `npm_config_user_agent` is set by npm/yarn/pnpm — first token is
|
|
524
530
|
// `<name>/<version>`. Reuse it so `gjsify foreach build` invoked
|
|
525
531
|
// through `yarn run` keeps using yarn, etc.
|
package/lib/commands/index.d.ts
CHANGED
package/lib/commands/index.js
CHANGED
package/lib/commands/run.js
CHANGED
|
@@ -127,6 +127,15 @@ async function runScript(script, extraArgs) {
|
|
|
127
127
|
if (monorepoRoot && monorepoRoot !== cwd) {
|
|
128
128
|
binDirs.push(join(monorepoRoot, 'node_modules', '.bin'));
|
|
129
129
|
}
|
|
130
|
+
// Under GJS, the GJS-runnable `gjsify` shim (see `ensureGjsifyShimOnPath`)
|
|
131
|
+
// MUST shadow the workspace `node_modules/.bin/gjsify`, which is the Node
|
|
132
|
+
// entry and unusable in a node-free sandbox. So put the shim dir ahead of
|
|
133
|
+
// the `.bin` dirs in the child's PATH — otherwise a compound script like
|
|
134
|
+
// `gjsify run a && gjsify run b` resolves `gjsify` to the Node bin and
|
|
135
|
+
// fails. `GJSIFY_SHIM_DIR` is only set under GJS, so this is a no-op on Node.
|
|
136
|
+
if (process.env.GJSIFY_SHIM_DIR) {
|
|
137
|
+
binDirs.unshift(process.env.GJSIFY_SHIM_DIR);
|
|
138
|
+
}
|
|
130
139
|
// Default FORCE_COLOR=1 when not already set, matching yarn / npm
|
|
131
140
|
// script-runner behaviour. Without this, tools that check
|
|
132
141
|
// `process.stdout.isTTY` (chalk, picocolors, biome, …) disable colors
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { spawn } from 'node:child_process';
|
|
18
18
|
import { buildDependencyGraph, discoverWorkspaces, topologicalSort, } from '@gjsify/workspace';
|
|
19
19
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
20
|
+
import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
20
21
|
export const workspaceCommand = {
|
|
21
22
|
command: 'workspace <name> <script> [args..]',
|
|
22
23
|
description: 'Run a workspace script (`yarn workspace <name> run <script>` equivalent).',
|
|
@@ -203,6 +204,12 @@ async function runOne(ws, script, extraArgs, runner, verbose) {
|
|
|
203
204
|
});
|
|
204
205
|
}
|
|
205
206
|
function detectPackageManager() {
|
|
207
|
+
// Under GJS there is no Node, so neither `npm` nor `yarn` can run scripts —
|
|
208
|
+
// the only option is `gjsify` itself, resolved via the GJS shim on PATH
|
|
209
|
+
// (see `ensureGjsifyShimOnPath`). This is what makes node-free multi-package
|
|
210
|
+
// orchestration work in the Flatpak sandbox.
|
|
211
|
+
if (isGjs())
|
|
212
|
+
return 'gjsify';
|
|
206
213
|
const ua = process.env.npm_config_user_agent ?? '';
|
|
207
214
|
if (ua.startsWith('yarn/'))
|
|
208
215
|
return 'yarn';
|
package/lib/config.js
CHANGED
|
@@ -137,6 +137,12 @@ export class Config {
|
|
|
137
137
|
configData.consoleShim = cliArgs.consoleShim;
|
|
138
138
|
if (cliArgs.globals !== undefined)
|
|
139
139
|
configData.globals = cliArgs.globals;
|
|
140
|
+
// Fallback applied post-merge (not as a yargs `default:`) so a
|
|
141
|
+
// `globals` declared in package.json#gjsify / `.gjsifyrc.*` survives —
|
|
142
|
+
// a yargs default is indistinguishable from a user value and would
|
|
143
|
+
// overwrite the config one. Precedence: CLI flag > config file > 'auto'
|
|
144
|
+
// (mirrors the `bundler.input` fallback below).
|
|
145
|
+
configData.globals ??= 'auto';
|
|
140
146
|
if (cliArgs.shebang !== undefined)
|
|
141
147
|
configData.shebang = cliArgs.shebang;
|
|
142
148
|
if (cliArgs.excludeGlobals) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure a GJS-runnable `gjsify` is on `process.env.PATH` for child processes.
|
|
3
|
+
* Idempotent per process; children inherit the shim via `GJSIFY_SHIM_DIR` and
|
|
4
|
+
* reuse it instead of creating their own. No-op when not running under GJS.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ensureGjsifyShimOnPath(): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Node-free orchestration: a GJS-runnable `gjsify` on PATH.
|
|
2
|
+
//
|
|
3
|
+
// The CLI's npm `bin` (`node_modules/.bin/gjsify`) is the NODE entry — useless
|
|
4
|
+
// in a node-free environment (the Flatpak sandbox has no Node). But multi-
|
|
5
|
+
// package orchestration and compound package scripts resolve `gjsify` from
|
|
6
|
+
// PATH and would hit that Node bin:
|
|
7
|
+
// - `gjsify workspace`/`foreach` spawn `<pm> run <script>` per package; under
|
|
8
|
+
// GJS the package manager is `gjsify` itself, and `spawn('gjsify', …)`
|
|
9
|
+
// searches PATH.
|
|
10
|
+
// - a compound script like `gjsify run a && gjsify run b` is executed via
|
|
11
|
+
// `/bin/sh -c …`, and the shell resolves each `gjsify` from PATH.
|
|
12
|
+
// Both fail without Node (`spawn npm ENOENT`, `gjsify: not found`).
|
|
13
|
+
//
|
|
14
|
+
// Fix: under GJS, write a tiny wrapper that re-invokes the running GJS bundle
|
|
15
|
+
// under `gjs -m`, and prepend its directory to `process.env.PATH` so every
|
|
16
|
+
// child process (and grandchild, via the inherited `GJSIFY_SHIM_DIR` marker)
|
|
17
|
+
// resolves `gjsify` to a GJS-runnable executable. No-op on Node.
|
|
18
|
+
import { mkdtempSync, writeFileSync, chmodSync, existsSync } from 'node:fs';
|
|
19
|
+
import { tmpdir } from 'node:os';
|
|
20
|
+
import { join, delimiter } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
23
|
+
let _ensured = false;
|
|
24
|
+
/**
|
|
25
|
+
* Ensure a GJS-runnable `gjsify` is on `process.env.PATH` for child processes.
|
|
26
|
+
* Idempotent per process; children inherit the shim via `GJSIFY_SHIM_DIR` and
|
|
27
|
+
* reuse it instead of creating their own. No-op when not running under GJS.
|
|
28
|
+
*/
|
|
29
|
+
export function ensureGjsifyShimOnPath() {
|
|
30
|
+
if (_ensured || !isGjs())
|
|
31
|
+
return;
|
|
32
|
+
_ensured = true;
|
|
33
|
+
// A parent gjsify (also under GJS) already created the shim — reuse it so
|
|
34
|
+
// nested orchestration doesn't pile up temp dirs / PATH entries.
|
|
35
|
+
const inherited = process.env.GJSIFY_SHIM_DIR;
|
|
36
|
+
if (inherited && existsSync(join(inherited, 'gjsify'))) {
|
|
37
|
+
if (!(process.env.PATH ?? '').split(delimiter).includes(inherited)) {
|
|
38
|
+
process.env.PATH = inherited + delimiter + (process.env.PATH ?? '');
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// `import.meta.url` resolves to the running GJS bundle (cli.gjs.mjs) — the
|
|
43
|
+
// whole CLI collapses into that single file, so this module's URL is it.
|
|
44
|
+
const selfBundle = fileURLToPath(import.meta.url);
|
|
45
|
+
const gjs = process.env.GJS_CONSOLE || 'gjs';
|
|
46
|
+
const dir = mkdtempSync(join(tmpdir(), 'gjsify-shim-'));
|
|
47
|
+
const shim = join(dir, 'gjsify');
|
|
48
|
+
writeFileSync(shim, `#!/bin/sh\nexec "${gjs}" -m "${selfBundle}" "$@"\n`, { mode: 0o755 });
|
|
49
|
+
chmodSync(shim, 0o755);
|
|
50
|
+
process.env.GJSIFY_SHIM_DIR = dir;
|
|
51
|
+
process.env.PATH = dir + delimiter + (process.env.PATH ?? '');
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -120,19 +120,19 @@
|
|
|
120
120
|
"cli"
|
|
121
121
|
],
|
|
122
122
|
"dependencies": {
|
|
123
|
-
"@gjsify/buffer": "^0.
|
|
124
|
-
"@gjsify/create-app": "^0.
|
|
125
|
-
"@gjsify/node-globals": "^0.
|
|
126
|
-
"@gjsify/node-polyfills": "^0.
|
|
127
|
-
"@gjsify/npm-registry": "^0.
|
|
128
|
-
"@gjsify/resolve-npm": "^0.
|
|
129
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
130
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
131
|
-
"@gjsify/semver": "^0.
|
|
132
|
-
"@gjsify/tar": "^0.
|
|
133
|
-
"@gjsify/tsc": "^0.
|
|
134
|
-
"@gjsify/web-polyfills": "^0.
|
|
135
|
-
"@gjsify/workspace": "^0.
|
|
123
|
+
"@gjsify/buffer": "^0.11.0",
|
|
124
|
+
"@gjsify/create-app": "^0.11.0",
|
|
125
|
+
"@gjsify/node-globals": "^0.11.0",
|
|
126
|
+
"@gjsify/node-polyfills": "^0.11.0",
|
|
127
|
+
"@gjsify/npm-registry": "^0.11.0",
|
|
128
|
+
"@gjsify/resolve-npm": "^0.11.0",
|
|
129
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.11.0",
|
|
130
|
+
"@gjsify/rolldown-plugin-pnp": "^0.11.0",
|
|
131
|
+
"@gjsify/semver": "^0.11.0",
|
|
132
|
+
"@gjsify/tar": "^0.11.0",
|
|
133
|
+
"@gjsify/tsc": "^0.11.0",
|
|
134
|
+
"@gjsify/web-polyfills": "^0.11.0",
|
|
135
|
+
"@gjsify/workspace": "^0.11.0",
|
|
136
136
|
"cosmiconfig": "^9.0.2",
|
|
137
137
|
"get-tsconfig": "^4.14.0",
|
|
138
138
|
"pkg-types": "^2.3.1",
|
|
@@ -140,13 +140,13 @@
|
|
|
140
140
|
"yargs": "^18.0.0"
|
|
141
141
|
},
|
|
142
142
|
"devDependencies": {
|
|
143
|
-
"@gjsify/unit": "^0.
|
|
143
|
+
"@gjsify/unit": "^0.11.0",
|
|
144
144
|
"@types/yargs": "^17.0.35",
|
|
145
145
|
"typescript": "^6.0.3"
|
|
146
146
|
},
|
|
147
147
|
"peerDependencies": {
|
|
148
|
-
"@gjsify/oxfmt-native": "^0.
|
|
149
|
-
"@gjsify/rolldown-native": "^0.
|
|
148
|
+
"@gjsify/oxfmt-native": "^0.11.0",
|
|
149
|
+
"@gjsify/rolldown-native": "^0.11.0"
|
|
150
150
|
},
|
|
151
151
|
"peerDependenciesMeta": {
|
|
152
152
|
"@gjsify/oxfmt-native": {
|