@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,98 @@
|
|
|
1
|
+
// `gjsify format` — wraps biome's `format` mode.
|
|
2
|
+
//
|
|
3
|
+
// Resolves biome's native binary from node_modules (skipping the Node
|
|
4
|
+
// launcher in @biomejs/biome/bin/biome) and spawns it directly. This
|
|
5
|
+
// keeps the Node-free promise intact: biome's per-platform package
|
|
6
|
+
// (e.g. @biomejs/cli-linux-x64) carries a self-contained Rust binary.
|
|
7
|
+
//
|
|
8
|
+
// `--init` writes a recommended biome.json template tuned for GJS/GNOME
|
|
9
|
+
// projects (4-space JS/TS, 2-space JSON+CSS, single-quotes, lineWidth
|
|
10
|
+
// 120, biome's recommended linter + a few GJS-specific opt-outs).
|
|
11
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { resolve } from 'node:path';
|
|
13
|
+
import { BiomeNotFoundError, findBiomeConfig, loadBiomeTemplate, printBiomeNotFound, runBiome, } from '../utils/biome-resolve.js';
|
|
14
|
+
export const formatCommand = {
|
|
15
|
+
command: 'format [paths..]',
|
|
16
|
+
description: 'Format source files via Biome (native binary spawn — no Node launcher).',
|
|
17
|
+
builder: (yargs) => {
|
|
18
|
+
return yargs
|
|
19
|
+
.positional('paths', {
|
|
20
|
+
description: 'Files or directories to format. Default: `.`',
|
|
21
|
+
type: 'string',
|
|
22
|
+
array: true,
|
|
23
|
+
})
|
|
24
|
+
.option('write', {
|
|
25
|
+
description: 'Modify files in place (default: stdout / report).',
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
default: false,
|
|
28
|
+
})
|
|
29
|
+
.option('check', {
|
|
30
|
+
description: 'Report formatting drift without modifying files; exit non-zero if any file is unformatted. Useful for CI.',
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
default: false,
|
|
33
|
+
})
|
|
34
|
+
.option('config-path', {
|
|
35
|
+
description: 'Path to a biome.json. Default: walks up from cwd to find one.',
|
|
36
|
+
type: 'string',
|
|
37
|
+
normalize: true,
|
|
38
|
+
})
|
|
39
|
+
.option('init', {
|
|
40
|
+
description: 'Write a recommended biome.json into cwd (skips if one exists; --force to overwrite).',
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
default: false,
|
|
43
|
+
})
|
|
44
|
+
.option('force', {
|
|
45
|
+
description: 'Overwrite an existing biome.json with --init.',
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
default: false,
|
|
48
|
+
})
|
|
49
|
+
.option('verbose', {
|
|
50
|
+
description: 'Echo the resolved biome binary + args before spawning.',
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
default: false,
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
handler: async (args) => {
|
|
56
|
+
const cwd = process.cwd();
|
|
57
|
+
if (args.init) {
|
|
58
|
+
await handleInit({ cwd, force: args.force ?? false });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const paths = args.paths?.length
|
|
62
|
+
? args.paths
|
|
63
|
+
: ['.'];
|
|
64
|
+
const biomeArgs = ['format'];
|
|
65
|
+
if (args.write && !args.check)
|
|
66
|
+
biomeArgs.push('--write');
|
|
67
|
+
// --check is the CI semantic: don't write, exit non-zero on drift.
|
|
68
|
+
// Biome's default mode (no --write) already reports drift + exits
|
|
69
|
+
// non-zero. We forward neither flag and let biome's defaults apply.
|
|
70
|
+
const configPath = args.configPath ?? findBiomeConfig(cwd) ?? undefined;
|
|
71
|
+
if (configPath)
|
|
72
|
+
biomeArgs.push(`--config-path=${resolve(configPath, '..')}`);
|
|
73
|
+
biomeArgs.push(...paths);
|
|
74
|
+
try {
|
|
75
|
+
const code = await runBiome(biomeArgs, { cwd, verbose: args.verbose });
|
|
76
|
+
process.exitCode = code;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (err instanceof BiomeNotFoundError) {
|
|
80
|
+
printBiomeNotFound(err);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
async function handleInit({ cwd, force }) {
|
|
89
|
+
const target = resolve(cwd, 'biome.json');
|
|
90
|
+
if (existsSync(target) && !force) {
|
|
91
|
+
console.log(`[gjsify format] biome.json exists at ${target} — pass --force to overwrite.`);
|
|
92
|
+
process.exitCode = 0;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
writeFileSync(target, loadBiomeTemplate(), 'utf-8');
|
|
96
|
+
console.log(`[gjsify format] wrote ${target}`);
|
|
97
|
+
console.log(`[gjsify format] Run \`gjsify format --write .\` to apply the formatter to the project.`);
|
|
98
|
+
}
|
package/lib/commands/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './build.js';
|
|
2
|
+
export * from './test.js';
|
|
2
3
|
export * from './run.js';
|
|
3
4
|
export * from './info.js';
|
|
4
5
|
export * from './check.js';
|
|
@@ -17,3 +18,8 @@ export * from './publish.js';
|
|
|
17
18
|
export * from './self-update.js';
|
|
18
19
|
export * from './generate-installer.js';
|
|
19
20
|
export * from './uninstall.js';
|
|
21
|
+
export * from './format.js';
|
|
22
|
+
export * from './lint.js';
|
|
23
|
+
export * from './fix.js';
|
|
24
|
+
export * from './upgrade.js';
|
|
25
|
+
export * from './barrels.js';
|
package/lib/commands/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './build.js';
|
|
2
|
+
export * from './test.js';
|
|
2
3
|
export * from './run.js';
|
|
3
4
|
export * from './info.js';
|
|
4
5
|
export * from './check.js';
|
|
@@ -17,3 +18,8 @@ export * from './publish.js';
|
|
|
17
18
|
export * from './self-update.js';
|
|
18
19
|
export * from './generate-installer.js';
|
|
19
20
|
export * from './uninstall.js';
|
|
21
|
+
export * from './format.js';
|
|
22
|
+
export * from './lint.js';
|
|
23
|
+
export * from './fix.js';
|
|
24
|
+
export * from './upgrade.js';
|
|
25
|
+
export * from './barrels.js';
|
package/lib/commands/install.js
CHANGED
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
// subprocess flow (useful as escape-hatch for projects that hit a
|
|
12
12
|
// missing native-backend feature).
|
|
13
13
|
//
|
|
14
|
-
// Workspace
|
|
15
|
-
// `"workspaces"` field)
|
|
16
|
-
//
|
|
14
|
+
// Workspace install (`gjsify install` in a monorepo root with a
|
|
15
|
+
// `"workspaces"` field) hoists every workspace's externals into the root
|
|
16
|
+
// `node_modules/` and symlinks `workspace:*` / `workspace:^` / `workspace:~`
|
|
17
|
+
// refs to their target source. Open follow-ups (see STATUS.md "Open TODOs"):
|
|
18
|
+
// per-workspace dedup of conflicting transitive version ranges (first-match
|
|
19
|
+
// wins today).
|
|
17
20
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
18
21
|
import { dirname, join, relative } from 'node:path';
|
|
19
22
|
import { spawn } from 'node:child_process';
|
|
@@ -115,7 +118,7 @@ async function projectInstallNative(args) {
|
|
|
115
118
|
'not PnP-aware yet. Use `yarn install` or set ' +
|
|
116
119
|
'GJSIFY_INSTALL_BACKEND=npm.');
|
|
117
120
|
}
|
|
118
|
-
// Workspace install (no args, root pkg.json has `workspaces`)
|
|
121
|
+
// Workspace install (no args, root pkg.json has `workspaces`).
|
|
119
122
|
// Project-local `gjsify install <pkg>` inside a workspace child still
|
|
120
123
|
// goes through the single-package code path below (this branch only
|
|
121
124
|
// fires for the root no-args case, which is the `yarn install`
|
|
@@ -199,8 +202,7 @@ function syncLockfileRequested(cwd, specs) {
|
|
|
199
202
|
}
|
|
200
203
|
}
|
|
201
204
|
/**
|
|
202
|
-
*
|
|
203
|
-
* at a monorepo root:
|
|
205
|
+
* Workspace-aware install. Mirrors what `yarn install` does at a monorepo root:
|
|
204
206
|
* 1. Discover every workspace under the root.
|
|
205
207
|
* 2. Aggregate the union of their external (non-`workspace:`) deps.
|
|
206
208
|
* 3. Run the native install backend ONCE at the root prefix so all
|
|
@@ -211,9 +213,9 @@ function syncLockfileRequested(cwd, specs) {
|
|
|
211
213
|
* directory into the requesting workspace's `node_modules/<dep>`
|
|
212
214
|
* so `import '@gjsify/utils'` resolves to the local source.
|
|
213
215
|
*
|
|
214
|
-
* Hoisting strategy is intentionally minimal —
|
|
215
|
-
*
|
|
216
|
-
*
|
|
216
|
+
* Hoisting strategy is intentionally minimal — per-workspace dedup +
|
|
217
|
+
* nested `node_modules/` for version conflicts are tracked as a follow-up
|
|
218
|
+
* in STATUS.md "Open TODOs".
|
|
217
219
|
*/
|
|
218
220
|
async function workspaceInstall(cwd, args) {
|
|
219
221
|
const workspaces = discoverWorkspaces(cwd, { includeRoot: true });
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// `gjsify lint` — wraps biome's `lint` mode.
|
|
2
|
+
//
|
|
3
|
+
// Sibling of `gjsify format`. Spawns biome from node_modules directly
|
|
4
|
+
// (no Node launcher). Default behaviour: report-only. Pass `--write`
|
|
5
|
+
// for biome's safe-fix mode, or use `gjsify fix` for the combined
|
|
6
|
+
// format + safe-lint-fix + organize-imports surface.
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
import { BiomeNotFoundError, findBiomeConfig, printBiomeNotFound, runBiome, } from '../utils/biome-resolve.js';
|
|
9
|
+
export const lintCommand = {
|
|
10
|
+
command: 'lint [paths..]',
|
|
11
|
+
description: 'Run Biome lint diagnostics (native binary spawn — no Node launcher).',
|
|
12
|
+
builder: (yargs) => {
|
|
13
|
+
return yargs
|
|
14
|
+
.positional('paths', {
|
|
15
|
+
description: 'Files or directories to lint. Default: `.`',
|
|
16
|
+
type: 'string',
|
|
17
|
+
array: true,
|
|
18
|
+
})
|
|
19
|
+
.option('write', {
|
|
20
|
+
description: 'Apply safe lint fixes in place.',
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
default: false,
|
|
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 = ['lint'];
|
|
41
|
+
if (args.write)
|
|
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
|
+
type Runtime = 'gjs' | 'node';
|
|
3
|
+
interface TestOptions {
|
|
4
|
+
runtime?: Runtime | 'all';
|
|
5
|
+
entry?: string;
|
|
6
|
+
outdir?: string;
|
|
7
|
+
rebuild?: boolean;
|
|
8
|
+
build?: boolean;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const testCommand: Command<unknown, TestOptions>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// `gjsify test` — build + run + aggregate per-runtime test suite.
|
|
2
|
+
//
|
|
3
|
+
// Eliminates the `build:test:{gjs,node}` + `test:{gjs,node}` + `test`
|
|
4
|
+
// script boilerplate that ~110 workspace packages repeat. Each package
|
|
5
|
+
// just needs `src/test.mts` aggregating its `@gjsify/unit` suites; this
|
|
6
|
+
// command builds it for GJS + Node and runs each output, aggregating
|
|
7
|
+
// exit codes.
|
|
8
|
+
import { existsSync, statSync, readdirSync } from 'node:fs';
|
|
9
|
+
import { join, dirname, resolve, relative } from 'node:path';
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { Config } from '../config.js';
|
|
12
|
+
import { BuildAction } from '../actions/build.js';
|
|
13
|
+
import { runGjsBundle } from '../utils/run-gjs.js';
|
|
14
|
+
export const testCommand = {
|
|
15
|
+
command: 'test',
|
|
16
|
+
description: 'Build + run the package’s `src/test.mts` suite on GJS and Node and aggregate the results. Replaces the per-package `build:test:{gjs,node}` + `test:{gjs,node}` script boilerplate.',
|
|
17
|
+
builder: (yargs) => {
|
|
18
|
+
return yargs
|
|
19
|
+
.option('runtime', {
|
|
20
|
+
description: 'Target runtime. Default: both.',
|
|
21
|
+
type: 'string',
|
|
22
|
+
choices: ['gjs', 'node', 'all'],
|
|
23
|
+
default: 'all',
|
|
24
|
+
})
|
|
25
|
+
.option('entry', {
|
|
26
|
+
description: 'Path to the test entry. Default: `src/test.mts` (or `gjsify.test.entry`).',
|
|
27
|
+
type: 'string',
|
|
28
|
+
normalize: true,
|
|
29
|
+
})
|
|
30
|
+
.option('outdir', {
|
|
31
|
+
description: 'Output directory for the built test bundles. Default: `dist/`.',
|
|
32
|
+
type: 'string',
|
|
33
|
+
normalize: true,
|
|
34
|
+
})
|
|
35
|
+
.option('rebuild', {
|
|
36
|
+
description: 'Always rebuild the test bundles, even when they look up-to-date.',
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
default: false,
|
|
39
|
+
})
|
|
40
|
+
.option('build', {
|
|
41
|
+
description: 'Build before running. Default: true (use --no-build to skip when bundles already exist).',
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
default: true,
|
|
44
|
+
})
|
|
45
|
+
.option('verbose', {
|
|
46
|
+
description: 'Print resolved entry/outdir + per-step timing.',
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
default: false,
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
handler: async (args) => {
|
|
52
|
+
const cwd = process.cwd();
|
|
53
|
+
// Resolve config: gjsify.test.{entry,outdir,runtimes}.
|
|
54
|
+
const cfg = new Config();
|
|
55
|
+
const configData = await cfg.forBuild({}).catch(() => ({}));
|
|
56
|
+
const testCfg = configData.test ?? {};
|
|
57
|
+
const entry = resolve(cwd, args.entry ?? testCfg.entry ?? 'src/test.mts');
|
|
58
|
+
const outdir = resolve(cwd, args.outdir ?? testCfg.outdir ?? 'dist');
|
|
59
|
+
if (!existsSync(entry)) {
|
|
60
|
+
console.error(`[gjsify test] no test entry at ${relative(cwd, entry)} — ` +
|
|
61
|
+
`add an \`src/test.mts\` that aggregates your \`@gjsify/unit\` suites, ` +
|
|
62
|
+
`or set \`gjsify.test.entry\` in package.json.`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const requested = args.runtime === 'gjs'
|
|
66
|
+
? ['gjs']
|
|
67
|
+
: args.runtime === 'node'
|
|
68
|
+
? ['node']
|
|
69
|
+
: (testCfg.runtimes && testCfg.runtimes.length > 0 ? testCfg.runtimes : ['gjs', 'node']);
|
|
70
|
+
const results = [];
|
|
71
|
+
for (const runtime of requested) {
|
|
72
|
+
const outfile = join(outdir, `test.${runtime}.mjs`);
|
|
73
|
+
// Build stage (skip if --no-build OR (not --rebuild AND outfile fresher than src)).
|
|
74
|
+
if (args.build !== false) {
|
|
75
|
+
const needsBuild = args.rebuild || !isFresh(outfile, entry, cwd);
|
|
76
|
+
if (needsBuild) {
|
|
77
|
+
const buildStart = Date.now();
|
|
78
|
+
if (args.verbose) {
|
|
79
|
+
console.log(`[gjsify test] building → ${relative(cwd, outfile)} (—app ${runtime})`);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await buildTestBundle(entry, outfile, runtime, args.verbose);
|
|
83
|
+
if (args.verbose) {
|
|
84
|
+
console.log(`[gjsify test] built ${runtime} in ${Date.now() - buildStart}ms`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.error(`[gjsify test] build failed for ${runtime}:`, err.message);
|
|
89
|
+
results.push({ runtime, ok: false, durationMs: 0, error: 'build failed' });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (args.verbose) {
|
|
94
|
+
console.log(`[gjsify test] ${runtime}: bundle is up-to-date — skipping build (use --rebuild to force)`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (!existsSync(outfile)) {
|
|
98
|
+
console.error(`[gjsify test] --no-build but ${relative(cwd, outfile)} doesn't exist. ` +
|
|
99
|
+
`Build first or drop --no-build.`);
|
|
100
|
+
results.push({ runtime, ok: false, durationMs: 0, error: 'no bundle' });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// Run stage.
|
|
104
|
+
const runStart = Date.now();
|
|
105
|
+
try {
|
|
106
|
+
await runTestBundle(outfile, runtime);
|
|
107
|
+
results.push({ runtime, ok: true, durationMs: Date.now() - runStart });
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
results.push({
|
|
111
|
+
runtime,
|
|
112
|
+
ok: false,
|
|
113
|
+
durationMs: Date.now() - runStart,
|
|
114
|
+
error: err.message,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Summary + aggregate exit.
|
|
119
|
+
const summary = results
|
|
120
|
+
.map((r) => `${r.ok ? '✅' : '❌'} ${r.runtime} (${r.durationMs}ms)${r.error ? ` — ${r.error}` : ''}`)
|
|
121
|
+
.join(' ');
|
|
122
|
+
console.log(`[gjsify test] ${summary}`);
|
|
123
|
+
const anyFailed = results.some((r) => !r.ok);
|
|
124
|
+
if (anyFailed) {
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
/** Build a single test bundle in-process via `BuildAction`. */
|
|
130
|
+
async function buildTestBundle(entry, outfile, runtime, verbose) {
|
|
131
|
+
const config = new Config();
|
|
132
|
+
// forBuild's interactive prompts are skipped because we pass through
|
|
133
|
+
// ArgumentsCamelCase shape with only the relevant fields.
|
|
134
|
+
const configData = await config.forBuild({
|
|
135
|
+
entryPoints: [entry],
|
|
136
|
+
outfile,
|
|
137
|
+
app: runtime,
|
|
138
|
+
verbose: verbose ?? false,
|
|
139
|
+
logLevel: 'warning',
|
|
140
|
+
exclude: [],
|
|
141
|
+
});
|
|
142
|
+
// Override bundler entry-input so gjsify.test.entry doesn't fight with
|
|
143
|
+
// gjsify.bundler.input. The build action picks `output.file` straight from
|
|
144
|
+
// the merged config; we set it explicitly here so package.json#main /
|
|
145
|
+
// bundler.output.file from the surrounding project don't redirect the
|
|
146
|
+
// bundle elsewhere.
|
|
147
|
+
configData.library = { ...(configData.library ?? {}) };
|
|
148
|
+
configData.bundler = {
|
|
149
|
+
...(configData.bundler ?? {}),
|
|
150
|
+
input: [entry],
|
|
151
|
+
output: { ...(configData.bundler?.output ?? {}), file: outfile },
|
|
152
|
+
};
|
|
153
|
+
const action = new BuildAction(configData);
|
|
154
|
+
await action.start({ app: runtime, library: false });
|
|
155
|
+
}
|
|
156
|
+
/** Run a single test bundle and reject on non-zero exit. */
|
|
157
|
+
async function runTestBundle(outfile, runtime) {
|
|
158
|
+
if (runtime === 'gjs') {
|
|
159
|
+
await runGjsBundle(outfile);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
await new Promise((resolvePromise, reject) => {
|
|
163
|
+
const child = spawn('node', [outfile], { stdio: 'inherit' });
|
|
164
|
+
child.on('error', reject);
|
|
165
|
+
child.on('exit', (code) => {
|
|
166
|
+
if (code === 0)
|
|
167
|
+
resolvePromise();
|
|
168
|
+
else
|
|
169
|
+
reject(new Error(`node exited with code ${code}`));
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** True when `outfile` exists and is newer than every `.ts`/`.mts` file under the entry's directory tree. */
|
|
174
|
+
function isFresh(outfile, entry, cwd) {
|
|
175
|
+
if (!existsSync(outfile))
|
|
176
|
+
return false;
|
|
177
|
+
const outMtime = statSync(outfile).mtimeMs;
|
|
178
|
+
const srcRoot = dirname(entry);
|
|
179
|
+
// Conservative: walk the src tree once. If the package has no `src/`,
|
|
180
|
+
// fall back to entry-only check.
|
|
181
|
+
try {
|
|
182
|
+
const newest = newestMtimeUnder(existsSync(srcRoot) ? srcRoot : entry);
|
|
183
|
+
return outMtime >= newest;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// On any FS error, force rebuild to stay safe.
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
void cwd;
|
|
190
|
+
}
|
|
191
|
+
function newestMtimeUnder(path) {
|
|
192
|
+
const st = statSync(path);
|
|
193
|
+
if (st.isFile())
|
|
194
|
+
return st.mtimeMs;
|
|
195
|
+
let max = st.mtimeMs;
|
|
196
|
+
for (const entry of readdirSync(path, { withFileTypes: true })) {
|
|
197
|
+
if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'lib' || entry.name.startsWith('.')) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const child = join(path, entry.name);
|
|
201
|
+
const m = newestMtimeUnder(child);
|
|
202
|
+
if (m > max)
|
|
203
|
+
max = m;
|
|
204
|
+
}
|
|
205
|
+
return max;
|
|
206
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Command } from "../types/index.js";
|
|
2
|
+
interface UpgradeOptions {
|
|
3
|
+
latest?: boolean;
|
|
4
|
+
minor?: boolean;
|
|
5
|
+
patch?: boolean;
|
|
6
|
+
filter?: string;
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
yes?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const upgradeCommand: Command<unknown, UpgradeOptions>;
|
|
13
|
+
export {};
|