@gjsify/cli 0.22.0 → 0.24.1
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 +28 -1
- package/dist/cli.gjs.mjs +265 -184
- package/lib/actions/build.d.ts +12 -0
- package/lib/actions/build.js +26 -0
- package/lib/bundler-pick.js +56 -7
- package/lib/commands/affected.js +84 -14
- package/lib/commands/check.js +38 -29
- package/lib/commands/flatpak/build.js +19 -18
- package/lib/commands/foreach.js +119 -42
- package/lib/commands/info.js +11 -5
- package/lib/commands/install.d.ts +16 -0
- package/lib/commands/install.js +123 -36
- package/lib/commands/onboard.js +6 -1
- package/lib/commands/pack.js +69 -4
- package/lib/commands/run.js +26 -14
- package/lib/commands/showcase.js +17 -13
- package/lib/commands/storybook.js +5 -2
- package/lib/commands/trust.js +4 -62
- package/lib/commands/workspace.js +49 -30
- package/lib/utils/bin-shim.d.ts +66 -0
- package/lib/utils/bin-shim.js +246 -0
- package/lib/utils/build-cache.d.ts +42 -6
- package/lib/utils/build-cache.js +114 -42
- package/lib/utils/detect-native-packages.d.ts +118 -7
- package/lib/utils/detect-native-packages.js +213 -29
- package/lib/utils/gjs-bundle-guard.d.ts +32 -0
- package/lib/utils/gjs-bundle-guard.js +103 -0
- package/lib/utils/install-backend-native.d.ts +24 -0
- package/lib/utils/install-backend-native.js +89 -16
- package/lib/utils/install-global.d.ts +16 -0
- package/lib/utils/install-global.js +58 -8
- package/lib/utils/oxc-resolve.js +25 -57
- package/lib/utils/publish-headers.d.ts +2 -3
- package/lib/utils/publish-headers.js +64 -3
- package/lib/utils/publishable-packages.d.ts +22 -0
- package/lib/utils/publishable-packages.js +93 -0
- package/lib/utils/run-gjs.d.ts +6 -7
- package/lib/utils/run-gjs.js +4 -3
- package/lib/utils/run-node.d.ts +16 -0
- package/lib/utils/run-node.js +28 -7
- package/lib/utils/runtimes.d.ts +21 -0
- package/lib/utils/runtimes.js +23 -0
- package/lib/utils/spawn.d.ts +82 -0
- package/lib/utils/spawn.js +139 -0
- package/lib/utils/stdin.d.ts +25 -0
- package/lib/utils/stdin.js +132 -0
- package/package.json +23 -18
package/lib/commands/run.js
CHANGED
|
@@ -342,6 +342,17 @@ async function runScript(script, extraArgs, cwd = process.cwd()) {
|
|
|
342
342
|
process.env.npm_package_version = env.npm_package_version;
|
|
343
343
|
if (env.FORCE_COLOR !== undefined)
|
|
344
344
|
process.env.FORCE_COLOR = env.FORCE_COLOR;
|
|
345
|
+
// Track the failure LOCALLY rather than through `process.exitCode`.
|
|
346
|
+
// Under GJS `process.exit()` is deferred (see the note below), so the
|
|
347
|
+
// `process.exit(1)` that used to live in the catch returned instead of
|
|
348
|
+
// terminating, execution fell through to the exit-code line below, and
|
|
349
|
+
// whatever that computed WON. The dispatched command's failure was then
|
|
350
|
+
// reported as success: `gjsify run build:gjsify` exited 0 while the
|
|
351
|
+
// `gjsify build` it dispatched exited 1, so `gjsify onboard` happily
|
|
352
|
+
// published a package whose build had just failed. Anything chaining on
|
|
353
|
+
// `gjsify run <script>` — every `a && b` build script, CI, onboard —
|
|
354
|
+
// got a false green from it.
|
|
355
|
+
let dispatchFailed = false;
|
|
345
356
|
try {
|
|
346
357
|
// Dynamic import to avoid a static cli-app ↔ run.ts import cycle;
|
|
347
358
|
// cli-app is already loaded (the CLI is running through it), so this
|
|
@@ -351,21 +362,22 @@ async function runScript(script, extraArgs, cwd = process.cwd()) {
|
|
|
351
362
|
}
|
|
352
363
|
catch (err) {
|
|
353
364
|
console.error(err.message);
|
|
354
|
-
|
|
365
|
+
dispatchFailed = true;
|
|
355
366
|
}
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
// a bare exit(0) here masked
|
|
359
|
-
process.
|
|
360
|
-
// `process.exit()
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
|
|
367
|
+
// A thrown command wins over `process.exitCode`; otherwise honour a
|
|
368
|
+
// non-zero code the handler set instead of throwing (e.g. a failing
|
|
369
|
+
// check) — a bare exit(0) here masked those as success too.
|
|
370
|
+
const dispatchCode = dispatchFailed ? 1 : process.exitCode != null ? Number(process.exitCode) : 0;
|
|
371
|
+
// `return process.exit(…)`, never a bare call. Under GJS `process.exit()`
|
|
372
|
+
// is NOT synchronous — with the GLib main loop armed (`ensureMainLoop`,
|
|
373
|
+
// e.g. by a bundle the in-process dispatch just ran) it defers the
|
|
374
|
+
// actual teardown and RETURNS, so execution continues past the call.
|
|
375
|
+
// Without the `return` the function falls through to the shell-spawn
|
|
376
|
+
// fallback below and runs the SAME command a SECOND time (the
|
|
377
|
+
// double-execution bug). Mirrors the file-mode path's `return` after
|
|
378
|
+
// `runGjsBundle`. The return is the real guard; the exit sets the status
|
|
379
|
+
// code for when the loop finally unwinds.
|
|
380
|
+
return process.exit(dispatchCode);
|
|
369
381
|
}
|
|
370
382
|
const fullCmd = extraArgs.length > 0 ? `${literal} ${extraArgs.map(shellEscape).join(' ')}` : literal;
|
|
371
383
|
// ensureMainLoop() (called inside spawn) keeps GJS alive after the
|
package/lib/commands/showcase.js
CHANGED
|
@@ -7,8 +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 {
|
|
11
|
-
import { EXAMPLE_RUNTIMES, isExampleRuntime, readDeclaredRuntimes, checkRuntimeSupported, } from '../utils/runtimes.js';
|
|
10
|
+
import { EXAMPLE_RUNTIMES, isExampleRuntime, readDeclaredRuntimes, checkRuntimeSupported, defaultExampleRuntime, } from '../utils/runtimes.js';
|
|
12
11
|
function readCliVersion() {
|
|
13
12
|
try {
|
|
14
13
|
const pkgUrl = new URL('../../package.json', import.meta.url);
|
|
@@ -38,18 +37,23 @@ export const showcaseCommand = {
|
|
|
38
37
|
default: false,
|
|
39
38
|
})
|
|
40
39
|
.option('runtime', {
|
|
41
|
-
// Runtime selector, mirroring `gjsify storybook --runtime`.
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
40
|
+
// Runtime selector, mirroring `gjsify storybook --runtime`. gjs
|
|
41
|
+
// runs the showcase's GJS bundle via `gjsify dlx`; node/bun/deno
|
|
42
|
+
// run its `--app node` bundle on that runtime. Validated against
|
|
43
|
+
// the showcase's declared `gjsify.example.runtimes` — a GTK-only
|
|
44
|
+
// showcase requested under node fails with a clear message, not
|
|
45
|
+
// a crash.
|
|
46
|
+
//
|
|
47
|
+
// NO eager yargs `default:` — it is resolved in the handler via
|
|
48
|
+
// `defaultExampleRuntime()` (gjs when gjs is installed, else the
|
|
49
|
+
// host runtime). Two reasons: yargs evaluates a `default:` while
|
|
50
|
+
// BUILDING the command, before we know a showcase was even
|
|
51
|
+
// named, and resolving it probes PATH — work that must not
|
|
52
|
+
// happen for `gjsify showcase` in list mode.
|
|
48
53
|
type: 'string',
|
|
49
54
|
choices: EXAMPLE_RUNTIMES,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
description: 'Runtime to run the showcase on: gjs | node | bun | deno (default: the host runtime). node/bun/deno run its `--app node` bundle.',
|
|
55
|
+
defaultDescription: 'gjs when gjs is installed, else the host runtime',
|
|
56
|
+
description: 'Runtime to run the showcase on: gjs | node | bun | deno (default: gjs when installed, else the host runtime). node/bun/deno run its `--app node` bundle.',
|
|
53
57
|
}),
|
|
54
58
|
handler: async (args) => {
|
|
55
59
|
// List mode: no name given, or --list flag
|
|
@@ -116,7 +120,7 @@ export const showcaseCommand = {
|
|
|
116
120
|
// `return` — bare `process.exit` falls through under GJS (see above).
|
|
117
121
|
return process.exit(1);
|
|
118
122
|
}
|
|
119
|
-
const runtime = (args.runtime ??
|
|
123
|
+
const runtime = (args.runtime ?? defaultExampleRuntime());
|
|
120
124
|
if (!isExampleRuntime(runtime)) {
|
|
121
125
|
console.error(`Unknown --runtime "${runtime}" (expected: ${EXAMPLE_RUNTIMES.join(', ')}).`);
|
|
122
126
|
// `return` — bare `process.exit` falls through under GJS (see above).
|
|
@@ -181,9 +181,12 @@ export const storybookCommand = {
|
|
|
181
181
|
// typelib env wired like `gjsify run`. All three need @gjsify/node-gi
|
|
182
182
|
// (the storybook uses gi://), kept external by the bundler.
|
|
183
183
|
const { computeNativeEnvForBundle } = await import('../utils/run-gjs.js');
|
|
184
|
-
const {
|
|
184
|
+
const { resolveNodeGiForBundle, nodeBinary } = await import('../utils/run-node.js');
|
|
185
185
|
const { RUNTIMES } = await import('../utils/runtimes.js');
|
|
186
|
-
|
|
186
|
+
// Same resolution base as `runRuntimeBundle`: the bundle's own dir
|
|
187
|
+
// first (that is where the runtime resolves the external specifier
|
|
188
|
+
// from), cwd as the fallback.
|
|
189
|
+
if (!resolveNodeGiForBundle(outPath, cwd)) {
|
|
187
190
|
throw new Error(`Cannot run the storybook on ${runtime}: \`@gjsify/node-gi\` is not installed. ` +
|
|
188
191
|
`Add @gjsify/node-gi as a dependency to run the storybook on ${runtime}.`);
|
|
189
192
|
}
|
package/lib/commands/trust.js
CHANGED
|
@@ -16,70 +16,14 @@
|
|
|
16
16
|
//
|
|
17
17
|
// Reference: refs/npm-cli/lib/trust-cmd.js + lib/commands/trust/github.js.
|
|
18
18
|
import { spawnSync } from 'node:child_process';
|
|
19
|
-
import { existsSync
|
|
20
|
-
import {
|
|
19
|
+
import { existsSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
21
|
import { DEFAULT_REGISTRY, buildHeaders, registryFor, whoami } from '@gjsify/npm-registry';
|
|
22
22
|
import { discoverWorkspaces, filterWorkspaces } from '@gjsify/workspace';
|
|
23
|
+
import { mergePublishables } from '../utils/publishable-packages.js';
|
|
23
24
|
import { hasAnyCredential, loadNpmrc } from '../utils/load-npmrc.js';
|
|
24
25
|
import { OtpProvider, withOtpRetry } from '../utils/npm-otp.js';
|
|
25
26
|
import { classifyTrustList, githubTrustBody, normalizeWorkflowFile, parseRepoFromGitRemote, trustUrl, validateRepository, } from '../utils/trust-registry.js';
|
|
26
|
-
/**
|
|
27
|
-
* Publishable packages under `packages/node-gi/*` that are NOT root workspaces
|
|
28
|
-
* (the workspaces glob excludes `packages/node-gi/*`) yet publish via release.yml —
|
|
29
|
-
* `@gjsify/node-gi` + the `@gjsify/gtk-runtime-*` bundles. `discoverWorkspaces`
|
|
30
|
-
* misses them, so `gjsify trust` would never configure their Trusted Publisher.
|
|
31
|
-
* Enumerate them here as Workspace-shaped entries so the sweep covers them too.
|
|
32
|
-
* Best-effort: a missing dir / unreadable manifest is simply skipped.
|
|
33
|
-
*/
|
|
34
|
-
function discoverNodeGiPublishables(cwd) {
|
|
35
|
-
// Walk up from cwd to the repo root — the dir that holds `packages/node-gi` —
|
|
36
|
-
// so the command works from the root or any subdirectory.
|
|
37
|
-
let root = cwd;
|
|
38
|
-
for (let i = 0; i < 8; i++) {
|
|
39
|
-
if (existsSync(join(root, 'packages', 'node-gi')))
|
|
40
|
-
break;
|
|
41
|
-
const parent = dirname(root);
|
|
42
|
-
if (parent === root)
|
|
43
|
-
return [];
|
|
44
|
-
root = parent;
|
|
45
|
-
}
|
|
46
|
-
const base = join(root, 'packages', 'node-gi');
|
|
47
|
-
if (!existsSync(base))
|
|
48
|
-
return [];
|
|
49
|
-
const out = [];
|
|
50
|
-
let entries;
|
|
51
|
-
try {
|
|
52
|
-
entries = readdirSync(base, { withFileTypes: true });
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
for (const entry of entries) {
|
|
58
|
-
if (!entry.isDirectory())
|
|
59
|
-
continue;
|
|
60
|
-
const location = join(base, entry.name);
|
|
61
|
-
const pkgPath = join(location, 'package.json');
|
|
62
|
-
if (!existsSync(pkgPath))
|
|
63
|
-
continue;
|
|
64
|
-
try {
|
|
65
|
-
const manifest = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
66
|
-
if (!manifest || typeof manifest.name !== 'string' || manifest.name.length === 0)
|
|
67
|
-
continue;
|
|
68
|
-
out.push({
|
|
69
|
-
location,
|
|
70
|
-
relativeLocation: join('packages', 'node-gi', entry.name),
|
|
71
|
-
name: manifest.name,
|
|
72
|
-
version: typeof manifest.version === 'string' ? manifest.version : '0.0.0',
|
|
73
|
-
manifest,
|
|
74
|
-
private: manifest.private === true,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
/* unreadable / malformed — skip */
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return out;
|
|
82
|
-
}
|
|
83
27
|
export const trustCommand = {
|
|
84
28
|
command: 'trust [packages..]',
|
|
85
29
|
description: 'Configure npm Trusted Publishers (OIDC/GitHub Actions) for publishable workspace packages so release.yml can publish without a token. Native — no `npm` needed. Incremental: skips already-configured packages.',
|
|
@@ -197,9 +141,7 @@ export const trustCommand = {
|
|
|
197
141
|
// which publish via release.yml but are NOT root workspaces, so
|
|
198
142
|
// discoverWorkspaces misses them and their Trusted Publisher would never get
|
|
199
143
|
// configured. Dedup by name so a real workspace always wins.
|
|
200
|
-
const
|
|
201
|
-
const extra = discoverNodeGiPublishables(cwd).filter((ws) => !workspaceNames.has(ws.name));
|
|
202
|
-
const merged = [...all, ...extra];
|
|
144
|
+
const merged = mergePublishables(all, cwd);
|
|
203
145
|
const selected = filterWorkspaces(merged, {
|
|
204
146
|
noPrivate: args.private !== true,
|
|
205
147
|
include: args.packages && args.packages.length > 0 ? args.packages : undefined,
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
// `gjsify workspace <name> <script> [args..]` — yarn-workspace shortcut.
|
|
1
|
+
// `gjsify workspace <name> [run] <script> [args..]` — yarn-workspace shortcut.
|
|
2
2
|
//
|
|
3
3
|
// Equivalent to `yarn workspace <name> run <script>`: locates the named
|
|
4
4
|
// workspace in the current monorepo, then runs the script there. Used
|
|
5
5
|
// extensively in gjsify's own root `package.json` (17 call sites).
|
|
6
6
|
//
|
|
7
|
+
// BOTH spellings work — the yarn-style `run` prefix is accepted and optional.
|
|
8
|
+
// It is deliberately NOT the default and NOT a full CLI proxy: gjsify's command
|
|
9
|
+
// names collide with the most common script names, so `workspace <name> build`
|
|
10
|
+
// must keep meaning the SCRIPT. See the note at the normalization site below.
|
|
11
|
+
//
|
|
7
12
|
// With `--with-dependencies` / `-d` (synonym: `--topological` / `-t`) the
|
|
8
13
|
// command first runs the SAME script in every transitive workspace
|
|
9
14
|
// dependency, in topological build order, then in the target package.
|
|
@@ -14,7 +19,7 @@
|
|
|
14
19
|
// <B> build && …` chains in root `build:infra` scripts: cascade build
|
|
15
20
|
// failures on uninbuilt workspace deps are the single biggest local-dev
|
|
16
21
|
// pain point in this monorepo.
|
|
17
|
-
import {
|
|
22
|
+
import { spawnToCompletion } from '../utils/spawn.js';
|
|
18
23
|
import { buildDependencyGraph, discoverWorkspaces, topologicalSort } from '@gjsify/workspace';
|
|
19
24
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
20
25
|
import { BuildCacheRunner, buildCacheEnabledByEnv } from '../utils/build-cache.js';
|
|
@@ -22,7 +27,7 @@ import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
|
22
27
|
import { suggestClosest } from '../utils/suggest.js';
|
|
23
28
|
export const workspaceCommand = {
|
|
24
29
|
command: 'workspace <name> <script> [args..]',
|
|
25
|
-
description: 'Run a workspace script
|
|
30
|
+
description: 'Run a workspace script. Both `workspace <name> <script>` and the yarn spelling `workspace <name> run <script>` work.',
|
|
26
31
|
builder: (yargs) => yargs
|
|
27
32
|
.positional('name', {
|
|
28
33
|
description: 'Workspace name (matches package.json `name` field).',
|
|
@@ -97,10 +102,28 @@ export const workspaceCommand = {
|
|
|
97
102
|
const verbose = args.verbose === true;
|
|
98
103
|
const withDeps = args['with-dependencies'] === true;
|
|
99
104
|
const continueOnError = args['continue-on-error'] === true;
|
|
105
|
+
// yarn spells this `yarn workspace <name> run <script>`, and the header
|
|
106
|
+
// comment above has always advertised that form as the equivalent — so
|
|
107
|
+
// accept it. `run` is an OPTIONAL prefix, not the default: gjsify's own
|
|
108
|
+
// command surface collides head-on with the most common script names
|
|
109
|
+
// (`build`, `check`, `test`, `install`, `publish`, `pack`, `lint`,
|
|
110
|
+
// `format`), so proxying the whole CLI the way yarn does would make
|
|
111
|
+
// `gjsify workspace <name> build` ambiguous between the build COMMAND
|
|
112
|
+
// and the build SCRIPT — and the script reading is the one every script
|
|
113
|
+
// in this repo relies on.
|
|
114
|
+
//
|
|
115
|
+
// A package with a literal `run` script stays reachable: the prefix is
|
|
116
|
+
// only consumed when something follows it (`… run build`), so a bare
|
|
117
|
+
// `gjsify workspace <name> run` still runs that package's `run` script.
|
|
118
|
+
let script = args.script;
|
|
119
|
+
let extraArgs = args.args ?? [];
|
|
120
|
+
if (script === 'run' && extraArgs.length > 0) {
|
|
121
|
+
[script, ...extraArgs] = extraArgs;
|
|
122
|
+
}
|
|
100
123
|
const targetScripts = target.manifest.scripts ?? {};
|
|
101
|
-
if (typeof targetScripts[
|
|
102
|
-
const scriptHint = suggestClosest(
|
|
103
|
-
console.error(`gjsify workspace: workspace "${args.name}" has no script "${
|
|
124
|
+
if (typeof targetScripts[script] !== 'string') {
|
|
125
|
+
const scriptHint = suggestClosest(script, Object.keys(targetScripts));
|
|
126
|
+
console.error(`gjsify workspace: workspace "${args.name}" has no script "${script}"` +
|
|
104
127
|
(scriptHint ? ` — did you mean "${scriptHint}"?` : ''));
|
|
105
128
|
// `return process.exit` — see the not-found branch above for why a
|
|
106
129
|
// bare `process.exit` is unsafe under GJS.
|
|
@@ -129,26 +152,26 @@ export const workspaceCommand = {
|
|
|
129
152
|
? new BuildCacheRunner({
|
|
130
153
|
root,
|
|
131
154
|
workspaces: allWorkspaces,
|
|
132
|
-
script:
|
|
133
|
-
args:
|
|
155
|
+
script: script,
|
|
156
|
+
args: extraArgs,
|
|
134
157
|
targets: runList.map((w) => w.name),
|
|
135
158
|
})
|
|
136
159
|
: undefined;
|
|
137
160
|
const failures = [];
|
|
138
161
|
for (const ws of runList) {
|
|
139
162
|
const scripts = ws.manifest.scripts ?? {};
|
|
140
|
-
if (typeof scripts[
|
|
163
|
+
if (typeof scripts[script] !== 'string') {
|
|
141
164
|
// Skip deps that don't declare the script — mirrors
|
|
142
165
|
// `gjsify foreach`'s --if-present behaviour. The target
|
|
143
166
|
// itself was validated above; this branch only filters
|
|
144
167
|
// intermediate deps. A skipped package is also never touched
|
|
145
168
|
// by the build cache (the no-build-package safety rule).
|
|
146
169
|
if (verbose)
|
|
147
|
-
console.error(`[${ws.name}] (no "${
|
|
170
|
+
console.error(`[${ws.name}] (no "${script}" script — skipping)`);
|
|
148
171
|
continue;
|
|
149
172
|
}
|
|
150
173
|
try {
|
|
151
|
-
await runOne(ws,
|
|
174
|
+
await runOne(ws, script, extraArgs, runner, verbose, cache);
|
|
152
175
|
}
|
|
153
176
|
catch (err) {
|
|
154
177
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
@@ -230,26 +253,22 @@ async function runOne(ws, script, extraArgs, runner, verbose, cache) {
|
|
|
230
253
|
if (verbose) {
|
|
231
254
|
console.error(`[${ws.name}] $ ${runner} ${argv.join(' ')}`);
|
|
232
255
|
}
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
child.on('close', (code) => {
|
|
246
|
-
if (code === 0)
|
|
247
|
-
resolve();
|
|
248
|
-
else
|
|
249
|
-
reject(new Error(`${runner} ${argv.join(' ')} exited with code ${code}`));
|
|
250
|
-
});
|
|
251
|
-
child.on('error', reject);
|
|
256
|
+
// `completion: 'exit'` — every path out of this command's handler ends in
|
|
257
|
+
// `process.exit(…)`, which quits the GLib main loop `spawn()` arms under
|
|
258
|
+
// GJS, so the streaming async path stays safe here. See utils/spawn.ts.
|
|
259
|
+
//
|
|
260
|
+
// `color: true` defaults FORCE_COLOR=1 unless the user explicitly opted
|
|
261
|
+
// out (matches yarn / npm / gjsify run) — without it, tools that key on
|
|
262
|
+
// isTTY (chalk, picocolors, oxc) drop colors when stdout is a pipe,
|
|
263
|
+
// including GitHub Actions where the log viewer renders ANSI fine.
|
|
264
|
+
const { code } = await spawnToCompletion(runner, argv, {
|
|
265
|
+
completion: 'exit',
|
|
266
|
+
cwd: ws.location,
|
|
267
|
+
color: true,
|
|
252
268
|
});
|
|
269
|
+
if (code !== 0) {
|
|
270
|
+
throw new Error(`${runner} ${argv.join(' ')} exited with code ${code}`);
|
|
271
|
+
}
|
|
253
272
|
if (cache && before)
|
|
254
273
|
cache.storeAfterSuccess(ws, before);
|
|
255
274
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The three sibling files a Windows bin entry consists of. */
|
|
2
|
+
export interface WindowsShimFiles {
|
|
3
|
+
/** POSIX `sh` script — the extension-less `<name>` file. */
|
|
4
|
+
sh: string;
|
|
5
|
+
/** Batch file — `<name>.cmd`. */
|
|
6
|
+
cmd: string;
|
|
7
|
+
/** PowerShell script — `<name>.ps1`. */
|
|
8
|
+
ps1: string;
|
|
9
|
+
}
|
|
10
|
+
/** A parsed `#!` line. */
|
|
11
|
+
export interface Shebang {
|
|
12
|
+
/** Leading `KEY=VALUE ` assignments from `env -S KEY=VALUE prog` (may be ''). */
|
|
13
|
+
variables: string;
|
|
14
|
+
/** The interpreter, e.g. `node` or `gjs`. */
|
|
15
|
+
prog: string;
|
|
16
|
+
/** Everything after the interpreter, e.g. ` -m` (may be ''). */
|
|
17
|
+
args: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse the first line of a bin file. Returns `null` when it is not a shebang
|
|
21
|
+
* — cmd-shim then treats the target as directly executable.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseShebang(firstLine: string): Shebang | null;
|
|
24
|
+
/**
|
|
25
|
+
* Build the three Windows shim files for a package bin.
|
|
26
|
+
*
|
|
27
|
+
* @param relTargetPosix POSIX-style path from the `.bin` directory to the real
|
|
28
|
+
* bin file (i.e. `path.relative(binDir, target)` with `\` normalised to `/`).
|
|
29
|
+
* @param shebang the target's parsed `#!` line, or `null` when it has none —
|
|
30
|
+
* in which case the shims invoke the target directly, matching cmd-shim.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildCmdShim(relTargetPosix: string, shebang: Shebang | null): WindowsShimFiles;
|
|
33
|
+
/**
|
|
34
|
+
* `.cmd` / `.ps1` companions for a launcher gjsify *generates* itself
|
|
35
|
+
* (`linkGlobalBins`, `writeWorkspaceBinShims`) rather than links.
|
|
36
|
+
*
|
|
37
|
+
* Those launchers are not a symlink to a bin with a shebang — they are a
|
|
38
|
+
* hand-written `sh` script that picks an interpreter and passes an ABSOLUTE
|
|
39
|
+
* script path. `buildCmdShim` cannot express them (it derives everything from
|
|
40
|
+
* a relative target plus a shebang), so this builds the equivalent batch /
|
|
41
|
+
* PowerShell form: optional `SET` / `$env:` assignments, then
|
|
42
|
+
* `<interpreter> [args] "<absolute target>" <argv>`.
|
|
43
|
+
*
|
|
44
|
+
* Note the interpreter is invoked by NAME (resolved through `PATH`), matching
|
|
45
|
+
* the `sh` launcher's `exec gjs`/`exec node`. A missing interpreter therefore
|
|
46
|
+
* fails the same way on every OS.
|
|
47
|
+
*/
|
|
48
|
+
export declare function buildLauncherShims(opts: {
|
|
49
|
+
/** Interpreter binary name, e.g. `node` or `gjs`. */
|
|
50
|
+
interpreter: string;
|
|
51
|
+
/** Arguments before the script path, e.g. `['-m']` for `gjs -m`. */
|
|
52
|
+
interpreterArgs?: readonly string[];
|
|
53
|
+
/** Absolute path of the script to run. */
|
|
54
|
+
target: string;
|
|
55
|
+
/**
|
|
56
|
+
* Search-path variables to PREPEND to before the call, preserving whatever
|
|
57
|
+
* the caller's environment already had. Rendered with each shell's own
|
|
58
|
+
* expansion syntax (`%K%` in batch, `$env:K` in PowerShell) — a plain
|
|
59
|
+
* assignment would clobber `PATH`, which on Windows is also the DLL search
|
|
60
|
+
* path the prebuilds have to be reachable through.
|
|
61
|
+
*/
|
|
62
|
+
prependEnv?: Readonly<Record<string, string>>;
|
|
63
|
+
}): {
|
|
64
|
+
cmd: string;
|
|
65
|
+
ps1: string;
|
|
66
|
+
};
|