@gjsify/cli 0.4.28 → 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 (62) hide show
  1. package/dist/cli.gjs.mjs +33 -33
  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.js +17 -18
  30. package/lib/commands/run.js +2 -6
  31. package/lib/commands/self-update.js +1 -3
  32. package/lib/commands/showcase.js +1 -1
  33. package/lib/commands/system-check.js +8 -11
  34. package/lib/commands/test.js +12 -8
  35. package/lib/commands/uninstall.js +1 -3
  36. package/lib/commands/upgrade.d.ts +1 -1
  37. package/lib/commands/upgrade.js +109 -120
  38. package/lib/commands/workspace.js +1 -3
  39. package/lib/config.js +18 -13
  40. package/lib/index.js +3 -1
  41. package/lib/templates/install.mjs.tmpl +20 -14
  42. package/lib/templates/oxfmtrc.tmpl +54 -0
  43. package/lib/templates/oxlintrc.json.tmpl +35 -0
  44. package/lib/types/config-data.d.ts +23 -13
  45. package/lib/utils/check-system-deps.js +10 -4
  46. package/lib/utils/detect-native-packages.js +1 -1
  47. package/lib/utils/dlx-cache.js +2 -7
  48. package/lib/utils/install-backend-native.d.ts +2 -2
  49. package/lib/utils/install-backend-native.js +65 -58
  50. package/lib/utils/install-backend.js +2 -1
  51. package/lib/utils/install-global.js +1 -3
  52. package/lib/utils/normalize-bundler-options.js +52 -17
  53. package/lib/utils/oxc-resolve.d.ts +63 -0
  54. package/lib/utils/oxc-resolve.js +264 -0
  55. package/lib/utils/pkg-json-edit.js +1 -6
  56. package/lib/utils/run-gjs.js +1 -4
  57. package/lib/utils/run-lifecycle-script.js +3 -7
  58. package/lib/utils/workspace-root.js +3 -1
  59. package/package.json +17 -17
  60. package/lib/templates/biome.json.tmpl +0 -79
  61. package/lib/utils/biome-resolve.d.ts +0 -47
  62. package/lib/utils/biome-resolve.js +0 -204
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "useTabs": false,
4
+ "tabWidth": 4,
5
+ "printWidth": 120,
6
+ "singleQuote": true,
7
+ "jsxSingleQuote": false,
8
+ "quoteProps": "as-needed",
9
+ "trailingComma": "all",
10
+ "semi": true,
11
+ "arrowParens": "always",
12
+ "bracketSameLine": false,
13
+ "bracketSpacing": true,
14
+ "ignorePatterns": [
15
+ "**/*.json",
16
+ "**/*.jsonc",
17
+ "**/*.json5",
18
+ "**/*.css",
19
+ "**/*.scss",
20
+ "**/*.sass",
21
+ "**/*.less",
22
+ "**/*.html",
23
+ "**/*.htm",
24
+ "**/*.md",
25
+ "**/*.mdx",
26
+ "**/*.yml",
27
+ "**/*.yaml",
28
+ "**/*.vue",
29
+ "**/*.svelte",
30
+ "**/*.toml",
31
+ "**/*.graphql",
32
+ "**/*.gql",
33
+ "**/node_modules",
34
+ "**/dist",
35
+ "**/lib",
36
+ "**/build",
37
+ "**/build-dir",
38
+ "**/builddir",
39
+ "**/flatpak-build",
40
+ "**/.flatpak-builder",
41
+ "**/repo",
42
+ "**/coverage",
43
+ "**/refs",
44
+ "**/@types",
45
+ "**/templates",
46
+ "**/prebuilds",
47
+ "**/cli.gjs.mjs",
48
+ "**/test.gjs.mjs",
49
+ "**/test.node.mjs",
50
+ "**/*.gresource",
51
+ "**/*.compiled",
52
+ "**/*.metainfo.xml"
53
+ ]
54
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "categories": {
4
+ "correctness": "error"
5
+ },
6
+ "rules": {
7
+ "typescript/consistent-type-imports": "warn",
8
+ "typescript/no-explicit-any": "warn",
9
+ "typescript/no-non-null-assertion": "off",
10
+ "unicorn/prefer-node-protocol": "error",
11
+ "eslint/no-unused-vars": "warn"
12
+ },
13
+ "ignorePatterns": [
14
+ "**/node_modules",
15
+ "**/dist",
16
+ "**/lib",
17
+ "**/build",
18
+ "**/build-dir",
19
+ "**/builddir",
20
+ "**/flatpak-build",
21
+ "**/.flatpak-builder",
22
+ "**/repo",
23
+ "**/coverage",
24
+ "**/refs",
25
+ "**/@types",
26
+ "**/templates",
27
+ "**/prebuilds",
28
+ "**/cli.gjs.mjs",
29
+ "**/test.gjs.mjs",
30
+ "**/test.node.mjs",
31
+ "**/*.gresource",
32
+ "**/*.compiled",
33
+ "**/*.metainfo.xml"
34
+ ]
35
+ }
@@ -165,20 +165,30 @@ export interface ConfigData {
165
165
  }>;
