argsbarg 5.1.9 → 5.1.11
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/CHANGELOG.md +16 -1
- package/docs/distribution-homebrew.md +14 -1
- package/examples/full-example/justfile +4 -0
- package/examples/full-example/scripts/formula-shared.test.ts +68 -0
- package/examples/full-example/scripts/formula-shared.ts +39 -1
- package/examples/full-example/scripts/release.ts +212 -0
- package/examples/full-example/scripts/schemagen.ts +7 -3
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.1.11] - 2026-07-05
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [5.1.10] - 2026-07-05
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Zip release assets** — Homebrew formulae download `{key}.zip` from GitHub Releases; `buildReleaseArchive` in `formula-shared.ts`; `just release --purge` to delete stale releases.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Release workflow** — `scripts/release.ts` uploads a zip archive (smaller download) instead of a bare Mach-O binary; formula `sha256` pins the archive.
|
|
22
|
+
|
|
10
23
|
## [5.1.9] - 2026-07-05
|
|
11
24
|
|
|
12
25
|
### Added
|
|
@@ -646,7 +659,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
646
659
|
- Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
|
|
647
660
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
648
661
|
|
|
649
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.
|
|
662
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.11...HEAD
|
|
663
|
+
[5.1.11]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.11
|
|
664
|
+
[5.1.10]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.10
|
|
650
665
|
[5.1.9]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.9
|
|
651
666
|
[5.1.8]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.8
|
|
652
667
|
[5.1.7]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.7
|
|
@@ -118,9 +118,22 @@ Template source: [`examples/full-example/`](../examples/full-example/) in the ar
|
|
|
118
118
|
## Release workflow
|
|
119
119
|
|
|
120
120
|
1. `just build` → `dist/{key}`
|
|
121
|
-
2. `scripts/release.ts` → writes `Formula/{key}.rb` (GitHub URL + sha256), commits, tags, uploads `dist/{key}` to GitHub Releases
|
|
121
|
+
2. `scripts/release.ts` → zips `dist/{key}` to `dist/{key}.zip`, writes `Formula/{key}.rb` (GitHub zip URL + archive sha256), commits, tags, uploads `dist/{key}.zip` to GitHub Releases
|
|
122
122
|
3. Users `brew upgrade {key}` from the tap
|
|
123
123
|
|
|
124
|
+
Requires the `zip` CLI on the release machine. `just install-local` still stages the bare binary via `file://` (no zip).
|
|
125
|
+
|
|
126
|
+
### Stale release cleanup
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
just release --purge # delete all GitHub releases except the newest (confirm on TTY)
|
|
130
|
+
just release --purge --yes # skip confirmation
|
|
131
|
+
just release --purge --dry-run # list tags that would be deleted
|
|
132
|
+
just release patch --purge # release, then purge older releases
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`--purge` removes GitHub Release records and attached assets only — git tags remain on the remote. Old formula pins that reference deleted release URLs will fail until users upgrade.
|
|
136
|
+
|
|
124
137
|
## Removed (breaking)
|
|
125
138
|
|
|
126
139
|
- Self-install to `~/.local/bin`
|
|
@@ -88,6 +88,10 @@ setup:
|
|
|
88
88
|
test: check
|
|
89
89
|
bun test .
|
|
90
90
|
|
|
91
|
+
# Bump version, build, publish; or pass --purge to delete stale GitHub releases
|
|
92
|
+
release *ARGS:
|
|
93
|
+
bun scripts/release.ts {{ARGS}}
|
|
94
|
+
|
|
91
95
|
# Install release formula from tap and run formula test
|
|
92
96
|
test-release:
|
|
93
97
|
brew untap {{tap}} 2>/dev/null || true
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import {
|
|
8
|
+
buildReleaseArchive,
|
|
9
|
+
releaseArchiveName,
|
|
10
|
+
releaseFormulaUrl,
|
|
11
|
+
selectStaleReleaseTags,
|
|
12
|
+
} from "./formula-shared.ts";
|
|
13
|
+
|
|
14
|
+
describe("releaseArchiveName", () => {
|
|
15
|
+
test("returns key.zip", () => {
|
|
16
|
+
expect(releaseArchiveName()).toBe("full-example.zip");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("releaseFormulaUrl", () => {
|
|
21
|
+
test("points at zip asset on GitHub releases", () => {
|
|
22
|
+
expect(releaseFormulaUrl("1.2.3")).toBe(
|
|
23
|
+
"https://github.com/bdombro/bun-argsbarg/releases/download/v1.2.3/full-example.zip",
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("selectStaleReleaseTags", () => {
|
|
29
|
+
test("returns empty for zero or one release", () => {
|
|
30
|
+
expect(selectStaleReleaseTags([])).toEqual([]);
|
|
31
|
+
expect(selectStaleReleaseTags([{ tagName: "v1.0.0", publishedAt: "2026-01-01T00:00:00Z" }])).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("keeps newest by publishedAt and returns the rest", () => {
|
|
35
|
+
const releases = [
|
|
36
|
+
{ tagName: "v1.0.0", publishedAt: "2026-01-01T00:00:00Z" },
|
|
37
|
+
{ tagName: "v1.1.0", publishedAt: "2026-02-01T00:00:00Z" },
|
|
38
|
+
{ tagName: "v1.0.1", publishedAt: "2026-01-15T00:00:00Z" },
|
|
39
|
+
];
|
|
40
|
+
expect(selectStaleReleaseTags(releases)).toEqual(["v1.0.1", "v1.0.0"]);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("buildReleaseArchive", () => {
|
|
45
|
+
const workDirs: string[] = [];
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
for (const dir of workDirs.splice(0)) {
|
|
49
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("creates zip with binary at archive root and matching sha256", async () => {
|
|
54
|
+
const work = mkdtempSync(join(tmpdir(), "argsbarg-release-zip-"));
|
|
55
|
+
workDirs.push(work);
|
|
56
|
+
const binaryPath = join(work, "full-example");
|
|
57
|
+
writeFileSync(binaryPath, "#!/bin/sh\necho hi\n", { mode: 0o755 });
|
|
58
|
+
|
|
59
|
+
const { archivePath, sha256 } = await buildReleaseArchive(binaryPath);
|
|
60
|
+
|
|
61
|
+
expect(archivePath).toBe(`${binaryPath}.zip`);
|
|
62
|
+
const onDisk = createHash("sha256").update(readFileSync(archivePath)).digest("hex");
|
|
63
|
+
expect(sha256).toBe(onDisk);
|
|
64
|
+
|
|
65
|
+
const listing = execFileSync("unzip", ["-l", archivePath], { encoding: "utf8" });
|
|
66
|
+
expect(listing).toContain("full-example");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
/** Shared Ruby fragments embedded in Homebrew formulae. */
|
|
2
2
|
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { $ } from "bun";
|
|
3
6
|
import { createIdentity } from "./create-identity.ts";
|
|
4
7
|
|
|
5
8
|
const { key, className, desc, homepage, releaseRepo } = createIdentity;
|
|
6
9
|
|
|
10
|
+
/** GitHub release row used by {@link selectStaleReleaseTags}. */
|
|
11
|
+
export interface ReleaseTag {
|
|
12
|
+
tagName: string;
|
|
13
|
+
publishedAt: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export const formulaInstallRuby = `def install
|
|
8
17
|
bin.install "${key}"
|
|
9
18
|
chmod 0755, bin/"${key}"
|
|
@@ -36,11 +45,40 @@ export interface FormulaCoords {
|
|
|
36
45
|
url: string;
|
|
37
46
|
urlStanza: string;
|
|
38
47
|
version: string;
|
|
48
|
+
/** SHA-256 hex digest of the release archive at `url` (zip for GitHub releases). */
|
|
39
49
|
sha256: string;
|
|
40
50
|
/** When true, embed {@link githubPrivateReleaseDownloadStrategyRuby} in the formula class. */
|
|
41
51
|
privateRelease?: boolean;
|
|
42
52
|
}
|
|
43
53
|
|
|
54
|
+
/** Release asset filename on GitHub (zip containing the bare binary at archive root). */
|
|
55
|
+
export function releaseArchiveName(): string {
|
|
56
|
+
return `${key}.zip`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Build `{binaryPath}.zip` from a compiled binary; return archive path and sha256 of the zip. */
|
|
60
|
+
export async function buildReleaseArchive(binaryPath: string): Promise<{ archivePath: string; sha256: string }> {
|
|
61
|
+
const archivePath = `${binaryPath}.zip`;
|
|
62
|
+
const result = await $`zip -j -9 ${archivePath} ${binaryPath}`.nothrow();
|
|
63
|
+
if (result.exitCode !== 0) {
|
|
64
|
+
throw new Error(`zip failed: ${result.stderr}`);
|
|
65
|
+
}
|
|
66
|
+
const sha256 = createHash("sha256").update(readFileSync(archivePath)).digest("hex");
|
|
67
|
+
return { archivePath, sha256 };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Tags to delete when keeping only the newest release (sorted by `publishedAt` desc). */
|
|
71
|
+
export function selectStaleReleaseTags(releases: ReleaseTag[]): string[] {
|
|
72
|
+
if (releases.length <= 1) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const sorted = [...releases].sort((a, b) => b.publishedAt.localeCompare(a.publishedAt));
|
|
76
|
+
return sorted.slice(1).map((r) => r.tagName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** GitHub `org/repo` slug for release and purge commands. */
|
|
80
|
+
export const releaseRepoSlug = releaseRepo;
|
|
81
|
+
|
|
44
82
|
/** Resolves private GitHub release assets via the API at download time. */
|
|
45
83
|
export const githubPrivateReleaseDownloadStrategyRuby = ` class GitHubPrivateReleaseDownloadStrategy < CurlDownloadStrategy
|
|
46
84
|
def initialize(url, name, version, **meta)
|
|
@@ -82,7 +120,7 @@ export function releaseUrlStanza(url: string): string {
|
|
|
82
120
|
}
|
|
83
121
|
|
|
84
122
|
export function releaseFormulaUrl(version: string): string {
|
|
85
|
-
return `https://github.com/${releaseRepo}/releases/download/v${version}/${
|
|
123
|
+
return `https://github.com/${releaseRepo}/releases/download/v${version}/${releaseArchiveName()}`;
|
|
86
124
|
}
|
|
87
125
|
|
|
88
126
|
export function renderFormula(coords: FormulaCoords): string {
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** Bump version, build, publish zip release; optional `--purge` to drop stale GitHub releases. */
|
|
3
|
+
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
6
|
+
import * as readline from "node:readline/promises";
|
|
7
|
+
import { $ } from "bun";
|
|
8
|
+
import { createIdentity } from "./create-identity.ts";
|
|
9
|
+
import {
|
|
10
|
+
buildReleaseArchive,
|
|
11
|
+
type ReleaseTag,
|
|
12
|
+
releaseRepoSlug,
|
|
13
|
+
renderReleaseFormula,
|
|
14
|
+
selectStaleReleaseTags,
|
|
15
|
+
} from "./formula-shared.ts";
|
|
16
|
+
|
|
17
|
+
const { key } = createIdentity;
|
|
18
|
+
const formulaPath = `Formula/${key}.rb`;
|
|
19
|
+
const binaryPath = `dist/${key}`;
|
|
20
|
+
const programPath = "src/program.ts";
|
|
21
|
+
|
|
22
|
+
/** Allowed semver bump kinds for `scripts/release.ts`. */
|
|
23
|
+
type Bump = "major" | "minor" | "patch";
|
|
24
|
+
|
|
25
|
+
interface ReleaseOptions {
|
|
26
|
+
bump?: Bump;
|
|
27
|
+
purge: boolean;
|
|
28
|
+
yes: boolean;
|
|
29
|
+
dryRun: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Entry point: release bump, purge-only, or release then purge. */
|
|
33
|
+
async function main(): Promise<void> {
|
|
34
|
+
const options = parseOptions(process.argv.slice(2));
|
|
35
|
+
if (options.purge && !options.bump) {
|
|
36
|
+
await purgeStaleReleases(options);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!options.bump) {
|
|
40
|
+
usage();
|
|
41
|
+
}
|
|
42
|
+
await runRelease(options.bump, options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Prints usage and exits. */
|
|
46
|
+
function usage(): never {
|
|
47
|
+
process.stderr.write(
|
|
48
|
+
"Usage:\n" +
|
|
49
|
+
" bun scripts/release.ts <major|minor|patch> [--purge]\n" +
|
|
50
|
+
" bun scripts/release.ts --purge [--yes] [--dry-run]\n",
|
|
51
|
+
);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Parses argv into release options. */
|
|
56
|
+
function parseOptions(argv: string[]): ReleaseOptions {
|
|
57
|
+
const yes = argv.includes("--yes");
|
|
58
|
+
const dryRun = argv.includes("--dry-run");
|
|
59
|
+
const purge = argv.includes("--purge");
|
|
60
|
+
const bump = argv.find((a): a is Bump => a === "major" || a === "minor" || a === "patch");
|
|
61
|
+
for (const arg of argv) {
|
|
62
|
+
if (arg.startsWith("--") && arg !== "--purge" && arg !== "--yes" && arg !== "--dry-run") {
|
|
63
|
+
usage();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!purge && !bump) {
|
|
67
|
+
usage();
|
|
68
|
+
}
|
|
69
|
+
return { bump, purge, yes, dryRun };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Full release pipeline for a semver bump. */
|
|
73
|
+
async function runRelease(bump: Bump, options: ReleaseOptions): Promise<void> {
|
|
74
|
+
const testResult = await $`just test`.nothrow();
|
|
75
|
+
if (testResult.exitCode !== 0) process.exit(testResult.exitCode);
|
|
76
|
+
|
|
77
|
+
const currentVersion = readCurrentVersion();
|
|
78
|
+
const newVersion = applyBump(currentVersion, bump);
|
|
79
|
+
console.log(`Releasing ${currentVersion} → ${newVersion}`);
|
|
80
|
+
|
|
81
|
+
updateVersion(newVersion);
|
|
82
|
+
updateChangelog(newVersion);
|
|
83
|
+
|
|
84
|
+
const buildResult = await $`just build`.nothrow();
|
|
85
|
+
if (buildResult.exitCode !== 0) process.exit(buildResult.exitCode);
|
|
86
|
+
|
|
87
|
+
const archivePath = await updateReleaseFormula(newVersion);
|
|
88
|
+
|
|
89
|
+
const docgenResult = await $`just docgen`.nothrow();
|
|
90
|
+
if (docgenResult.exitCode !== 0) process.exit(docgenResult.exitCode);
|
|
91
|
+
|
|
92
|
+
await commitAndTag(newVersion);
|
|
93
|
+
await createGithubRelease(`v${newVersion}`, archivePath);
|
|
94
|
+
|
|
95
|
+
console.log(`Released v${newVersion}`);
|
|
96
|
+
|
|
97
|
+
if (options.purge) {
|
|
98
|
+
await purgeStaleReleases(options);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Applies a semver bump to `current` and returns the new version string. */
|
|
103
|
+
function applyBump(current: string, bump: Bump): string {
|
|
104
|
+
const [major, minor, patch] = current.split(".").map(Number) as [number, number, number];
|
|
105
|
+
if (bump === "major") return `${major + 1}.0.0`;
|
|
106
|
+
if (bump === "minor") return `${major}.${minor + 1}.0`;
|
|
107
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Commits all staged changes, creates an annotated tag, and pushes both to origin. */
|
|
111
|
+
async function commitAndTag(newVersion: string): Promise<void> {
|
|
112
|
+
await $`git add -A`;
|
|
113
|
+
await $`git commit -m ${`chore: release v${newVersion}`}`;
|
|
114
|
+
await $`git tag v${newVersion}`;
|
|
115
|
+
await $`git push`;
|
|
116
|
+
await $`git push origin v${newVersion}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Creates a GitHub release for `tag` with the zip archive attached. */
|
|
120
|
+
async function createGithubRelease(tag: string, archivePath: string): Promise<void> {
|
|
121
|
+
await $`gh release create ${tag} ${archivePath} --title ${tag} --generate-notes`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Reads the CLI version from `src/program.ts`. */
|
|
125
|
+
function readCurrentVersion(): string {
|
|
126
|
+
const content = fs.readFileSync(programPath, "utf-8");
|
|
127
|
+
const match = /version:\s*"([^"]+)"/.exec(content);
|
|
128
|
+
if (!match) {
|
|
129
|
+
process.stderr.write(`Could not read version from ${programPath}\n`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
const version = match[1];
|
|
133
|
+
if (!version) {
|
|
134
|
+
process.stderr.write(`Could not read version from ${programPath}\n`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const parts = version.split(".").map(Number);
|
|
138
|
+
if (parts.length !== 3 || parts.some(Number.isNaN)) {
|
|
139
|
+
process.stderr.write(`Invalid semver in ${programPath}: ${version}\n`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
return version;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Promotes `[Unreleased]` to a dated version section in `CHANGELOG.md`. */
|
|
146
|
+
function updateChangelog(newVersion: string): void {
|
|
147
|
+
const changelogPath = "CHANGELOG.md";
|
|
148
|
+
const content = fs.readFileSync(changelogPath, "utf-8");
|
|
149
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
150
|
+
fs.writeFileSync(
|
|
151
|
+
changelogPath,
|
|
152
|
+
content.replace(/^## \[Unreleased\]/m, `## [Unreleased]\n\n## [${newVersion}] - ${date}`),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Overwrites the version literal in `src/program.ts`. */
|
|
157
|
+
function updateVersion(newVersion: string): void {
|
|
158
|
+
const content = fs.readFileSync(programPath, "utf-8");
|
|
159
|
+
fs.writeFileSync(programPath, content.replace(/version:\s*"[^"]+"/, `version: "${newVersion}"`));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Writes the release formula with zip URL and archive sha256; returns the archive path. */
|
|
163
|
+
async function updateReleaseFormula(version: string): Promise<string> {
|
|
164
|
+
const { archivePath, sha256 } = await buildReleaseArchive(binaryPath);
|
|
165
|
+
fs.writeFileSync(formulaPath, renderReleaseFormula(version, sha256));
|
|
166
|
+
return archivePath;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Deletes all GitHub releases except the most recent. */
|
|
170
|
+
async function purgeStaleReleases(options: ReleaseOptions): Promise<void> {
|
|
171
|
+
const list = await $`gh release list -R ${releaseRepoSlug} --json tagName,publishedAt`.nothrow();
|
|
172
|
+
if (list.exitCode !== 0) process.exit(list.exitCode);
|
|
173
|
+
|
|
174
|
+
const releases = JSON.parse(list.stdout.toString()) as ReleaseTag[];
|
|
175
|
+
const toDelete = selectStaleReleaseTags(releases);
|
|
176
|
+
|
|
177
|
+
if (toDelete.length === 0) {
|
|
178
|
+
console.log("No stale releases to delete.");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log(`Will delete ${toDelete.length} release(s):`);
|
|
183
|
+
for (const tag of toDelete) {
|
|
184
|
+
console.log(` ${tag}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (options.dryRun) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!options.yes) {
|
|
192
|
+
if (!input.isTTY) {
|
|
193
|
+
process.stderr.write("Not a TTY; pass --yes to confirm purge.\n");
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
const rl = readline.createInterface({ input, output });
|
|
197
|
+
const answer = await rl.question("Delete these releases? [y/N] ");
|
|
198
|
+
rl.close();
|
|
199
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
200
|
+
console.log("Aborted.");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const tag of toDelete) {
|
|
206
|
+
const del = await $`gh release delete ${tag} -R ${releaseRepoSlug} --yes`.nothrow();
|
|
207
|
+
if (del.exitCode !== 0) process.exit(del.exitCode);
|
|
208
|
+
console.log(`Deleted ${tag}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await main();
|
|
@@ -29,15 +29,19 @@ function generateJson(root: SchemaRoot): Record<string, unknown> {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function writeBridge(path: string, scriptName: string, roots: SchemaRoot[], banner: string): void {
|
|
32
|
+
/** Sort by import binding so Biome `organizeImports` does not reorder the bridge file. */
|
|
33
|
+
const sorted = [...roots].sort((left, right) =>
|
|
34
|
+
jsonImportVar(left.outfile).localeCompare(jsonImportVar(right.outfile)),
|
|
35
|
+
);
|
|
32
36
|
const lines = [`// Auto-generated by scripts/${scriptName} — do not edit by hand.`, ""];
|
|
33
|
-
for (const root of
|
|
37
|
+
for (const root of sorted) {
|
|
34
38
|
const varName = jsonImportVar(root.outfile);
|
|
35
39
|
lines.push(`import ${varName} from "./generated/${root.outfile}";`);
|
|
36
40
|
}
|
|
37
|
-
if (
|
|
41
|
+
if (sorted.length > 0) {
|
|
38
42
|
lines.push("");
|
|
39
43
|
}
|
|
40
|
-
for (const root of
|
|
44
|
+
for (const root of sorted) {
|
|
41
45
|
const varName = jsonImportVar(root.outfile);
|
|
42
46
|
lines.push(`/** ${banner} \`${root.typeName}\`. */`);
|
|
43
47
|
lines.push(`export const ${root.exportName} = ${varName} as Record<string, unknown>;`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argsbarg",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"bun": ">=1.3"
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@biomejs/biome": "^2.5.0",
|
|
34
34
|
"@types/bun": "^1.3.12",
|
|
35
|
+
"dts-bundle-generator": "^9.5.1",
|
|
35
36
|
"typescript": "^5.9.3"
|
|
36
37
|
}
|
|
37
38
|
}
|