@gjsify/cli 0.4.12 → 0.4.14
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 +29 -23
- 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/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 +41 -7
- 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 +5 -0
- package/lib/commands/index.js +5 -0
- 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 +6 -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 +47 -0
- package/lib/utils/biome-resolve.d.ts +47 -0
- package/lib/utils/biome-resolve.js +204 -0
- package/package.json +16 -16
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// `gjsify flatpak release` — orchestrate the full release-to-Flathub flow.
|
|
2
|
+
//
|
|
3
|
+
// One command chains the same steps a maintainer otherwise runs by hand
|
|
4
|
+
// after cutting a release:
|
|
5
|
+
// 1. Regenerate Flathub assets from `gjsify.flatpak` config (delegates to
|
|
6
|
+
// `gjsify flatpak init --force`)
|
|
7
|
+
// 2. Run Flathub linters (delegates to `gjsify flatpak check`)
|
|
8
|
+
// 3. Create + push the git tag (unless `--skip-tag`)
|
|
9
|
+
// 4. Sync the per-app Flathub tracking-repo (delegates to
|
|
10
|
+
// `gjsify flatpak sync-flathub`)
|
|
11
|
+
//
|
|
12
|
+
// Each step shells out to the same CLI binary so the user sees identical
|
|
13
|
+
// behaviour to running the sub-commands directly. The orchestrator
|
|
14
|
+
// stops at the first failure and reports which step blocked, with a
|
|
15
|
+
// clear command the user can re-run by hand.
|
|
16
|
+
import { execFile, spawn } from 'node:child_process';
|
|
17
|
+
import { promisify } from 'node:util';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { resolve, dirname } from 'node:path';
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
21
|
+
export const flatpakReleaseCommand = {
|
|
22
|
+
command: 'release <version>',
|
|
23
|
+
description: 'Cut a release end-to-end: regenerate Flathub assets, run linters, create + push the git tag, then open the Flathub PR. Each step delegates to the equivalent `gjsify flatpak <sub>` command.',
|
|
24
|
+
builder: (yargs) => {
|
|
25
|
+
return yargs
|
|
26
|
+
// yargs' built-in `--version` flag would otherwise consume the
|
|
27
|
+
// positional value.
|
|
28
|
+
.version(false)
|
|
29
|
+
.positional('version', {
|
|
30
|
+
description: 'Release tag, e.g. `v0.6.6`.',
|
|
31
|
+
type: 'string',
|
|
32
|
+
demandOption: true,
|
|
33
|
+
})
|
|
34
|
+
.option('skip-init', {
|
|
35
|
+
description: 'Skip the `flatpak init --force` regen step.',
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
default: false,
|
|
38
|
+
})
|
|
39
|
+
.option('skip-check', {
|
|
40
|
+
description: 'Skip the `flatpak check` linter step.',
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
default: false,
|
|
43
|
+
})
|
|
44
|
+
.option('skip-tag', {
|
|
45
|
+
description: 'Skip the `git tag` + `git push --tags` step (use when the tag was already created out-of-band).',
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
default: false,
|
|
48
|
+
})
|
|
49
|
+
.option('push-tag', {
|
|
50
|
+
description: 'Push the created tag after creating it. Default: true.',
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
default: true,
|
|
53
|
+
})
|
|
54
|
+
.option('flathub-repo', {
|
|
55
|
+
description: 'Flathub tracking-repo override forwarded to sync-flathub.',
|
|
56
|
+
type: 'string',
|
|
57
|
+
})
|
|
58
|
+
.option('dry-run', {
|
|
59
|
+
description: 'Print each step that would run without executing any of them.',
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
default: false,
|
|
62
|
+
})
|
|
63
|
+
.option('verbose', {
|
|
64
|
+
description: 'Echo every sub-command invocation.',
|
|
65
|
+
type: 'boolean',
|
|
66
|
+
default: false,
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
handler: async (args) => {
|
|
70
|
+
const version = typeof args.version === 'string' ? args.version.trim() : '';
|
|
71
|
+
if (!version) {
|
|
72
|
+
throw new Error('[gjsify flatpak release] missing <version> positional');
|
|
73
|
+
}
|
|
74
|
+
const cliEntry = resolveCliEntry();
|
|
75
|
+
const cwd = process.cwd();
|
|
76
|
+
const steps = [];
|
|
77
|
+
if (!args.skipInit) {
|
|
78
|
+
steps.push({ name: 'init', args: [cliEntry, 'flatpak', 'init', '--force'] });
|
|
79
|
+
}
|
|
80
|
+
if (!args.skipCheck) {
|
|
81
|
+
steps.push({ name: 'check', args: [cliEntry, 'flatpak', 'check'] });
|
|
82
|
+
}
|
|
83
|
+
const syncArgs = [cliEntry, 'flatpak', 'sync-flathub', '--version', version];
|
|
84
|
+
if (args.flathubRepo)
|
|
85
|
+
syncArgs.push('--flathub-repo', args.flathubRepo);
|
|
86
|
+
if (args.verbose)
|
|
87
|
+
syncArgs.push('--verbose');
|
|
88
|
+
console.log(`[gjsify flatpak release] starting release of ${version}`);
|
|
89
|
+
if (args.dryRun) {
|
|
90
|
+
console.log('[gjsify flatpak release] --dry-run set; printing plan only:');
|
|
91
|
+
for (const s of steps)
|
|
92
|
+
console.log(` · ${s.name}: node ${s.args.join(' ')}`);
|
|
93
|
+
if (!args.skipTag)
|
|
94
|
+
console.log(` · tag: git tag ${version}${args.pushTag !== false ? ' && git push origin ' + version : ''}`);
|
|
95
|
+
console.log(` · sync: node ${syncArgs.join(' ')}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Run init + check in sequence (must succeed before any git mutation).
|
|
99
|
+
for (const step of steps) {
|
|
100
|
+
if (args.verbose)
|
|
101
|
+
console.log(`[gjsify flatpak release] step ${step.name}: node ${step.args.join(' ')}`);
|
|
102
|
+
await runNode(step.args, cwd);
|
|
103
|
+
console.log(`[gjsify flatpak release] ${step.name} ✔`);
|
|
104
|
+
}
|
|
105
|
+
// Tag creation — only after init + check succeed so we don't end up
|
|
106
|
+
// with a tag pointing at a broken release.
|
|
107
|
+
if (!args.skipTag) {
|
|
108
|
+
if (args.verbose)
|
|
109
|
+
console.log(`[gjsify flatpak release] git tag ${version}`);
|
|
110
|
+
try {
|
|
111
|
+
await execFileAsync('git', ['tag', version], { cwd });
|
|
112
|
+
console.log(`[gjsify flatpak release] tag ${version} created`);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
throw new Error(`[gjsify flatpak release] git tag failed (${err.message}). ` +
|
|
116
|
+
`If the tag already exists, re-run with --skip-tag.`);
|
|
117
|
+
}
|
|
118
|
+
if (args.pushTag !== false) {
|
|
119
|
+
if (args.verbose)
|
|
120
|
+
console.log(`[gjsify flatpak release] git push origin ${version}`);
|
|
121
|
+
await execFileAsync('git', ['push', 'origin', version], { cwd });
|
|
122
|
+
console.log(`[gjsify flatpak release] tag pushed`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Sync flathub last.
|
|
126
|
+
if (args.verbose)
|
|
127
|
+
console.log(`[gjsify flatpak release] sync: node ${syncArgs.join(' ')}`);
|
|
128
|
+
await runNode(syncArgs, cwd);
|
|
129
|
+
console.log(`[gjsify flatpak release] ✅ release ${version} complete`);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
/** Resolve a path to the gjsify CLI entry to invoke sub-commands. */
|
|
133
|
+
function resolveCliEntry() {
|
|
134
|
+
// `commands/flatpak/release.ts` → compiled into
|
|
135
|
+
// `lib/commands/flatpak/release.js`. The CLI entry is `lib/index.js`.
|
|
136
|
+
const here = fileURLToPath(import.meta.url);
|
|
137
|
+
const cliRoot = resolve(dirname(here), '..', '..');
|
|
138
|
+
return resolve(cliRoot, 'index.js');
|
|
139
|
+
}
|
|
140
|
+
/** Spawn `node <args>` and reject on non-zero exit. */
|
|
141
|
+
async function runNode(args, cwd) {
|
|
142
|
+
await new Promise((resolvePromise, reject) => {
|
|
143
|
+
const child = spawn('node', args, { cwd, stdio: 'inherit' });
|
|
144
|
+
child.on('error', reject);
|
|
145
|
+
child.on('exit', (code) => {
|
|
146
|
+
if (code === 0)
|
|
147
|
+
resolvePromise();
|
|
148
|
+
else
|
|
149
|
+
reject(new Error(`sub-command exited with code ${code}: node ${args.join(' ')}`));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
interface SyncFlathubOptions {
|
|
3
|
+
version?: string;
|
|
4
|
+
appId?: string;
|
|
5
|
+
flathubRepo?: string;
|
|
6
|
+
commit?: string;
|
|
7
|
+
branch?: string;
|
|
8
|
+
sourceIndex?: number;
|
|
9
|
+
pr?: boolean;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const flatpakSyncFlathubCommand: Command<unknown, SyncFlathubOptions>;
|
|
14
|
+
/**
|
|
15
|
+
* Surgically edit a Flathub manifest to update the git source's `tag` +
|
|
16
|
+
* `commit` (and inject `x-checker-data` if missing). Preserves the
|
|
17
|
+
* original indent + whitespace + key ordering of the surrounding JSON
|
|
18
|
+
* by parsing through JSON.parse + re-stringifying with the detected
|
|
19
|
+
* indent.
|
|
20
|
+
*/
|
|
21
|
+
export declare function editManifest(original: string, args: {
|
|
22
|
+
tag: string;
|
|
23
|
+
commit: string;
|
|
24
|
+
sourceIndex?: number;
|
|
25
|
+
}): string;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// `gjsify flatpak sync-flathub` — sync the per-app Flathub tracking-repo.
|
|
2
|
+
//
|
|
3
|
+
// Flathub distributes each app via a separate `flathub/<app-id>` repo
|
|
4
|
+
// whose manifest pins a specific git tag + commit-SHA of the upstream
|
|
5
|
+
// source. After cutting a new release in the source repo, the Flathub
|
|
6
|
+
// repo's manifest needs to be updated to reference the new tag/commit.
|
|
7
|
+
// This command automates that workflow:
|
|
8
|
+
//
|
|
9
|
+
// 1. Resolve appId + flathub-repo (from gjsify.flatpak config / flags)
|
|
10
|
+
// 2. Resolve --version (explicit flag or `git describe --tags --abbrev=0`)
|
|
11
|
+
// 3. Resolve commit SHA via `git rev-list -n 1 <version>`
|
|
12
|
+
// 4. Clone (or update) the flathub tracking-repo into $XDG_CACHE_HOME
|
|
13
|
+
// 5. Surgically edit the manifest: modules[].sources[].{tag,commit}
|
|
14
|
+
// plus x-checker-data block (inject if missing)
|
|
15
|
+
// 6. Create a branch, commit the changes
|
|
16
|
+
// 7. (unless --no-pr) push + open a PR via `gh pr create`
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { join, resolve } from 'node:path';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { execFile, spawn } from 'node:child_process';
|
|
21
|
+
import { promisify } from 'node:util';
|
|
22
|
+
import { Config } from '../../config.js';
|
|
23
|
+
import { readPackageJson } from './utils.js';
|
|
24
|
+
const execFileAsync = promisify(execFile);
|
|
25
|
+
export const flatpakSyncFlathubCommand = {
|
|
26
|
+
command: 'sync-flathub',
|
|
27
|
+
description: 'Update the per-app Flathub tracking-repo manifest to a new git tag + commit. Clones, edits, commits, optionally opens a PR.',
|
|
28
|
+
builder: (yargs) => {
|
|
29
|
+
return yargs
|
|
30
|
+
// Disable yargs' built-in `--version` (which would otherwise
|
|
31
|
+
// print the package version) so this command's `--version
|
|
32
|
+
// <tag>` flag works.
|
|
33
|
+
.version(false)
|
|
34
|
+
.option('version', {
|
|
35
|
+
description: 'Git tag to sync to. Default: `git describe --tags --abbrev=0` in cwd.',
|
|
36
|
+
type: 'string',
|
|
37
|
+
})
|
|
38
|
+
.option('app-id', {
|
|
39
|
+
description: 'Reverse-DNS app id. Default: `gjsify.flatpak.appId`.',
|
|
40
|
+
type: 'string',
|
|
41
|
+
})
|
|
42
|
+
.option('flathub-repo', {
|
|
43
|
+
description: 'Flathub tracking-repo (owner/name). Default: `flathub/<appId>`.',
|
|
44
|
+
type: 'string',
|
|
45
|
+
})
|
|
46
|
+
.option('commit', {
|
|
47
|
+
description: 'Commit SHA to pin. Default: resolved via `git rev-list -n 1 <version>` in cwd.',
|
|
48
|
+
type: 'string',
|
|
49
|
+
})
|
|
50
|
+
.option('branch', {
|
|
51
|
+
description: 'Branch name in the flathub-repo. Default: `update-to-<version>`.',
|
|
52
|
+
type: 'string',
|
|
53
|
+
})
|
|
54
|
+
.option('source-index', {
|
|
55
|
+
description: 'Index into modules[0].sources[] to update (when manifest has multiple sources). Default: first `type: git` source.',
|
|
56
|
+
type: 'number',
|
|
57
|
+
})
|
|
58
|
+
.option('pr', {
|
|
59
|
+
description: 'After commit + push, open a PR via `gh pr create`. Pass `--no-pr` to skip and stop after push.',
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
default: true,
|
|
62
|
+
})
|
|
63
|
+
.option('dry-run', {
|
|
64
|
+
description: 'Show what would be edited; touch no files, run no git commands.',
|
|
65
|
+
type: 'boolean',
|
|
66
|
+
default: false,
|
|
67
|
+
})
|
|
68
|
+
.option('verbose', {
|
|
69
|
+
description: 'Echo every git / gh invocation before running.',
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
default: false,
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
handler: async (args) => {
|
|
75
|
+
const cwd = process.cwd();
|
|
76
|
+
const cfg = new Config();
|
|
77
|
+
const configData = await cfg.forBuild({}).catch(() => ({}));
|
|
78
|
+
const flatpak = configData.flatpak ?? {};
|
|
79
|
+
const appId = args.appId ??
|
|
80
|
+
flatpak.appId ??
|
|
81
|
+
readPackageJson(cwd).name;
|
|
82
|
+
if (!appId) {
|
|
83
|
+
throw new Error('[gjsify flatpak sync-flathub] no app id available — pass --app-id or set gjsify.flatpak.appId.');
|
|
84
|
+
}
|
|
85
|
+
const flathubRepo = args.flathubRepo ??
|
|
86
|
+
flatpak.flathubRepo ??
|
|
87
|
+
`flathub/${appId}`;
|
|
88
|
+
const version = args.version ?? (await resolveLatestTag(cwd, args.verbose));
|
|
89
|
+
if (!version) {
|
|
90
|
+
throw new Error('[gjsify flatpak sync-flathub] no version resolved — pass --version vX.Y.Z or create a git tag locally.');
|
|
91
|
+
}
|
|
92
|
+
const commitSha = args.commit ?? (await resolveCommitForTag(cwd, version, args.verbose));
|
|
93
|
+
const branch = args.branch ?? `update-to-${normaliseBranchSegment(version)}`;
|
|
94
|
+
console.log(`[gjsify flatpak sync-flathub] appId=${appId}`);
|
|
95
|
+
console.log(`[gjsify flatpak sync-flathub] flathubRepo=${flathubRepo}`);
|
|
96
|
+
console.log(`[gjsify flatpak sync-flathub] version=${version}`);
|
|
97
|
+
console.log(`[gjsify flatpak sync-flathub] commit=${commitSha}`);
|
|
98
|
+
console.log(`[gjsify flatpak sync-flathub] branch=${branch}`);
|
|
99
|
+
if (args.dryRun) {
|
|
100
|
+
console.log(`[gjsify flatpak sync-flathub] --dry-run set; not cloning / writing / pushing.`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const cacheRoot = flathubCacheRoot();
|
|
104
|
+
const cloneDir = join(cacheRoot, flathubRepo.replace('/', '__'));
|
|
105
|
+
mkdirSync(cacheRoot, { recursive: true });
|
|
106
|
+
await ensureClone(cloneDir, flathubRepo, args.verbose);
|
|
107
|
+
const manifestPath = join(cloneDir, `${appId}.json`);
|
|
108
|
+
if (!existsSync(manifestPath)) {
|
|
109
|
+
throw new Error(`[gjsify flatpak sync-flathub] Flathub manifest not found at ${manifestPath} — wrong appId / wrong flathub-repo?`);
|
|
110
|
+
}
|
|
111
|
+
const original = readFileSync(manifestPath, 'utf-8');
|
|
112
|
+
const updated = editManifest(original, {
|
|
113
|
+
tag: version,
|
|
114
|
+
commit: commitSha,
|
|
115
|
+
sourceIndex: args.sourceIndex,
|
|
116
|
+
});
|
|
117
|
+
if (updated === original) {
|
|
118
|
+
console.log(`[gjsify flatpak sync-flathub] manifest already at ${version} — nothing to do.`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
writeFileSync(manifestPath, updated, 'utf-8');
|
|
122
|
+
console.log(`[gjsify flatpak sync-flathub] manifest patched at ${manifestPath}`);
|
|
123
|
+
await gitInRepo(cloneDir, ['checkout', '-B', branch], args.verbose);
|
|
124
|
+
await gitInRepo(cloneDir, ['add', '.'], args.verbose);
|
|
125
|
+
await gitInRepo(cloneDir, ['commit', '-m', `Update to ${version}`], args.verbose);
|
|
126
|
+
if (args.pr === false) {
|
|
127
|
+
console.log(`[gjsify flatpak sync-flathub] --no-pr set; branch ${branch} committed locally in ${cloneDir}.`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await gitInRepo(cloneDir, ['push', '-u', 'origin', branch, '--force-with-lease'], args.verbose);
|
|
131
|
+
const prBody = `Auto-generated by \`gjsify flatpak sync-flathub\`.\n\n- Version: \`${version}\`\n- Commit: \`${commitSha}\`\n`;
|
|
132
|
+
await ghCreate(cloneDir, flathubRepo, branch, version, prBody, args.verbose);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
// ─── Internal helpers ────────────────────────────────────────────────────
|
|
136
|
+
async function resolveLatestTag(cwd, verbose) {
|
|
137
|
+
try {
|
|
138
|
+
const { stdout } = await execFileAsync('git', ['describe', '--tags', '--abbrev=0'], { cwd });
|
|
139
|
+
if (verbose)
|
|
140
|
+
console.log(`[gjsify flatpak sync-flathub] resolved latest tag → ${stdout.trim()}`);
|
|
141
|
+
return stdout.trim() || null;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function resolveCommitForTag(cwd, tag, verbose) {
|
|
148
|
+
try {
|
|
149
|
+
const { stdout } = await execFileAsync('git', ['rev-list', '-n', '1', tag], { cwd });
|
|
150
|
+
const sha = stdout.trim();
|
|
151
|
+
if (!sha)
|
|
152
|
+
throw new Error(`empty rev-list output`);
|
|
153
|
+
if (verbose)
|
|
154
|
+
console.log(`[gjsify flatpak sync-flathub] resolved ${tag} → ${sha}`);
|
|
155
|
+
return sha;
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
throw new Error(`[gjsify flatpak sync-flathub] tag ${tag} not found locally. Run \`git fetch --tags\` or pass --commit <sha>.\n` +
|
|
159
|
+
` underlying error: ${err?.message ?? err}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function normaliseBranchSegment(version) {
|
|
163
|
+
return version.replace(/^v/, '').replace(/[^A-Za-z0-9._-]/g, '-');
|
|
164
|
+
}
|
|
165
|
+
function flathubCacheRoot() {
|
|
166
|
+
const base = process.env.XDG_CACHE_HOME || join(homedir(), '.cache');
|
|
167
|
+
return join(base, 'gjsify', 'flathub-sync');
|
|
168
|
+
}
|
|
169
|
+
async function ensureClone(cloneDir, flathubRepo, verbose) {
|
|
170
|
+
if (existsSync(join(cloneDir, '.git'))) {
|
|
171
|
+
if (verbose)
|
|
172
|
+
console.log(`[gjsify flatpak sync-flathub] reusing clone at ${cloneDir}`);
|
|
173
|
+
// Hard-reset to remote HEAD to clear stale state from a previous run
|
|
174
|
+
await gitInRepo(cloneDir, ['fetch', 'origin'], verbose);
|
|
175
|
+
const defaultBranch = await detectDefaultBranch(cloneDir, verbose);
|
|
176
|
+
await gitInRepo(cloneDir, ['checkout', defaultBranch], verbose);
|
|
177
|
+
await gitInRepo(cloneDir, ['reset', '--hard', `origin/${defaultBranch}`], verbose);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
mkdirSync(resolve(cloneDir, '..'), { recursive: true });
|
|
181
|
+
const url = `https://github.com/${flathubRepo}.git`;
|
|
182
|
+
if (verbose)
|
|
183
|
+
console.log(`[gjsify flatpak sync-flathub] git clone ${url} ${cloneDir}`);
|
|
184
|
+
try {
|
|
185
|
+
await execFileAsync('git', ['clone', url, cloneDir]);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
if (err?.code === 'ENOENT') {
|
|
189
|
+
throw new Error('[gjsify flatpak sync-flathub] `git` not found. Install git from your distro (Fedora: `dnf install git`, Debian: `apt install git`).');
|
|
190
|
+
}
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function detectDefaultBranch(cloneDir, verbose) {
|
|
195
|
+
// Most flathub repos use `master`; some newer ones use `main`. Probe
|
|
196
|
+
// via `git remote show` which surfaces the HEAD branch.
|
|
197
|
+
try {
|
|
198
|
+
const { stdout } = await execFileAsync('git', ['remote', 'show', 'origin'], { cwd: cloneDir });
|
|
199
|
+
const m = stdout.match(/HEAD branch: (\S+)/);
|
|
200
|
+
if (m && m[1] && m[1] !== '(unknown)') {
|
|
201
|
+
if (verbose)
|
|
202
|
+
console.log(`[gjsify flatpak sync-flathub] default branch → ${m[1]}`);
|
|
203
|
+
return m[1];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// fall through to master fallback
|
|
208
|
+
}
|
|
209
|
+
return 'master';
|
|
210
|
+
}
|
|
211
|
+
function gitInRepo(cwd, args, verbose) {
|
|
212
|
+
if (verbose)
|
|
213
|
+
console.log(`[gjsify flatpak sync-flathub] git ${args.join(' ')} (in ${cwd})`);
|
|
214
|
+
return new Promise((res, rej) => {
|
|
215
|
+
const child = spawn('git', args, { cwd, stdio: 'inherit' });
|
|
216
|
+
child.on('error', (err) => {
|
|
217
|
+
if (err.code === 'ENOENT') {
|
|
218
|
+
rej(new Error('[gjsify flatpak sync-flathub] `git` not found. Install it from your distro.'));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
rej(err);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
child.on('exit', (code) => {
|
|
225
|
+
if (code === 0)
|
|
226
|
+
res();
|
|
227
|
+
else
|
|
228
|
+
rej(new Error(`git ${args[0]} exited ${code}`));
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function ghCreate(cloneDir, flathubRepo, branch, version, body, verbose) {
|
|
233
|
+
const args = [
|
|
234
|
+
'pr',
|
|
235
|
+
'create',
|
|
236
|
+
'--repo',
|
|
237
|
+
flathubRepo,
|
|
238
|
+
'--head',
|
|
239
|
+
branch,
|
|
240
|
+
'--title',
|
|
241
|
+
`Update to ${version}`,
|
|
242
|
+
'--body',
|
|
243
|
+
body,
|
|
244
|
+
];
|
|
245
|
+
if (verbose)
|
|
246
|
+
console.log(`[gjsify flatpak sync-flathub] gh ${args.join(' ')} (in ${cloneDir})`);
|
|
247
|
+
return new Promise((res, rej) => {
|
|
248
|
+
const child = spawn('gh', args, { cwd: cloneDir, stdio: 'inherit' });
|
|
249
|
+
child.on('error', (err) => {
|
|
250
|
+
if (err.code === 'ENOENT') {
|
|
251
|
+
rej(new Error('[gjsify flatpak sync-flathub] `gh` (GitHub CLI) not found. Install via `dnf install gh` (Fedora), `apt install gh` (Debian/Ubuntu), or `flatpak install -y flathub com.github.cli`.'));
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
rej(err);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
child.on('exit', (code) => {
|
|
258
|
+
if (code === 0)
|
|
259
|
+
res();
|
|
260
|
+
else
|
|
261
|
+
rej(new Error(`gh pr create exited ${code}`));
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Surgically edit a Flathub manifest to update the git source's `tag` +
|
|
267
|
+
* `commit` (and inject `x-checker-data` if missing). Preserves the
|
|
268
|
+
* original indent + whitespace + key ordering of the surrounding JSON
|
|
269
|
+
* by parsing through JSON.parse + re-stringifying with the detected
|
|
270
|
+
* indent.
|
|
271
|
+
*/
|
|
272
|
+
export function editManifest(original, args) {
|
|
273
|
+
const manifest = JSON.parse(original);
|
|
274
|
+
const modules = manifest.modules ?? [];
|
|
275
|
+
if (modules.length === 0) {
|
|
276
|
+
throw new Error('[gjsify flatpak sync-flathub] manifest has no modules');
|
|
277
|
+
}
|
|
278
|
+
const mainModule = modules[0];
|
|
279
|
+
const sources = mainModule.sources ?? [];
|
|
280
|
+
if (sources.length === 0) {
|
|
281
|
+
throw new Error('[gjsify flatpak sync-flathub] modules[0] has no sources');
|
|
282
|
+
}
|
|
283
|
+
let idx = args.sourceIndex ?? sources.findIndex((s) => s.type === 'git');
|
|
284
|
+
if (idx < 0 || idx >= sources.length) {
|
|
285
|
+
throw new Error(`[gjsify flatpak sync-flathub] no git source found in modules[0].sources (use --source-index <n>)`);
|
|
286
|
+
}
|
|
287
|
+
const source = sources[idx];
|
|
288
|
+
if (source.type !== 'git') {
|
|
289
|
+
throw new Error(`[gjsify flatpak sync-flathub] modules[0].sources[${idx}].type is "${source.type}", expected "git"`);
|
|
290
|
+
}
|
|
291
|
+
source.tag = args.tag;
|
|
292
|
+
source.commit = args.commit;
|
|
293
|
+
if (!source['x-checker-data']) {
|
|
294
|
+
source['x-checker-data'] = {
|
|
295
|
+
type: 'git',
|
|
296
|
+
'tag-pattern': '^v(\\d+\\.\\d+\\.\\d+)$',
|
|
297
|
+
'version-scheme': 'semantic',
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// Detect the original indent (2 vs 4 spaces) by inspecting the second
|
|
301
|
+
// line — Flathub manifests are all 2-space in practice, but some
|
|
302
|
+
// older ones might be 4. Preserve original convention.
|
|
303
|
+
const indent = detectIndent(original);
|
|
304
|
+
return JSON.stringify(manifest, null, indent) + (original.endsWith('\n') ? '\n' : '');
|
|
305
|
+
}
|
|
306
|
+
function detectIndent(json) {
|
|
307
|
+
const match = json.match(/^\{\n( +)/);
|
|
308
|
+
if (match)
|
|
309
|
+
return match[1].length;
|
|
310
|
+
return 2;
|
|
311
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
interface FormatOptions {
|
|
3
|
+
paths?: string[];
|
|
4
|
+
write?: boolean;
|
|
5
|
+
check?: boolean;
|
|
6
|
+
configPath?: string;
|
|
7
|
+
init?: boolean;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const formatCommand: Command<unknown, FormatOptions>;
|
|
12
|
+
export {};
|
|
@@ -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,7 @@ 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';
|
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,7 @@ 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';
|