@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
@@ -2,10 +2,46 @@
2
2
  // field on `.gjsifyrc.js` / `package.json#gjsify` into the equivalent
3
3
  // `bundler?: RolldownOptions` shape. Logs a single warning per build.
4
4
  //
5
- // Drop in 0.5.0.
5
+ // Also handles the top-level `bundler.define` alias: Rolldown reads
6
+ // `transform.define`, not a top-level `define`. A user who flat-renames
7
+ // `esbuild: { define: {...} }` → `bundler: { define: {...} }` puts `define`
8
+ // at the top level where Rolldown silently ignores it, causing
9
+ // `ReferenceError: <TOKEN> is not defined` at GJS load time. We auto-map
10
+ // top-level `bundler.define` into `bundler.transform.define` and emit a
11
+ // one-time warning so the user can correct their config.
12
+ //
13
+ // Drop `esbuild` shim in 0.5.0; the `bundler.define` alias is permanent.
6
14
  let warnedOnce = false;
15
+ let warnedDefineOnce = false;
7
16
  export function normalizeBundlerOptions(configData) {
8
- const fromBundler = (configData.bundler ?? {});
17
+ const raw = (configData.bundler ?? {});
18
+ // --- Top-level bundler.define alias ------------------------------------------
19
+ // Rolldown only reads `transform.define`; a top-level `define` key is silently
20
+ // ignored. Detect it, warn once, and move it into `transform.define` so the
21
+ // user's flat esbuild→bundler rename of `define` Just Works.
22
+ let fromBundler = raw;
23
+ if (typeof raw['define'] === 'object' &&
24
+ raw['define'] !== null) {
25
+ if (!warnedDefineOnce) {
26
+ warnedDefineOnce = true;
27
+ // eslint-disable-next-line no-console
28
+ console.warn("[gjsify] WARNING: 'bundler.define' is not a valid Rolldown option and would be " +
29
+ "silently ignored — it has been auto-mapped to 'bundler.transform.define'. " +
30
+ "Move 'define' under 'bundler.transform.define' in your config to suppress " +
31
+ 'this warning and avoid a ReferenceError at GJS load time.');
32
+ }
33
+ const { define: topLevelDefine, ...rest } = raw;
34
+ fromBundler = {
35
+ ...rest,
36
+ transform: {
37
+ ...rest.transform,
38
+ define: {
39
+ ...topLevelDefine,
40
+ ...rest.transform?.define,
41
+ },
42
+ },
43
+ };
44
+ }
9
45
  if (!configData.esbuild)
10
46
  return fromBundler;
11
47
  if (!warnedOnce) {
@@ -22,19 +58,19 @@ export function normalizeBundlerOptions(configData) {
22
58
  // user-provided config and `input` must survive the merge.
23
59
  const out = { ...fromEsbuild, ...fromBundler };
24
60
  if (fromEsbuild.output || fromBundler.output) {
25
- out.output = { ...(fromEsbuild.output ?? {}), ...(fromBundler.output ?? {}) };
61
+ out.output = { ...fromEsbuild.output, ...fromBundler.output };
26
62
  }
27
63
  if (fromEsbuild.transform || fromBundler.transform) {
28
- out.transform = { ...(fromEsbuild.transform ?? {}), ...(fromBundler.transform ?? {}) };
64
+ out.transform = { ...fromEsbuild.transform, ...fromBundler.transform };
29
65
  if (fromEsbuild.transform?.define || fromBundler.transform?.define) {
30
66
  out.transform.define = {
31
- ...(fromEsbuild.transform?.define ?? {}),
32
- ...(fromBundler.transform?.define ?? {}),
67
+ ...fromEsbuild.transform?.define,
68
+ ...fromBundler.transform?.define,
33
69
  };
34
70
  }
35
71
  }
36
72
  if (fromEsbuild.resolve || fromBundler.resolve) {
37
- out.resolve = { ...(fromEsbuild.resolve ?? {}), ...(fromBundler.resolve ?? {}) };
73
+ out.resolve = { ...fromEsbuild.resolve, ...fromBundler.resolve };
38
74
  }
39
75
  return out;
40
76
  }
@@ -54,10 +90,7 @@ function legacyEsbuildToRolldown(esb) {
54
90
  output.minify = esb.minify;
55
91
  if (esb.sourcemap !== undefined) {
56
92
  // esbuild has 'external' / 'both' which Rolldown doesn't — coerce to boolean.
57
- output.sourcemap =
58
- esb.sourcemap === 'inline'
59
- ? 'inline'
60
- : Boolean(esb.sourcemap);
93
+ output.sourcemap = esb.sourcemap === 'inline' ? 'inline' : Boolean(esb.sourcemap);
61
94
  }
62
95
  if (esb.banner?.js !== undefined)
63
96
  output.banner = esb.banner.js;
@@ -80,10 +113,12 @@ function legacyEsbuildToRolldown(esb) {
80
113
  out.transform = transform;
81
114
  if (Object.keys(resolve).length > 0)
82
115
  out.resolve = resolve;
83
- // Discarded silently:
116
+ // Discarded (handled elsewhere):
84
117
  // esb.inject — esbuild's array-of-side-effect-files; surfaced at the
85
118
  // CLI layer instead, via input expansion.
86
- // esb.loader — Rolldown infers module types from extensions natively.
119
+ // esb.loader — replaced by top-level `gjsify.loaders` (see ConfigData);
120
+ // migration: `esbuild.loader: { '.png': 'dataurl', '.glsl': 'text' }`
121
+ // → `loaders: { '.png': 'dataurl', '.glsl': 'text' }`.
87
122
  return out;
88
123
  }
89
124
  /**
@@ -108,16 +143,16 @@ export function mergeBundlerOptions(base, overrides) {
108
143
  const { input: _ignoredInput, external: _ignoredExternal, ...overridesRest } = overrides;
109
144
  const out = { ...base, ...overridesRest };
110
145
  if (base.output || overrides.output) {
111
- out.output = { ...(base.output ?? {}), ...(overrides.output ?? {}) };
146
+ out.output = { ...base.output, ...overrides.output };
112
147
  }
113
148
  if (base.transform || overrides.transform) {
114
- out.transform = { ...(base.transform ?? {}), ...(overrides.transform ?? {}) };
149
+ out.transform = { ...base.transform, ...overrides.transform };
115
150
  if (base.transform?.define || overrides.transform?.define) {
116
- out.transform.define = { ...(base.transform?.define ?? {}), ...(overrides.transform?.define ?? {}) };
151
+ out.transform.define = { ...base.transform?.define, ...overrides.transform?.define };
117
152
  }
118
153
  }
119
154
  if (base.resolve || overrides.resolve) {
120
- out.resolve = { ...(base.resolve ?? {}), ...(overrides.resolve ?? {}) };
155
+ out.resolve = { ...base.resolve, ...overrides.resolve };
121
156
  }
122
157
  return out;
123
158
  }
@@ -0,0 +1,63 @@
1
+ export type OxcTool = 'oxlint' | 'oxfmt';
2
+ /**
3
+ * Resolve the absolute path to a tool's Node ESM launcher
4
+ * (`node_modules/<tool>/bin/<tool>`). Walks cwd → workspace-root → parents.
5
+ *
6
+ * Throws {@link OxcNotFoundError} with a clear install hint when not found.
7
+ */
8
+ export declare function findOxcLauncher(tool: OxcTool, cwd?: string): string;
9
+ export declare class OxcNotFoundError extends Error {
10
+ tool: OxcTool;
11
+ cwd: string;
12
+ constructor(tool: OxcTool, cwd: string);
13
+ }
14
+ /**
15
+ * Walk up from a starting directory to find the nearest oxlint config
16
+ * (`.oxlintrc.json`). Returns absolute path or null.
17
+ */
18
+ export declare function findOxlintConfig(cwd?: string): string | null;
19
+ /**
20
+ * Walk up from a starting directory to find the nearest oxfmt config
21
+ * (`.oxfmtrc` / `.oxfmtrc.json`). Returns absolute path or null.
22
+ */
23
+ export declare function findOxfmtConfig(cwd?: string): string | null;
24
+ export interface RunOxcOptions {
25
+ cwd?: string;
26
+ verbose?: boolean;
27
+ }
28
+ /**
29
+ * Spawn an oxc tool (oxlint / oxfmt) via its Node ESM launcher. Inherits
30
+ * stdio so the tool's own output (diagnostics, reformatted files, summary
31
+ * lines) reaches the user.
32
+ *
33
+ * The launcher is run with the current Node executable (`process.execPath`)
34
+ * so the JS plugin host is available — required for oxlint's `jsPlugins`.
35
+ *
36
+ * Returns the exit code; never throws on non-zero exit (callers check it).
37
+ */
38
+ export declare function runOxc(tool: OxcTool, args: string[], opts?: RunOxcOptions): Promise<number>;
39
+ /** Convenience wrappers. */
40
+ export declare function runOxlint(args: string[], opts?: RunOxcOptions): Promise<number>;
41
+ export declare function runOxfmt(args: string[], opts?: RunOxcOptions): Promise<number>;
42
+ /**
43
+ * Lazy-load the embedded `.oxlintrc.json` scaffold template. The
44
+ * static-read-inliner matches this `readFileSync(new URL(<lit>,
45
+ * import.meta.url), 'utf-8')` shape at build time and inlines the file into
46
+ * the GJS bundle, so the template is available without runtime file I/O
47
+ * against the install dir. (Same mechanism the old `loadBiomeTemplate()`
48
+ * relied on — the shape must stay exactly this for the inliner to fire.)
49
+ */
50
+ export declare function loadOxlintTemplate(): string;
51
+ /** Lazy-load the embedded `.oxfmtrc` scaffold template (same inliner shape). */
52
+ export declare function loadOxfmtTemplate(): string;
53
+ /** Helper for callers to surface the install hint to the user cleanly. */
54
+ export declare function printOxcNotFound(err: OxcNotFoundError): void;
55
+ /**
56
+ * Has the given oxc tool's npm package (or its companion) been declared in the
57
+ * project's dependencies? Useful as a cheap pre-flight check — `gjsify flatpak
58
+ * init`'s post-format hook uses this to decide whether to auto-format outputs.
59
+ *
60
+ * Checks for `oxfmt` by default (the formatter), since that is what the
61
+ * post-format hook would invoke.
62
+ */
63
+ export declare function hasOxcDevDep(cwd?: string, tool?: OxcTool): boolean;
@@ -0,0 +1,264 @@
1
+ // oxc (oxlint + oxfmt) resolution + spawn helpers.
2
+ //
3
+ // Replaces the former `biome-resolve.ts`. oxc ships its tools differently
4
+ // from Biome:
5
+ //
6
+ // - The user-facing npm packages `oxlint` and `oxfmt` are thin Node ESM
7
+ // launchers (`bin/oxlint` → `dist/cli.js`, `bin/oxfmt` → `dist/cli.js`).
8
+ // - The actual native code lives in per-platform NAPI binding packages
9
+ // (`@oxlint/binding-<target>`, `@oxfmt/binding-<target>`, e.g.
10
+ // `@oxlint/binding-linux-x64-gnu`), declared as optionalDependencies of
11
+ // the launcher package and loaded via napi-rs's `requireNative()`.
12
+ //
13
+ // Unlike Biome — whose per-platform package was a single self-contained Rust
14
+ // binary we could spawn directly, skipping its Node launcher — oxlint's JS
15
+ // plugin API (used by the internal `oxlint-plugin-gjsify` rule) lives in the
16
+ // JS launcher (`dist/cli.js`). The plugin host loads `.ts`/`.js` plugin files
17
+ // at runtime, so a configured plugin REQUIRES spawning oxlint through its Node
18
+ // launcher rather than calling a bare binary. We therefore resolve and spawn
19
+ // the package launcher with `node` for both tools — uniform and correct for
20
+ // the plugin case.
21
+ //
22
+ // Resolution order (workspace-aware) for each launcher:
23
+ // 1. Project's local node_modules (cwd)
24
+ // 2. Workspace root's node_modules (walk up via findWorkspaceRoot)
25
+ // 3. Parent dirs as a last resort
26
+ // 4. ENOENT → install hint
27
+ //
28
+ // We still reuse Biome's platform/arch/musl detection — it's used to name the
29
+ // expected NAPI binding package in the not-found install hint, so the user
30
+ // knows exactly which optionalDependency npm failed to place.
31
+ import { existsSync, readFileSync } from 'node:fs';
32
+ import { join, resolve } from 'node:path';
33
+ import { spawn } from 'node:child_process';
34
+ import { findWorkspaceRoot } from './workspace-root.js';
35
+ /**
36
+ * Map Node.js `process.platform` + `arch` (+ musl on Linux) to the
37
+ * napi-rs binding-package suffix oxc uses, e.g. `linux-x64-gnu`,
38
+ * `linux-x64-musl`, `darwin-arm64`, `win32-x64-msvc`.
39
+ *
40
+ * Mirrors biome-resolve's platform detection (same musl probe), adapted to
41
+ * oxc's napi-rs target naming (gnu/musl libc suffix on Linux, -msvc on
42
+ * Windows).
43
+ */
44
+ function oxcBindingSuffix() {
45
+ const platform = process.platform;
46
+ const arch = process.arch;
47
+ let plat;
48
+ if (platform === 'linux')
49
+ plat = 'linux';
50
+ else if (platform === 'darwin')
51
+ plat = 'darwin';
52
+ else if (platform === 'win32')
53
+ plat = 'win32';
54
+ else
55
+ throw new Error(`[gjsify oxc] Unsupported platform: ${platform}`);
56
+ let a;
57
+ if (arch === 'x64')
58
+ a = 'x64';
59
+ else if (arch === 'arm64')
60
+ a = 'arm64';
61
+ else
62
+ throw new Error(`[gjsify oxc] Unsupported arch on ${plat}: ${arch}`);
63
+ if (plat === 'linux') {
64
+ // musl detection on Linux — oxc ships separate musl/gnu bindings.
65
+ // Same approach as biome's launcher: probe for the musl loader.
66
+ // glibc systems have `/lib/ld-linux-*`, musl has `/lib/ld-musl-*`.
67
+ let libc = 'gnu';
68
+ try {
69
+ const { readdirSync } = require('node:fs');
70
+ const libEntries = readdirSync('/lib');
71
+ if (libEntries.some((e) => e.startsWith('ld-musl-'))) {
72
+ libc = 'musl';
73
+ }
74
+ }
75
+ catch {
76
+ // /lib unreadable — fall through, glibc is the safer default
77
+ }
78
+ return `${plat}-${a}-${libc}`;
79
+ }
80
+ if (plat === 'win32')
81
+ return `${plat}-${a}-msvc`;
82
+ // darwin
83
+ return `${plat}-${a}`;
84
+ }
85
+ /** NAPI binding scope per tool: `@oxlint/binding-*` / `@oxfmt/binding-*`. */
86
+ function bindingPackageName(tool) {
87
+ const scope = tool === 'oxlint' ? '@oxlint' : '@oxfmt';
88
+ return `${scope}/binding-${oxcBindingSuffix()}`;
89
+ }
90
+ /**
91
+ * Search a starting directory's `node_modules/<pkg>/<relPath>` and return the
92
+ * absolute path if it exists, else null.
93
+ */
94
+ function probeNodeModules(dir, pkg, relPath) {
95
+ const candidate = join(dir, 'node_modules', pkg, relPath);
96
+ return existsSync(candidate) ? candidate : null;
97
+ }
98
+ /**
99
+ * Resolve the absolute path to a tool's Node ESM launcher
100
+ * (`node_modules/<tool>/bin/<tool>`). Walks cwd → workspace-root → parents.
101
+ *
102
+ * Throws {@link OxcNotFoundError} with a clear install hint when not found.
103
+ */
104
+ export function findOxcLauncher(tool, cwd = process.cwd()) {
105
+ const relPath = join('bin', tool);
106
+ // 1. Local node_modules
107
+ const local = probeNodeModules(cwd, tool, relPath);
108
+ if (local)
109
+ return local;
110
+ // 2. Walk up to workspace root, probe its node_modules
111
+ const wsRoot = findWorkspaceRoot(cwd);
112
+ if (wsRoot && wsRoot !== cwd) {
113
+ const fromRoot = probeNodeModules(wsRoot, tool, relPath);
114
+ if (fromRoot)
115
+ return fromRoot;
116
+ }
117
+ // 3. Walk parent dirs as a last resort (nested-without-workspace setups)
118
+ let dir = resolve(cwd, '..');
119
+ for (let i = 0; i < 6; i++) {
120
+ const found = probeNodeModules(dir, tool, relPath);
121
+ if (found)
122
+ return found;
123
+ const parent = resolve(dir, '..');
124
+ if (parent === dir)
125
+ break;
126
+ dir = parent;
127
+ }
128
+ throw new OxcNotFoundError(tool, cwd);
129
+ }
130
+ export class OxcNotFoundError extends Error {
131
+ tool;
132
+ cwd;
133
+ constructor(tool, cwd) {
134
+ const binding = (() => {
135
+ try {
136
+ return bindingPackageName(tool);
137
+ }
138
+ catch {
139
+ return `@${tool === 'oxlint' ? 'oxlint' : 'oxfmt'}/binding-<platform>`;
140
+ }
141
+ })();
142
+ super(`[gjsify oxc] ${tool} not found.\n` +
143
+ ` Expected: ${tool}/bin/${tool} in node_modules of ${cwd} or any workspace root above it.\n` +
144
+ ` Install it via: gjsify install -D ${tool}\n` +
145
+ ` (this adds ${tool} to devDependencies; the matching ${binding} ` +
146
+ `napi binding lands automatically as an optionalDependency.)`);
147
+ this.tool = tool;
148
+ this.cwd = cwd;
149
+ this.name = 'OxcNotFoundError';
150
+ }
151
+ }
152
+ /**
153
+ * Walk up from a starting directory to find the nearest oxlint config
154
+ * (`.oxlintrc.json`). Returns absolute path or null.
155
+ */
156
+ export function findOxlintConfig(cwd = process.cwd()) {
157
+ return findConfigFile(cwd, ['.oxlintrc.json']);
158
+ }
159
+ /**
160
+ * Walk up from a starting directory to find the nearest oxfmt config
161
+ * (`.oxfmtrc` / `.oxfmtrc.json`). Returns absolute path or null.
162
+ */
163
+ export function findOxfmtConfig(cwd = process.cwd()) {
164
+ return findConfigFile(cwd, ['.oxfmtrc', '.oxfmtrc.json']);
165
+ }
166
+ function findConfigFile(cwd, names) {
167
+ let dir = cwd;
168
+ for (let i = 0; i < 12; i++) {
169
+ for (const name of names) {
170
+ const path = join(dir, name);
171
+ if (existsSync(path))
172
+ return path;
173
+ }
174
+ const parent = resolve(dir, '..');
175
+ if (parent === dir)
176
+ break;
177
+ dir = parent;
178
+ }
179
+ return null;
180
+ }
181
+ /**
182
+ * Spawn an oxc tool (oxlint / oxfmt) via its Node ESM launcher. Inherits
183
+ * stdio so the tool's own output (diagnostics, reformatted files, summary
184
+ * lines) reaches the user.
185
+ *
186
+ * The launcher is run with the current Node executable (`process.execPath`)
187
+ * so the JS plugin host is available — required for oxlint's `jsPlugins`.
188
+ *
189
+ * Returns the exit code; never throws on non-zero exit (callers check it).
190
+ */
191
+ export function runOxc(tool, args, opts = {}) {
192
+ const cwd = opts.cwd ?? process.cwd();
193
+ const launcher = findOxcLauncher(tool, cwd);
194
+ const node = process.execPath || 'node';
195
+ if (opts.verbose) {
196
+ console.log(`[gjsify oxc] ${node} ${launcher} ${args.join(' ')}`);
197
+ }
198
+ return new Promise((res, rej) => {
199
+ const spawnOpts = { stdio: 'inherit', cwd };
200
+ const child = spawn(node, [launcher, ...args], spawnOpts);
201
+ child.on('error', (err) => {
202
+ if (err.code === 'ENOENT') {
203
+ rej(new OxcNotFoundError(tool, cwd));
204
+ }
205
+ else {
206
+ rej(err);
207
+ }
208
+ });
209
+ child.on('exit', (code, signal) => {
210
+ if (signal) {
211
+ console.error(`[gjsify oxc] ${tool} terminated by signal ${signal}`);
212
+ res(1);
213
+ return;
214
+ }
215
+ res(code ?? 0);
216
+ });
217
+ });
218
+ }
219
+ /** Convenience wrappers. */
220
+ export function runOxlint(args, opts = {}) {
221
+ return runOxc('oxlint', args, opts);
222
+ }
223
+ export function runOxfmt(args, opts = {}) {
224
+ return runOxc('oxfmt', args, opts);
225
+ }
226
+ /**
227
+ * Lazy-load the embedded `.oxlintrc.json` scaffold template. The
228
+ * static-read-inliner matches this `readFileSync(new URL(<lit>,
229
+ * import.meta.url), 'utf-8')` shape at build time and inlines the file into
230
+ * the GJS bundle, so the template is available without runtime file I/O
231
+ * against the install dir. (Same mechanism the old `loadBiomeTemplate()`
232
+ * relied on — the shape must stay exactly this for the inliner to fire.)
233
+ */
234
+ export function loadOxlintTemplate() {
235
+ return readFileSync(new URL('../templates/oxlintrc.json.tmpl', import.meta.url), 'utf-8');
236
+ }
237
+ /** Lazy-load the embedded `.oxfmtrc` scaffold template (same inliner shape). */
238
+ export function loadOxfmtTemplate() {
239
+ return readFileSync(new URL('../templates/oxfmtrc.tmpl', import.meta.url), 'utf-8');
240
+ }
241
+ /** Helper for callers to surface the install hint to the user cleanly. */
242
+ export function printOxcNotFound(err) {
243
+ console.error(err.message);
244
+ }
245
+ /**
246
+ * Has the given oxc tool's npm package (or its companion) been declared in the
247
+ * project's dependencies? Useful as a cheap pre-flight check — `gjsify flatpak
248
+ * init`'s post-format hook uses this to decide whether to auto-format outputs.
249
+ *
250
+ * Checks for `oxfmt` by default (the formatter), since that is what the
251
+ * post-format hook would invoke.
252
+ */
253
+ export function hasOxcDevDep(cwd = process.cwd(), tool = 'oxfmt') {
254
+ const pkgPath = join(cwd, 'package.json');
255
+ if (!existsSync(pkgPath))
256
+ return false;
257
+ try {
258
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
259
+ return Boolean(pkg?.devDependencies?.[tool] || pkg?.dependencies?.[tool]);
260
+ }
261
+ catch {
262
+ return false;
263
+ }
264
+ }
@@ -93,12 +93,7 @@ export function defaultRangeFromVersion(version) {
93
93
  }
94
94
  function sortKnownDepFields(pkg) {
95
95
  const out = { ...pkg };
96
- for (const kind of [
97
- 'dependencies',
98
- 'devDependencies',
99
- 'peerDependencies',
100
- 'optionalDependencies',
101
- ]) {
96
+ for (const kind of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
102
97
  const block = out[kind];
103
98
  if (!block)
104
99
  continue;
@@ -30,10 +30,7 @@ export function computeNativeEnvForBundle(bundlePath, cwd = process.cwd()) {
30
30
  const cwdPackages = detectNativePackages(cwd);
31
31
  const bundlePackages = detectNativePackages(dirname(resolvedBundle));
32
32
  const seen = new Set(cwdPackages.map((p) => p.name));
33
- const nativePackages = [
34
- ...cwdPackages,
35
- ...bundlePackages.filter((p) => !seen.has(p.name)),
36
- ];
33
+ const nativePackages = [...cwdPackages, ...bundlePackages.filter((p) => !seen.has(p.name))];
37
34
  const env = buildNativeEnv(nativePackages);
38
35
  const envPrefix = Object.entries(env)
39
36
  .filter(([, value]) => value !== undefined && value !== '')
@@ -42,9 +42,7 @@ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
42
42
  // tools like biome / esbuild / tsc lose ANSI color in piped contexts
43
43
  // (CI logs, redirected output) because `process.stdout.isTTY` is
44
44
  // false for the spawned child.
45
- const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined
46
- ? {}
47
- : { FORCE_COLOR: '1' };
45
+ const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined ? {} : { FORCE_COLOR: '1' };
48
46
  const env = {
49
47
  ...process.env,
50
48
  ...colorEnv,
@@ -52,7 +50,7 @@ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
52
50
  npm_lifecycle_event: name,
53
51
  npm_package_name: pkg.name ?? '',
54
52
  npm_package_version: pkg.version ?? '',
55
- ...(opts.env ?? {}),
53
+ ...opts.env,
56
54
  };
57
55
  // `'inherit-stderr'` is our extension on top of node's stdio modes —
58
56
  // child stdin inherits, child stdout → parent's stderr (fd 2), child
@@ -60,9 +58,7 @@ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
60
58
  // prepack's log lines don't get interleaved with the JSON we emit on
61
59
  // parent stdout. `spawn`'s `stdio` accepts numeric fds in array form
62
60
  // and routes the child's matching stream to that fd.
63
- const stdioConfig = opts.stdio === 'inherit-stderr'
64
- ? ['inherit', 2, 2]
65
- : (opts.stdio ?? 'inherit');
61
+ const stdioConfig = opts.stdio === 'inherit-stderr' ? ['inherit', 2, 2] : (opts.stdio ?? 'inherit');
66
62
  await new Promise((resolveOk, reject) => {
67
63
  const child = spawn(literal, [], {
68
64
  cwd: wsDir,
@@ -34,7 +34,9 @@ export function findWorkspaceRoot(start) {
34
34
  if (dir === start || ws.some((w) => w.location === start))
35
35
  return dir;
36
36
  }
37
- catch { /* not a usable workspace root */ }
37
+ catch {
38
+ /* not a usable workspace root */
39
+ }
38
40
  }
39
41
  }
40
42
  const parent = resolve(dir, '..');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.4.27",
3
+ "version": "0.4.29",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -55,7 +55,7 @@
55
55
  "item": "`gjsify dlx <pkg>` — fetch and run any npm package's GJS bundle without persisting it"
56
56
  },
57
57
  {
58
- "item": "`gjsify lint` / `gjsify format` — Biome-powered linter + formatter"
58
+ "item": "`gjsify lint` / `gjsify format` — oxc-powered linter (oxlint) + formatter (oxfmt)"
59
59
  },
60
60
  {
61
61
  "item": "`gjsify flatpak {init,build,check,deps,ci}` — pack a GJS app or CLI into a Flatpak end-to-end"
@@ -106,7 +106,7 @@
106
106
  "clear": "rm -rf lib dist tsconfig.tsbuildinfo || exit 0",
107
107
  "check": "tsc --noEmit",
108
108
  "start": "node lib/index.js",
109
- "build": "tsc && mkdir -p lib/templates/flatpak && cp -L src/templates/install.mjs.tmpl lib/templates/install.mjs.tmpl && cp src/templates/flatpak/*.tmpl lib/templates/flatpak/ && cp src/templates/biome.json.tmpl lib/templates/biome.json.tmpl && gjsify run chmod",
109
+ "build": "tsc && mkdir -p lib/templates/flatpak && cp -L src/templates/install.mjs.tmpl lib/templates/install.mjs.tmpl && cp src/templates/flatpak/*.tmpl lib/templates/flatpak/ && cp src/templates/oxlintrc.json.tmpl lib/templates/oxlintrc.json.tmpl && cp src/templates/oxfmtrc.tmpl lib/templates/oxfmtrc.tmpl && gjsify run chmod",
110
110
  "build:gjs-bundle": "node lib/index.js build src/index.ts --app gjs --outfile dist/cli.gjs.mjs --shebang",
111
111
  "chmod": "chmod +x ./lib/index.js",
112
112
  "build:test:node": "node lib/index.js build src/test.mts --app node --outfile dist/test.node.mjs",
@@ -120,18 +120,18 @@
120
120
  "cli"
121
121
  ],
122
122
  "dependencies": {
123
- "@gjsify/buffer": "^0.4.27",
124
- "@gjsify/create-app": "^0.4.27",
125
- "@gjsify/node-globals": "^0.4.27",
126
- "@gjsify/node-polyfills": "^0.4.27",
127
- "@gjsify/npm-registry": "^0.4.27",
128
- "@gjsify/resolve-npm": "^0.4.27",
129
- "@gjsify/rolldown-plugin-gjsify": "^0.4.27",
130
- "@gjsify/rolldown-plugin-pnp": "^0.4.27",
131
- "@gjsify/semver": "^0.4.27",
132
- "@gjsify/tar": "^0.4.27",
133
- "@gjsify/web-polyfills": "^0.4.27",
134
- "@gjsify/workspace": "^0.4.27",
123
+ "@gjsify/buffer": "^0.4.29",
124
+ "@gjsify/create-app": "^0.4.29",
125
+ "@gjsify/node-globals": "^0.4.29",
126
+ "@gjsify/node-polyfills": "^0.4.29",
127
+ "@gjsify/npm-registry": "^0.4.29",
128
+ "@gjsify/resolve-npm": "^0.4.29",
129
+ "@gjsify/rolldown-plugin-gjsify": "^0.4.29",
130
+ "@gjsify/rolldown-plugin-pnp": "^0.4.29",
131
+ "@gjsify/semver": "^0.4.29",
132
+ "@gjsify/tar": "^0.4.29",
133
+ "@gjsify/web-polyfills": "^0.4.29",
134
+ "@gjsify/workspace": "^0.4.29",
135
135
  "cosmiconfig": "^9.0.1",
136
136
  "get-tsconfig": "^4.14.0",
137
137
  "pkg-types": "^2.3.1",
@@ -139,12 +139,12 @@
139
139
  "yargs": "^18.0.0"
140
140
  },
141
141
  "devDependencies": {
142
- "@gjsify/unit": "^0.4.27",
142
+ "@gjsify/unit": "^0.4.29",
143
143
  "@types/yargs": "^17.0.35",
144
144
  "typescript": "^6.0.3"
145
145
  },
146
146
  "peerDependencies": {
147
- "@gjsify/rolldown-native": "^0.4.27"
147
+ "@gjsify/rolldown-native": "^0.4.29"
148
148
  },
149
149
  "peerDependenciesMeta": {
150
150
  "@gjsify/rolldown-native": {
@@ -1,79 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/2.4.13/schema.json",
3
- "formatter": {
4
- "enabled": true,
5
- "indentStyle": "space",
6
- "indentWidth": 4,
7
- "lineWidth": 120,
8
- "lineEnding": "lf"
9
- },
10
- "linter": {
11
- "enabled": true,
12
- "rules": {
13
- "recommended": true,
14
- "style": {
15
- "useImportType": "warn",
16
- "useNodejsImportProtocol": "error",
17
- "noNonNullAssertion": "off"
18
- },
19
- "suspicious": {
20
- "noExplicitAny": "warn",
21
- "noConsole": "off"
22
- },
23
- "correctness": {
24
- "noUnusedImports": "warn"
25
- }
26
- }
27
- },
28
- "javascript": {
29
- "formatter": {
30
- "quoteStyle": "single",
31
- "jsxQuoteStyle": "double",
32
- "semicolons": "always",
33
- "trailingCommas": "all",
34
- "arrowParentheses": "always",
35
- "bracketSpacing": true,
36
- "bracketSameLine": false
37
- }
38
- },
39
- "json": {
40
- "formatter": {
41
- "indentWidth": 2,
42
- "trailingCommas": "none",
43
- "lineWidth": 120
44
- }
45
- },
46
- "css": {
47
- "formatter": {
48
- "indentWidth": 2,
49
- "quoteStyle": "single"
50
- }
51
- },
52
- "files": {
53
- "includes": [
54
- "**",
55
- "!**/node_modules",
56
- "!**/dist",
57
- "!**/lib",
58
- "!**/build",
59
- "!**/build-dir",
60
- "!**/builddir",
61
- "!**/flatpak-build",
62
- "!**/.flatpak-builder",
63
- "!**/repo",
64
- "!**/coverage",
65
- "!**/refs",
66
- "!**/@types",
67
- "!**/templates",
68
- "!**/prebuilds",
69
- "!**/.yarn/cache",
70
- "!**/.yarn/install-state.gz",
71
- "!**/cli.gjs.mjs",
72
- "!**/test.gjs.mjs",
73
- "!**/test.node.mjs",
74
- "!**/*.gresource",
75
- "!**/*.compiled",
76
- "!**/*.metainfo.xml"
77
- ]
78
- }
79
- }