@outfitter/tooling 0.3.3 → 0.3.4

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.
Files changed (67) hide show
  1. package/.markdownlint-cli2.jsonc +55 -55
  2. package/README.md +21 -21
  3. package/dist/bun-version-compat.d.ts +2 -0
  4. package/dist/bun-version-compat.js +10 -0
  5. package/dist/cli/check-boundary-invocations.d.ts +34 -0
  6. package/dist/cli/check-boundary-invocations.js +14 -0
  7. package/dist/cli/check-bunup-registry.d.ts +36 -0
  8. package/dist/cli/check-bunup-registry.js +12 -0
  9. package/dist/cli/check-changeset.d.ts +66 -0
  10. package/dist/cli/check-changeset.js +20 -0
  11. package/dist/cli/check-clean-tree.d.ts +36 -0
  12. package/dist/cli/check-clean-tree.js +14 -0
  13. package/dist/cli/check-exports.d.ts +2 -0
  14. package/dist/cli/check-exports.js +14 -0
  15. package/dist/cli/check-markdown-links.d.ts +42 -0
  16. package/dist/cli/check-markdown-links.js +13 -0
  17. package/dist/cli/check-readme-imports.d.ts +60 -0
  18. package/dist/{shared/chunk-7tdgbqb0.js → cli/check-readme-imports.js} +7 -6
  19. package/dist/cli/check-tsdoc.d.ts +2 -0
  20. package/dist/cli/check-tsdoc.js +36 -0
  21. package/dist/cli/check.d.ts +19 -0
  22. package/dist/cli/check.js +10 -0
  23. package/dist/cli/fix.d.ts +19 -0
  24. package/dist/cli/fix.js +10 -0
  25. package/dist/cli/index.js +49 -1218
  26. package/dist/cli/init.d.ts +31 -0
  27. package/dist/cli/init.js +12 -0
  28. package/dist/cli/pre-push.d.ts +60 -0
  29. package/dist/cli/pre-push.js +27 -0
  30. package/dist/cli/upgrade-bun.d.ts +8 -0
  31. package/dist/cli/upgrade-bun.js +9 -0
  32. package/dist/index.d.ts +6 -186
  33. package/dist/index.js +4 -42
  34. package/dist/registry/build.d.ts +4 -0
  35. package/dist/registry/build.js +279 -0
  36. package/dist/registry/index.d.ts +3 -0
  37. package/dist/registry/index.js +1 -0
  38. package/dist/registry/schema.d.ts +2 -0
  39. package/dist/registry/schema.js +28 -0
  40. package/dist/shared/@outfitter/tooling-1hez6j9d.js +21 -0
  41. package/dist/shared/@outfitter/tooling-6cxfdx0q.js +187 -0
  42. package/dist/shared/{chunk-cmde0fwx.js → @outfitter/tooling-875svjnz.js} +16 -31
  43. package/dist/shared/@outfitter/tooling-9ram55dd.js +69 -0
  44. package/dist/shared/@outfitter/tooling-9vs606gq.d.ts +3 -0
  45. package/dist/shared/@outfitter/tooling-a4bfx4be.js +21 -0
  46. package/dist/shared/@outfitter/tooling-amrbp7cm.js +102 -0
  47. package/dist/shared/@outfitter/tooling-ctmgnap5.js +19 -0
  48. package/dist/shared/@outfitter/tooling-d363b88r.js +349 -0
  49. package/dist/shared/@outfitter/tooling-gcdvsqqp.js +73 -0
  50. package/dist/shared/@outfitter/tooling-h04te11c.js +231 -0
  51. package/dist/shared/@outfitter/tooling-ja1zg5yc.js +214 -0
  52. package/dist/shared/@outfitter/tooling-jnrs9rqd.js +4 -0
  53. package/dist/shared/@outfitter/tooling-mkynjra9.js +23 -0
  54. package/dist/shared/@outfitter/tooling-njw4z34x.d.ts +140 -0
  55. package/dist/shared/@outfitter/tooling-pq47jv6t.js +213 -0
  56. package/dist/shared/@outfitter/tooling-sjm8nebx.d.ts +109 -0
  57. package/dist/shared/@outfitter/tooling-vjmhvpjq.d.ts +29 -0
  58. package/dist/shared/@outfitter/tooling-wesswf21.d.ts +59 -0
  59. package/dist/shared/@outfitter/tooling-wwm97f47.js +81 -0
  60. package/dist/version.d.ts +2 -0
  61. package/dist/version.js +8 -0
  62. package/package.json +134 -131
  63. package/registry/registry.json +17 -10
  64. package/tsconfig.preset.bun.json +5 -5
  65. package/tsconfig.preset.json +33 -33
  66. package/biome.json +0 -81
  67. package/dist/shared/chunk-3s189drz.js +0 -4
