@gjsify/cli 0.4.27 → 0.4.29

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.
Files changed (64) hide show
  1. package/dist/cli.gjs.mjs +34 -32
  2. package/lib/actions/barrels-generate.js +1 -5
  3. package/lib/actions/build.d.ts +3 -3
  4. package/lib/actions/build.js +56 -64
  5. package/lib/bundler-pick.d.ts +3 -3
  6. package/lib/bundler-pick.js +5 -6
  7. package/lib/commands/build.js +37 -31
  8. package/lib/commands/check.js +3 -3
  9. package/lib/commands/fix.js +33 -23
  10. package/lib/commands/flatpak/build.js +6 -2
  11. package/lib/commands/flatpak/check.js +9 -3
  12. package/lib/commands/flatpak/ci.js +1 -2
  13. package/lib/commands/flatpak/deps.js +1 -2
  14. package/lib/commands/flatpak/diff.js +2 -6
  15. package/lib/commands/flatpak/init.js +19 -19
  16. package/lib/commands/flatpak/release.js +2 -2
  17. package/lib/commands/flatpak/scaffold.js +3 -11
  18. package/lib/commands/flatpak/sync-flathub.js +4 -8
  19. package/lib/commands/flatpak/utils.js +1 -6
  20. package/lib/commands/foreach.js +5 -14
  21. package/lib/commands/format.js +54 -41
  22. package/lib/commands/gettext.js +2 -10
  23. package/lib/commands/gresource.js +2 -8
  24. package/lib/commands/gsettings.js +1 -3
  25. package/lib/commands/install.js +13 -6
  26. package/lib/commands/lint.d.ts +1 -1
  27. package/lib/commands/lint.js +22 -22
  28. package/lib/commands/pack.js +29 -17
  29. package/lib/commands/publish.d.ts +1 -0
  30. package/lib/commands/publish.js +113 -21
  31. package/lib/commands/run.js +2 -6
  32. package/lib/commands/self-update.js +36 -8
  33. package/lib/commands/showcase.js +1 -1
  34. package/lib/commands/system-check.js +8 -11
  35. package/lib/commands/test.js +12 -8
  36. package/lib/commands/uninstall.js +1 -3
  37. package/lib/commands/upgrade.d.ts +1 -1
  38. package/lib/commands/upgrade.js +109 -120
  39. package/lib/commands/workspace.js +1 -3
  40. package/lib/config.js +18 -13
  41. package/lib/index.js +21 -0
  42. package/lib/templates/install.mjs.tmpl +20 -14
  43. package/lib/templates/oxfmtrc.tmpl +54 -0
  44. package/lib/templates/oxlintrc.json.tmpl +35 -0
  45. package/lib/types/config-data.d.ts +23 -13
  46. package/lib/utils/check-system-deps.js +10 -4
  47. package/lib/utils/detect-native-packages.js +1 -1
  48. package/lib/utils/dlx-cache.js +2 -7
  49. package/lib/utils/install-backend-native.d.ts +2 -2
  50. package/lib/utils/install-backend-native.js +72 -63
  51. package/lib/utils/install-backend.d.ts +13 -0
  52. package/lib/utils/install-backend.js +2 -1
  53. package/lib/utils/install-global.js +1 -3
  54. package/lib/utils/normalize-bundler-options.js +52 -17
  55. package/lib/utils/oxc-resolve.d.ts +63 -0
  56. package/lib/utils/oxc-resolve.js +264 -0
  57. package/lib/utils/pkg-json-edit.js +1 -6
  58. package/lib/utils/run-gjs.js +1 -4
  59. package/lib/utils/run-lifecycle-script.js +3 -7
  60. package/lib/utils/workspace-root.js +3 -1
  61. package/package.json +17 -17
  62. package/lib/templates/biome.json.tmpl +0 -79
  63. package/lib/utils/biome-resolve.d.ts +0 -47
  64. package/lib/utils/biome-resolve.js +0 -204
@@ -12,104 +12,104 @@
12
12
  // are skipped — those are the gjsify monorepo internal links and `gjsify
13
13
  // install` resolves them locally. Only external npm specs get checked
14
14
  // against the registry.