166
166
  /**
167
167
  * Extension → loader-kind map for files Rolldown does not classify
168
- * natively. Currently only `'text'` is implemented — the file's content
169
- * becomes the JS string default export (`export default "<content>"`).
170
- * Replaces the legacy esbuild `loader: { '.ui': 'text' }` pattern.
168
+ * natively. Replaces the legacy esbuild `loader: { '.ui': 'text', '.png': 'dataurl' }`
169
+ * pattern.
170
+ *
171
+ * Loader kinds:
172
+ * `'text'` — file contents as a JS string default export
173
+ * (`export default "<source>"`). Good for GLSL shaders,
174
+ * `.ui` GtkBuilder XML, `.asm`, etc.
175
+ * `'dataurl'` — `data:<mime>;base64,<b64>` string default export.
176
+ * MIME is inferred from the extension (.png → image/png,
177
+ * .jpg → image/jpeg, .gif → image/gif, .svg → image/svg+xml,
178
+ * .webp → image/webp, fallback application/octet-stream).
179
+ * Good for Excalibur's `ImageSource` and any library that
180
+ * accepts a data: URL rather than a separate asset file.
171
181
  *
172
182
  * Example:
173
183
  * ```jsonc
174
- * "loaders": { ".ui": "text", ".asm": "text" }
184
+ * "loaders": { ".ui": "text", ".glsl": "text", ".png": "dataurl" }
175
185
  * ```
176
186
  *
177
187
  * Lives at the top level (not under `bundler`) so it doesn't leak into
178
- * Rolldown's options on pass-through; the CLI converts it into a
179
- * `text-loader` plugin prepended to the bundler's plugin chain.
188
+ * Rolldown's options on pass-through; the CLI converts it into a plugin
189
+ * prepended to the bundler's plugin chain.
180
190
  */
181
- loaders?: Record<string, 'text'>;
191
+ loaders?: Record<string, 'text' | 'dataurl'>;
182
192
  /**
183
193
  * Flatpak-related configuration consumed by `gjsify flatpak <sub>`.
184
194
  * Lives in its own top-level namespace so the bundler config doesn't
@@ -188,8 +198,8 @@ export interface ConfigData {
188
198
  flatpak?: ConfigDataFlatpak;
189
199
  /**
190
200
  * Format/lint config consumed by `gjsify format` / `gjsify lint` /
191
- * `gjsify fix`. Thin shell — Biome's own `biome.json` is the real
192
- * configuration file; we only need a pointer here.
201
+ * `gjsify fix`. Thin shell — oxc's own `.oxfmtrc.json` / `.oxlintrc.json`
202
+ * are the real configuration files; we only need a pointer here.
193
203
  */
194
204
  format?: ConfigDataFormat;