@@ -0,0 +1,214 @@
1
+ // @bun
2
+ // packages/tooling/src/cli/check-markdown-links.ts
3
+ import { existsSync } from "fs";
4
+ import { dirname, resolve } from "path";
5
+ var MD_LINK_RE = /\[([^\]]*)\]\(([^)]+)\)/g;
6
+ var SKIP_PROTOCOLS = /^(https?:\/\/|mailto:|data:|tel:)/;
7
+ var FENCE_RE = /^(\s*)((`{3,})|~{3,})(.*)$/;
8
+ function stripInlineCodeSpans(line) {
9
+ let result = "";
10
+ let index = 0;
11
+ while (index < line.length) {
12
+ if (line[index] !== "`") {
13
+ result += line[index];
14
+ index += 1;
15
+ continue;
16
+ }
17
+ let tickCount = 1;
18
+ while (line[index + tickCount] === "`") {
19
+ tickCount += 1;
20
+ }
21
+ const opener = line.slice(index, index + tickCount);
22
+ index += tickCount;
23
+ let closingIndex = -1;
24
+ let cursor = index;
25
+ while (cursor < line.length) {
26
+ if (line[cursor] !== "`") {
27
+ cursor += 1;
28
+ continue;
29
+ }
30
+ let runLength = 1;
31
+ while (line[cursor + runLength] === "`") {
32
+ runLength += 1;
33
+ }
34
+ if (runLength === tickCount) {
35
+ closingIndex = cursor;
36
+ break;
37
+ }
38
+ cursor += runLength;
39
+ }
40
+ if (closingIndex === -1) {
41
+ result += opener;
42
+ continue;
43
+ }
44
+ index = closingIndex + tickCount;
45
+ }
46
+ return result;
47
+ }
48
+ function extractMarkdownLinks(content) {
49
+ const lines = content.split(`
50
+ `);
51
+ const links = [];
52
+ let inCodeFence = false;
53
+ let fenceChar = "";
54
+ let fenceCount = 0;
55
+ for (let i = 0;i < lines.length; i++) {
56
+ const line = lines[i];
57
+ const trimmed = line.trim();
58
+ const fenceMatch = FENCE_RE.exec(trimmed);
59
+ if (fenceMatch) {
60
+ const marker = fenceMatch[2] ?? "";
61
+ const char = marker[0] ?? "`";
62
+ const count = marker.length;
63
+ if (!inCodeFence) {
64
+ inCodeFence = true;
65
+ fenceChar = char;
66
+ fenceCount = count;
67
+ continue;
68
+ }
69
+ if (char === fenceChar && count >= fenceCount && !(fenceMatch[4] ?? "").trim()) {
70
+ inCodeFence = false;
71
+ continue;
72
+ }
73
+ }
74
+ if (inCodeFence)
75
+ continue;
76
+ const stripped = stripInlineCodeSpans(line);
77
+ for (const match of stripped.matchAll(MD_LINK_RE)) {
78
+ let target = match[2];
79
+ if (SKIP_PROTOCOLS.test(target))
80
+ continue;
81
+ if (target.startsWith("#"))
82
+ continue;
83
+ const hashIndex = target.indexOf("#");
84
+ if (hashIndex !== -1) {
85
+ target = target.substring(0, hashIndex);
86
+ }
87
+ if (target === "")
88
+ continue;
89
+ links.push({ target, line: i + 1 });
90
+ }
91
+ }
92
+ return links;
93
+ }
94
+ async function validateLinks(rootDir, files) {
95
+ const broken = [];
96
+ for (const file of files) {
97
+ const absolutePath = resolve(rootDir, file);
98
+ const content = await Bun.file(absolutePath).text();
99
+ const links = extractMarkdownLinks(content);
100
+ const fileDir = dirname(absolutePath);
101
+ for (const link of links) {
102
+ if (link.target.startsWith("docs/packages/") || link.target.startsWith("./docs/packages/") || link.target.startsWith("../docs/packages/")) {
103
+ continue;
104
+ }
105
+ const resolvedTarget = resolve(fileDir, link.target);
106
+ const relativeFromRoot = resolvedTarget.substring(rootDir.length + 1);
107
+ if (relativeFromRoot.startsWith("docs/packages/")) {
108
+ continue;
109
+ }
110
+ if (!existsSync(resolvedTarget)) {
111
+ broken.push({
112
+ source: file,
113
+ target: link.target,
114
+ line: link.line
115
+ });
116
+ }
117
+ }
118
+ }
119
+ return broken;
120
+ }
121
+ var DEFAULT_PATTERNS = [
122
+ "docs/**/*.md",
123
+ "packages/*/README.md",
124
+ "AGENTS.md",
125
+ "CLAUDE.md",
126
+ "README.md",
127
+ ".claude/CLAUDE.md"
128
+ ];
129
+ async function discoverFiles(rootDir, patterns) {
130
+ const files = new Set;
131
+ for (const pattern of patterns) {
132
+ const glob = new Bun.Glob(pattern);
133
+ for await (const match of glob.scan({ cwd: rootDir, absolute: false })) {
134
+ files.add(match);
135
+ }
136
+ }
137
+ return [...files].toSorted();
138
+ }
139
+ var COLORS = {
140
+ reset: "\x1B[0m",
141
+ red: "\x1B[31m",
142
+ green: "\x1B[32m",
143
+ yellow: "\x1B[33m",
144
+ dim: "\x1B[2m",
145
+ cyan: "\x1B[36m"
146
+ };
147
+ async function runCheckMarkdownLinks(rootDir, patterns) {
148
+ const effectivePatterns = patterns ?? DEFAULT_PATTERNS;
149
+ const files = await discoverFiles(rootDir, effectivePatterns);
150
+ if (files.length === 0) {
151
+ process.stdout.write(`No markdown files found.
152
+ `);
153
+ return 0;
154
+ }
155
+ const broken = await validateLinks(rootDir, files);
156
+ if (broken.length === 0) {
157
+ process.stdout.write(`${COLORS.green}All links valid across ${files.length} markdown file(s).${COLORS.reset}
158
+ `);
159
+ return 0;
160
+ }
161
+ const bySource = new Map;
162
+ for (const link of broken) {
163
+ const existing = bySource.get(link.source);
164
+ if (existing) {
165
+ existing.push(link);
166
+ } else {
167
+ bySource.set(link.source, [link]);
168
+ }
169
+ }
170
+ process.stderr.write(`${COLORS.red}Found ${broken.length} broken link(s):${COLORS.reset}
171
+
172
+ `);
173
+ for (const [source, links] of bySource) {
174
+ process.stderr.write(` ${COLORS.yellow}${source}${COLORS.reset}
175
+ `);
176
+ for (const link of links) {
177
+ process.stderr.write(` ${COLORS.cyan}${link.line}${COLORS.reset} ${COLORS.dim}${link.target}${COLORS.reset}
178
+ `);
179
+ }
180
+ process.stderr.write(`
181
+ `);
182
+ }
183
+ return 1;
184
+ }
185
+ async function main() {
186
+ const args = process.argv.slice(2);
187
+ if (args.includes("--help") || args.includes("-h")) {
188
+ process.stdout.write(`Usage: check-markdown-links [dirs/patterns...]
189
+
190
+ Validates that relative links in markdown files resolve to existing files.
191
+
192
+ Arguments:
193
+ dirs/patterns Glob patterns to scan (defaults: docs/, packages/*/README.md, etc.)
194
+
195
+ Options:
196
+ --help Show this help message
197
+
198
+ Exit codes:
199
+ 0 All links valid
200
+ 1 Broken links found
201
+ `);
202
+ process.exitCode = 0;
203
+ return;
204
+ }
205
+ const cwd = process.cwd();
206
+ const patterns = args.length > 0 ? args.filter((a) => !a.startsWith("--")) : undefined;
207
+ const exitCode = await runCheckMarkdownLinks(cwd, patterns);
208
+ process.exitCode = exitCode;
209
+ }
210
+ if (import.meta.main) {
211
+ main();
212
+ }
213
+
214
+ export { extractMarkdownLinks, validateLinks, runCheckMarkdownLinks };
@@ -0,0 +1,4 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ export { __require };
@@ -0,0 +1,23 @@
1
+ // @bun
2
+ // packages/tooling/src/bun-version-compat.ts
3
+ function parseSemver(version) {
4
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
5
+ if (!match?.[1] || !match[2] || !match[3]) {
6
+ return;
7
+ }
8
+ return {
9
+ major: Number.parseInt(match[1], 10),
10
+ minor: Number.parseInt(match[2], 10),
11
+ patch: Number.parseInt(match[3], 10)
12
+ };
13
+ }
14
+ function isTypesBunVersionCompatible(bunVersion, bunTypesVersion) {
15
+ const parsedBun = parseSemver(bunVersion);
16
+ const parsedTypes = parseSemver(bunTypesVersion);
17
+ if (!parsedBun || !parsedTypes) {
18
+ return bunVersion === bunTypesVersion;
19
+ }
20
+ return parsedBun.major === parsedTypes.major && parsedBun.minor === parsedTypes.minor && parsedTypes.patch <= parsedBun.patch;
21
+ }
22
+
23
+ export { parseSemver, isTypesBunVersionCompatible };
@@ -0,0 +1,140 @@
1
+ import ts from "typescript";
2
+ import { ZodType } from "zod";
3
+ /** Coverage classification for a single declaration. */
4
+ type CoverageLevel = "documented" | "partial" | "undocumented";
5
+ /** Result for a single exported declaration. */
6
+ interface DeclarationCoverage {
7
+ readonly name: string;
8
+ readonly kind: string;
9
+ readonly level: CoverageLevel;
10
+ readonly file: string;
11
+ readonly line: number;
12
+ }
13
+ /** Coverage summary statistics. */
14
+ interface CoverageSummary {
15
+ readonly documented: number;
16
+ readonly partial: number;
17
+ readonly undocumented: number;
18
+ readonly total: number;
19
+ readonly percentage: number;
20
+ }
21
+ /** Per-package TSDoc coverage stats. */
22
+ interface PackageCoverage {
23
+ readonly name: string;
24
+ readonly path: string;
25
+ readonly declarations: readonly DeclarationCoverage[];
26
+ readonly documented: number;
27
+ readonly partial: number;
28
+ readonly undocumented: number;
29
+ readonly total: number;
30
+ readonly percentage: number;
31
+ }
32
+ /** Aggregated result across all packages. */
33
+ interface TsDocCheckResult {
34
+ readonly ok: boolean;
35
+ readonly packages: readonly PackageCoverage[];
36
+ readonly summary: CoverageSummary;
37
+ }
38
+ /** Zod schema for {@link CoverageLevel}. */
39
+ declare const coverageLevelSchema: ZodType<CoverageLevel>;
40
+ /** Zod schema for {@link DeclarationCoverage}. */
41
+ declare const declarationCoverageSchema: ZodType<DeclarationCoverage>;
42
+ /** Zod schema for {@link CoverageSummary}. */
43
+ declare const coverageSummarySchema: ZodType<CoverageSummary>;
44
+ /** Zod schema for {@link PackageCoverage}. */
45
+ declare const packageCoverageSchema: ZodType<PackageCoverage>;
46
+ /** Zod schema for {@link TsDocCheckResult}. */
47
+ declare const tsDocCheckResultSchema: ZodType<TsDocCheckResult>;
48
+ /** Options for the check-tsdoc command. */
49
+ interface CheckTsDocOptions {
50
+ readonly strict?: boolean | undefined;
51
+ readonly json?: boolean | undefined;
52
+ readonly minCoverage?: number | undefined;
53
+ readonly cwd?: string | undefined;
54
+ readonly paths?: readonly string[] | undefined;
55
+ }
56
+ /**
57
+ * Check whether a node is an exported declaration worth checking.
58
+ *
59
+ * Returns true for function, interface, type alias, class, enum, and variable
60
+ * declarations that carry the `export` keyword. Re-exports (`{ ... } from`)
61
+ * and `*` are excluded since TSDoc belongs at the definition site.
62
+ */
63
+ declare function isExportedDeclaration(node: ts.Node): boolean;
64
+ /**
65
+ * Extract the name of a declaration node.
66
+ *
67
+ * For variable statements, returns the name of the first variable declarator.
68
+ * Returns `undefined` for anonymous declarations (e.g., `function() {}`).
69
+ */
70
+ declare function getDeclarationName(node: ts.Node): string | undefined;
71
+ /**
72
+ * Determine the kind label for a declaration node.
73
+ *
74
+ * Maps AST node types to human-readable kind strings used in coverage reports.
75
+ */
76
+ declare function getDeclarationKind(node: ts.Node): string;
77
+ /**
78
+ * Classify a declaration's TSDoc coverage level.
79
+ *
80
+ * - `"documented"` -- has a JSDoc comment with a description. For interfaces
81
+ * and classes, all members must also have JSDoc comments.
82
+ * - `"partial"` -- the declaration has a JSDoc comment but some members
83
+ * (in interfaces/classes) lack documentation.
84
+ * - `"undocumented"` -- no JSDoc comment at all.
85
+ */
86
+ declare function classifyDeclaration(node: ts.Node, sourceFile: ts.SourceFile): CoverageLevel;
87
+ /**
88
+ * Analyze all exported declarations in a source file for TSDoc coverage.
89
+ *
90
+ * Walks top-level statements, filters to exported declarations, and
91
+ * classifies each for documentation coverage.
92
+ */
93
+ declare function analyzeSourceFile(sourceFile: ts.SourceFile): DeclarationCoverage[];
94
+ /**
95
+ * Calculate aggregate coverage statistics from declaration results.
96
+ *
97
+ * Partial documentation counts as half coverage in the percentage calculation.
98
+ * An empty array returns 100% (no declarations to check).
99
+ */
100
+ declare function calculateCoverage(declarations: readonly DeclarationCoverage[]): {
101
+ documented: number;
102
+ partial: number;
103
+ undocumented: number;
104
+ total: number;
105
+ percentage: number;
106
+ };
107
+ /** Resolve whether JSON output mode is active. */
108
+ declare function resolveJsonMode(options?: CheckTsDocOptions): boolean;
109
+ /**
110
+ * Analyze TSDoc coverage across workspace packages.
111
+ *
112
+ * Pure function that discovers packages, analyzes TSDoc coverage on exported
113
+ * declarations, and returns the aggregated result. Does not print output or
114
+ * call `process.exit()`.
115
+ *
116
+ * @param options - Analysis options (paths, strict mode, coverage threshold)
117
+ * @returns Aggregated coverage result across all packages, or `null` if no packages found
118
+ */
119
+ declare function analyzeCheckTsdoc(options?: CheckTsDocOptions): TsDocCheckResult | null;
120
+ /**
121
+ * Print a TSDoc coverage result in human-readable format.
122
+ *
123
+ * Renders a bar chart per package with summary statistics. Writes to stdout/stderr.
124
+ *
125
+ * @param result - The coverage result to print
126
+ * @param options - Display options (strict mode, coverage threshold for warning)
127
+ */
128
+ declare function printCheckTsdocHuman(result: TsDocCheckResult, options?: {
129
+ strict?: boolean | undefined;
130
+ minCoverage?: number | undefined;
131
+ }): void;
132
+ /**
133
+ * Run check-tsdoc across workspace packages.
134
+ *
135
+ * Discovers packages with `src/index.ts` entry points, analyzes TSDoc
136
+ * coverage on exported declarations, and reports per-package statistics.
137
+ * Calls `process.exit()` on completion.
138
+ */
139
+ declare function runCheckTsdoc(options?: CheckTsDocOptions): Promise<void>;
140
+ export { CoverageLevel, DeclarationCoverage, CoverageSummary, PackageCoverage, TsDocCheckResult, coverageLevelSchema, declarationCoverageSchema, coverageSummarySchema, packageCoverageSchema, tsDocCheckResultSchema, CheckTsDocOptions, isExportedDeclaration, getDeclarationName, getDeclarationKind, classifyDeclaration, analyzeSourceFile, calculateCoverage, resolveJsonMode, analyzeCheckTsdoc, printCheckTsdocHuman, runCheckTsdoc };
@@ -0,0 +1,213 @@
1
+ // @bun
2
+ import {
3
+ isTypesBunVersionCompatible,
4
+ parseSemver
5
+ } from "./tooling-mkynjra9.js";
6
+
7
+ // packages/tooling/src/cli/upgrade-bun.ts
8
+ import { existsSync, readFileSync, writeFileSync } from "fs";
9
+ import { join } from "path";
10
+ var COLORS = {
11
+ reset: "\x1B[0m",
12
+ red: "\x1B[31m",
13
+ green: "\x1B[32m",
14
+ yellow: "\x1B[33m",
15
+ blue: "\x1B[34m"
16
+ };
17
+ function log(msg) {
18
+ process.stdout.write(`${msg}
19
+ `);
20
+ }
21
+ function info(msg) {
22
+ process.stdout.write(`${COLORS.blue}\u25B8${COLORS.reset} ${msg}
23
+ `);
24
+ }
25
+ function success(msg) {
26
+ process.stdout.write(`${COLORS.green}\u2713${COLORS.reset} ${msg}
27
+ `);
28
+ }
29
+ function warn(msg) {
30
+ process.stdout.write(`${COLORS.yellow}!${COLORS.reset} ${msg}
31
+ `);
32
+ }
33
+ async function fetchLatestVersion() {
34
+ const response = await fetch("https://api.github.com/repos/oven-sh/bun/releases/latest");
35
+ const data = await response.json();
36
+ const match = data.tag_name.match(/bun-v(.+)/);
37
+ if (!match?.[1]) {
38
+ throw new Error(`Could not parse version from tag: ${data.tag_name}`);
39
+ }
40
+ return match[1];
41
+ }
42
+ async function resolveTypesBunVersion(targetVersion) {
43
+ const response = await fetch("https://registry.npmjs.org/@types%2fbun");
44
+ if (!response.ok) {
45
+ throw new Error(`Failed to fetch @types/bun metadata: ${response.status}`);
46
+ }
47
+ const data = await response.json();
48
+ if (data.versions && Object.hasOwn(data.versions, targetVersion)) {
49
+ return targetVersion;
50
+ }
51
+ if (data.versions) {
52
+ const compatible = Object.keys(data.versions).filter((candidate) => isTypesBunVersionCompatible(targetVersion, candidate)).map((candidate) => ({
53
+ version: candidate,
54
+ parsed: parseSemver(candidate)
55
+ })).filter((candidate) => !!candidate.parsed).toSorted((left, right) => right.parsed.patch - left.parsed.patch);
56
+ const preferred = compatible[0];
57
+ if (preferred) {
58
+ return preferred.version;
59
+ }
60
+ }
61
+ const latest = data["dist-tags"]?.latest;
62
+ if (latest && isTypesBunVersionCompatible(targetVersion, latest)) {
63
+ return latest;
64
+ }
65
+ return targetVersion;
66
+ }
67
+ function findPackageJsonFiles(dir) {
68
+ const files = new Set;
69
+ const glob = new Bun.Glob("**/package.json");
70
+ for (const path of glob.scanSync({ cwd: dir })) {
71
+ if (!path.includes("node_modules")) {
72
+ files.add(join(dir, path));
73
+ }
74
+ }
75
+ const gitList = Bun.spawnSync(["git", "ls-files", "--cached", "--others", "--exclude-standard"], { cwd: dir });
76
+ if (gitList.exitCode === 0) {
77
+ const trackedAndUntrackedFiles = new TextDecoder().decode(gitList.stdout).split(`
78
+ `).filter((path) => path.endsWith("package.json")).filter((path) => !path.includes("node_modules")).map((path) => join(dir, path)).filter((path) => existsSync(path));
79
+ for (const filePath of trackedAndUntrackedFiles) {
80
+ files.add(filePath);
81
+ }
82
+ }
83
+ return [...files].toSorted();
84
+ }
85
+ function updatePackageManager(filePath, version) {
86
+ const content = readFileSync(filePath, "utf-8");
87
+ const pattern = /"packageManager":\s*"bun@[\d.]+"/;
88
+ if (!pattern.test(content)) {
89
+ return false;
90
+ }
91
+ const updated = content.replace(pattern, `"packageManager": "bun@${version}"`);
92
+ if (updated !== content) {
93
+ writeFileSync(filePath, updated);
94
+ return true;
95
+ }
96
+ return false;
97
+ }
98
+ function updateEnginesBun(filePath, version) {
99
+ const content = readFileSync(filePath, "utf-8");
100
+ const pattern = /"bun":\s*">=[\d.]+"/;
101
+ if (!pattern.test(content)) {
102
+ return false;
103
+ }
104
+ const updated = content.replace(pattern, `"bun": ">=${version}"`);
105
+ if (updated !== content) {
106
+ writeFileSync(filePath, updated);
107
+ return true;
108
+ }
109
+ return false;
110
+ }
111
+ function updateTypesBun(filePath, version) {
112
+ const content = readFileSync(filePath, "utf-8");
113
+ const pattern = /"@types\/bun":\s*"\^[\d.]+"/;
114
+ if (!pattern.test(content)) {
115
+ return false;
116
+ }
117
+ const updated = content.replace(pattern, `"@types/bun": "^${version}"`);
118
+ if (updated !== content) {
119
+ writeFileSync(filePath, updated);
120
+ return true;
121
+ }
122
+ return false;
123
+ }
124
+ async function runUpgradeBun(targetVersion, options = {}) {
125
+ const cwd = process.cwd();
126
+ const bunVersionFile = join(cwd, ".bun-version");
127
+ let version = targetVersion;
128
+ if (!version) {
129
+ info("Fetching latest Bun version...");
130
+ version = await fetchLatestVersion();
131
+ log(`Latest version: ${version}`);
132
+ }
133
+ const currentVersion = existsSync(bunVersionFile) ? readFileSync(bunVersionFile, "utf-8").trim() : "unknown";
134
+ log(`Current version: ${currentVersion}`);
135
+ if (currentVersion === version) {
136
+ success(`Already on version ${version}`);
137
+ return;
138
+ }
139
+ log("");
140
+ info(`Upgrading Bun: ${currentVersion} \u2192 ${version}`);
141
+ log("");
142
+ let typesVersion = version;
143
+ try {
144
+ info("Resolving @types/bun version...");
145
+ typesVersion = await resolveTypesBunVersion(version);
146
+ if (typesVersion !== version) {
147
+ warn(`@types/bun ${version} is not published yet; using @types/bun ${typesVersion}`);
148
+ }
149
+ } catch (error) {
150
+ warn(`Could not resolve @types/bun metadata (${error instanceof Error ? error.message : "unknown error"}), defaulting to ${version}`);
151
+ }
152
+ writeFileSync(bunVersionFile, `${version}
153
+ `);
154
+ success("Updated .bun-version");
155
+ const packageFiles = findPackageJsonFiles(cwd);
156
+ info("Updating packageManager...");
157
+ for (const file of packageFiles) {
158
+ if (updatePackageManager(file, version)) {
159
+ log(` ${file.replace(`${cwd}/`, "")}`);
160
+ }
161
+ }
162
+ info("Updating engines.bun...");
163
+ for (const file of packageFiles) {
164
+ if (updateEnginesBun(file, version)) {
165
+ log(` ${file.replace(`${cwd}/`, "")}`);
166
+ }
167
+ }
168
+ info("Updating @types/bun...");
169
+ for (const file of packageFiles) {
170
+ if (updateTypesBun(file, typesVersion)) {
171
+ log(` ${file.replace(`${cwd}/`, "")}`);
172
+ }
173
+ }
174
+ if (options.install !== false) {
175
+ log("");
176
+ info(`Installing Bun ${version}...`);
177
+ const installResult = Bun.spawnSync([
178
+ "bash",
179
+ "-c",
180
+ `curl -fsSL https://bun.sh/install | bash -s "bun-v${version}"`
181
+ ]);
182
+ if (installResult.exitCode !== 0) {
183
+ warn("Could not install Bun automatically");
184
+ log("Install manually: curl -fsSL https://bun.sh/install | bash");
185
+ } else {
186
+ success(`Bun ${version} installed`);
187
+ log("");
188
+ info("Updating lockfile...");
189
+ const bunInstall = Bun.spawnSync(["bun", "install"], {
190
+ cwd,
191
+ env: {
192
+ ...process.env,
193
+ BUN_INSTALL: `${process.env["HOME"]}/.bun`,
194
+ PATH: `${process.env["HOME"]}/.bun/bin:${process.env["PATH"]}`
195
+ }
196
+ });
197
+ if (bunInstall.exitCode === 0) {
198
+ success("Lockfile updated");
199
+ } else {
200
+ warn("Could not update lockfile - run 'bun install' manually");
201
+ }
202
+ }
203
+ }
204
+ log("");
205
+ success("Done! Changes ready to commit:");
206
+ log(" - .bun-version");
207
+ log(" - package.json files (packageManager, engines.bun, @types/bun)");
208
+ log(" - bun.lock");
209
+ log("");
210
+ log(`Commit with: git add -A && git commit -m 'chore: upgrade Bun to ${version}'`);
211
+ }
212
+
213
+ export { runUpgradeBun };
@@ -0,0 +1,109 @@
1
+ import { ZodType } from "zod";
2
+ /**
3
+ * File entry in a block.
4
+ */
5
+ interface FileEntry {
6
+ /** Destination path relative to project root */
7
+ path: string;
8
+ /** File contents (embedded in registry) */
9
+ content: string;
10
+ /** Whether to chmod +x after copying */
11
+ executable?: boolean | undefined;
12
+ /** Whether to process as a template (future) */
13
+ template?: boolean | undefined;
14
+ }
15
+ /**
16
+ * Schema for a file entry in a block.
17
+ * Represents a file that will be copied to the user's project.
18
+ */
19
+ declare const FileEntrySchema: ZodType<FileEntry>;
20
+ /**
21
+ * Block in the registry.
22
+ */
23
+ interface Block {
24
+ /** Block name (matches the key in blocks record) */
25
+ name: string;
26
+ /** Human-readable description */
27
+ description: string;
28
+ /** Files included in this block */
29
+ files?: FileEntry[] | undefined;
30
+ /** npm dependencies to add to package.json */
31
+ dependencies?: Record<string, string> | undefined;
32
+ /** npm devDependencies to add to package.json */
33
+ devDependencies?: Record<string, string> | undefined;
34
+ /** Other blocks this block extends (for composite blocks) */
35
+ extends?: string[] | undefined;
36
+ }
37
+ /**
38
+ * Schema for a block in the registry.
39
+ * A block is a collection of related files that can be added together.
40
+ */
41
+ declare const BlockSchema: ZodType<Block>;
42
+ /**
43
+ * Complete registry structure.
44
+ */
45
+ interface Registry {
46
+ /** Registry schema version */
47
+ version: string;
48
+ /** Map of block name to block definition */
49
+ blocks: Record<string, Block>;
50
+ }
51
+ /**
52
+ * Schema for the complete registry.
53
+ * Contains all available blocks with their files and metadata.
54
+ */
55
+ declare const RegistrySchema: ZodType<Registry>;
56
+ /**
57
+ * Block definition used in the build script.
58
+ * Specifies how to collect source files into a block.
59
+ */
60
+ interface BlockDefinition {
61
+ /** Human-readable description */
62
+ description: string;
63
+ /** Source file paths (relative to repo root) */
64
+ files?: string[];
65
+ /** Remap source paths to destination paths */
66
+ remap?: Record<string, string>;
67
+ /** npm dependencies */
68
+ dependencies?: Record<string, string>;
69
+ /** npm devDependencies */
70
+ devDependencies?: Record<string, string>;
71
+ /** Other blocks this block extends */
72
+ extends?: string[];
73
+ }
74
+ /**
75
+ * Configuration for the registry build.
76
+ */
77
+ interface RegistryBuildConfig {
78
+ /** Registry schema version */
79
+ version: string;
80
+ /** Block definitions */
81
+ blocks: Record<string, BlockDefinition>;
82
+ }
83
+ /**
84
+ * Result of adding a block to a project.
85
+ */
86
+ interface AddBlockResult {
87
+ /** Files that were created */
88
+ created: string[];
89
+ /** Files that were skipped (already exist) */
90
+ skipped: string[];
91
+ /** Files that were overwritten (with --force) */
92
+ overwritten: string[];
93
+ /** Dependencies added to package.json */
94
+ dependencies: Record<string, string>;
95
+ /** devDependencies added to package.json */
96
+ devDependencies: Record<string, string>;
97
+ }
98
+ /**
99
+ * Options for the add command.
100
+ */
101
+ interface AddBlockOptions {
102
+ /** Overwrite existing files */
103
+ force?: boolean;
104
+ /** Show what would be added without making changes */
105
+ dryRun?: boolean;
106
+ /** Working directory (defaults to cwd) */
107
+ cwd?: string;
108
+ }
109
+ export { FileEntry, FileEntrySchema, Block, BlockSchema, Registry, RegistrySchema, BlockDefinition, RegistryBuildConfig, AddBlockResult, AddBlockOptions };