@gjsify/cli 0.4.13 → 0.4.15
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 +86 -78
- package/lib/actions/barrels-generate.d.ts +31 -0
- package/lib/actions/barrels-generate.js +78 -0
- package/lib/actions/build.d.ts +11 -1
- package/lib/actions/build.js +79 -3
- package/lib/bundler-pick.d.ts +7 -0
- package/lib/bundler-pick.js +17 -0
- package/lib/commands/barrels.d.ts +15 -0
- package/lib/commands/barrels.js +103 -0
- package/lib/commands/build.js +8 -0
- package/lib/commands/fix.d.ts +9 -0
- package/lib/commands/fix.js +60 -0
- package/lib/commands/flatpak/diff.d.ts +12 -0
- package/lib/commands/flatpak/diff.js +165 -0
- package/lib/commands/flatpak/index.d.ts +4 -1
- package/lib/commands/flatpak/index.js +11 -5
- package/lib/commands/flatpak/init.d.ts +1 -0
- package/lib/commands/flatpak/init.js +39 -5
- package/lib/commands/flatpak/release.d.ts +13 -0
- package/lib/commands/flatpak/release.js +152 -0
- package/lib/commands/flatpak/sync-flathub.d.ts +26 -0
- package/lib/commands/flatpak/sync-flathub.js +311 -0
- package/lib/commands/format.d.ts +12 -0
- package/lib/commands/format.js +98 -0
- package/lib/commands/index.d.ts +6 -0
- package/lib/commands/index.js +6 -0
- package/lib/commands/install.js +11 -9
- package/lib/commands/lint.d.ts +9 -0
- package/lib/commands/lint.js +60 -0
- package/lib/commands/test.d.ts +12 -0
- package/lib/commands/test.js +206 -0
- package/lib/commands/upgrade.d.ts +13 -0
- package/lib/commands/upgrade.js +402 -0
- package/lib/index.js +7 -1
- package/lib/templates/biome.json.tmpl +79 -0
- package/lib/types/cli-build-options.d.ts +7 -0
- package/lib/types/config-data.d.ts +39 -0
- package/lib/utils/biome-resolve.d.ts +47 -0
- package/lib/utils/biome-resolve.js +204 -0
- package/package.json +16 -16
- package/showcases.json +14 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type BarrelExtension = 'js' | 'ts' | 'none';
|
|
2
|
+
export interface BarrelsArgs {
|
|
3
|
+
/** Directories to scan. Each gets an `index.ts` re-exporting its sibling source files. */
|
|
4
|
+
paths: string[];
|
|
5
|
+
/** Import-specifier extension. `js`/`ts`/`none`. */
|
|
6
|
+
extension: BarrelExtension;
|
|
7
|
+
/** Resolve `paths` against this directory. Default: `process.cwd()`. */
|
|
8
|
+
baseDir: string;
|
|
9
|
+
/** Skip files whose names match any regex. */
|
|
10
|
+
exclude: RegExp[];
|
|
11
|
+
/** Header comment prepended to every generated file. */
|
|
12
|
+
header: string;
|
|
13
|
+
/** Omit trailing `;` on each export line. */
|
|
14
|
+
noSemicolon: boolean;
|
|
15
|
+
/** Use `'` quotes (default). When false, uses `"`. */
|
|
16
|
+
singleQuotes: boolean;
|
|
17
|
+
/** Report drift without writing. Returns drift count from generator. */
|
|
18
|
+
check: boolean;
|
|
19
|
+
/** Log each file scanned + written. */
|
|
20
|
+
verbose: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare const DEFAULT_BARRELS_HEADER = "// Auto-generated by `gjsify barrels` \u2014 do not edit by hand.";
|
|
23
|
+
export declare const DEFAULT_BARRELS_EXCLUDES: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Regenerate `index.ts` in every directory in `args.paths`.
|
|
26
|
+
*
|
|
27
|
+
* Returns the number of files that drifted from the canonical output —
|
|
28
|
+
* always 0 when `check` is false (drift is rewritten in-place); non-zero
|
|
29
|
+
* under `check: true` signals CI failure.
|
|
30
|
+
*/
|
|
31
|
+
export declare function generateBarrels(args: BarrelsArgs): Promise<number>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// `gjsify barrels` action — regenerate `index.ts` barrel files for one or
|
|
2
|
+
// more directories. Pure async generator, decoupled from yargs so it can
|
|
3
|
+
// be unit-tested in isolation.
|
|
4
|
+
//
|
|
5
|
+
// Adapted from beabee-communityrm/monorepo apps/dev-cli's `generate-index`
|
|
6
|
+
// action (refs/dev-cli — not vendored here): same separation of action vs.
|
|
7
|
+
// command module, same `types/`-dir auto-detection that emits
|
|
8
|
+
// `export type *`, same `export {};` fallback for empty directories,
|
|
9
|
+
// same sorted-output principle. Rewritten for gjsify's yargs CommandModule
|
|
10
|
+
// and `@gjsify/unit` test framework; no external deps. Replaces
|
|
11
|
+
// `barrelsby` (unmaintained since 2022) at consumer sites.
|
|
12
|
+
//
|
|
13
|
+
// Original: Copyright (c) Beabee Community Repo contributors. AGPL-3.0.
|
|
14
|
+
// Reimplemented for gjsify under the project's MIT license.
|
|
15
|
+
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { basename, extname, join, resolve } from 'node:path';
|
|
17
|
+
export const DEFAULT_BARRELS_HEADER = '// Auto-generated by `gjsify barrels` — do not edit by hand.';
|
|
18
|
+
export const DEFAULT_BARRELS_EXCLUDES = [
|
|
19
|
+
'\\.test\\.',
|
|
20
|
+
'\\.spec\\.',
|
|
21
|
+
'\\.test-data\\.',
|
|
22
|
+
];
|
|
23
|
+
const SOURCE_FILE_RE = /\.(ts|tsx|mts|cts)$/;
|
|
24
|
+
/**
|
|
25
|
+
* Regenerate `index.ts` in every directory in `args.paths`.
|
|
26
|
+
*
|
|
27
|
+
* Returns the number of files that drifted from the canonical output —
|
|
28
|
+
* always 0 when `check` is false (drift is rewritten in-place); non-zero
|
|
29
|
+
* under `check: true` signals CI failure.
|
|
30
|
+
*/
|
|
31
|
+
export async function generateBarrels(args) {
|
|
32
|
+
const quote = args.singleQuotes ? "'" : '"';
|
|
33
|
+
const semicolon = args.noSemicolon ? '' : ';';
|
|
34
|
+
const extSuffix = args.extension === 'none' ? '' : `.${args.extension}`;
|
|
35
|
+
let drift = 0;
|
|
36
|
+
for (const p of args.paths) {
|
|
37
|
+
const dir = resolve(args.baseDir, p);
|
|
38
|
+
const isTypesDir = basename(dir) === 'types';
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = (await readdir(dir, { withFileTypes: true }))
|
|
42
|
+
.filter((e) => e.isFile())
|
|
43
|
+
.filter((e) => SOURCE_FILE_RE.test(e.name))
|
|
44
|
+
.filter((e) => e.name !== 'index.ts')
|
|
45
|
+
.filter((e) => !args.exclude.some((r) => r.test(e.name)))
|
|
46
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (args.verbose)
|
|
50
|
+
console.error(`[gjsify barrels] skip ${dir}: ${err.message}`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const lines = entries.map((e) => {
|
|
54
|
+
const stem = basename(e.name, extname(e.name));
|
|
55
|
+
const keyword = isTypesDir ? 'export type *' : 'export *';
|
|
56
|
+
return `${keyword} from ${quote}./${stem}${extSuffix}${quote}${semicolon}`;
|
|
57
|
+
});
|
|
58
|
+
const body = lines.length ? `${lines.join('\n')}\n` : 'export {};\n';
|
|
59
|
+
const out = `${args.header}\n\n${body}`;
|
|
60
|
+
const indexPath = join(dir, 'index.ts');
|
|
61
|
+
const previous = await readFile(indexPath, 'utf-8').catch(() => '');
|
|
62
|
+
if (previous === out) {
|
|
63
|
+
if (args.verbose)
|
|
64
|
+
console.log(`[gjsify barrels] up-to-date: ${indexPath}`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (args.check) {
|
|
68
|
+
console.error(`[gjsify barrels] drift: ${indexPath}`);
|
|
69
|
+
drift++;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
await writeFile(indexPath, out, 'utf-8');
|
|
73
|
+
if (args.verbose)
|
|
74
|
+
console.log(`[gjsify barrels] wrote: ${indexPath}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return drift;
|
|
78
|
+
}
|
package/lib/actions/build.d.ts
CHANGED
|
@@ -35,9 +35,19 @@ export declare class BuildAction {
|
|
|
35
35
|
*/
|
|
36
36
|
private applyShebang;
|
|
37
37
|
/** Application mode */
|
|
38
|
-
buildApp(app?: App
|
|
38
|
+
buildApp(app?: App, opts?: {
|
|
39
|
+
watch?: boolean;
|
|
40
|
+
}): Promise<RolldownOutput[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Drive `rolldown.watch(...)`: rebuild on source change, apply the
|
|
43
|
+
* post-bundle shebang hook on each successful build, surface errors
|
|
44
|
+
* without exiting, clean up on SIGINT/SIGTERM. Resolves only when the
|
|
45
|
+
* watcher closes — keeps the CLI process alive across rebuilds.
|
|
46
|
+
*/
|
|
47
|
+
private runWatchLoop;
|
|
39
48
|
start(buildType?: {
|
|
40
49
|
library?: boolean;
|
|
41
50
|
app?: App;
|
|
51
|
+
watch?: boolean;
|
|
42
52
|
}): Promise<RolldownOutput[]>;
|
|
43
53
|
}
|
package/lib/actions/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runBundle, bundleToChunks } from "../bundler-pick.js";
|
|
1
|
+
import { runBundle, runWatch, bundleToChunks } from "../bundler-pick.js";
|
|
2
2
|
import { gjsifyPlugin, textLoaderPlugin, resolveShebangLine } from "@gjsify/rolldown-plugin-gjsify";
|
|
3
3
|
import { resolveUserPlugins } from "../utils/resolve-plugin-by-name.js";
|
|
4
4
|
import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals, } from "@gjsify/rolldown-plugin-gjsify/globals";
|
|
@@ -178,7 +178,7 @@ export class BuildAction {
|
|
|
178
178
|
console.debug(`[gjsify] --shebang: wrote ${line} + chmod 0o755 to ${outfile}`);
|
|
179
179
|
}
|
|
180
180
|
/** Application mode */
|
|
181
|
-
async buildApp(app = "gjs") {
|
|
181
|
+
async buildApp(app = "gjs", opts = {}) {
|
|
182
182
|
const { verbose, typescript, exclude, library: pkg, aliases, excludeGlobals, } = this.configData;
|
|
183
183
|
const userBundler = normalizeBundlerOptions(this.configData);
|
|
184
184
|
const formatRaw = userBundler.output?.format ??
|
|
@@ -281,17 +281,93 @@ export class BuildAction {
|
|
|
281
281
|
// Rolldown doesn't understand) would crash the build.
|
|
282
282
|
plugins: [...pnpPlugins, ...userPlugins, ...cfg.plugins],
|
|
283
283
|
};
|
|
284
|
+
if (opts.watch) {
|
|
285
|
+
await this.runWatchLoop(finalOpts, app, outfile, verbose);
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
284
288
|
const writeResult = await runBundle(finalOpts);
|
|
285
289
|
if (app === "gjs" && this.configData.shebang) {
|
|
286
290
|
await this.applyShebang(outfile, verbose);
|
|
287
291
|
}
|
|
288
292
|
return [writeResult];
|
|
289
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Drive `rolldown.watch(...)`: rebuild on source change, apply the
|
|
296
|
+
* post-bundle shebang hook on each successful build, surface errors
|
|
297
|
+
* without exiting, clean up on SIGINT/SIGTERM. Resolves only when the
|
|
298
|
+
* watcher closes — keeps the CLI process alive across rebuilds.
|
|
299
|
+
*/
|
|
300
|
+
async runWatchLoop(finalOpts, app, outfile, verbose) {
|
|
301
|
+
const watcher = await runWatch(finalOpts);
|
|
302
|
+
const closed = new Promise((resolve) => {
|
|
303
|
+
watcher.on("close", () => resolve());
|
|
304
|
+
});
|
|
305
|
+
let closing = false;
|
|
306
|
+
const shutdown = async () => {
|
|
307
|
+
if (closing)
|
|
308
|
+
return;
|
|
309
|
+
closing = true;
|
|
310
|
+
console.log("\n[gjsify build --watch] stopping watcher…");
|
|
311
|
+
try {
|
|
312
|
+
await watcher.close();
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
console.error("[gjsify build --watch] watcher close error:", err);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
process.on("SIGINT", shutdown);
|
|
319
|
+
process.on("SIGTERM", shutdown);
|
|
320
|
+
watcher.on("event", async (event) => {
|
|
321
|
+
switch (event.code) {
|
|
322
|
+
case "START":
|
|
323
|
+
if (verbose)
|
|
324
|
+
console.log("[gjsify build --watch] rebuild start");
|
|
325
|
+
break;
|
|
326
|
+
case "BUNDLE_START":
|
|
327
|
+
console.log("[gjsify build --watch] building…");
|
|
328
|
+
break;
|
|
329
|
+
case "BUNDLE_END":
|
|
330
|
+
console.log(`[gjsify build --watch] built in ${event.duration}ms`);
|
|
331
|
+
try {
|
|
332
|
+
if (app === "gjs" && this.configData.shebang) {
|
|
333
|
+
await this.applyShebang(outfile, verbose);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
await event.result.close();
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
case "END":
|
|
341
|
+
console.log("[gjsify build --watch] waiting for changes…");
|
|
342
|
+
break;
|
|
343
|
+
case "ERROR":
|
|
344
|
+
console.error("[gjsify build --watch] build failed:", event.error?.message ?? event.error);
|
|
345
|
+
if (verbose && event.error?.stack)
|
|
346
|
+
console.error(event.error.stack);
|
|
347
|
+
try {
|
|
348
|
+
await event.result.close();
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// best-effort cleanup
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
if (verbose) {
|
|
357
|
+
watcher.on("change", (id, change) => {
|
|
358
|
+
console.log(`[gjsify build --watch] ${change.event}: ${id}`);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
await closed;
|
|
362
|
+
}
|
|
290
363
|
async start(buildType = { app: "gjs" }) {
|
|
291
364
|
if (buildType.library) {
|
|
365
|
+
if (buildType.watch) {
|
|
366
|
+
throw new Error("gjsify build: --watch is not supported with --library (library mode would emit watcher rebuilds for every produced format; use --app gjs|node|browser instead).");
|
|
367
|
+
}
|
|
292
368
|
return await this.buildLibrary();
|
|
293
369
|
}
|
|
294
|
-
return await this.buildApp(buildType.app);
|
|
370
|
+
return await this.buildApp(buildType.app, { watch: buildType.watch });
|
|
295
371
|
}
|
|
296
372
|
}
|
|
297
373
|
async function runOneLibraryBuild(args) {
|
package/lib/bundler-pick.d.ts
CHANGED
|
@@ -65,6 +65,13 @@ export declare function bundleToChunks(input: {
|
|
|
65
65
|
rolldownInput: import('rolldown').InputOptions;
|
|
66
66
|
format: 'esm' | 'cjs' | 'iife';
|
|
67
67
|
}): Promise<string[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Watch source files and rebuild on change. Only npm rolldown supports
|
|
70
|
+
* this path — `@gjsify/rolldown-native` does not surface a watcher API yet.
|
|
71
|
+
* Returns the watcher; the caller registers `event` / `close` listeners
|
|
72
|
+
* and is responsible for invoking `watcher.close()` on shutdown.
|
|
73
|
+
*/
|
|
74
|
+
export declare function runWatch(finalOpts: BundlerOptions): Promise<import('rolldown').RolldownWatcher>;
|
|
68
75
|
/**
|
|
69
76
|
* Run a bundle with the picked engine. Drop-in replacement for the
|
|
70
77
|
* `rolldown(opts).write(opts.output)` flow used directly in build.ts.
|
package/lib/bundler-pick.js
CHANGED
|
@@ -86,6 +86,23 @@ export async function bundleToChunks(input) {
|
|
|
86
86
|
await build.close();
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Watch source files and rebuild on change. Only npm rolldown supports
|
|
91
|
+
* this path — `@gjsify/rolldown-native` does not surface a watcher API yet.
|
|
92
|
+
* Returns the watcher; the caller registers `event` / `close` listeners
|
|
93
|
+
* and is responsible for invoking `watcher.close()` on shutdown.
|
|
94
|
+
*/
|
|
95
|
+
export async function runWatch(finalOpts) {
|
|
96
|
+
if (await shouldUseNative()) {
|
|
97
|
+
throw new Error('`gjsify build --watch` requires the npm `rolldown` engine. The native engine ' +
|
|
98
|
+
'(`@gjsify/rolldown-native`) does not expose a watcher API. Run the watch loop ' +
|
|
99
|
+
'under Node (`node lib/index.js build … --watch`) or set `GJSIFY_BUNDLER=npm`.');
|
|
100
|
+
}
|
|
101
|
+
const specifier = 'rolldown';
|
|
102
|
+
const mod = (await import(/* @vite-ignore */ specifier));
|
|
103
|
+
const output = finalOpts.output ?? {};
|
|
104
|
+
return mod.watch({ ...finalOpts, output });
|
|
105
|
+
}
|
|
89
106
|
/**
|
|
90
107
|
* Run a bundle with the picked engine. Drop-in replacement for the
|
|
91
108
|
* `rolldown(opts).write(opts.output)` flow used directly in build.ts.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
import { type BarrelExtension } from '../actions/barrels-generate.js';
|
|
3
|
+
interface BarrelsOptions {
|
|
4
|
+
paths?: string[];
|
|
5
|
+
ext?: BarrelExtension;
|
|
6
|
+
baseDir?: string;
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
header?: string;
|
|
9
|
+
semicolon?: boolean;
|
|
10
|
+
singleQuotes?: boolean;
|
|
11
|
+
check?: boolean;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const barrelsCommand: Command<unknown, BarrelsOptions>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// `gjsify barrels` — regenerate `index.ts` barrel files for a set of
|
|
2
|
+
// directories. Drop-in replacement for `barrelsby` (unmaintained 2022+).
|
|
3
|
+
//
|
|
4
|
+
// Examples:
|
|
5
|
+
// gjsify barrels src/systems src/components # regenerate two barrels
|
|
6
|
+
// gjsify barrels --check src/types # CI guard — exit 1 on drift
|
|
7
|
+
// gjsify barrels --ext js src/utils # NodeNext-style import-extensions
|
|
8
|
+
//
|
|
9
|
+
// Conventions:
|
|
10
|
+
// - A directory literally named `types/` emits `export type *` (type-only
|
|
11
|
+
// re-export) — avoids dragging value imports through type-only barrels.
|
|
12
|
+
// - An empty directory yields `export {};` so TypeScript still parses the
|
|
13
|
+
// file as a module.
|
|
14
|
+
// - Output is sorted by file name (locale-compare) — deterministic diffs.
|
|
15
|
+
// - `--check` exits non-zero on drift without writing (pre-commit / CI use).
|
|
16
|
+
import { DEFAULT_BARRELS_EXCLUDES, DEFAULT_BARRELS_HEADER, generateBarrels, } from '../actions/barrels-generate.js';
|
|
17
|
+
export const barrelsCommand = {
|
|
18
|
+
command: 'barrels [paths..]',
|
|
19
|
+
description: 'Regenerate `index.ts` barrel files for the given directories. Drop-in replacement for `barrelsby`.',
|
|
20
|
+
builder: (yargs) => {
|
|
21
|
+
return yargs
|
|
22
|
+
.positional('paths', {
|
|
23
|
+
description: 'Directories whose `index.ts` to (re)generate. May also be passed via `--paths`.',
|
|
24
|
+
type: 'string',
|
|
25
|
+
array: true,
|
|
26
|
+
})
|
|
27
|
+
.option('paths', {
|
|
28
|
+
alias: 'p',
|
|
29
|
+
description: 'Alternative to positional — repeatable list of directories.',
|
|
30
|
+
type: 'string',
|
|
31
|
+
array: true,
|
|
32
|
+
})
|
|
33
|
+
.option('ext', {
|
|
34
|
+
description: 'Import-specifier extension. Default: `none` (bundler-mode resolution).',
|
|
35
|
+
type: 'string',
|
|
36
|
+
choices: ['js', 'ts', 'none'],
|
|
37
|
+
default: 'none',
|
|
38
|
+
})
|
|
39
|
+
.option('base-dir', {
|
|
40
|
+
alias: 'b',
|
|
41
|
+
description: 'Resolve `paths` against this directory. Default: cwd.',
|
|
42
|
+
type: 'string',
|
|
43
|
+
})
|
|
44
|
+
.option('exclude', {
|
|
45
|
+
description: 'Regex(es) of file names to skip. Repeatable. Defaults: `\\.test\\.`, `\\.spec\\.`, `\\.test-data\\.`.',
|
|
46
|
+
type: 'string',
|
|
47
|
+
array: true,
|
|
48
|
+
})
|
|
49
|
+
.option('header', {
|
|
50
|
+
description: 'Header comment prepended to every generated file.',
|
|
51
|
+
type: 'string',
|
|
52
|
+
})
|
|
53
|
+
.option('semicolon', {
|
|
54
|
+
description: 'Emit trailing `;` on each export line. Negate with `--no-semicolon`. Default: omitted.',
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
default: false,
|
|
57
|
+
})
|
|
58
|
+
.option('single-quotes', {
|
|
59
|
+
description: 'Use `\'` for import specifiers. Default: true. Pass `--no-single-quotes` for `"`.',
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
default: true,
|
|
62
|
+
})
|
|
63
|
+
.option('check', {
|
|
64
|
+
description: 'Report drift without modifying files; exit non-zero if any barrel is stale.',
|
|
65
|
+
type: 'boolean',
|
|
66
|
+
default: false,
|
|
67
|
+
})
|
|
68
|
+
.option('verbose', {
|
|
69
|
+
description: 'Log each file scanned + written.',
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
default: false,
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
handler: async (args) => {
|
|
75
|
+
// Both positional (`gjsify barrels src/a src/b`) and the named option
|
|
76
|
+
// (`--paths src/a --paths src/b`) land in `args.paths` since they share
|
|
77
|
+
// the same name. Yargs concatenates when both are present.
|
|
78
|
+
const paths = Array.from(new Set((args.paths ?? []).filter(Boolean)));
|
|
79
|
+
if (paths.length === 0) {
|
|
80
|
+
console.error('[gjsify barrels] no paths provided. Pass directories as positional arguments or via --paths.');
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const excludePatterns = args.exclude?.length
|
|
85
|
+
? args.exclude
|
|
86
|
+
: [...DEFAULT_BARRELS_EXCLUDES];
|
|
87
|
+
const drift = await generateBarrels({
|
|
88
|
+
paths,
|
|
89
|
+
extension: args.ext ?? 'none',
|
|
90
|
+
baseDir: args.baseDir ?? process.cwd(),
|
|
91
|
+
exclude: excludePatterns.map((src) => new RegExp(src)),
|
|
92
|
+
header: args.header ?? DEFAULT_BARRELS_HEADER,
|
|
93
|
+
noSemicolon: args.semicolon === false || args.semicolon === undefined,
|
|
94
|
+
singleQuotes: args.singleQuotes !== false,
|
|
95
|
+
check: args.check ?? false,
|
|
96
|
+
verbose: args.verbose ?? false,
|
|
97
|
+
});
|
|
98
|
+
if (args.check && drift > 0) {
|
|
99
|
+
console.error(`[gjsify barrels] ${drift} barrel file(s) drifted. Run without --check to fix.`);
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
package/lib/commands/build.js
CHANGED
|
@@ -130,6 +130,13 @@ export const buildCommand = {
|
|
|
130
130
|
description: "Comma-separated global identifiers to remove from auto-detection results. Use for false positives from dead browser-compat code whose polyfills require unavailable native libraries (e.g. --exclude-globals fetch,XMLHttpRequest).",
|
|
131
131
|
type: 'string',
|
|
132
132
|
normalize: true,
|
|
133
|
+
})
|
|
134
|
+
.option('watch', {
|
|
135
|
+
alias: 'w',
|
|
136
|
+
description: "Watch source files and rebuild on change. Logs each rebuild with duration; clean SIGINT shutdown. Only valid with --app gjs|node|browser (rejected with --library). Requires the npm `rolldown` engine — run under Node, not the GJS-bundled CLI.",
|
|
137
|
+
type: 'boolean',
|
|
138
|
+
normalize: true,
|
|
139
|
+
default: false,
|
|
133
140
|
});
|
|
134
141
|
},
|
|
135
142
|
handler: async (args) => {
|
|
@@ -139,6 +146,7 @@ export const buildCommand = {
|
|
|
139
146
|
await action.start({
|
|
140
147
|
library: args.library,
|
|
141
148
|
app: args.app,
|
|
149
|
+
watch: args.watch,
|
|
142
150
|
});
|
|
143
151
|
}
|
|
144
152
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// `gjsify fix` — runs biome's combined `check --write` mode.
|
|
2
|
+
//
|
|
3
|
+
// Equivalent to biome's `check` (format + safe-lint-fix + organize-imports).
|
|
4
|
+
// Default writes fixes in-place; pass `--no-write` to report-only.
|
|
5
|
+
// Naming: deliberately distinct from `gjsify check` (which verifies
|
|
6
|
+
// system dependencies).
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
import { BiomeNotFoundError, findBiomeConfig, printBiomeNotFound, runBiome, } from '../utils/biome-resolve.js';
|
|
9
|
+
export const fixCommand = {
|
|
10
|
+
command: 'fix [paths..]',
|
|
11
|
+
description: 'Run Biome check --write — format + safe-lint-fix + organize-imports in one pass.',
|
|
12
|
+
builder: (yargs) => {
|
|
13
|
+
return yargs
|
|
14
|
+
.positional('paths', {
|
|
15
|
+
description: 'Files or directories to fix. Default: `.`',
|
|
16
|
+
type: 'string',
|
|
17
|
+
array: true,
|
|
18
|
+
})
|
|
19
|
+
.option('write', {
|
|
20
|
+
description: 'Apply fixes in place (default: true). Pass --no-write to report only.',
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
default: true,
|
|
23
|
+
})
|
|
24
|
+
.option('config-path', {
|
|
25
|
+
description: 'Path to a biome.json. Default: walks up from cwd to find one.',
|
|
26
|
+
type: 'string',
|
|
27
|
+
normalize: true,
|
|
28
|
+
})
|
|
29
|
+
.option('verbose', {
|
|
30
|
+
description: 'Echo the resolved biome binary + args before spawning.',
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
default: false,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
handler: async (args) => {
|
|
36
|
+
const cwd = process.cwd();
|
|
37
|
+
const paths = args.paths?.length
|
|
38
|
+
? args.paths
|
|
39
|
+
: ['.'];
|
|
40
|
+
const biomeArgs = ['check'];
|
|
41
|
+
if (args.write !== false)
|
|
42
|
+
biomeArgs.push('--write');
|
|
43
|
+
const configPath = args.configPath ?? findBiomeConfig(cwd) ?? undefined;
|
|
44
|
+
if (configPath)
|
|
45
|
+
biomeArgs.push(`--config-path=${resolve(configPath, '..')}`);
|
|
46
|
+
biomeArgs.push(...paths);
|
|
47
|
+
try {
|
|
48
|
+
const code = await runBiome(biomeArgs, { cwd, verbose: args.verbose });
|
|
49
|
+
process.exitCode = code;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (err instanceof BiomeNotFoundError) {
|
|
53
|
+
printBiomeNotFound(err);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
interface DiffOptions {
|
|
3
|
+
version?: string;
|
|
4
|
+
appId?: string;
|
|
5
|
+
flathubRepo?: string;
|
|
6
|
+
against?: string;
|
|
7
|
+
detail?: boolean;
|
|
8
|
+
sourceIndex?: number;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const flatpakDiffCommand: Command<unknown, DiffOptions>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// `gjsify flatpak diff` — report drift between the local config + git
|
|
2
|
+
// state and the per-app Flathub tracking-repo manifest.
|
|
3
|
+
//
|
|
4
|
+
// The most common failure mode is *version drift*: a release was cut
|
|
5
|
+
// locally (a new `git tag`), but the Flathub-repo manifest still pins
|
|
6
|
+
// the previous tag, so end-users on flatpak install never see the
|
|
7
|
+
// release. This command surfaces that drift before
|
|
8
|
+
// `gjsify flatpak sync-flathub` is needed (the symmetric command that
|
|
9
|
+
// fixes it).
|
|
10
|
+
//
|
|
11
|
+
// Workflow:
|
|
12
|
+
// 1. Resolve appId + flathub-repo (same precedence as sync-flathub)
|
|
13
|
+
// 2. Fetch the Flathub manifest (or read `--against <local>`)
|
|
14
|
+
// 3. Resolve the local latest git tag (`git describe --tags --abbrev=0`)
|
|
15
|
+
// 4. Compare module[0].sources's `tag` + `commit` to the local state
|
|
16
|
+
// 5. Exit 0 when in sync, 1 when there's drift, with a clear message
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { execFile } from 'node:child_process';
|
|
19
|
+
import { promisify } from 'node:util';
|
|
20
|
+
import { Config } from '../../config.js';
|
|
21
|
+
import { readPackageJson } from './utils.js';
|
|
22
|
+
const execFileAsync = promisify(execFile);
|
|
23
|
+
export const flatpakDiffCommand = {
|
|
24
|
+
command: 'diff',
|
|
25
|
+
description: 'Compare the per-app Flathub tracking-repo manifest against the local git state and report version / commit drift.',
|
|
26
|
+
builder: (yargs) => {
|
|
27
|
+
return yargs
|
|
28
|
+
.version(false)
|
|
29
|
+
.option('version', {
|
|
30
|
+
description: 'Local version to compare against. Default: `git describe --tags --abbrev=0` in cwd.',
|
|
31
|
+
type: 'string',
|
|
32
|
+
})
|
|
33
|
+
.option('app-id', {
|
|
34
|
+
description: 'Reverse-DNS app id. Default: `gjsify.flatpak.appId`.',
|
|
35
|
+
type: 'string',
|
|
36
|
+
})
|
|
37
|
+
.option('flathub-repo', {
|
|
38
|
+
description: 'Flathub tracking-repo (owner/name). Default: `gjsify.flatpak.flathubRepo` or `flathub/<appId>`.',
|
|
39
|
+
type: 'string',
|
|
40
|
+
})
|
|
41
|
+
.option('against', {
|
|
42
|
+
description: 'Read the Flathub manifest from a local file instead of fetching it. Useful in CI or offline.',
|
|
43
|
+
type: 'string',
|
|
44
|
+
})
|
|
45
|
+
.option('detail', {
|
|
46
|
+
description: 'Also print the full Flathub manifest source entry alongside the resolved local version.',
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
default: false,
|
|
49
|
+
})
|
|
50
|
+
.option('source-index', {
|
|
51
|
+
description: 'Index into modules[0].sources[] to inspect (when the manifest has multiple sources).',
|
|
52
|
+
type: 'number',
|
|
53
|
+
})
|
|
54
|
+
.option('verbose', {
|
|
55
|
+
description: 'Echo fetch URL + resolved values.',
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
default: false,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
handler: async (args) => {
|
|
61
|
+
const cwd = process.cwd();
|
|
62
|
+
const cfg = new Config();
|
|
63
|
+
const configData = await cfg.forBuild({}).catch(() => ({}));
|
|
64
|
+
const flatpak = configData.flatpak ?? {};
|
|
65
|
+
const appId = args.appId ??
|
|
66
|
+
flatpak.appId ??
|
|
67
|
+
readPackageJson(cwd).name;
|
|
68
|
+
if (!appId) {
|
|
69
|
+
throw new Error('[gjsify flatpak diff] no app id available — pass --app-id or set gjsify.flatpak.appId.');
|
|
70
|
+
}
|
|
71
|
+
const flathubRepo = args.flathubRepo ??
|
|
72
|
+
flatpak.flathubRepo ??
|
|
73
|
+
`flathub/${appId}`;
|
|
74
|
+
const localVersion = args.version ?? (await resolveLatestTag(cwd, args.verbose));
|
|
75
|
+
const remoteSource = await loadFlathubSource({ appId, flathubRepo, against: args.against, verbose: args.verbose }, args.sourceIndex);
|
|
76
|
+
const remoteTag = remoteSource?.tag;
|
|
77
|
+
const remoteCommit = remoteSource?.commit;
|
|
78
|
+
console.log(`[gjsify flatpak diff] appId=${appId}`);
|
|
79
|
+
console.log(`[gjsify flatpak diff] flathubRepo=${flathubRepo}`);
|
|
80
|
+
console.log(`[gjsify flatpak diff] flathub: tag=${remoteTag ?? '(missing)'} commit=${remoteCommit ?? '(missing)'}`);
|
|
81
|
+
console.log(`[gjsify flatpak diff] local: tag=${localVersion ?? '(none)'}`);
|
|
82
|
+
if (args.detail && remoteSource) {
|
|
83
|
+
console.log('[gjsify flatpak diff] flathub manifest source:');
|
|
84
|
+
console.log(JSON.stringify(remoteSource, null, 2));
|
|
85
|
+
}
|
|
86
|
+
if (!localVersion) {
|
|
87
|
+
console.warn('[gjsify flatpak diff] no local git tag found — cut a release (`git tag vX.Y.Z`) ' +
|
|
88
|
+
'or pass --version vX.Y.Z to compare against an explicit value.');
|
|
89
|
+
// Still surface remote state, but exit cleanly so this isn't a fatal CI step.
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!remoteTag) {
|
|
93
|
+
console.warn('[gjsify flatpak diff] flathub manifest has no `tag` field on the inspected source.');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
if (remoteTag === localVersion) {
|
|
97
|
+
console.log(`✅ in sync (${localVersion})`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(`❌ drift detected — flathub=${remoteTag} vs local=${localVersion}`);
|
|
101
|
+
console.log(` run \`gjsify flatpak sync-flathub --version ${localVersion}\` to update the Flathub manifest.`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
// ─── Internal helpers ────────────────────────────────────────────────────
|
|
106
|
+
async function loadFlathubSource(args, sourceIndex) {
|
|
107
|
+
let raw;
|
|
108
|
+
if (args.against) {
|
|
109
|
+
if (!existsSync(args.against)) {
|
|
110
|
+
throw new Error(`[gjsify flatpak diff] --against path ${args.against} does not exist`);
|
|
111
|
+
}
|
|
112
|
+
raw = readFileSync(args.against, 'utf-8');
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Try main + master raw URLs on GitHub. Flathub repos historically use
|
|
116
|
+
// master; newer ones may use main. No XDG cache: fresh fetch each run.
|
|
117
|
+
raw = await fetchFlathubManifest(args.flathubRepo, args.appId, args.verbose);
|
|
118
|
+
}
|
|
119
|
+
let manifest;
|
|
120
|
+
try {
|
|
121
|
+
manifest = JSON.parse(raw);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
throw new Error(`[gjsify flatpak diff] failed to parse Flathub manifest as JSON: ${err.message}`);
|
|
125
|
+
}
|
|
126
|
+
const modules = manifest.modules ?? [];
|
|
127
|
+
const sources = modules[0]?.sources ?? [];
|
|
128
|
+
if (sources.length === 0)
|
|
129
|
+
return null;
|
|
130
|
+
const idx = sourceIndex ?? sources.findIndex((s) => s?.type === 'git');
|
|
131
|
+
if (idx < 0 || idx >= sources.length)
|
|
132
|
+
return null;
|
|
133
|
+
return sources[idx] ?? null;
|
|
134
|
+
}
|
|
135
|
+
async function fetchFlathubManifest(flathubRepo, appId, verbose) {
|
|
136
|
+
for (const branch of ['master', 'main']) {
|
|
137
|
+
const url = `https://raw.githubusercontent.com/${flathubRepo}/${branch}/${appId}.json`;
|
|
138
|
+
if (verbose)
|
|
139
|
+
console.log(`[gjsify flatpak diff] fetch ${url}`);
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch(url);
|
|
142
|
+
if (res.ok)
|
|
143
|
+
return await res.text();
|
|
144
|
+
if (verbose)
|
|
145
|
+
console.log(`[gjsify flatpak diff] ${branch} → HTTP ${res.status}`);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
if (verbose)
|
|
149
|
+
console.log(`[gjsify flatpak diff] ${branch} fetch error: ${err.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`[gjsify flatpak diff] could not fetch flathub manifest from ${flathubRepo} on master or main`);
|
|
153
|
+
}
|
|
154
|
+
async function resolveLatestTag(cwd, verbose) {
|
|
155
|
+
try {
|
|
156
|
+
const { stdout } = await execFileAsync('git', ['describe', '--tags', '--abbrev=0'], { cwd });
|
|
157
|
+
const tag = stdout.trim();
|
|
158
|
+
if (verbose)
|
|
159
|
+
console.log(`[gjsify flatpak diff] local latest tag → ${tag}`);
|
|
160
|
+
return tag || null;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|