195
205
  /**
@@ -198,12 +208,12 @@ export interface ConfigData {
198
208
  */
199
209
  test?: ConfigDataTest;
200
210
  }
201
- /** Optional pointer to a non-default biome.json. */
211
+ /** Optional pointer to a non-default oxc config file. */
202
212
  export interface ConfigDataFormat {
203
213
  /**
204
- * Path to biome.json. Default: walks up from cwd to find one;
205
- * falls back to the recommended template shipped with `gjsify`
206
- * (writable via `gjsify format --init`).
214
+ * Path to an `.oxfmtrc.json` / `.oxlintrc.json`. Default: walks up from
215
+ * cwd to find one; falls back to the recommended templates shipped with
216
+ * `gjsify` (writable via `gjsify format --init`).
207
217
  */
208
218
  configPath?: string;
209
219
  }
@@ -29,7 +29,7 @@ function checkBinary(id, name, binary, versionArgs, severity, parseVersion, requ
29
29
  const out = tryExecFile(binary, versionArgs);
30
30
  if (out === null)
31
31
  return { id, name, found: false, severity, requiredBy };
32
- const version = parseVersion ? parseVersion(out) : out.split('\n')[0] ?? out;
32
+ const version = parseVersion ? parseVersion(out) : (out.split('\n')[0] ?? out);
33
33
  return { id, name, found: true, version, severity, requiredBy };
34
34
  }
35
35
  /** Check a pkg-config library. pkg-config --modversion returns version on stdout. */
@@ -51,7 +51,9 @@ function checkNpmPackage(id, name, packageName, cwd, severity, requiredBy) {
51
51
  requireFromCwd.resolve(packageName);
52
52
  return { id, name, found: true, severity, requiredBy };
53
53
  }
54
- catch { /* not in project, try CLI fallback */ }
54
+ catch {
55
+ /* not in project, try CLI fallback */
56
+ }
55
57
  // 2. Fallback: CLI's own node_modules