15
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
16
- import { join, resolve } from "node:path";
17
- import { homedir } from "node:os";
18
- import { createInterface } from "node:readline/promises";
19
- import { parse } from "@gjsify/semver";
20
- import { DEFAULT_REGISTRY, fetchPackument, parseNpmrc, } from "@gjsify/npm-registry";
15
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
16
+ import { join, resolve } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import { createInterface } from 'node:readline/promises';
19
+ import { parse } from '@gjsify/semver';
20
+ import { DEFAULT_REGISTRY, fetchPackument, parseNpmrc } from '@gjsify/npm-registry';
21
21
  export const upgradeCommand = {
22
- command: "upgrade",
23
- description: "Check the npm registry for newer versions of declared dependencies and update package.json. Interactive by default; `--latest` / `--minor` / `--patch` switch to non-interactive bulk-update mode.",
22
+ command: 'upgrade',
23
+ description: 'Check the npm registry for newer versions of declared dependencies and update package.json. Interactive by default; `--latest` / `--minor` / `--patch` switch to non-interactive bulk-update mode.',
24
24
  builder: (yargs) => {
25
25
  return yargs
26
- .option("latest", {
27
- description: "Non-interactive: bump every dependency to its latest version (allows major).",
28
- type: "boolean",
26
+ .option('latest', {
27
+ description: 'Non-interactive: bump every dependency to its latest version (allows major).',
28
+ type: 'boolean',
29
29
  default: false,
30
30
  })
31
- .option("minor", {
32
- description: "Non-interactive: bump every dependency to the latest within the same major (semver-minor + semver-patch).",
33
- type: "boolean",
31
+ .option('minor', {
32
+ description: 'Non-interactive: bump every dependency to the latest within the same major (semver-minor + semver-patch).',
33
+ type: 'boolean',
34
34
  default: false,
35
35
  })
36
- .option("patch", {
37
- description: "Non-interactive: bump every dependency to the latest within the same minor (semver-patch only).",
38
- type: "boolean",
36
+ .option('patch', {
37
+ description: 'Non-interactive: bump every dependency to the latest within the same minor (semver-patch only).',
38
+ type: 'boolean',
39
39
  default: false,
40
40
  })
41
- .option("filter", {
42
- description: "Only consider packages whose name matches this substring (case-insensitive). Repeatable; comma-separated values are split.",
43
- type: "string",
41
+ .option('filter', {
42
+ description: 'Only consider packages whose name matches this substring (case-insensitive). Repeatable; comma-separated values are split.',
43
+ type: 'string',
44
44
  })
45
- .option("dry-run", {
46
- description: "Print the upgrade plan without writing package.json.",
47
- type: "boolean",
45
+ .option('dry-run', {
46
+ description: 'Print the upgrade plan without writing package.json.',
47
+ type: 'boolean',
48
48
  default: false,
49
49
  })
50
- .option("cwd", {
51
- description: "Project directory. Default: process.cwd().",
52
- type: "string",
50
+ .option('cwd', {
51
+ description: 'Project directory. Default: process.cwd().',
52
+ type: 'string',
53
53
  })
54
- .option("yes", {
55
- alias: "y",
56
- description: "Interactive mode: select all without prompting.",
57
- type: "boolean",
54
+ .option('yes', {
55
+ alias: 'y',
56
+ description: 'Interactive mode: select all without prompting.',
57
+ type: 'boolean',
58
58
  default: false,
59
59
  })
60
- .option("verbose", {
61
- description: "Print extra resolution details.",
62
- type: "boolean",
60
+ .option('verbose', {
61
+ description: 'Print extra resolution details.',
62
+ type: 'boolean',
63
63
  default: false,
64
64
  });
65
65
  },
66
66
  handler: async (args) => {
67
67
  const cwd = resolve(args.cwd ?? process.cwd());
68
- const pkgJsonPath = join(cwd, "package.json");
68
+ const pkgJsonPath = join(cwd, 'package.json');
69
69
  if (!existsSync(pkgJsonPath)) {
70
70
  throw new Error(`[gjsify upgrade] no package.json at ${pkgJsonPath}`);
71
71
  }
72
- const rawPkg = readFileSync(pkgJsonPath, "utf-8");
72
+ const rawPkg = readFileSync(pkgJsonPath, 'utf-8');
73
73
  const pkg = JSON.parse(rawPkg);
74
74
  const filters = args.filter
75
75
  ? args.filter
76
- .split(",")
76
+ .split(',')
77
77
  .map((s) => s.trim().toLowerCase())
78
78
  .filter(Boolean)
79
79
  : [];
80
80
  const entries = collectExternalDeps(pkg, filters);
81
81
  if (entries.length === 0) {
82
- console.log("[gjsify upgrade] no external npm dependencies to check.");
82
+ console.log('[gjsify upgrade] no external npm dependencies to check.');
83
83
  return;
84
84
  }
85
85
  const npmrc = await loadNpmrcLight(cwd);
86
86
  const mode = args.latest
87
- ? "latest"
87
+ ? 'latest'
88
88
  : args.minor
89
- ? "minor"
89
+ ? 'minor'
90
90
  : args.patch
91
- ? "patch"
92
- : "interactive";
91
+ ? 'patch'
92
+ : 'interactive';
93
93
  console.log(`[gjsify upgrade] checking ${entries.length} dependencies against ${npmrc.registry}…`);
94
94
  const candidates = await resolveCandidates(entries, npmrc, args.verbose ?? false, mode);
95
95
  if (candidates.length === 0) {
96
- console.log("✅ all dependencies are up to date");
96
+ console.log('✅ all dependencies are up to date');
97
97
  return;
98
98
  }
99
99
  printTable(candidates);
100
100
  let selected;
101
- if (mode === "interactive" && !args.yes) {
101
+ if (mode === 'interactive' && !args.yes) {
102
102
  selected = await promptSelection(candidates);
103
103
  }
104
- else if (args.yes && mode === "interactive") {
105
- console.log("[gjsify upgrade] -y / --yes: selecting all");
104
+ else if (args.yes && mode === 'interactive') {
105
+ console.log('[gjsify upgrade] -y / --yes: selecting all');
106
106
  selected = candidates;
107
107
  }
108
108
  else {
109
109
  selected = candidates;
110
110
  }
111
111
  if (selected.length === 0) {
112
- console.log("[gjsify upgrade] nothing selected; package.json unchanged.");
112
+ console.log('[gjsify upgrade] nothing selected; package.json unchanged.');
113
113
  return;
114
114
  }
115
115
  if (args.dryRun) {
@@ -121,34 +121,29 @@ export const upgradeCommand = {
121
121
  },
122
122
  };
123
123
  // ─── Resolution ─────────────────────────────────────────────────────────
124
- const DEP_FIELDS = [
125
- "dependencies",
126
- "devDependencies",
127
- "optionalDependencies",
128
- "peerDependencies",
129
- ];
124
+ const DEP_FIELDS = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
130
125
  function collectExternalDeps(pkg, filters) {
131
126
  const out = [];
132
127
  for (const field of DEP_FIELDS) {
133
128
  const map = pkg[field];
134
- if (!map || typeof map !== "object")
129
+ if (!map || typeof map !== 'object')
135
130
  continue;
136
131
  for (const [name, raw] of Object.entries(map)) {
137
- if (typeof raw !== "string")
132
+ if (typeof raw !== 'string')
138
133
  continue;
139
134
  if (filters.length && !filters.some((f) => name.toLowerCase().includes(f))) {
140
135
  continue;
141
136
  }
142
137
  // Skip workspace-protocol + file: + link: + git: + http(s): specs.
143
- if (raw.startsWith("workspace:") ||
144
- raw.startsWith("file:") ||
145
- raw.startsWith("link:") ||
146
- raw.startsWith("git+") ||
147
- raw.startsWith("git:") ||
148
- raw.startsWith("http") ||
149
- raw.startsWith("npm:") || // e.g. `foo: npm:@scope/foo@^1`
150
- raw === "*" ||
151
- raw === "latest") {
138
+ if (raw.startsWith('workspace:') ||
139
+ raw.startsWith('file:') ||
140
+ raw.startsWith('link:') ||
141
+ raw.startsWith('git+') ||
142
+ raw.startsWith('git:') ||
143
+ raw.startsWith('http') ||
144
+ raw.startsWith('npm:') || // e.g. `foo: npm:@scope/foo@^1`
145
+ raw === '*' ||
146
+ raw === 'latest') {
152
147
  continue;
153
148
  }
154
149
  const { prefix, version } = splitRange(raw);
@@ -171,8 +166,8 @@ function collectExternalDeps(pkg, filters) {
171
166
  function splitRange(range) {
172
167
  const m = range.match(/^(\^|~|>=|<=|>|<|=)?\s*([0-9].*)$/);
173
168
  if (!m)
174
- return { prefix: "", version: null };
175
- const prefix = m[1] ?? "";
169
+ return { prefix: '', version: null };
170
+ const prefix = m[1] ?? '';
176
171
  const version = m[2]?.split(/\s|[|&,]/)[0] ?? null; // strip range modifiers (`||`, ` - `, etc.)
177
172
  const parsed = version ? parse(version) : null;
178
173
  return { prefix, version: parsed?.version ?? null };
@@ -190,7 +185,7 @@ async function resolveCandidates(entries, npmrc, verbose, mode) {
190
185
  const entry = entries[i];
191
186
  try {
192
187
  const packument = await fetchPackument(entry.name, { npmrc });
193
- const latest = packument["dist-tags"]?.latest;
188
+ const latest = packument['dist-tags']?.latest;
194
189
  if (!latest) {
195
190
  if (verbose)
196
191
  console.warn(` ${entry.name}: no dist-tags.latest, skipping`);
@@ -202,11 +197,11 @@ async function resolveCandidates(entries, npmrc, verbose, mode) {
202
197
  continue;
203
198
  }
204
199
  const diff = classifyDiff(entry.currentVersion, latest);
205
- if (diff === "none")
200
+ if (diff === 'none')
206
201
  continue;
207
- if (mode === "minor" && diff === "major")
202
+ if (mode === 'minor' && diff === 'major')
208
203
  continue;
209
- if (mode === "patch" && (diff === "major" || diff === "minor"))
204
+ if (mode === 'patch' && (diff === 'major' || diff === 'minor'))
210
205
  continue;
211
206
  results.push({
212
207
  ...entry,
@@ -219,7 +214,6 @@ async function resolveCandidates(entries, npmrc, verbose, mode) {
219
214
  console.warn(` ${entry.name}: fetch failed (${err.message})`);
220
215
  }
221
216
  }
222
- void packumentToString;
223
217
  }
224
218
  await Promise.all(Array.from({ length: cap }, () => worker()));
225
219
  results.sort((a, b) => a.name.localeCompare(b.name));
@@ -229,39 +223,39 @@ function classifyDiff(current, latest) {
229
223
  const c = parse(current);
230
224
  const l = parse(latest);
231
225
  if (!c || !l)
232
- return "none";
226
+ return 'none';
233
227
  if (c.major !== l.major)
234
- return l.major > c.major ? "major" : "none";
228
+ return l.major > c.major ? 'major' : 'none';
235
229
  if (c.minor !== l.minor)
236
- return l.minor > c.minor ? "minor" : "none";
230
+ return l.minor > c.minor ? 'minor' : 'none';
237
231
  if (c.patch !== l.patch)
238
- return l.patch > c.patch ? "patch" : "none";
239
- if ((c.prerelease ?? []).join(".") !== (l.prerelease ?? []).join("."))
240
- return "prerelease";
241
- return "none";
232
+ return l.patch > c.patch ? 'patch' : 'none';
233
+ if ((c.prerelease ?? []).join('.') !== (l.prerelease ?? []).join('.'))
234
+ return 'prerelease';
235
+ return 'none';
242
236
  }
243
237
  // ─── Output / Interaction ──────────────────────────────────────────────
244
238
  const ANSI = {
245
- reset: "\x1b[0m",
246
- bold: "\x1b[1m",
247
- dim: "\x1b[2m",
248
- red: "\x1b[31m",
249
- yellow: "\x1b[33m",
250
- green: "\x1b[32m",
251
- cyan: "\x1b[36m",
239
+ reset: '\x1b[0m',
240
+ bold: '\x1b[1m',
241
+ dim: '\x1b[2m',
242
+ red: '\x1b[31m',
243
+ yellow: '\x1b[33m',
244
+ green: '\x1b[32m',
245
+ cyan: '\x1b[36m',
252
246
  };
253
247
  function colorForDiff(diff) {
254
248
  switch (diff) {
255
- case "major":
249
+ case 'major':
256
250
  return ANSI.red;
257
- case "minor":
251
+ case 'minor':
258
252
  return ANSI.yellow;
259
- case "patch":
253
+ case 'patch':
260
254
  return ANSI.green;
261
- case "prerelease":
255
+ case 'prerelease':
262
256
  return ANSI.cyan;
263
257
  default:
264
- return "";
258
+ return '';
265
259
  }
266
260
  }
267
261
  function printTable(candidates) {
@@ -269,33 +263,33 @@ function printTable(candidates) {
269
263
  const curW = Math.max(...candidates.map((c) => c.currentRange.length), 7);
270
264
  const newW = Math.max(...candidates.map((c) => c.latestVersion.length), 6);
271
265
  const idxW = String(candidates.length).length + 2;
272
- const head = " ".repeat(idxW) +
266
+ const head = ' '.repeat(idxW) +
273
267
  ANSI.bold +
274
- "name".padEnd(nameW) +
275
- " " +
276
- "current".padEnd(curW) +
277
- " " +
278
- "latest".padEnd(newW) +
279
- " " +
280
- "kind" +
268
+ 'name'.padEnd(nameW) +
269
+ ' ' +
270
+ 'current'.padEnd(curW) +
271
+ ' ' +
272
+ 'latest'.padEnd(newW) +
273
+ ' ' +
274
+ 'kind' +
281
275
  ANSI.reset;
282
276
  console.log(head);
283
- console.log(" ".repeat(idxW) + ANSI.dim + "".repeat(nameW + curW + newW + 12) + ANSI.reset);
277
+ console.log(' '.repeat(idxW) + ANSI.dim + ''.repeat(nameW + curW + newW + 12) + ANSI.reset);
284
278
  for (let i = 0; i < candidates.length; i++) {
285
279
  const c = candidates[i];
286
280
  const idx = `${i + 1}.`.padEnd(idxW);
287
281
  const color = colorForDiff(c.diff);
288
282
  console.log(idx +
289
283
  c.name.padEnd(nameW) +
290
- " " +
284
+ ' ' +
291
285
  ANSI.dim +
292
286
  c.currentRange.padEnd(curW) +
293
287
  ANSI.reset +
294
- " " +
288
+ ' ' +
295
289
  color +
296
290
  c.latestVersion.padEnd(newW) +
297
291
  ANSI.reset +
298
- " " +
292
+ ' ' +
299
293
  color +
300
294
  c.diff +
301
295
  ANSI.reset);
@@ -303,28 +297,28 @@ function printTable(candidates) {
303
297
  }
304
298
  async function promptSelection(candidates) {
305
299
  if (!process.stdin.isTTY) {
306
- console.log("[gjsify upgrade] non-TTY stdin: pass --latest / --minor / --patch (or --yes for interactive-all) to upgrade non-interactively.");
300
+ console.log('[gjsify upgrade] non-TTY stdin: pass --latest / --minor / --patch (or --yes for interactive-all) to upgrade non-interactively.');
307
301
  return [];
308
302
  }
309
303
  const rl = createInterface({ input: process.stdin, output: process.stdout });
310
304
  try {
311
- console.log("\nSelect upgrades: comma- or space-separated indices, " +
305
+ console.log('\nSelect upgrades: comma- or space-separated indices, ' +
312
306
  ANSI.bold +
313
- "a" +
307
+ 'a' +
314
308
  ANSI.reset +
315
- " for all, ranges like " +
309
+ ' for all, ranges like ' +
316
310
  ANSI.bold +
317
- "1-3" +
311
+ '1-3' +
318
312
  ANSI.reset +
319
- ", or " +
313
+ ', or ' +
320
314
  ANSI.bold +
321
- "ENTER" +
315
+ 'ENTER' +
322
316
  ANSI.reset +
323
- " to skip:");
324
- const answer = (await rl.question("> ")).trim();
317
+ ' to skip:');
318
+ const answer = (await rl.question('> ')).trim();
325
319
  if (!answer)
326
320
  return [];
327
- if (answer.toLowerCase() === "a" || answer.toLowerCase() === "all")
321
+ if (answer.toLowerCase() === 'a' || answer.toLowerCase() === 'all')
328
322
  return candidates;
329
323
  const picked = new Set();
330
324
  for (const token of answer.split(/[\s,]+/).filter(Boolean)) {
@@ -339,9 +333,7 @@ async function promptSelection(candidates) {
339
333
  picked.add(Number(token) - 1);
340
334
  }
341
335
  }
342
- return [...picked]
343
- .filter((i) => i >= 0 && i < candidates.length)
344
- .map((i) => candidates[i]);
336
+ return [...picked].filter((i) => i >= 0 && i < candidates.length).map((i) => candidates[i]);
345
337
  }
346
338
  finally {
347
339
  rl.close();
@@ -357,7 +349,7 @@ function writePackageJson(path, rawText, parsed, selected) {
357
349
  map[c.name] = c.prefix + c.latestVersion;
358
350
  }
359
351
  const indent = detectIndent(rawText);
360
- writeFileSync(path, JSON.stringify(parsed, null, indent) + (rawText.endsWith("\n") ? "\n" : ""), "utf-8");
352
+ writeFileSync(path, JSON.stringify(parsed, null, indent) + (rawText.endsWith('\n') ? '\n' : ''), 'utf-8');
361
353
  }
362
354
  function detectIndent(json) {
363
355
  const m = json.match(/^\{\n( +)/);
@@ -377,11 +369,11 @@ async function loadNpmrcLight(cwd) {
377
369
  // as install-backend-native, except env-var `npm_config_registry` wins
378
370
  // over file values (matches npm's real semantics, lets the test harness
379
371
  // point at a mock registry without touching `~/.npmrc`).
380
- for (const candidate of [join(homedir(), ".npmrc"), join(cwd, ".npmrc")]) {
372
+ for (const candidate of [join(homedir(), '.npmrc'), join(cwd, '.npmrc')]) {
381
373
  if (!existsSync(candidate))
382
374
  continue;
383
375
  try {
384
- const proj = parseNpmrc(readFileSync(candidate, "utf-8"));
376
+ const proj = parseNpmrc(readFileSync(candidate, 'utf-8'));
385
377
  parsed = {
386
378
  ...parsed,
387
379
  ...proj,
@@ -397,6 +389,3 @@ async function loadNpmrcLight(cwd) {
397
389
  }
398
390
  return parsed;
399
391
  }
400
- function packumentToString(p) {
401
- return `${p.name}@${p["dist-tags"]?.latest ?? "?"}`;
402
- }
@@ -51,9 +51,7 @@ export const workspaceCommand = {
51
51
  // on isTTY (chalk, picocolors, biome) drop colors when stdout is a
52
52
  // pipe, including GitHub Actions where the log viewer renders ANSI
53
53
  // fine.
54
- const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined
55
- ? {}
56
- : { FORCE_COLOR: '1' };
54
+ const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined ? {} : { FORCE_COLOR: '1' };
57
55
  await new Promise((resolve, reject) => {
58
56
  const child = spawn(runner, argv, {
59
57
  cwd: target.location,
package/lib/config.js CHANGED
@@ -41,7 +41,10 @@ function merge(target, ...sources) {
41
41
  return target;
42
42
  }
43
43
  function isPlainObject(val) {
44
- return typeof val === 'object' && val !== null && !Array.isArray(val) && Object.getPrototypeOf(val) === Object.prototype;
44
+ return (typeof val === 'object' &&
45
+ val !== null &&
46
+ !Array.isArray(val) &&
47
+ Object.getPrototypeOf(val) === Object.prototype);
45
48
  }
46
49
  /**
47
50
  * Read a dotted path (`a.b.c`) from a plain object. Returns `undefined` for
@@ -85,13 +88,12 @@ export class Config {
85
88
  // package.json#gjsify exists.
86
89
  const fileExplorer = cosmiconfig(APP_NAME, {
87
90
  ...this.loadOptions,
88
- searchPlaces: (this.loadOptions.searchPlaces ?? defaultSearchPlaces(APP_NAME))
89
- .filter((p) => p !== 'package.json'),
91
+ searchPlaces: (this.loadOptions.searchPlaces ?? defaultSearchPlaces(APP_NAME)).filter((p) => p !== 'package.json'),
90
92
  });
91
- const fileResult = await fileExplorer.search(searchFrom);
93
+ const fileResult = (await fileExplorer.search(searchFrom));
92
94
  const merged = {};
93
95
  try {
94
- const pkg = await this.readPackageJSON(searchFrom);
96
+ const pkg = (await this.readPackageJSON(searchFrom));
95
97
  if (isPlainObject(pkg?.gjsify))
96
98
  merge(merged, pkg.gjsify);
97
99
  }
@@ -125,8 +127,8 @@ export class Config {
125
127
  const configFile = await this.load(process.cwd());
126
128
  const configData = { ...configFile.config };
127
129
  const configFilePath = configFile.filepath || process.cwd();
128
- const pkg = await this.readPackageJSON(configFilePath);
129
- const tsConfig = await this.readTSConfig(configFilePath);
130
+ const pkg = (await this.readPackageJSON(configFilePath));
131
+ const tsConfig = (await this.readTSConfig(configFilePath));
130
132
  tsConfig.reflection ||= cliArgs.reflection;
131
133
  // TODO replace with `cliArgs.logLevel`
132
134
  configData.verbose = cliArgs.verbose || false;
@@ -141,12 +143,15 @@ export class Config {
141
143
  const raw = Array.isArray(cliArgs.excludeGlobals)
142
144
  ? cliArgs.excludeGlobals.join(',')
143
145
  : String(cliArgs.excludeGlobals);
144
- const ids = raw.split(',').map((s) => s.trim()).filter(Boolean);
146
+ const ids = raw
147
+ .split(',')
148
+ .map((s) => s.trim())
149
+ .filter(Boolean);
145
150
  if (ids.length)
146
151
  configData.excludeGlobals = [...(configData.excludeGlobals ?? []), ...ids];
147
152
  }
148
- merge(configData.library ??= {}, pkg, configData.library);
149
- merge(configData.typescript ??= {}, tsConfig, configData.typescript);
153
+ merge((configData.library ??= {}), pkg, configData.library);
154
+ merge((configData.typescript ??= {}), tsConfig, configData.typescript);
150
155
  // Parse `KEY=VALUE` style flags into Record<string, string>.
151
156
  // - `--define`: VALUE is a JS expression (string literals must be
152
157
  // pre-quoted by the caller, e.g. `'"1.2.3"'`).
@@ -170,7 +175,7 @@ export class Config {
170
175
  const defineMap = parseKvPairs(cliArgs.define ?? [], 'define');
171
176
  const aliasMap = parseKvPairs(cliArgs.alias ?? [], 'alias');
172
177
  if (Object.keys(aliasMap).length) {
173
- configData.aliases = { ...(configData.aliases ?? {}), ...aliasMap };
178
+ configData.aliases = { ...configData.aliases, ...aliasMap };
174
179
  }
175
180
  // Resolve `defineFromPackageJson` / `defineFromEnv` into raw
176
181
  // KEY=<JSON-stringified value> entries that get merged into the
@@ -289,14 +294,14 @@ export class Config {
289
294
  // CLI --define wins over package.json/env (manual overrides during
290
295
  // debugging beat declarative config).
291
296
  transform.define = {
292
- ...(transform.define ?? {}),
297
+ ...transform.define,
293
298
  ...fromPkgDefines,
294
299
  ...fromEnvDefines,
295
300
  ...defineMap,
296
301
  };
297
302
  }
298
303
  if (configData.verbose)
299
- console.debug("configData", configData);
304
+ console.debug('configData', configData);
300
305
  return configData;
301
306
  }
302
307
  }
package/lib/index.js CHANGED
@@ -6,6 +6,26 @@ import yargs from 'yargs';
6
6
  import { hideBin } from 'yargs/helpers';
7
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';
8
8
  import { APP_NAME } from './constants.js';
9
+ // Detect which runtime is executing the CLI (GJS or Node.js).
10
+ // GJS MUST be checked first because @gjsify/process sets
11
+ // `process.versions.node = '20.0.0'` for compatibility — a plain
12
+ // `process.versions.node` check would be a false Node positive under GJS.
13
+ function runtimeLabel() {
14
+ try {
15
+ const sys = globalThis.imports?.system;
16
+ if (sys?.version !== undefined) {
17
+ const v = Number(sys.version);
18
+ return `GJS ${Math.floor(v / 10000)}.${Math.floor((v % 10000) / 100)}.${v % 100} (SpiderMonkey)`;
19
+ }
20
+ }
21
+ catch {
22
+ /* not GJS */
23
+ }
24
+ if (typeof process !== 'undefined' && typeof process.versions?.node === 'string') {
25
+ return `Node.js ${process.version}`;
26
+ }
27
+ return 'unknown runtime';
28
+ }
9
29
  // Read the version from package.json adjacent to the bundle. yargs's
10
30
  // auto-version-discovery (its `pkg-up`-driven default) doesn't reach
11
31
  // through the bundled `dist/cli.gjs.mjs` path on GJS — falls back to
@@ -68,5 +88,6 @@ await cli
68
88
  .command(fix.command, fix.description, fix.builder, fix.handler)
69
89
  .command(barrels.command, barrels.description, barrels.builder, barrels.handler)
70
90
  .demandCommand(1)
91
+ .epilogue(`Running on ${runtimeLabel()}`)
71
92
  .help()
72
93
  .parseAsync();
@@ -45,14 +45,17 @@ Gio._promisify(Gio.Subprocess.prototype, 'wait_check_async');
45
45
  // Substituted by `gjsify generate-installer` for end-user apps.
46
46
  const DEFAULT_TARGET = '@gjsify/cli';
47
47
  const DEFAULT_BIN_NAME = 'gjsify';
48
- const DEFAULT_BOOTSTRAP_URL =
49
- 'https://github.com/gjsify/gjsify/releases/latest/download/cli.gjs.mjs';
48
+ const DEFAULT_BOOTSTRAP_URL = 'https://github.com/gjsify/gjsify/releases/latest/download/cli.gjs.mjs';
50
49
  const DEFAULT_BOOTSTRAP_SHA256_URL = `${DEFAULT_BOOTSTRAP_URL}.sha256`;
51
50
 
52
51
  const USER_AGENT = 'gjsify-installer/1.0';
53
52
 
54
- function info(msg) { print(`[gjsify] ${msg}`); }
55
- function error(msg) { printerr(`[gjsify] ERROR: ${msg}`); }
53
+ function info(msg) {
54
+ print(`[gjsify] ${msg}`);
55
+ }
56
+ function error(msg) {
57
+ printerr(`[gjsify] ERROR: ${msg}`);
58
+ }
56
59
 
57
60
  function parseArgs() {
58
61
  const argv = system?.programArgs ?? [];
@@ -63,9 +66,8 @@ function parseArgs() {
63
66
  let bootstrapUrl = GLib.getenv('GJSIFY_INSTALL_BOOTSTRAP_URL') || DEFAULT_BOOTSTRAP_URL;
64
67
  let bootstrapSha256Url = GLib.getenv('GJSIFY_INSTALL_BOOTSTRAP_SHA256_URL');
65
68
  if (bootstrapSha256Url === null || bootstrapSha256Url === undefined) {
66
- bootstrapSha256Url = bootstrapUrl === DEFAULT_BOOTSTRAP_URL
67
- ? DEFAULT_BOOTSTRAP_SHA256_URL
68
- : `${bootstrapUrl}.sha256`;
69
+ bootstrapSha256Url =
70
+ bootstrapUrl === DEFAULT_BOOTSTRAP_URL ? DEFAULT_BOOTSTRAP_SHA256_URL : `${bootstrapUrl}.sha256`;
69
71
  }
70
72
  for (let i = 0; i < argv.length; i++) {
71
73
  const a = argv[i];
@@ -154,8 +156,7 @@ function sha256Hex(bytes) {
154
156
  function cacheDir() {
155
157
  const override = GLib.getenv('GJSIFY_INSTALL_BOOTSTRAP_CACHE');
156
158
  if (override) return override;
157
- const xdg = GLib.getenv('XDG_CACHE_HOME') ||
158
- GLib.build_filenamev([GLib.get_home_dir(), '.cache']);
159
+ const xdg = GLib.getenv('XDG_CACHE_HOME') || GLib.build_filenamev([GLib.get_home_dir(), '.cache']);
159
160
  return GLib.build_filenamev([xdg, 'gjsify', 'bootstrap']);
160
161
  }
161
162
 
@@ -164,9 +165,7 @@ function ensureDir(dir) {
164
165
  }
165
166
 
166
167
  function writeBytes(path, bytes) {
167
- Gio.File.new_for_path(path).replace_contents(
168
- bytes, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null,
169
- );
168
+ Gio.File.new_for_path(path).replace_contents(bytes, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
170
169
  }
171
170
 
172
171
  async function downloadBootstrap(session, bootstrapUrl, sha256Url) {
@@ -190,7 +189,11 @@ async function downloadBootstrap(session, bootstrapUrl, sha256Url) {
190
189
  }
191
190
  }
192
191
  const dir = cacheDir();
193
- try { ensureDir(dir); } catch { /* exists */ }
192
+ try {
193
+ ensureDir(dir);
194
+ } catch {
195
+ /* exists */
196
+ }
194
197
  const bundlePath = GLib.build_filenamev([dir, 'cli.gjs.mjs']);
195
198
  writeBytes(bundlePath, bundleBytes);
196
199
  info(`Bootstrap cached at ${bundlePath} (${bundleBytes.length} bytes)`);
@@ -220,7 +223,10 @@ async function runInstall(bundlePath, spec) {
220
223
 
221
224
  async function main() {
222
225
  const opts = parseArgs();
223
- if (opts.help) { printUsage(); exit(0); }
226
+ if (opts.help) {
227
+ printUsage();
228
+ exit(0);
229
+ }
224
230
  checkGjsVersion();
225
231
 
226
232
  const session = new Soup.Session();