@gjsify/cli 0.4.35 → 0.4.36
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 +85 -75
- package/lib/bundler-pick.js +43 -4
- package/lib/commands/affected.d.ts +10 -0
- package/lib/commands/affected.js +303 -0
- package/lib/commands/build.js +1 -1
- package/lib/commands/dlx.js +8 -1
- package/lib/commands/index.d.ts +3 -0
- package/lib/commands/index.js +3 -0
- package/lib/commands/install.d.ts +3 -0
- package/lib/commands/install.js +324 -77
- package/lib/commands/publish.js +36 -35
- package/lib/commands/tsc.d.ts +6 -0
- package/lib/commands/tsc.js +109 -0
- package/lib/commands/whoami.d.ts +7 -0
- package/lib/commands/whoami.js +118 -0
- package/lib/commands/workspace.d.ts +4 -0
- package/lib/commands/workspace.js +159 -32
- package/lib/index.js +4 -1
- package/lib/utils/install-backend-native.js +58 -15
- package/lib/utils/install-backend.d.ts +19 -0
- package/lib/utils/install-backend.js +1 -0
- package/lib/utils/install-progress.d.ts +26 -0
- package/lib/utils/install-progress.js +109 -0
- package/lib/utils/install-tarball-cache.d.ts +23 -0
- package/lib/utils/install-tarball-cache.js +140 -0
- package/lib/utils/load-npmrc.d.ts +14 -0
- package/lib/utils/load-npmrc.js +61 -0
- package/lib/utils/publish-diagnose.d.ts +38 -0
- package/lib/utils/publish-diagnose.js +99 -0
- package/lib/utils/resolve-npm-package.d.ts +21 -0
- package/lib/utils/resolve-npm-package.js +121 -0
- package/package.json +29 -18
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// `gjsify tsc [tscArgs..]` — Run the TypeScript compiler under GJS via the
|
|
2
|
+
// committed `@gjsify/tsc` bundle (`@gjsify/tsc/bundle` → `dist/tsc.gjs.mjs`).
|
|
3
|
+
//
|
|
4
|
+
// Thin delegator: resolves the bundle path through `createRequire` anchored
|
|
5
|
+
// at the workspace root (falling back to cwd), then spawns
|
|
6
|
+
// `gjs -m <bundle> [tscArgs..]` with LD_LIBRARY_PATH + GI_TYPELIB_PATH set
|
|
7
|
+
// for any installed native gjsify packages (mirrors `gjsify run` exactly).
|
|
8
|
+
// Forwards the child's exit code so CI/scripts see real tsc semantics.
|
|
9
|
+
//
|
|
10
|
+
// Equivalent to invoking the `gjsify-tsc` bin from the @gjsify/tsc package
|
|
11
|
+
// directly — this command just spares the user from having to discover the
|
|
12
|
+
// bin path or remember the bare package name.
|
|
13
|
+
//
|
|
14
|
+
// @gjsify/tsc is a runtime dependency of @gjsify/cli (declared in
|
|
15
|
+
// package.json) so the resolve below succeeds out of the box for any
|
|
16
|
+
// `gjsify install`-managed workspace. The install-hint branch only fires
|
|
17
|
+
// if the dep was removed or the consumer is running a forked CLI.
|
|
18
|
+
//
|
|
19
|
+
// Reference: packages/infra/cli/src/commands/run.ts (env-setup precedent).
|
|
20
|
+
import { spawn } from 'node:child_process';
|
|
21
|
+
import { createRequire } from 'node:module';
|
|
22
|
+
import { pathToFileURL } from 'node:url';
|
|
23
|
+
import { detectNativePackages, buildNativeEnv } from '../utils/detect-native-packages.js';
|
|
24
|
+
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
25
|
+
export const tscCommand = {
|
|
26
|
+
command: 'tsc [tscArgs..]',
|
|
27
|
+
description: 'Run TypeScript compiler (tsc) under GJS via the @gjsify/tsc bundle. All arguments are passed through to tsc. Equivalent to `gjsify-tsc <args>` from the @gjsify/tsc bin.',
|
|
28
|
+
builder: (yargs) =>
|
|
29
|
+
// Pass-through subcommand: tsc owns the entire flag namespace
|
|
30
|
+
// (`--version`, `--help`, `--noEmit`, `-p`, …). Disable yargs's
|
|
31
|
+
// built-in `--version` / `--help` for this command so they don't
|
|
32
|
+
// intercept tsc's own flags (without this, `gjsify tsc --version`
|
|
33
|
+
// would print the gjsify CLI version instead of tsc's). Treat any
|
|
34
|
+
// unknown option as a positional so EVERY flag flows through to
|
|
35
|
+
// `tscArgs`.
|
|
36
|
+
yargs
|
|
37
|
+
.parserConfiguration({
|
|
38
|
+
'unknown-options-as-args': true,
|
|
39
|
+
})
|
|
40
|
+
.version(false)
|
|
41
|
+
.help(false)
|
|
42
|
+
.positional('tscArgs', {
|
|
43
|
+
description: 'Arguments forwarded verbatim to tsc (e.g. `--version`, `-p tsconfig.json`).',
|
|
44
|
+
type: 'string',
|
|
45
|
+
array: true,
|
|
46
|
+
default: [],
|
|
47
|
+
}),
|
|
48
|
+
handler: async (args) => {
|
|
49
|
+
// `unknown-options-as-args` parks unrecognised flags in `args._`
|
|
50
|
+
// (yargs's positional-overflow channel) — `args.tscArgs` only
|
|
51
|
+
// captures bare positionals, not pass-through flags. `_[0]` is
|
|
52
|
+
// the command name (`tsc`), strip it; the rest is forwarded
|
|
53
|
+
// verbatim, preserving the original flag order.
|
|
54
|
+
const overflow = (args._ ?? []).slice(1).map(String);
|
|
55
|
+
const explicit = args.tscArgs ?? [];
|
|
56
|
+
// When `_` has anything beyond the command name, it already
|
|
57
|
+
// includes both flags AND any bare positionals in the original
|
|
58
|
+
// order — use it directly. Fall back to the explicit positional
|
|
59
|
+
// array for the args-free invocation.
|
|
60
|
+
const tscArgs = overflow.length > 0 ? overflow : explicit;
|
|
61
|
+
const cwd = process.cwd();
|
|
62
|
+
// Anchor resolution at the workspace root so a sub-package `cwd`
|
|
63
|
+
// (e.g. `packages/web/fetch`) still finds the hoisted `@gjsify/tsc`
|
|
64
|
+
// at the root's `node_modules`. Falls back to cwd for standalone
|
|
65
|
+
// (non-monorepo) projects that have the package locally.
|
|
66
|
+
const anchorDir = findWorkspaceRoot(cwd) ?? cwd;
|
|
67
|
+
const anchor = pathToFileURL(`${anchorDir}/__gjsify_tsc__.js`).href;
|
|
68
|
+
const require = createRequire(anchor);
|
|
69
|
+
let bundlePath;
|
|
70
|
+
try {
|
|
71
|
+
bundlePath = require.resolve('@gjsify/tsc/bundle');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
console.error('gjsify tsc: @gjsify/tsc is not installed.');
|
|
75
|
+
console.error(' Install with: gjsify install --save-dev @gjsify/tsc');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
// Mirror `gjsify run`'s native-env composition so any future
|
|
79
|
+
// typescript-the-bundle code path that reaches a native bridge
|
|
80
|
+
// (today: none — tsc itself is pure JS) still finds the right
|
|
81
|
+
// typelibs. Costs ~one fs scan per invocation, same as run.
|
|
82
|
+
const nativePackages = detectNativePackages(cwd);
|
|
83
|
+
const nativeEnv = buildNativeEnv(nativePackages);
|
|
84
|
+
const env = {
|
|
85
|
+
...process.env,
|
|
86
|
+
...nativeEnv,
|
|
87
|
+
};
|
|
88
|
+
const gjsArgs = ['-m', bundlePath, ...tscArgs];
|
|
89
|
+
const child = spawn('gjs', gjsArgs, { env, stdio: 'inherit' });
|
|
90
|
+
await new Promise((resolvePromise) => {
|
|
91
|
+
child.on('close', (code) => {
|
|
92
|
+
process.exit(code ?? 1);
|
|
93
|
+
});
|
|
94
|
+
child.on('error', (err) => {
|
|
95
|
+
if (err.code === 'ENOENT') {
|
|
96
|
+
console.error('gjsify tsc: `gjs` not found on PATH. Install GJS (e.g. `dnf install gjs`).');
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.error(`gjsify tsc: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
103
|
+
// Never resolves naturally — process.exit() above terminates
|
|
104
|
+
// the program once the child closes. The promise just keeps
|
|
105
|
+
// the handler alive while gjs runs.
|
|
106
|
+
void resolvePromise;
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// `gjsify whoami [--registry <url>] [--json]`
|
|
2
|
+
//
|
|
3
|
+
// Prints the npm-registry username for the current `~/.npmrc` token. The
|
|
4
|
+
// natural CLI surface for the `whoami(registry, npmrc)` helper introduced
|
|
5
|
+
// in #390 (originally as the dead-token-vs-missing-package diagnostic for
|
|
6
|
+
// `gjsify publish`).
|
|
7
|
+
//
|
|
8
|
+
// Goal: maintainers should be able to verify `~/.npmrc` auth without the
|
|
9
|
+
// extra `curl + Authorization: Bearer …` dance documented in AGENTS.md.
|
|
10
|
+
// The four observable outcomes map to:
|
|
11
|
+
//
|
|
12
|
+
// 1. `{username: "..."}` from the registry → print
|
|
13
|
+
// Logged in as: <username>
|
|
14
|
+
// Registry: <url>
|
|
15
|
+
// exit 0.
|
|
16
|
+
// 2. `{}` from the registry → the token exists in npmrc but the
|
|
17
|
+
// registry no longer accepts it. Emit a clear "token appears dead or
|
|
18
|
+
// revoked" message plus the refresh instructions, exit 1.
|
|
19
|
+
// 3. No `_authToken` (and no basic-auth) in any resolved npmrc → emit
|
|
20
|
+
// a "no token configured" message pointing at `npm login`, exit 1.
|
|
21
|
+
// 4. Network / non-2xx / non-JSON error → surface the underlying
|
|
22
|
+
// error message + registry URL, exit 1.
|
|
23
|
+
//
|
|
24
|
+
// With `--json` every branch emits a single-line JSON object instead of
|
|
25
|
+
// the human-readable text — useful for CI scripts that want to gate
|
|
26
|
+
// behavior on `username` without parsing free-form output.
|
|
27
|
+
//
|
|
28
|
+
// Reference: `npm whoami` (refs/npm-cli/lib/commands/whoami.js).
|
|
29
|
+
import { DEFAULT_REGISTRY, whoami } from '@gjsify/npm-registry';
|
|
30
|
+
import { hasAnyCredential, loadNpmrc } from '../utils/load-npmrc.js';
|
|
31
|
+
export const whoamiCommand = {
|
|
32
|
+
command: 'whoami',
|
|
33
|
+
description: 'Print the npm-registry username for the current ~/.npmrc token. Clear failure message when the token is dead, missing, or the registry is unreachable.',
|
|
34
|
+
builder: (yargs) => yargs
|
|
35
|
+
.option('registry', {
|
|
36
|
+
description: `Registry URL to probe. Default: scope-aware lookup from .npmrc (falls back to ${DEFAULT_REGISTRY}).`,
|
|
37
|
+
type: 'string',
|
|
38
|
+
})
|
|
39
|
+
.option('json', {
|
|
40
|
+
description: 'Emit `{username, registry}` (or `{error, registry}`) as a single-line JSON object.',
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
default: false,
|
|
43
|
+
}),
|
|
44
|
+
handler: async (args) => {
|
|
45
|
+
const npmrc = await loadNpmrc(process.cwd());
|
|
46
|
+
// Registry resolution order matches `gjsify publish`:
|
|
47
|
+
// `--registry` flag > `$npm_config_registry` > `.npmrc` registry >
|
|
48
|
+
// `DEFAULT_REGISTRY`. The scope-aware `registryFor()` lookup is
|
|
49
|
+
// intentionally NOT used here — `whoami` is account-scoped, not
|
|
50
|
+
// package-scoped, so the user's *default* registry is what they
|
|
51
|
+
// want to verify.
|
|
52
|
+
const registry = args.registry ?? process.env.npm_config_registry ?? npmrc.registry ?? DEFAULT_REGISTRY;
|
|
53
|
+
const registryClean = registry.endsWith('/') ? registry.slice(0, -1) : registry;
|
|
54
|
+
const asJson = args.json === true;
|
|
55
|
+
// Branch 3 — no credential configured at all. Catch this before
|
|
56
|
+
// the network call so the message points at the real fix
|
|
57
|
+
// (`npm login`) instead of a registry-side 401.
|
|
58
|
+
if (!hasAnyCredential(npmrc)) {
|
|
59
|
+
if (asJson) {
|
|
60
|
+
process.stdout.write(`${JSON.stringify({ error: 'no-token-configured', registry: registryClean })}\n`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
process.stderr.write([
|
|
64
|
+
'gjsify whoami: no npm token configured.',
|
|
65
|
+
'',
|
|
66
|
+
`No \`_authToken\` found in ~/.npmrc / $NPM_CONFIG_USERCONFIG / .npmrc for ${registryClean}.`,
|
|
67
|
+
'',
|
|
68
|
+
'Add one via: npm login',
|
|
69
|
+
'Then verify: gjsify whoami',
|
|
70
|
+
].join('\n') + '\n');
|
|
71
|
+
}
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
let result;
|
|
75
|
+
try {
|
|
76
|
+
result = await whoami(registry, npmrc);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
// Branch 4 — network / non-2xx / parse failure.
|
|
80
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
81
|
+
if (asJson) {
|
|
82
|
+
process.stdout.write(`${JSON.stringify({ error: message, registry: registryClean })}\n`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
process.stderr.write(`gjsify whoami: ${message}\n`);
|
|
86
|
+
process.stderr.write(`Registry: ${registryClean}\n`);
|
|
87
|
+
}
|
|
88
|
+
process.exit(1);
|
|
89
|
+
return; // unreachable but pleases the type narrower
|
|
90
|
+
}
|
|
91
|
+
if (result.username && result.username.length > 0) {
|
|
92
|
+
// Branch 1 — live token.
|
|
93
|
+
if (asJson) {
|
|
94
|
+
process.stdout.write(`${JSON.stringify({ username: result.username, registry: registryClean })}\n`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
process.stdout.write(`Logged in as: ${result.username}\n`);
|
|
98
|
+
process.stdout.write(`Registry: ${registryClean}\n`);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Branch 2 — dead/revoked token (registry returned `{}`).
|
|
103
|
+
if (asJson) {
|
|
104
|
+
process.stdout.write(`${JSON.stringify({ error: 'dead-token', registry: registryClean })}\n`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
process.stderr.write([
|
|
108
|
+
'gjsify whoami: token appears dead or revoked (registry returned an empty whoami response).',
|
|
109
|
+
'',
|
|
110
|
+
'Refresh via: npm login',
|
|
111
|
+
'Then verify: gjsify whoami',
|
|
112
|
+
'',
|
|
113
|
+
'For the @gjsify scope, see AGENTS.md > "New @gjsify/* package: first-publish + Trusted Publisher bootstrap".',
|
|
114
|
+
].join('\n') + '\n');
|
|
115
|
+
}
|
|
116
|
+
process.exit(1);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
@@ -3,6 +3,10 @@ interface WorkspaceCmdOptions {
|
|
|
3
3
|
name: string;
|
|
4
4
|
script: string;
|
|
5
5
|
args?: string[];
|
|
6
|
+
'with-dependencies'?: boolean;
|
|
7
|
+
'continue-on-error'?: boolean;
|
|
8
|
+
'include-dev'?: boolean;
|
|
9
|
+
verbose?: boolean;
|
|
6
10
|
}
|
|
7
11
|
export declare const workspaceCommand: Command<unknown, WorkspaceCmdOptions>;
|
|
8
12
|
export {};
|
|
@@ -3,8 +3,19 @@
|
|
|
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
|
+
//
|
|
7
|
+
// With `--with-dependencies` / `-d` (synonym: `--topological` / `-t`) the
|
|
8
|
+
// command first runs the SAME script in every transitive workspace
|
|
9
|
+
// dependency, in topological build order, then in the target package.
|
|
10
|
+
// Skips deps that don't declare the script (`--if-present` behaviour).
|
|
11
|
+
// Stops on the first failure unless `--continue-on-error` is passed.
|
|
12
|
+
//
|
|
13
|
+
// This replaces the manual `gjsify workspace <A> build && gjsify workspace
|
|
14
|
+
// <B> build && …` chains in root `build:infra` scripts: cascade build
|
|
15
|
+
// failures on uninbuilt workspace deps are the single biggest local-dev
|
|
16
|
+
// pain point in this monorepo.
|
|
6
17
|
import { spawn } from 'node:child_process';
|
|
7
|
-
import { discoverWorkspaces } from '@gjsify/workspace';
|
|
18
|
+
import { buildDependencyGraph, discoverWorkspaces, topologicalSort, } from '@gjsify/workspace';
|
|
8
19
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
9
20
|
export const workspaceCommand = {
|
|
10
21
|
command: 'workspace <name> <script> [args..]',
|
|
@@ -24,6 +35,31 @@ export const workspaceCommand = {
|
|
|
24
35
|
description: 'Extra arguments forwarded to the script.',
|
|
25
36
|
type: 'string',
|
|
26
37
|
array: true,
|
|
38
|
+
})
|
|
39
|
+
.option('with-dependencies', {
|
|
40
|
+
// `-t`/`--topological` is the same flag spelled the way
|
|
41
|
+
// `gjsify foreach` already spells it — kept as an alias so
|
|
42
|
+
// muscle memory transfers.
|
|
43
|
+
description: 'Pre-build the target workspace\'s transitive workspace dependencies in topological order before running the script in the target. Deps that don\'t declare the script are skipped (--if-present behaviour). Replaces manual `gjsify workspace A build && gjsify workspace B build && …` chains.',
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
alias: ['d', 't', 'topological'],
|
|
46
|
+
default: false,
|
|
47
|
+
})
|
|
48
|
+
.option('include-dev', {
|
|
49
|
+
description: 'When --with-dependencies is set, also walk devDependencies (production deps only by default — matches `gjsify foreach -t`).',
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
default: false,
|
|
52
|
+
})
|
|
53
|
+
.option('continue-on-error', {
|
|
54
|
+
description: 'When --with-dependencies is set, keep running remaining deps after one fails (default: stop on first failure).',
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
default: false,
|
|
57
|
+
})
|
|
58
|
+
.option('verbose', {
|
|
59
|
+
description: 'Echo every spawned command before running it.',
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
alias: 'v',
|
|
62
|
+
default: false,
|
|
27
63
|
}),
|
|
28
64
|
handler: async (args) => {
|
|
29
65
|
// Walk up to the monorepo root — `gjsify workspace` is often
|
|
@@ -31,50 +67,141 @@ export const workspaceCommand = {
|
|
|
31
67
|
// `build:deps` calls `gjsify workspace @gjsify/adwaita-web …`),
|
|
32
68
|
// where process.cwd() is the child workspace, not the monorepo root.
|
|
33
69
|
const root = findWorkspaceRoot(process.cwd()) ?? process.cwd();
|
|
34
|
-
const
|
|
35
|
-
const target =
|
|
70
|
+
const allWorkspaces = discoverWorkspaces(root);
|
|
71
|
+
const target = allWorkspaces.find((w) => w.name === args.name);
|
|
36
72
|
if (!target) {
|
|
37
|
-
console.error(`gjsify workspace: no workspace named "${args.name}" — discovered ${
|
|
73
|
+
console.error(`gjsify workspace: no workspace named "${args.name}" — discovered ${allWorkspaces.length} workspace(s)`);
|
|
38
74
|
process.exit(1);
|
|
39
75
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
76
|
+
const runner = detectPackageManager();
|
|
77
|
+
const verbose = args.verbose === true;
|
|
78
|
+
const withDeps = args['with-dependencies'] === true;
|
|
79
|
+
const continueOnError = args['continue-on-error'] === true;
|
|
80
|
+
const targetScripts = target.manifest.scripts ?? {};
|
|
81
|
+
if (typeof targetScripts[args.script] !== 'string') {
|
|
42
82
|
console.error(`gjsify workspace: workspace "${args.name}" has no script "${args.script}"`);
|
|
43
83
|
process.exit(1);
|
|
44
84
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
// Build the run-list: either just the target, or its transitive
|
|
86
|
+
// workspace-dep closure in topological order followed by the
|
|
87
|
+
// target itself.
|
|
88
|
+
let runList;
|
|
89
|
+
if (withDeps) {
|
|
90
|
+
const closure = collectTransitiveClosure(target, allWorkspaces, args['include-dev'] === true);
|
|
91
|
+
// Sort the closure topologically using the shared @gjsify/workspace
|
|
92
|
+
// algorithm — same code path that drives `gjsify foreach -t`.
|
|
93
|
+
const graph = buildDependencyGraph(closure, { includeDev: args['include-dev'] === true });
|
|
94
|
+
runList = topologicalSort(graph);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
runList = [target];
|
|
98
|
+
}
|
|
99
|
+
const failures = [];
|
|
100
|
+
for (const ws of runList) {
|
|
101
|
+
const scripts = ws.manifest.scripts ?? {};
|
|
102
|
+
if (typeof scripts[args.script] !== 'string') {
|
|
103
|
+
// Skip deps that don't declare the script — mirrors
|
|
104
|
+
// `gjsify foreach`'s --if-present behaviour. The target
|
|
105
|
+
// itself was validated above; this branch only filters
|
|
106
|
+
// intermediate deps.
|
|
107
|
+
if (verbose)
|
|
108
|
+
console.error(`[${ws.name}] (no "${args.script}" script — skipping)`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
await runOne(ws, args.script, args.args ?? [], runner, verbose);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
116
|
+
console.error(`[${ws.name}] ${e.message}`);
|
|
117
|
+
if (!continueOnError) {
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
failures.push({ workspace: ws.name, error: e });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (failures.length > 0) {
|
|
124
|
+
console.error(`gjsify workspace: ${failures.length} workspace(s) failed: ${failures.map((f) => f.workspace).join(', ')}`);
|
|
70
125
|
process.exit(1);
|
|
71
|
-
}
|
|
126
|
+
}
|
|
72
127
|
// ensureMainLoop() (called inside spawn) keeps GJS alive after the
|
|
73
128
|
// child exits — without an explicit process.exit() the success path
|
|
74
129
|
// would park the loop forever.
|
|
75
130
|
process.exit(0);
|
|
76
131
|
},
|
|
77
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Walk the workspace-dep graph from `target`, collecting every transitive
|
|
135
|
+
* workspace dependency (production + optional, plus devDependencies when
|
|
136
|
+
* `includeDev`). The returned list includes `target` itself so the caller
|
|
137
|
+
* can pass it straight to `buildDependencyGraph` / `topologicalSort`.
|
|
138
|
+
*/
|
|
139
|
+
function collectTransitiveClosure(target, allWorkspaces, includeDev) {
|
|
140
|
+
const byName = new Map();
|
|
141
|
+
for (const ws of allWorkspaces)
|
|
142
|
+
byName.set(ws.name, ws);
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
const out = [];
|
|
145
|
+
const stack = [target];
|
|
146
|
+
while (stack.length > 0) {
|
|
147
|
+
const ws = stack.pop();
|
|
148
|
+
if (seen.has(ws.name))
|
|
149
|
+
continue;
|
|
150
|
+
seen.add(ws.name);
|
|
151
|
+
out.push(ws);
|
|
152
|
+
const m = ws.manifest;
|
|
153
|
+
const blocks = [
|
|
154
|
+
m.dependencies,
|
|
155
|
+
includeDev ? m.devDependencies : undefined,
|
|
156
|
+
m.optionalDependencies,
|
|
157
|
+
// peerDependencies excluded by default (yarn does the same)
|
|
158
|
+
];
|
|
159
|
+
for (const block of blocks) {
|
|
160
|
+
if (!block)
|
|
161
|
+
continue;
|
|
162
|
+
for (const [depName, spec] of Object.entries(block)) {
|
|
163
|
+
if (typeof spec !== 'string')
|
|
164
|
+
continue;
|
|
165
|
+
if (!spec.startsWith('workspace:'))
|
|
166
|
+
continue;
|
|
167
|
+
const dep = byName.get(depName);
|
|
168
|
+
if (!dep)
|
|
169
|
+
continue;
|
|
170
|
+
if (!seen.has(dep.name))
|
|
171
|
+
stack.push(dep);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
async function runOne(ws, script, extraArgs, runner, verbose) {
|
|
178
|
+
const argv = runner === 'gjsify'
|
|
179
|
+
? ['run', script, ...extraArgs]
|
|
180
|
+
: ['run', script, ...(extraArgs.length > 0 ? ['--', ...extraArgs] : [])];
|
|
181
|
+
if (verbose) {
|
|
182
|
+
console.error(`[${ws.name}] $ ${runner} ${argv.join(' ')}`);
|
|
183
|
+
}
|
|
184
|
+
// Default FORCE_COLOR=1 unless the user explicitly opted out (matches
|
|
185
|
+
// yarn / npm / gjsify run behaviour) — without this, tools that key
|
|
186
|
+
// on isTTY (chalk, picocolors, biome) drop colors when stdout is a
|
|
187
|
+
// pipe, including GitHub Actions where the log viewer renders ANSI
|
|
188
|
+
// fine.
|
|
189
|
+
const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined ? {} : { FORCE_COLOR: '1' };
|
|
190
|
+
await new Promise((resolve, reject) => {
|
|
191
|
+
const child = spawn(runner, argv, {
|
|
192
|
+
cwd: ws.location,
|
|
193
|
+
stdio: 'inherit',
|
|
194
|
+
env: { ...process.env, ...colorEnv },
|
|
195
|
+
});
|
|
196
|
+
child.on('close', (code) => {
|
|
197
|
+
if (code === 0)
|
|
198
|
+
resolve();
|
|
199
|
+
else
|
|
200
|
+
reject(new Error(`${runner} ${argv.join(' ')} exited with code ${code}`));
|
|
201
|
+
});
|
|
202
|
+
child.on('error', reject);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
78
205
|
function detectPackageManager() {
|
|
79
206
|
const ua = process.env.npm_config_user_agent ?? '';
|
|
80
207
|
if (ua.startsWith('yarn/'))
|
package/lib/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import yargs from 'yargs';
|
|
6
6
|
import { hideBin } from 'yargs/helpers';
|
|
7
|
-
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, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, uninstallCommand as uninstall, formatCommand as format, lintCommand as lint, fixCommand as fix, upgradeCommand as upgrade, barrelsCommand as barrels, } from './commands/index.js';
|
|
7
|
+
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, 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, } from './commands/index.js';
|
|
8
8
|
import { APP_NAME } from './constants.js';
|
|
9
9
|
// Detect which runtime is executing the CLI (GJS or Node.js).
|
|
10
10
|
// GJS MUST be checked first because @gjsify/process sets
|
|
@@ -79,6 +79,7 @@ await cli
|
|
|
79
79
|
.command(workspace.command, workspace.description, workspace.builder, workspace.handler)
|
|
80
80
|
.command(pack.command, pack.description, pack.builder, pack.handler)
|
|
81
81
|
.command(publish.command, publish.description, publish.builder, publish.handler)
|
|
82
|
+
.command(whoami.command, whoami.description, whoami.builder, whoami.handler)
|
|
82
83
|
.command(selfUpdate.command, selfUpdate.description, selfUpdate.builder, selfUpdate.handler)
|
|
83
84
|
.command(generateInstaller.command, generateInstaller.description, generateInstaller.builder, generateInstaller.handler)
|
|
84
85
|
.command(uninstall.command, uninstall.description, uninstall.builder, uninstall.handler)
|
|
@@ -87,6 +88,8 @@ await cli
|
|
|
87
88
|
.command(lint.command, lint.description, lint.builder, lint.handler)
|
|
88
89
|
.command(fix.command, fix.description, fix.builder, fix.handler)
|
|
89
90
|
.command(barrels.command, barrels.description, barrels.builder, barrels.handler)
|
|
91
|
+
.command(tsc.command, tsc.description, tsc.builder, tsc.handler)
|
|
92
|
+
.command(affected.command, affected.description, affected.builder, affected.handler)
|
|
90
93
|
.demandCommand(1)
|
|
91
94
|
.epilogue(`Running on ${runtimeLabel()}`)
|
|
92
95
|
.help()
|