56
58
  try {
57
59
  const requireFromCli = createRequire(import.meta.url);
@@ -156,7 +158,9 @@ function discoverGjsifyPackages(cwd) {
156
158
  try {
157
159
  topPkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
158
160
  }
159
- catch { /* ignore */ }
161
+ catch {
162
+ /* ignore */
163
+ }
160
164
  const directDeps = {
161
165
  ...topPkg.dependencies,
162
166
  ...topPkg.devDependencies,
@@ -169,7 +173,9 @@ function discoverGjsifyPackages(cwd) {
169
173
  }
170
174
  }
171
175
  }
172
- catch { /* ignore */ }
176
+ catch {
177
+ /* ignore */
178
+ }
173
179
  // Also include direct deps even if node_modules walk failed
174
180
  for (const dep of Object.keys(directDeps)) {
175
181
  if (dep.startsWith('@gjsify/'))
@@ -131,7 +131,7 @@ export function detectNativePackages(startDir) {
131
131
  * Prepends the new paths to any existing values from the environment.
132
132
  */
133
133
  export function buildNativeEnv(packages) {
134
- const dirs = packages.map(p => p.prebuildsDir);
134
+ const dirs = packages.map((p) => p.prebuildsDir);
135
135
  const existing_ld = process.env['LD_LIBRARY_PATH'] ?? '';
136
136
  const existing_gi = process.env['GI_TYPELIB_PATH'] ?? '';
137
137
  const LD_LIBRARY_PATH = [...dirs, ...(existing_ld ? [existing_ld] : [])].join(':');
@@ -84,13 +84,8 @@ export function symlinkSwap(cacheDir, prepareDir) {
84
84
  const linkPath = join(cacheDir, 'pkg');
85
85
  const tmpName = `pkg.tmp-${Date.now().toString(16)}-${process.pid.toString(16)}`;
86
86
  const tmpLink = join(cacheDir, tmpName);
87
- try {
88
- symlinkSync(prepareDir, tmpLink, 'dir');
89
- }
90
- catch (err) {
91
- // If we cannot even create the tmp link, give up.
92
- throw err;
93
- }
87
+ // If we cannot even create the tmp link, give up (the error propagates).
88
+ symlinkSync(prepareDir, tmpLink, 'dir');
94
89
  try {
95
90
  renameSync(tmpLink, linkPath);
96
91
  }
@@ -1,5 +1,5 @@
1
- import { type Packument } from "@gjsify/npm-registry";
2
- import type { InstallOptions } from "./install-backend.ts";
1
+ import { type Packument } from '@gjsify/npm-registry';
2
+ import type { InstallOptions } from './install-backend.ts';
3
3
  interface ParsedSpec {
4
4
  name: string;
5
5
  range: string;
@@ -13,18 +13,18 @@
13
13
  //
14
14
  // Out of scope (still deferred): peerDependencies validation,
15
15
  // lifecycle scripts, git/file specs.
16
- import * as fs from "node:fs";
17
- import * as path from "node:path";
18
- import * as os from "node:os";
19
- import { Range, SemVer, maxSatisfying, satisfies, } from "@gjsify/semver";
20
- import { DEFAULT_REGISTRY, fetchPackument, fetchTarball, parseNpmrc, } from "@gjsify/npm-registry";
21
- import { extractTarball } from "@gjsify/tar";
22
- const DEFAULT_CONCURRENCY = Number(process.env.GJSIFY_INSTALL_CONCURRENCY ?? "8") || 8;
23
- const LOCKFILE_NAME = "gjsify-lock.json";
16
+ import * as fs from 'node:fs';
17
+ import * as path from 'node:path';
18
+ import * as os from 'node:os';
19
+ import { Range, SemVer, maxSatisfying, satisfies } from '@gjsify/semver';
20
+ import { DEFAULT_REGISTRY, fetchPackument, fetchTarball, parseNpmrc, } from '@gjsify/npm-registry';
21
+ import { extractTarball } from '@gjsify/tar';
22
+ const DEFAULT_CONCURRENCY = Number(process.env.GJSIFY_INSTALL_CONCURRENCY ?? '8') || 8;
23
+ const LOCKFILE_NAME = 'gjsify-lock.json';
24
24
  const LOCKFILE_VERSION = 2;
25
25
  export async function installPackagesNative(opts) {
26
26
  if (opts.specs.length === 0) {
27
- throw new Error("installPackagesNative: empty specs list");
27
+ throw new Error('installPackagesNative: empty specs list');
28
28
  }
29
29
  fs.mkdirSync(opts.prefix, { recursive: true });
30
30
  const npmrc = await loadNpmrc(opts);
@@ -47,25 +47,25 @@ export async function installPackagesNative(opts) {
47
47
  throw new Error(`install: --immutable but ${lockfilePath} is stale.\n${drift}\n` +
48
48
  `Re-run \`gjsify install\` (without --immutable) to refresh the lockfile.`);
49
49
  }
50
- log("install: --immutable, using lockfile (%d package(s))", Object.keys(existingLock.packages).length);
50
+ log('install: --immutable, using lockfile (%d package(s))', Object.keys(existingLock.packages).length);
51
51
  nodes = lockfileToNodes(existingLock);
52
52
  }
53
53
  else if (existingLock && lockfileMatchesRequest(existingLock, opts.specs)) {
54
- log("install: using lockfile (%d package(s))", Object.keys(existingLock.packages).length);
54
+ log('install: using lockfile (%d package(s))', Object.keys(existingLock.packages).length);
55
55
  nodes = lockfileToNodes(existingLock);
56
56
  }
57
57
  else {
58
- log("install: resolving %d top-level spec(s) → %s", opts.specs.length, opts.prefix);
58
+ log('install: resolving %d top-level spec(s) → %s', opts.specs.length, opts.prefix);
59
59
  nodes = await resolveDeps(opts.specs, npmrc, log, opts.overrides, opts.skipDeps);
60
60
  if (opts.lockfile) {
61
61
  writeLockfile(lockfilePath, opts.specs, nodes);
62
- log("install: wrote %s (%d entries)", LOCKFILE_NAME, nodes.length);
62
+ log('install: wrote %s (%d entries)', LOCKFILE_NAME, nodes.length);
63
63
  }
64
64
  }
65
- log("install: downloading %d tarball(s)", nodes.length);
65
+ log('install: downloading %d tarball(s)', nodes.length);
66
66
  await downloadAndExtractAll(nodes, opts.prefix, npmrc, log);
67
67
  await linkBins(nodes, opts.prefix, log);
68
- log("install: done");
68
+ log('install: done');
69
69
  // Surface the top-level requested packages so callers can update
70
70
  // package.json with the resolved version (mirrors `npm install --save`
71
71
  // behavior). Sub-deps are not included.
@@ -94,14 +94,14 @@ function topLevelResolutions(specs, nodes) {
94
94
  return out;
95
95
  }
96
96
  function parseSpecName(spec) {
97
- if (spec.startsWith("@")) {
98
- const slash = spec.indexOf("/");
97
+ if (spec.startsWith('@')) {
98
+ const slash = spec.indexOf('/');
99
99
  if (slash === -1)
100
100
  return spec;
101
- const at = spec.indexOf("@", slash + 1);
101
+ const at = spec.indexOf('@', slash + 1);
102
102
  return at === -1 ? spec : spec.slice(0, at);
103
103
  }
104
- const at = spec.indexOf("@");
104
+ const at = spec.indexOf('@');
105
105
  return at === -1 ? spec : spec.slice(0, at);
106
106
  }
107
107
  /**
@@ -128,7 +128,7 @@ async function resolveDeps(specs, npmrc, log, overrides, skipDeps) {
128
128
  return range;
129
129
  if (override === range)
130
130
  return range;
131
- log("install: override %s %s → %s", name, range, override);
131
+ log('install: override %s %s → %s', name, range, override);
132
132
  return override;
133
133
  };
134
134
  const packumentCache = new Map();
@@ -139,7 +139,7 @@ async function resolveDeps(specs, npmrc, log, overrides, skipDeps) {
139
139
  const fresh = fetchPackument(name, {
140
140
  npmrc,
141
141
  onRetry: ({ attempt, error, delayMs }) => {
142
- log("packument %s: retry %d after %dms (%s)", name, attempt, delayMs, errMsg(error));
142
+ log('packument %s: retry %d after %dms (%s)', name, attempt, delayMs, errMsg(error));
143
143
  },
144
144
  });
145
145
  packumentCache.set(name, fresh);
@@ -200,13 +200,23 @@ async function resolveDeps(specs, npmrc, log, overrides, skipDeps) {
200
200
  if (installPath === `node_modules/${edge.name}`) {
201
201
  root.set(edge.name, node);
202
202
  }
203
- log("resolve: %s@%s ← %s (at %s)", edge.name, version, edge.range, installPath);
203
+ log('resolve: %s@%s ← %s (at %s)', edge.name, version, edge.range, installPath);
204
204
  if (!skipDeps) {
205
205
  for (const [depName, depRange] of Object.entries(node.dependencies)) {
206
- queue.push({ from: installPath, name: depName, range: applyOverride(depName, depRange), required: true });
206
+ queue.push({
207
+ from: installPath,
208
+ name: depName,
209
+ range: applyOverride(depName, depRange),
210
+ required: true,
211
+ });
207
212
  }
208
213
  for (const [depName, depRange] of Object.entries(node.optionalDependencies)) {
209
- queue.push({ from: installPath, name: depName, range: applyOverride(depName, depRange), required: false });
214
+ queue.push({
215
+ from: installPath,
216
+ name: depName,
217
+ range: applyOverride(depName, depRange),
218
+ required: false,
219
+ });
210
220
  }
211
221
  }
212
222
  }
@@ -214,7 +224,7 @@ async function resolveDeps(specs, npmrc, log, overrides, skipDeps) {
214
224
  // Optional deps that fail to resolve are skipped — yarn/npm
215
225
  // behavior. Required deps re-throw.
216
226
  if (!edge.required) {
217
- log("resolve: optional dep %s@%s skipped (%s)", edge.name, edge.range, e.message);
227
+ log('resolve: optional dep %s@%s skipped (%s)', edge.name, edge.range, e.message);
218
228
  continue;
219
229
  }
220
230
  throw e;
@@ -245,12 +255,12 @@ function findVisible(requesterPath, name, byPath) {
245
255
  // eslint-disable-next-line no-constant-condition
246
256
  while (true) {
247
257
  // Find the deepest `/node_modules/<pkg>` in `p`, strip it.
248
- const idx = p.lastIndexOf("/node_modules/");
258
+ const idx = p.lastIndexOf('/node_modules/');
249
259
  if (idx < 0)
250
260
  break;
251
261
  p = p.slice(0, idx);
252
262
  candidates.push(`${p}/node_modules/${name}`);
253
- if (p === "")
263
+ if (p === '')
254
264
  break;
255
265
  }
256
266
  }
@@ -301,10 +311,10 @@ function readLockfile(lockfilePath) {
301
311
  if (!fs.existsSync(lockfilePath))
302
312
  return null;
303
313
  try {
304
- const parsed = JSON.parse(fs.readFileSync(lockfilePath, "utf-8"));
314
+ const parsed = JSON.parse(fs.readFileSync(lockfilePath, 'utf-8'));
305
315
  if (parsed.lockfileVersion !== LOCKFILE_VERSION)
306
316
  return null;
307
- if (!parsed.packages || typeof parsed.packages !== "object")
317
+ if (!parsed.packages || typeof parsed.packages !== 'object')
308
318
  return null;
309
319
  return parsed;
310
320
  }
@@ -330,7 +340,7 @@ function writeLockfile(lockfilePath, specs, nodes) {
330
340
  requested: [...specs],
331
341
  packages,
332
342
  };
333
- fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2) + "\n");
343
+ fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2) + '\n');
334
344
  }
335
345
  function lockfileToNodes(lockfile) {
336
346
  return Object.entries(lockfile.packages).map(([installPath, entry]) => ({
@@ -349,8 +359,8 @@ function lockfileToNodes(lockfile) {
349
359
  function nameFromInstallPath(installPath) {
350
360
  // Last `node_modules/` boundary, then the rest is the package name
351
361
  // (single segment unscoped, or `@scope/pkg` scoped).
352
- const idx = installPath.lastIndexOf("/node_modules/");
353
- const after = idx < 0 ? installPath.replace(/^node_modules\//, "") : installPath.slice(idx + "/node_modules/".length);
362
+ const idx = installPath.lastIndexOf('/node_modules/');
363
+ const after = idx < 0 ? installPath.replace(/^node_modules\//, '') : installPath.slice(idx + '/node_modules/'.length);
354
364
  return after;
355
365
  }
356
366
  function lockfileMatchesRequest(lockfile, specs) {
@@ -381,10 +391,10 @@ function describeLockfileDrift(lockfile, specs) {
381
391
  return null;
382
392
  const lines = [];
383
393
  if (added.length > 0)
384
- lines.push(` + ${added.sort().join("\n + ")}`);
394
+ lines.push(` + ${added.sort().join('\n + ')}`);
385
395
  if (removed.length > 0)
386
- lines.push(` - ${removed.sort().join("\n - ")}`);
387
- return lines.join("\n");
396
+ lines.push(` - ${removed.sort().join('\n - ')}`);
397
+ return lines.join('\n');
388
398
  }
389
399
  // Exported for unit-testing — keep the function name + signature
390
400
  // stable, the install-backend itself still calls it via the local
@@ -400,25 +410,25 @@ export function parseSpec(raw) {
400
410
  // shipped only prereleases (4.0.0-rc.17 is the `latest` tag, no
401
411
  // stable 4.x yet) and `*` was selecting the abandoned 3.3.0
402
412
  // instead.
403
- if (raw.startsWith("@")) {
404
- const slash = raw.indexOf("/");
413
+ if (raw.startsWith('@')) {
414
+ const slash = raw.indexOf('/');
405
415
  if (slash < 0)
406
416
  throw new Error(`Invalid spec (scoped name without slash): ${raw}`);
407
- const at = raw.indexOf("@", slash);
417
+ const at = raw.indexOf('@', slash);
408
418
  if (at < 0)
409
- return { name: raw, range: "latest" };
410
- return { name: raw.slice(0, at), range: raw.slice(at + 1) || "latest" };
419
+ return { name: raw, range: 'latest' };
420
+ return { name: raw.slice(0, at), range: raw.slice(at + 1) || 'latest' };
411
421
  }
412
- const at = raw.indexOf("@");
422
+ const at = raw.indexOf('@');
413
423
  if (at < 0)
414
- return { name: raw, range: "latest" };
415
- return { name: raw.slice(0, at), range: raw.slice(at + 1) || "latest" };
424
+ return { name: raw, range: 'latest' };
425
+ return { name: raw.slice(0, at), range: raw.slice(at + 1) || 'latest' };
416
426
  }
417
427
  // Exported for unit-testing. Internal API.
418
428
  export function pickVersion(packument, range) {
419
429
  // dist-tag fast path: `latest`, `next`, ...
420
- if (packument["dist-tags"][range])
421
- return packument["dist-tags"][range];
430
+ if (packument['dist-tags'][range])
431
+ return packument['dist-tags'][range];
422
432
  // Validate range early so a typo fails loudly.
423
433
  let parsedRange;
424
434
  try {
@@ -442,8 +452,7 @@ async function downloadAndExtractAll(nodes, prefix, npmrc, log) {
442
452
  // Sort by install-path depth ascending so parents extract before
443
453
  // children. Extracting a parent on top of an existing child would
444
454
  // wipe out the child.
445
- const queue = [...nodes].sort((a, b) => depth(a.installPath) - depth(b.installPath) ||
446
- (a.installPath < b.installPath ? -1 : 1));
455
+ const queue = [...nodes].sort((a, b) => depth(a.installPath) - depth(b.installPath) || (a.installPath < b.installPath ? -1 : 1));
447
456
  const workers = [];
448
457
  const concurrency = Math.max(1, Math.min(DEFAULT_CONCURRENCY, queue.length));
449
458
  // Parents (depth 1) are extracted serially first to avoid concurrent
@@ -477,12 +486,12 @@ async function downloadAndExtractAll(nodes, prefix, npmrc, log) {
477
486
  }
478
487
  async function extractOne(node, prefix, npmrc, log) {
479
488
  const dest = path.join(prefix, node.installPath);
480
- log("fetch: %s@%s ← %s (→ %s)", node.name, node.version, node.tarballUrl, node.installPath);
489
+ log('fetch: %s@%s ← %s (→ %s)', node.name, node.version, node.tarballUrl, node.installPath);
481
490
  const bytes = await fetchTarball(node.tarballUrl, {
482
491
  npmrc,
483
492
  integrity: node.integrity,
484
493
  onRetry: ({ attempt, error, delayMs }) => {
485
- log("tarball %s@%s: retry %d after %dms (%s)", node.name, node.version, attempt, delayMs, errMsg(error));
494
+ log('tarball %s@%s: retry %d after %dms (%s)', node.name, node.version, attempt, delayMs, errMsg(error));
486
495
  },
487
496
  });
488
497
  fs.rmSync(dest, { recursive: true, force: true });
@@ -492,7 +501,7 @@ async function extractOne(node, prefix, npmrc, log) {
492
501
  function depth(installPath) {
493
502
  // Count `node_modules/` segments to know nesting depth.
494
503
  // `node_modules/foo` = 1, `node_modules/foo/node_modules/bar` = 2, etc.
495
- return installPath.split("/node_modules/").length;
504
+ return installPath.split('/node_modules/').length;
496
505
  }
497
506
  async function linkBins(nodes, prefix, log) {
498
507
  // Only root-level packages publish bins into the top-level
@@ -500,7 +509,7 @@ async function linkBins(nodes, prefix, log) {
500
509
  // direct dependents through the nested .bin (npm matches this) — we
501
510
  // omit nested-bin linking for now since no consumer of the install
502
511
  // backend depends on it (gjsify's own use cases all hit root bins).
503
- const binDir = path.join(prefix, "node_modules", ".bin");
512
+ const binDir = path.join(prefix, 'node_modules', '.bin');
504
513
  let created = 0;
505
514
  for (const node of nodes) {
506
515
  if (!node.bin)
@@ -536,15 +545,13 @@ async function linkBins(nodes, prefix, log) {
536
545
  }
537
546
  }
538
547
  if (created > 0)
539
- log("bin: linked %d entry(ies) under .bin/", created);
548
+ log('bin: linked %d entry(ies) under .bin/', created);
540
549
  }
541
550
  function normalizeBin(pkgName, bin) {
542
551
  const out = new Map();
543
- if (typeof bin === "string") {
552
+ if (typeof bin === 'string') {
544
553
  // String form is shorthand for `{ <last-segment-of-pkgName>: <bin> }`.
545
- const baseName = pkgName.startsWith("@")
546
- ? pkgName.slice(pkgName.indexOf("/") + 1)
547
- : pkgName;
554
+ const baseName = pkgName.startsWith('@') ? pkgName.slice(pkgName.indexOf('/') + 1) : pkgName;
548
555
  out.set(baseName, bin);
549
556
  return out;
550
557
  }
@@ -565,11 +572,11 @@ async function loadNpmrc(opts) {
565
572
  // workspace-root one too; the gjsify project-local case is what users
566
573
  // hit most often (mock-registry tests, scoped-registry overrides), so
567
574
  // we cover that explicitly.
568
- for (const candidate of [path.join(home, ".npmrc"), path.join(opts.prefix, ".npmrc")]) {
575
+ for (const candidate of [path.join(home, '.npmrc'), path.join(opts.prefix, '.npmrc')]) {
569
576
  if (!fs.existsSync(candidate))
570
577
  continue;
571
578
  try {
572
- const projectParsed = parseNpmrc(fs.readFileSync(candidate, "utf-8"));
579
+ const projectParsed = parseNpmrc(fs.readFileSync(candidate, 'utf-8'));
573
580
  parsed = { ...parsed, ...projectParsed, scopes: { ...parsed.scopes, ...projectParsed.scopes } };
574
581
  }
575
582
  catch (e) {
@@ -40,7 +40,8 @@ async function installViaNpm({ prefix, specs, verbose, registry }) {
40
40
  '--no-package-lock',
41
41
  '--no-audit',
42
42
  '--no-fund',
43
- '--prefix', prefix,
43
+ '--prefix',
44
+ prefix,
44
45
  ...(verbose ? ['--loglevel', 'verbose'] : ['--loglevel', 'warn']),
45
46
  ...specs,
46
47
  ];
@@ -161,9 +161,7 @@ function pickBinMap(pkgName, pkgJson) {
161
161
  function normalizeBin(pkgName, bin) {
162
162
  const out = new Map();
163
163
  if (typeof bin === 'string') {
164
- const baseName = pkgName.startsWith('@')
165
- ? pkgName.slice(pkgName.indexOf('/') + 1)
166
- : pkgName;
164
+ const baseName = pkgName.startsWith('@') ? pkgName.slice(pkgName.indexOf('/') + 1) : pkgName;
167
165
  out.set(baseName, bin);
168
166
  return out;
169
167
  }