@gjsify/cli 0.3.21 → 0.4.3
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 +791 -0
- package/lib/actions/build.js +4 -17
- package/lib/bundler-pick.d.ts +79 -0
- package/lib/bundler-pick.js +436 -0
- package/lib/commands/foreach.d.ts +17 -0
- package/lib/commands/foreach.js +341 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/install.d.ts +1 -0
- package/lib/commands/install.js +401 -27
- package/lib/commands/run.d.ts +1 -1
- package/lib/commands/run.js +113 -20
- package/lib/commands/workspace.d.ts +8 -0
- package/lib/commands/workspace.js +79 -0
- package/lib/config.js +12 -1
- package/lib/index.js +11 -3
- package/lib/types/config-data.d.ts +10 -1
- package/lib/utils/install-backend-native.d.ts +5 -1
- package/lib/utils/install-backend-native.js +329 -70
- package/lib/utils/install-backend.d.ts +11 -1
- package/lib/utils/install-backend.js +4 -2
- package/lib/utils/pkg-json-edit.d.ts +47 -0
- package/lib/utils/pkg-json-edit.js +108 -0
- package/lib/utils/workspace-root.d.ts +1 -0
- package/lib/utils/workspace-root.js +46 -0
- package/package.json +70 -44
- package/src/actions/build.ts +0 -431
- package/src/actions/index.ts +0 -1
- package/src/commands/build.ts +0 -146
- package/src/commands/check.ts +0 -87
- package/src/commands/create.ts +0 -63
- package/src/commands/dlx.ts +0 -195
- package/src/commands/flatpak/build.ts +0 -225
- package/src/commands/flatpak/ci.ts +0 -173
- package/src/commands/flatpak/deps.ts +0 -120
- package/src/commands/flatpak/index.ts +0 -53
- package/src/commands/flatpak/init.ts +0 -191
- package/src/commands/flatpak/utils.ts +0 -76
- package/src/commands/gettext.ts +0 -258
- package/src/commands/gresource.ts +0 -97
- package/src/commands/gsettings.ts +0 -87
- package/src/commands/index.ts +0 -12
- package/src/commands/info.ts +0 -70
- package/src/commands/install.ts +0 -195
- package/src/commands/run.ts +0 -33
- package/src/commands/showcase.ts +0 -149
- package/src/config.ts +0 -304
- package/src/constants.ts +0 -1
- package/src/index.ts +0 -37
- package/src/types/cli-build-options.ts +0 -100
- package/src/types/command.ts +0 -10
- package/src/types/config-data-library.ts +0 -5
- package/src/types/config-data-typescript.ts +0 -6
- package/src/types/config-data.ts +0 -225
- package/src/types/cosmiconfig-result.ts +0 -5
- package/src/types/index.ts +0 -6
- package/src/utils/check-system-deps.ts +0 -480
- package/src/utils/detect-native-packages.ts +0 -153
- package/src/utils/discover-showcases.ts +0 -75
- package/src/utils/dlx-cache.ts +0 -135
- package/src/utils/install-backend-native.ts +0 -363
- package/src/utils/install-backend.ts +0 -88
- package/src/utils/install-global.ts +0 -182
- package/src/utils/normalize-bundler-options.ts +0 -129
- package/src/utils/parse-spec.ts +0 -48
- package/src/utils/resolve-gjs-entry.ts +0 -96
- package/src/utils/resolve-plugin-by-name.ts +0 -106
- package/src/utils/run-gjs.ts +0 -90
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// `gjsify foreach [flags] <script>` — yarn-workspaces-foreach replacement.
|
|
2
|
+
//
|
|
3
|
+
// Replaces every `yarn workspaces foreach -A -p --no-private --exclude
|
|
4
|
+
// '@girs/*' --topological run build` style invocation in monorepo
|
|
5
|
+
// scripts. Flags mirror yarn 4's shape so root package.json scripts can
|
|
6
|
+
// move over with a 1:1 substitution.
|
|
7
|
+
//
|
|
8
|
+
// Output is line-prefixed `[<workspace-name>]` when --parallel is set,
|
|
9
|
+
// matching yarn's interactive flow. Exit code is non-zero if any child
|
|
10
|
+
// process failed; first failure's stderr is forwarded.
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import { cpus } from 'node:os';
|
|
13
|
+
import { buildDependencyGraph, discoverWorkspaces, filterWorkspaces, topologicalSort, } from '@gjsify/workspace';
|
|
14
|
+
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
15
|
+
export const foreachCommand = {
|
|
16
|
+
command: 'foreach [script] [args..]',
|
|
17
|
+
description: 'Run a workspace script across all (or filtered) workspaces. Drop-in for `yarn workspaces foreach`: -A/--all, -p/--parallel, -t/--topological, --include, --exclude, --no-private. Pass --exec to run an arbitrary command instead of a script.',
|
|
18
|
+
builder: (yargs) => yargs
|
|
19
|
+
.positional('script', {
|
|
20
|
+
description: 'Script name to run in each workspace (`run <name>`-equivalent). With --exec, the command to run instead.',
|
|
21
|
+
type: 'string',
|
|
22
|
+
})
|
|
23
|
+
.positional('args', {
|
|
24
|
+
description: 'Extra arguments forwarded to each child invocation.',
|
|
25
|
+
type: 'string',
|
|
26
|
+
array: true,
|
|
27
|
+
})
|
|
28
|
+
.option('all', {
|
|
29
|
+
description: 'Include workspaces declared as `private: true`.',
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
alias: 'A',
|
|
32
|
+
default: false,
|
|
33
|
+
})
|
|
34
|
+
.option('parallel', {
|
|
35
|
+
description: 'Run workspaces in parallel (capped by --jobs).',
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
alias: 'p',
|
|
38
|
+
default: false,
|
|
39
|
+
})
|
|
40
|
+
.option('topological', {
|
|
41
|
+
description: 'Wait for each workspace\'s deps to finish before starting it (production deps only).',
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
alias: 't',
|
|
44
|
+
default: false,
|
|
45
|
+
})
|
|
46
|
+
.option('topological-dev', {
|
|
47
|
+
description: 'Like --topological but also respects devDependencies (often cyclic — use sparingly).',
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
default: false,
|
|
50
|
+
})
|
|
51
|
+
.option('include', {
|
|
52
|
+
description: 'Glob pattern to include workspaces by name (repeatable).',
|
|
53
|
+
type: 'string',
|
|
54
|
+
array: true,
|
|
55
|
+
})
|
|
56
|
+
.option('exclude', {
|
|
57
|
+
description: 'Glob pattern to exclude workspaces by name (repeatable).',
|
|
58
|
+
type: 'string',
|
|
59
|
+
array: true,
|
|
60
|
+
})
|
|
61
|
+
.option('private', {
|
|
62
|
+
// Yargs auto-negates `--no-private` to `private=false`, so the
|
|
63
|
+
// user-facing flag stays `--no-private` (yarn-compatible).
|
|
64
|
+
description: 'Include private workspaces (default true). Pass --no-private to skip them.',
|
|
65
|
+
type: 'boolean',
|
|
66
|
+
default: true,
|
|
67
|
+
})
|
|
68
|
+
.option('verbose', {
|
|
69
|
+
description: 'Echo every spawned command before running it.',
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
alias: 'v',
|
|
72
|
+
default: false,
|
|
73
|
+
})
|
|
74
|
+
.option('jobs', {
|
|
75
|
+
description: 'Maximum concurrent workspaces in --parallel mode (default: cpu count).',
|
|
76
|
+
type: 'number',
|
|
77
|
+
alias: 'j',
|
|
78
|
+
})
|
|
79
|
+
.option('exec', {
|
|
80
|
+
description: 'Treat <script> [args..] as an arbitrary command (yarn `workspaces foreach exec`-equivalent) instead of a package.json script lookup. Workspace filtering by script presence is skipped. Use `-- <cmd> <args...>` to pass flags to the command without yargs intercepting them.',
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
default: false,
|
|
83
|
+
})
|
|
84
|
+
.parserConfiguration({
|
|
85
|
+
// Preserve `--` as args._['--'] so callers can write
|
|
86
|
+
// gjsify foreach --exec -- npm publish --tag latest
|
|
87
|
+
// without yargs grabbing --tag/--access/etc.
|
|
88
|
+
'populate--': true,
|
|
89
|
+
}),
|
|
90
|
+
handler: async (args) => {
|
|
91
|
+
// Walk up to the monorepo root — foreach is sometimes invoked
|
|
92
|
+
// from inside a child workspace's script chain.
|
|
93
|
+
const cwd = findWorkspaceRoot(process.cwd()) ?? process.cwd();
|
|
94
|
+
const allWorkspaces = discoverWorkspaces(cwd);
|
|
95
|
+
const exec = args.exec === true;
|
|
96
|
+
// In --exec mode, support both
|
|
97
|
+
// gjsify foreach --exec npm something (no flags in command)
|
|
98
|
+
// gjsify foreach --exec -- npm publish --tag X (flags in command)
|
|
99
|
+
// The `--` form is the typical one: `populate--: true` puts the
|
|
100
|
+
// post-separator argv into args._['--'], where yargs cannot grab
|
|
101
|
+
// --tag/--access/etc. as its own options.
|
|
102
|
+
let cmd = args.script;
|
|
103
|
+
let cmdArgs = args.args ?? [];
|
|
104
|
+
if (exec) {
|
|
105
|
+
// With populate--:true, anything after the literal `--`
|
|
106
|
+
// separator lands in top-level args['--']. yargs DOES NOT
|
|
107
|
+
// attach it to args._ — it's a sibling array.
|
|
108
|
+
const fromDoubleDash = (args['--'] ?? [])
|
|
109
|
+
.filter((v) => typeof v === 'string');
|
|
110
|
+
if (fromDoubleDash.length > 0) {
|
|
111
|
+
if (!cmd) {
|
|
112
|
+
cmd = fromDoubleDash[0];
|
|
113
|
+
cmdArgs = [...cmdArgs, ...fromDoubleDash.slice(1)];
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
cmdArgs = [...cmdArgs, ...fromDoubleDash];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!cmd) {
|
|
120
|
+
console.error('gjsify foreach --exec: missing command. Pass it after `--`, e.g. `gjsify foreach --exec -- npm publish --tag latest`.');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
let selected = filterWorkspaces(allWorkspaces, {
|
|
125
|
+
include: args.include,
|
|
126
|
+
exclude: args.exclude,
|
|
127
|
+
noPrivate: args.private === false,
|
|
128
|
+
});
|
|
129
|
+
// In script mode, only run on workspaces that actually have the
|
|
130
|
+
// requested script — yarn does this too, otherwise every project
|
|
131
|
+
// that doesn't declare `<script>` would fail and force the user to
|
|
132
|
+
// `--exclude` it. In --exec mode the command runs unconditionally
|
|
133
|
+
// (yarn's `workspaces foreach exec` semantics).
|
|
134
|
+
if (!exec) {
|
|
135
|
+
if (!cmd) {
|
|
136
|
+
console.error('gjsify foreach: missing <script> positional. Pass --exec to run an arbitrary command instead.');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
const scriptName = cmd;
|
|
140
|
+
selected = selected.filter((ws) => {
|
|
141
|
+
const scripts = ws.manifest.scripts ?? {};
|
|
142
|
+
return typeof scripts[scriptName] === 'string';
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (selected.length === 0) {
|
|
146
|
+
console.log(`gjsify foreach: no workspaces match (${exec ? 'exec' : 'script'}="${cmd}", include=${JSON.stringify(args.include ?? [])}, exclude=${JSON.stringify(args.exclude ?? [])})`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (args.topological || args['topological-dev']) {
|
|
150
|
+
const graph = buildDependencyGraph(selected, {
|
|
151
|
+
includeDev: args['topological-dev'] === true,
|
|
152
|
+
});
|
|
153
|
+
selected = topologicalSort(graph);
|
|
154
|
+
}
|
|
155
|
+
const verbose = args.verbose === true;
|
|
156
|
+
// `cmd` is guaranteed string at this point — both branches above
|
|
157
|
+
// exit on undefined, but TS doesn't narrow through them.
|
|
158
|
+
const finalCmd = cmd;
|
|
159
|
+
try {
|
|
160
|
+
if (args.parallel && !args.topological && !args['topological-dev']) {
|
|
161
|
+
const jobs = args.jobs && args.jobs > 0 ? args.jobs : cpus().length;
|
|
162
|
+
await runParallel(selected, finalCmd, cmdArgs, jobs, verbose, exec);
|
|
163
|
+
}
|
|
164
|
+
else if (args.parallel) {
|
|
165
|
+
// Topological + parallel: each workspace starts as soon as its
|
|
166
|
+
// deps (in the selected set) have finished. Yarn calls this
|
|
167
|
+
// "topological order with concurrency"; we cap at --jobs.
|
|
168
|
+
const jobs = args.jobs && args.jobs > 0 ? args.jobs : cpus().length;
|
|
169
|
+
await runTopologicalParallel(selected, finalCmd, cmdArgs, jobs, verbose, args['topological-dev'] === true, exec);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
await runSequential(selected, finalCmd, cmdArgs, verbose, exec);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
console.error(err.message);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
// ensureMainLoop() (called inside spawn) keeps GJS alive after every
|
|
180
|
+
// child exits — without an explicit process.exit() the success path
|
|
181
|
+
// would park the loop forever.
|
|
182
|
+
process.exit(0);
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
async function runSequential(workspaces, script, args, verbose, exec) {
|
|
186
|
+
for (const ws of workspaces) {
|
|
187
|
+
await runOne(ws, script, args, /* prefixOutput */ false, verbose, exec);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function runParallel(workspaces, script, args, concurrency, verbose, exec) {
|
|
191
|
+
let cursor = 0;
|
|
192
|
+
const workers = [];
|
|
193
|
+
for (let w = 0; w < concurrency; w++) {
|
|
194
|
+
workers.push((async () => {
|
|
195
|
+
while (cursor < workspaces.length) {
|
|
196
|
+
const i = cursor++;
|
|
197
|
+
await runOne(workspaces[i], script, args, /* prefixOutput */ true, verbose, exec);
|
|
198
|
+
}
|
|
199
|
+
})());
|
|
200
|
+
}
|
|
201
|
+
await Promise.all(workers);
|
|
202
|
+
}
|
|
203
|
+
async function runTopologicalParallel(workspaces, script, args, concurrency, verbose, includeDev, exec) {
|
|
204
|
+
const selectedNames = new Set(workspaces.map((w) => w.name));
|
|
205
|
+
const remaining = new Map();
|
|
206
|
+
for (const ws of workspaces) {
|
|
207
|
+
const wsDeps = new Set();
|
|
208
|
+
const m = ws.manifest;
|
|
209
|
+
for (const block of [
|
|
210
|
+
m.dependencies,
|
|
211
|
+
includeDev ? m.devDependencies : undefined,
|
|
212
|
+
m.optionalDependencies,
|
|
213
|
+
]) {
|
|
214
|
+
if (!block)
|
|
215
|
+
continue;
|
|
216
|
+
for (const [name, spec] of Object.entries(block)) {
|
|
217
|
+
if (typeof spec !== 'string')
|
|
218
|
+
continue;
|
|
219
|
+
if (!spec.startsWith('workspace:'))
|
|
220
|
+
continue;
|
|
221
|
+
if (selectedNames.has(name))
|
|
222
|
+
wsDeps.add(name);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
remaining.set(ws.name, wsDeps);
|
|
226
|
+
}
|
|
227
|
+
const byName = new Map(workspaces.map((w) => [w.name, w]));
|
|
228
|
+
const done = new Set();
|
|
229
|
+
let inflight = 0;
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
let error = null;
|
|
232
|
+
const pump = () => {
|
|
233
|
+
if (error)
|
|
234
|
+
return;
|
|
235
|
+
while (inflight < concurrency) {
|
|
236
|
+
const ready = [...remaining.entries()]
|
|
237
|
+
.filter(([, deps]) => [...deps].every((d) => done.has(d)))
|
|
238
|
+
.map(([n]) => n);
|
|
239
|
+
if (ready.length === 0)
|
|
240
|
+
break;
|
|
241
|
+
const next = ready.sort()[0];
|
|
242
|
+
remaining.delete(next);
|
|
243
|
+
inflight++;
|
|
244
|
+
runOne(byName.get(next), script, args, /* prefixOutput */ true, verbose, exec)
|
|
245
|
+
.then(() => {
|
|
246
|
+
inflight--;
|
|
247
|
+
done.add(next);
|
|
248
|
+
if (remaining.size === 0 && inflight === 0) {
|
|
249
|
+
resolve();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
pump();
|
|
253
|
+
})
|
|
254
|
+
.catch((e) => {
|
|
255
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
256
|
+
// Wait for in-flight tasks to finish (yarn does the
|
|
257
|
+
// same — surfaces all errors instead of abruptly
|
|
258
|
+
// killing siblings).
|
|
259
|
+
if (inflight === 0)
|
|
260
|
+
reject(error);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (remaining.size > 0 && inflight === 0 && !error) {
|
|
264
|
+
reject(new Error(`gjsify foreach --topological: stuck — workspaces ${[...remaining.keys()].join(', ')} have unsatisfied deps in the selected set`));
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
pump();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async function runOne(ws, script, args, prefixOutput, verbose, exec) {
|
|
271
|
+
if (exec) {
|
|
272
|
+
// Arbitrary-command mode: spawn `<script> <args...>` directly
|
|
273
|
+
// (yarn `workspaces foreach exec`-equivalent). Used by callers
|
|
274
|
+
// that need to run binaries the workspace doesn't expose as a
|
|
275
|
+
// package.json script — e.g. `gjsify foreach --exec npm publish`.
|
|
276
|
+
if (verbose) {
|
|
277
|
+
console.error(`[${ws.name}] $ ${script} ${args.join(' ')}`);
|
|
278
|
+
}
|
|
279
|
+
await spawnPrefixed(script, args, ws.location, prefixOutput ? `[${ws.name}] ` : null);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Use the same package manager that invoked us — yarn under yarn,
|
|
283
|
+
// npm under npm, gjsify under gjsify. Default to `npm` for portability
|
|
284
|
+
// when nothing is detectable; the script-runner (D.5) will replace
|
|
285
|
+
// this once `gjsify run` ships.
|
|
286
|
+
const runner = detectPackageManager();
|
|
287
|
+
const argv = runner === 'gjsify'
|
|
288
|
+
? ['run', script, ...args]
|
|
289
|
+
: ['run', script, ...(args.length > 0 ? ['--', ...args] : [])];
|
|
290
|
+
if (verbose) {
|
|
291
|
+
console.error(`[${ws.name}] $ ${runner} ${argv.join(' ')}`);
|
|
292
|
+
}
|
|
293
|
+
await spawnPrefixed(runner, argv, ws.location, prefixOutput ? `[${ws.name}] ` : null);
|
|
294
|
+
}
|
|
295
|
+
function detectPackageManager() {
|
|
296
|
+
// `npm_config_user_agent` is set by npm/yarn/pnpm — first token is
|
|
297
|
+
// `<name>/<version>`. Reuse it so `gjsify foreach build` invoked
|
|
298
|
+
// through `yarn run` keeps using yarn, etc.
|
|
299
|
+
const ua = process.env.npm_config_user_agent ?? '';
|
|
300
|
+
if (ua.startsWith('yarn/'))
|
|
301
|
+
return 'yarn';
|
|
302
|
+
if (ua.startsWith('gjsify/'))
|
|
303
|
+
return 'gjsify';
|
|
304
|
+
return 'npm';
|
|
305
|
+
}
|
|
306
|
+
function spawnPrefixed(cmd, args, cwd, prefix) {
|
|
307
|
+
return new Promise((resolve, reject) => {
|
|
308
|
+
const child = spawn(cmd, args, {
|
|
309
|
+
cwd,
|
|
310
|
+
stdio: prefix ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
311
|
+
env: process.env,
|
|
312
|
+
});
|
|
313
|
+
if (prefix && child.stdout && child.stderr) {
|
|
314
|
+
prefixLines(child.stdout, process.stdout, prefix);
|
|
315
|
+
prefixLines(child.stderr, process.stderr, prefix);
|
|
316
|
+
}
|
|
317
|
+
child.on('close', (code) => {
|
|
318
|
+
if (code === 0)
|
|
319
|
+
resolve();
|
|
320
|
+
else
|
|
321
|
+
reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
|
|
322
|
+
});
|
|
323
|
+
child.on('error', (err) => reject(err));
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function prefixLines(src, sink, prefix) {
|
|
327
|
+
let buf = '';
|
|
328
|
+
src.setEncoding('utf-8');
|
|
329
|
+
src.on('data', (chunk) => {
|
|
330
|
+
buf += chunk;
|
|
331
|
+
let idx;
|
|
332
|
+
while ((idx = buf.indexOf('\n')) !== -1) {
|
|
333
|
+
sink.write(prefix + buf.slice(0, idx + 1));
|
|
334
|
+
buf = buf.slice(idx + 1);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
src.on('end', () => {
|
|
338
|
+
if (buf.length > 0)
|
|
339
|
+
sink.write(prefix + buf + '\n');
|
|
340
|
+
});
|
|
341
|
+
}
|
package/lib/commands/index.d.ts
CHANGED
package/lib/commands/index.js
CHANGED