@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.
- package/dist/cli.gjs.mjs +34 -32
- package/lib/actions/barrels-generate.js +1 -5
- package/lib/actions/build.d.ts +3 -3
- package/lib/actions/build.js +56 -64
- package/lib/bundler-pick.d.ts +3 -3
- package/lib/bundler-pick.js +5 -6
- package/lib/commands/build.js +37 -31
- package/lib/commands/check.js +3 -3
- package/lib/commands/fix.js +33 -23
- package/lib/commands/flatpak/build.js +6 -2
- package/lib/commands/flatpak/check.js +9 -3
- package/lib/commands/flatpak/ci.js +1 -2
- package/lib/commands/flatpak/deps.js +1 -2
- package/lib/commands/flatpak/diff.js +2 -6
- package/lib/commands/flatpak/init.js +19 -19
- package/lib/commands/flatpak/release.js +2 -2
- package/lib/commands/flatpak/scaffold.js +3 -11
- package/lib/commands/flatpak/sync-flathub.js +4 -8
- package/lib/commands/flatpak/utils.js +1 -6
- package/lib/commands/foreach.js +5 -14
- package/lib/commands/format.js +54 -41
- package/lib/commands/gettext.js +2 -10
- package/lib/commands/gresource.js +2 -8
- package/lib/commands/gsettings.js +1 -3
- package/lib/commands/install.js +13 -6
- package/lib/commands/lint.d.ts +1 -1
- package/lib/commands/lint.js +22 -22
- package/lib/commands/pack.js +29 -17
- package/lib/commands/publish.d.ts +1 -0
- package/lib/commands/publish.js +113 -21
- package/lib/commands/run.js +2 -6
- package/lib/commands/self-update.js +36 -8
- package/lib/commands/showcase.js +1 -1
- package/lib/commands/system-check.js +8 -11
- package/lib/commands/test.js +12 -8
- package/lib/commands/uninstall.js +1 -3
- package/lib/commands/upgrade.d.ts +1 -1
- package/lib/commands/upgrade.js +109 -120
- package/lib/commands/workspace.js +1 -3
- package/lib/config.js +18 -13
- package/lib/index.js +21 -0
- package/lib/templates/install.mjs.tmpl +20 -14
- package/lib/templates/oxfmtrc.tmpl +54 -0
- package/lib/templates/oxlintrc.json.tmpl +35 -0
- package/lib/types/config-data.d.ts +23 -13
- package/lib/utils/check-system-deps.js +10 -4
- package/lib/utils/detect-native-packages.js +1 -1
- package/lib/utils/dlx-cache.js +2 -7
- package/lib/utils/install-backend-native.d.ts +2 -2
- package/lib/utils/install-backend-native.js +72 -63
- package/lib/utils/install-backend.d.ts +13 -0
- package/lib/utils/install-backend.js +2 -1
- package/lib/utils/install-global.js +1 -3
- package/lib/utils/normalize-bundler-options.js +52 -17
- package/lib/utils/oxc-resolve.d.ts +63 -0
- package/lib/utils/oxc-resolve.js +264 -0
- package/lib/utils/pkg-json-edit.js +1 -6
- package/lib/utils/run-gjs.js +1 -4
- package/lib/utils/run-lifecycle-script.js +3 -7
- package/lib/utils/workspace-root.js +3 -1
- package/package.json +17 -17
- package/lib/templates/biome.json.tmpl +0 -79
- package/lib/utils/biome-resolve.d.ts +0 -47
- 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
|
-
//
|
|
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
|
|
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 = { ...
|
|
61
|
+
out.output = { ...fromEsbuild.output, ...fromBundler.output };
|
|
26
62
|
}
|
|
27
63
|
if (fromEsbuild.transform || fromBundler.transform) {
|
|
28
|
-
out.transform = { ...
|
|
64
|
+
out.transform = { ...fromEsbuild.transform, ...fromBundler.transform };
|
|
29
65
|
if (fromEsbuild.transform?.define || fromBundler.transform?.define) {
|
|
30
66
|
out.transform.define = {
|
|
31
|
-
...
|
|
32
|
-
...
|
|
67
|
+
...fromEsbuild.transform?.define,
|
|
68
|
+
...fromBundler.transform?.define,
|
|
33
69
|
};
|
|
34
70
|
}
|
|
35
71
|
}
|
|
36
72
|
if (fromEsbuild.resolve || fromBundler.resolve) {
|
|
37
|
-
out.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
|
|
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 —
|
|
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 = { ...
|
|
146
|
+
out.output = { ...base.output, ...overrides.output };
|
|
112
147
|
}
|
|
113
148
|
if (base.transform || overrides.transform) {
|
|
114
|
-
out.transform = { ...
|
|
149
|
+
out.transform = { ...base.transform, ...overrides.transform };
|
|
115
150
|
if (base.transform?.define || overrides.transform?.define) {
|
|
116
|
-
out.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 = { ...
|
|
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;
|
package/lib/utils/run-gjs.js
CHANGED
|
@@ -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
|
-
...
|
|
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 {
|
|
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.
|
|
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` —
|
|
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/
|
|
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.
|
|
124
|
-
"@gjsify/create-app": "^0.4.
|
|
125
|
-
"@gjsify/node-globals": "^0.4.
|
|
126
|
-
"@gjsify/node-polyfills": "^0.4.
|
|
127
|
-
"@gjsify/npm-registry": "^0.4.
|
|
128
|
-
"@gjsify/resolve-npm": "^0.4.
|
|
129
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.4.
|
|
130
|
-
"@gjsify/rolldown-plugin-pnp": "^0.4.
|
|
131
|
-
"@gjsify/semver": "^0.4.
|
|
132
|
-
"@gjsify/tar": "^0.4.
|
|
133
|
-
"@gjsify/web-polyfills": "^0.4.
|
|
134
|
-
"@gjsify/workspace": "^0.4.
|
|
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.
|
|
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.
|
|
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
|
-
}
|