@outfitter/tooling 0.3.3 → 0.3.5
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/.markdownlint-cli2.jsonc +55 -55
- package/README.md +33 -24
- package/dist/bun-version-compat.d.ts +2 -0
- package/dist/bun-version-compat.js +10 -0
- package/dist/cli/check-boundary-invocations.d.ts +34 -0
- package/dist/cli/check-boundary-invocations.js +14 -0
- package/dist/cli/check-bunup-registry.d.ts +36 -0
- package/dist/cli/check-bunup-registry.js +12 -0
- package/dist/cli/check-changeset.d.ts +82 -0
- package/dist/cli/check-changeset.js +24 -0
- package/dist/cli/check-clean-tree.d.ts +36 -0
- package/dist/cli/check-clean-tree.js +14 -0
- package/dist/cli/check-exports.d.ts +3 -0
- package/dist/cli/check-exports.js +17 -0
- package/dist/cli/check-home-paths.d.ts +31 -0
- package/dist/cli/check-home-paths.js +12 -0
- package/dist/cli/check-markdown-links.d.ts +42 -0
- package/dist/cli/check-markdown-links.js +13 -0
- package/dist/cli/check-readme-imports.d.ts +61 -0
- package/dist/{shared/chunk-7tdgbqb0.js → cli/check-readme-imports.js} +7 -6
- package/dist/cli/check-tsdoc.d.ts +5 -0
- package/dist/cli/check-tsdoc.js +42 -0
- package/dist/cli/check.d.ts +19 -0
- package/dist/cli/check.js +10 -0
- package/dist/cli/fix.d.ts +19 -0
- package/dist/cli/fix.js +10 -0
- package/dist/cli/index.js +61 -1218
- package/dist/cli/init.d.ts +31 -0
- package/dist/cli/init.js +12 -0
- package/dist/cli/internal/exports-analysis.d.ts +2 -0
- package/dist/cli/internal/exports-analysis.js +10 -0
- package/dist/cli/internal/exports-fs.d.ts +17 -0
- package/dist/cli/internal/exports-fs.js +9 -0
- package/dist/cli/internal/pre-push-checks.d.ts +2 -0
- package/dist/cli/internal/pre-push-checks.js +37 -0
- package/dist/cli/internal/tsdoc-analysis.d.ts +3 -0
- package/dist/cli/internal/tsdoc-analysis.js +26 -0
- package/dist/cli/internal/tsdoc-formatting.d.ts +3 -0
- package/dist/cli/internal/tsdoc-formatting.js +10 -0
- package/dist/cli/internal/tsdoc-types.d.ts +2 -0
- package/dist/cli/internal/tsdoc-types.js +16 -0
- package/dist/cli/pre-push.d.ts +7 -0
- package/dist/cli/pre-push.js +29 -0
- package/dist/cli/upgrade-bun.d.ts +8 -0
- package/dist/cli/upgrade-bun.js +9 -0
- package/dist/index.d.ts +9 -186
- package/dist/index.js +4 -42
- package/dist/registry/build.d.ts +4 -0
- package/dist/registry/build.js +279 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.js +1 -0
- package/dist/registry/schema.d.ts +2 -0
- package/dist/registry/schema.js +28 -0
- package/dist/shared/@outfitter/tooling-0zjz8eg9.js +106 -0
- package/dist/shared/@outfitter/tooling-1hez6j9d.js +21 -0
- package/dist/shared/@outfitter/tooling-2vv5y3s4.js +145 -0
- package/dist/shared/{chunk-cmde0fwx.js → @outfitter/tooling-5xxctk9b.js} +12 -138
- package/dist/shared/@outfitter/tooling-5ynz680q.js +59 -0
- package/dist/shared/@outfitter/tooling-7437rmy6.js +39 -0
- package/dist/shared/@outfitter/tooling-8qcwr06t.d.ts +74 -0
- package/dist/shared/@outfitter/tooling-9ram55dd.js +69 -0
- package/dist/shared/@outfitter/tooling-9vs606gq.d.ts +3 -0
- package/dist/shared/@outfitter/tooling-a4bfx4be.js +21 -0
- package/dist/shared/@outfitter/tooling-a59br34g.js +32 -0
- package/dist/shared/@outfitter/tooling-a6q3zh7t.js +86 -0
- package/dist/shared/@outfitter/tooling-amrbp7cm.js +102 -0
- package/dist/shared/@outfitter/tooling-ayps7c4x.js +58 -0
- package/dist/shared/@outfitter/tooling-c8q6mj8z.js +228 -0
- package/dist/shared/@outfitter/tooling-cb0b8wsx.d.ts +57 -0
- package/dist/shared/@outfitter/tooling-ctmgnap5.js +19 -0
- package/dist/shared/@outfitter/tooling-f8q38e9z.d.ts +16 -0
- package/dist/shared/@outfitter/tooling-gcdvsqqp.js +73 -0
- package/dist/shared/@outfitter/tooling-h5dnevjw.js +139 -0
- package/dist/shared/@outfitter/tooling-j8d1h2zd.d.ts +10 -0
- package/dist/shared/@outfitter/tooling-ja1zg5yc.js +214 -0
- package/dist/shared/@outfitter/tooling-jnrs9rqd.js +4 -0
- package/dist/shared/@outfitter/tooling-mkynjra9.js +23 -0
- package/dist/shared/@outfitter/tooling-mq2xvz96.js +285 -0
- package/dist/shared/@outfitter/tooling-pq47jv6t.js +213 -0
- package/dist/shared/@outfitter/tooling-sjm8nebx.d.ts +109 -0
- package/dist/shared/@outfitter/tooling-stgnc2zx.d.ts +85 -0
- package/dist/shared/@outfitter/tooling-tj9p41vj.d.ts +55 -0
- package/dist/shared/@outfitter/tooling-vjmhvpjq.d.ts +29 -0
- package/dist/shared/@outfitter/tooling-wwm97f47.js +81 -0
- package/dist/shared/@outfitter/tooling-y43b117h.d.ts +13 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +8 -0
- package/lefthook.yml +5 -1
- package/package.json +140 -131
- package/registry/registry.json +19 -12
- package/tsconfig.preset.bun.json +5 -5
- package/tsconfig.preset.json +33 -33
- package/biome.json +0 -81
- package/dist/shared/chunk-3s189drz.js +0 -4
package/dist/cli/index.js
CHANGED
|
@@ -1,1229 +1,62 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
import {
|
|
4
|
+
runCheckCleanTree
|
|
5
|
+
} from "../shared/@outfitter/tooling-gcdvsqqp.js";
|
|
6
|
+
import {
|
|
7
|
+
runCheckHomePaths
|
|
8
|
+
} from "../shared/@outfitter/tooling-h5dnevjw.js";
|
|
9
|
+
import {
|
|
10
|
+
runInit
|
|
11
|
+
} from "../shared/@outfitter/tooling-9ram55dd.js";
|
|
12
|
+
import {
|
|
13
|
+
runPrePush
|
|
14
|
+
} from "../shared/@outfitter/tooling-2vv5y3s4.js";
|
|
15
|
+
import"../shared/@outfitter/tooling-c8q6mj8z.js";
|
|
16
|
+
import {
|
|
17
|
+
runFix
|
|
18
|
+
} from "../shared/@outfitter/tooling-1hez6j9d.js";
|
|
19
|
+
import {
|
|
20
|
+
runCheckBoundaryInvocations
|
|
21
|
+
} from "../shared/@outfitter/tooling-amrbp7cm.js";
|
|
2
22
|
import {
|
|
3
|
-
VERSION,
|
|
4
|
-
analyzeSourceFile,
|
|
5
|
-
calculateCoverage,
|
|
6
23
|
runCheckTsdoc
|
|
7
|
-
} from "../shared/
|
|
24
|
+
} from "../shared/@outfitter/tooling-a59br34g.js";
|
|
25
|
+
import"../shared/@outfitter/tooling-7437rmy6.js";
|
|
26
|
+
import"../shared/@outfitter/tooling-5ynz680q.js";
|
|
27
|
+
import"../shared/@outfitter/tooling-5xxctk9b.js";
|
|
28
|
+
import {
|
|
29
|
+
runCheckExports
|
|
30
|
+
} from "../shared/@outfitter/tooling-0zjz8eg9.js";
|
|
31
|
+
import"../shared/@outfitter/tooling-a6q3zh7t.js";
|
|
32
|
+
import"../shared/@outfitter/tooling-ayps7c4x.js";
|
|
33
|
+
import {
|
|
34
|
+
runCheck
|
|
35
|
+
} from "../shared/@outfitter/tooling-a4bfx4be.js";
|
|
36
|
+
import {
|
|
37
|
+
runCheckBunupRegistry
|
|
38
|
+
} from "../shared/@outfitter/tooling-wwm97f47.js";
|
|
39
|
+
import {
|
|
40
|
+
runUpgradeBun
|
|
41
|
+
} from "../shared/@outfitter/tooling-pq47jv6t.js";
|
|
42
|
+
import"../shared/@outfitter/tooling-mkynjra9.js";
|
|
43
|
+
import {
|
|
44
|
+
VERSION
|
|
45
|
+
} from "../shared/@outfitter/tooling-ctmgnap5.js";
|
|
46
|
+
import {
|
|
47
|
+
runCheckChangeset
|
|
48
|
+
} from "../shared/@outfitter/tooling-mq2xvz96.js";
|
|
49
|
+
import {
|
|
50
|
+
runCheckMarkdownLinks
|
|
51
|
+
} from "../shared/@outfitter/tooling-ja1zg5yc.js";
|
|
8
52
|
import {
|
|
9
53
|
__require
|
|
10
|
-
} from "../shared/
|
|
54
|
+
} from "../shared/@outfitter/tooling-jnrs9rqd.js";
|
|
11
55
|
|
|
12
|
-
// src/cli/index.ts
|
|
56
|
+
// packages/tooling/src/cli/index.ts
|
|
13
57
|
import { exitWithError } from "@outfitter/cli";
|
|
14
58
|
import { createCLI } from "@outfitter/cli/command";
|
|
15
59
|
import { Command } from "commander";
|
|
16
|
-
|
|
17
|
-
// src/cli/check.ts
|
|
18
|
-
function buildCheckCommand(options) {
|
|
19
|
-
const cmd = ["ultracite", "check"];
|
|
20
|
-
if (options.paths && options.paths.length > 0) {
|
|
21
|
-
cmd.push(...options.paths);
|
|
22
|
-
}
|
|
23
|
-
return cmd;
|
|
24
|
-
}
|
|
25
|
-
async function runCheck(paths = []) {
|
|
26
|
-
const cmd = buildCheckCommand({ paths });
|
|
27
|
-
process.stdout.write(`Running: bun x ${cmd.join(" ")}
|
|
28
|
-
`);
|
|
29
|
-
const proc = Bun.spawn(["bun", "x", ...cmd], {
|
|
30
|
-
stdio: ["inherit", "inherit", "inherit"]
|
|
31
|
-
});
|
|
32
|
-
const exitCode = await proc.exited;
|
|
33
|
-
process.exit(exitCode);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// src/cli/check-boundary-invocations.ts
|
|
37
|
-
import { relative, resolve } from "node:path";
|
|
38
|
-
var ROOT_RUNS_PACKAGE_SRC = /\bbun(?:x)?\s+(?:run\s+)?(?:\.\.\/|\.\/)?packages\/[^/\s]+\/src\/\S+/;
|
|
39
|
-
var CD_PACKAGE_THEN_RUNS_SRC = /\bcd\s+(?:\.\.\/|\.\/)?packages\/[^/\s]+\s*&&\s*bun(?:x)?\s+(?:run\s+)?(?:\.\.\/|\.\/)?src\/\S+/;
|
|
40
|
-
function detectBoundaryViolation(location) {
|
|
41
|
-
if (ROOT_RUNS_PACKAGE_SRC.test(location.command)) {
|
|
42
|
-
return { ...location, rule: "root-runs-package-src" };
|
|
43
|
-
}
|
|
44
|
-
if (CD_PACKAGE_THEN_RUNS_SRC.test(location.command)) {
|
|
45
|
-
return { ...location, rule: "cd-package-then-runs-src" };
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
function findBoundaryViolations(entries) {
|
|
50
|
-
const violations = [];
|
|
51
|
-
for (const entry of entries) {
|
|
52
|
-
for (const [scriptName, command] of Object.entries(entry.scripts)) {
|
|
53
|
-
const violation = detectBoundaryViolation({
|
|
54
|
-
file: entry.file,
|
|
55
|
-
scriptName,
|
|
56
|
-
command
|
|
57
|
-
});
|
|
58
|
-
if (violation) {
|
|
59
|
-
violations.push(violation);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return violations.sort((a, b) => {
|
|
64
|
-
const fileCompare = a.file.localeCompare(b.file);
|
|
65
|
-
if (fileCompare !== 0) {
|
|
66
|
-
return fileCompare;
|
|
67
|
-
}
|
|
68
|
-
return a.scriptName.localeCompare(b.scriptName);
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
async function readScriptEntries(cwd, options = {}) {
|
|
72
|
-
const entries = [];
|
|
73
|
-
const rootPackagePath = resolve(cwd, "package.json");
|
|
74
|
-
const candidatePaths = [rootPackagePath];
|
|
75
|
-
if (options.appManifestRelativePaths) {
|
|
76
|
-
for (const file of options.appManifestRelativePaths) {
|
|
77
|
-
candidatePaths.push(resolve(cwd, file));
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
const appPackageGlob = new Bun.Glob("apps/*/package.json");
|
|
81
|
-
for (const match of appPackageGlob.scanSync({ cwd })) {
|
|
82
|
-
candidatePaths.push(resolve(cwd, match));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
const readPackageJson = options.readPackageJson ?? (async (filePath) => await Bun.file(filePath).json());
|
|
86
|
-
for (const filePath of candidatePaths) {
|
|
87
|
-
const isRootManifest = filePath === rootPackagePath;
|
|
88
|
-
try {
|
|
89
|
-
const pkg = await readPackageJson(filePath);
|
|
90
|
-
if (!pkg.scripts) {
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
entries.push({
|
|
94
|
-
file: relative(cwd, filePath),
|
|
95
|
-
scripts: pkg.scripts
|
|
96
|
-
});
|
|
97
|
-
} catch (error) {
|
|
98
|
-
if (isRootManifest) {
|
|
99
|
-
const message = error instanceof Error ? error.message : "unknown parse error";
|
|
100
|
-
throw new Error(`Failed to read root package manifest (${filePath}): ${message}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return entries;
|
|
105
|
-
}
|
|
106
|
-
async function runCheckBoundaryInvocations() {
|
|
107
|
-
const cwd = process.cwd();
|
|
108
|
-
let entries;
|
|
109
|
-
try {
|
|
110
|
-
entries = await readScriptEntries(cwd);
|
|
111
|
-
} catch (error) {
|
|
112
|
-
const message = error instanceof Error ? error.message : "unknown read failure";
|
|
113
|
-
process.stderr.write(`Boundary invocation check failed before evaluation: ${message}
|
|
114
|
-
`);
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
const violations = findBoundaryViolations(entries);
|
|
118
|
-
if (violations.length === 0) {
|
|
119
|
-
process.stdout.write(`No boundary invocation violations detected in root/apps scripts.
|
|
120
|
-
`);
|
|
121
|
-
process.exit(0);
|
|
122
|
-
}
|
|
123
|
-
process.stderr.write(`Boundary invocation violations detected:
|
|
124
|
-
|
|
125
|
-
`);
|
|
126
|
-
for (const violation of violations) {
|
|
127
|
-
process.stderr.write(`- ${violation.file}#${violation.scriptName}: ${violation.command}
|
|
128
|
-
`);
|
|
129
|
-
}
|
|
130
|
-
process.stderr.write("\nUse canonical command surfaces (e.g. `outfitter repo ...` or package bins) instead of executing packages/*/src directly.\n");
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// src/cli/check-bunup-registry.ts
|
|
135
|
-
import { resolve as resolve2 } from "node:path";
|
|
136
|
-
function extractBunupFilterName(script) {
|
|
137
|
-
const match = script.match(/bunup\s+--filter\s+(\S+)/);
|
|
138
|
-
return match?.[1] ?? null;
|
|
139
|
-
}
|
|
140
|
-
function findUnregisteredPackages(packagesWithFilter, registeredNames) {
|
|
141
|
-
const registered = new Set(registeredNames);
|
|
142
|
-
const missing = packagesWithFilter.filter((name) => !registered.has(name)).sort();
|
|
143
|
-
return {
|
|
144
|
-
ok: missing.length === 0,
|
|
145
|
-
missing
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
var COLORS = {
|
|
149
|
-
reset: "\x1B[0m",
|
|
150
|
-
red: "\x1B[31m",
|
|
151
|
-
green: "\x1B[32m",
|
|
152
|
-
yellow: "\x1B[33m",
|
|
153
|
-
blue: "\x1B[34m",
|
|
154
|
-
dim: "\x1B[2m"
|
|
155
|
-
};
|
|
156
|
-
async function runCheckBunupRegistry() {
|
|
157
|
-
const cwd = process.cwd();
|
|
158
|
-
const configPath = resolve2(cwd, "bunup.config.ts");
|
|
159
|
-
let registeredNames;
|
|
160
|
-
try {
|
|
161
|
-
const configModule = await import(configPath);
|
|
162
|
-
const rawConfig = configModule.default;
|
|
163
|
-
if (!Array.isArray(rawConfig)) {
|
|
164
|
-
process.stderr.write(`bunup.config.ts must export a workspace array
|
|
165
|
-
`);
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
registeredNames = rawConfig.map((entry) => entry.name);
|
|
169
|
-
} catch {
|
|
170
|
-
process.stderr.write(`Could not load bunup.config.ts from ${cwd}
|
|
171
|
-
`);
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
const packagesWithFilter = [];
|
|
175
|
-
const glob = new Bun.Glob("{packages,apps}/*/package.json");
|
|
176
|
-
for (const match of glob.scanSync({ cwd })) {
|
|
177
|
-
const pkgPath = resolve2(cwd, match);
|
|
178
|
-
try {
|
|
179
|
-
const pkg = await Bun.file(pkgPath).json();
|
|
180
|
-
const buildScript = pkg.scripts?.["build"];
|
|
181
|
-
if (!buildScript)
|
|
182
|
-
continue;
|
|
183
|
-
const filterName = extractBunupFilterName(buildScript);
|
|
184
|
-
if (filterName) {
|
|
185
|
-
packagesWithFilter.push(filterName);
|
|
186
|
-
}
|
|
187
|
-
} catch {}
|
|
188
|
-
}
|
|
189
|
-
const result = findUnregisteredPackages(packagesWithFilter, registeredNames);
|
|
190
|
-
if (result.ok) {
|
|
191
|
-
process.stdout.write(`${COLORS.green}All ${packagesWithFilter.length} packages with bunup --filter are registered in bunup.config.ts.${COLORS.reset}
|
|
192
|
-
`);
|
|
193
|
-
process.exit(0);
|
|
194
|
-
}
|
|
195
|
-
process.stderr.write(`${COLORS.red}${result.missing.length} package(s) have bunup --filter build scripts but are not registered in bunup.config.ts:${COLORS.reset}
|
|
196
|
-
|
|
197
|
-
`);
|
|
198
|
-
for (const name of result.missing) {
|
|
199
|
-
process.stderr.write(` ${COLORS.yellow}${name}${COLORS.reset} ${COLORS.dim}(missing from workspace array)${COLORS.reset}
|
|
200
|
-
`);
|
|
201
|
-
}
|
|
202
|
-
process.stderr.write(`
|
|
203
|
-
Add the missing entries to ${COLORS.blue}bunup.config.ts${COLORS.reset} defineWorkspace array.
|
|
204
|
-
`);
|
|
205
|
-
process.stderr.write(`Without registration, ${COLORS.dim}bunup --filter <name>${COLORS.reset} silently exits with no output.
|
|
206
|
-
`);
|
|
207
|
-
process.exit(1);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// src/cli/check-changeset.ts
|
|
211
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
212
|
-
import { join } from "node:path";
|
|
213
|
-
function getChangedPackagePaths(files) {
|
|
214
|
-
const packageNames = new Set;
|
|
215
|
-
const pattern = /^packages\/([^/]+)\/src\//;
|
|
216
|
-
for (const file of files) {
|
|
217
|
-
const match = pattern.exec(file);
|
|
218
|
-
if (match?.[1]) {
|
|
219
|
-
packageNames.add(match[1]);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return [...packageNames].sort();
|
|
223
|
-
}
|
|
224
|
-
function getChangedChangesetFiles(files) {
|
|
225
|
-
const pattern = /^\.changeset\/([^/]+\.md)$/;
|
|
226
|
-
const results = [];
|
|
227
|
-
for (const file of files) {
|
|
228
|
-
const match = pattern.exec(file);
|
|
229
|
-
if (match?.[1] && match[1] !== "README.md") {
|
|
230
|
-
results.push(match[1]);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return results.sort();
|
|
234
|
-
}
|
|
235
|
-
function checkChangesetRequired(changedPackages, changesetFiles) {
|
|
236
|
-
if (changedPackages.length === 0) {
|
|
237
|
-
return { ok: true, missingFor: [] };
|
|
238
|
-
}
|
|
239
|
-
if (changesetFiles.length > 0) {
|
|
240
|
-
return { ok: true, missingFor: [] };
|
|
241
|
-
}
|
|
242
|
-
return { ok: false, missingFor: changedPackages };
|
|
243
|
-
}
|
|
244
|
-
function parseIgnoredPackagesFromChangesetConfig(jsonContent) {
|
|
245
|
-
try {
|
|
246
|
-
const parsed = JSON.parse(jsonContent);
|
|
247
|
-
if (!Array.isArray(parsed.ignore)) {
|
|
248
|
-
return [];
|
|
249
|
-
}
|
|
250
|
-
return parsed.ignore.filter((entry) => typeof entry === "string");
|
|
251
|
-
} catch {
|
|
252
|
-
return [];
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
function parseChangesetFrontmatterPackageNames(markdownContent) {
|
|
256
|
-
const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdownContent);
|
|
257
|
-
if (!frontmatterMatch?.[1]) {
|
|
258
|
-
return [];
|
|
259
|
-
}
|
|
260
|
-
const packages = new Set;
|
|
261
|
-
for (const line of frontmatterMatch[1].split(/\r?\n/)) {
|
|
262
|
-
const trimmed = line.trim();
|
|
263
|
-
const match = /^(["']?)(@[^"':\s]+\/[^"':\s]+)\1\s*:/.exec(trimmed);
|
|
264
|
-
if (match?.[2]) {
|
|
265
|
-
packages.add(match[2]);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return [...packages].sort();
|
|
269
|
-
}
|
|
270
|
-
function findIgnoredPackageReferences(input) {
|
|
271
|
-
if (input.ignoredPackages.length === 0 || input.changesetFiles.length === 0) {
|
|
272
|
-
return [];
|
|
273
|
-
}
|
|
274
|
-
const ignored = new Set(input.ignoredPackages);
|
|
275
|
-
const results = [];
|
|
276
|
-
for (const file of input.changesetFiles) {
|
|
277
|
-
const content = input.readChangesetFile(file);
|
|
278
|
-
const referencedPackages = parseChangesetFrontmatterPackageNames(content);
|
|
279
|
-
const invalidReferences = referencedPackages.filter((pkg) => ignored.has(pkg));
|
|
280
|
-
if (invalidReferences.length > 0) {
|
|
281
|
-
results.push({ file, packages: invalidReferences.sort() });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return results.sort((a, b) => a.file.localeCompare(b.file));
|
|
285
|
-
}
|
|
286
|
-
function loadIgnoredPackages(cwd) {
|
|
287
|
-
const configPath = join(cwd, ".changeset", "config.json");
|
|
288
|
-
if (!existsSync(configPath)) {
|
|
289
|
-
return [];
|
|
290
|
-
}
|
|
291
|
-
try {
|
|
292
|
-
return parseIgnoredPackagesFromChangesetConfig(readFileSync(configPath, "utf-8"));
|
|
293
|
-
} catch {
|
|
294
|
-
return [];
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
function getIgnoredReferencesForChangedChangesets(cwd, changesetFiles) {
|
|
298
|
-
const ignoredPackages = loadIgnoredPackages(cwd);
|
|
299
|
-
return findIgnoredPackageReferences({
|
|
300
|
-
changesetFiles,
|
|
301
|
-
ignoredPackages,
|
|
302
|
-
readChangesetFile: (filename) => {
|
|
303
|
-
try {
|
|
304
|
-
return readFileSync(join(cwd, ".changeset", filename), "utf-8");
|
|
305
|
-
} catch {
|
|
306
|
-
return "";
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
var COLORS2 = {
|
|
312
|
-
reset: "\x1B[0m",
|
|
313
|
-
red: "\x1B[31m",
|
|
314
|
-
green: "\x1B[32m",
|
|
315
|
-
yellow: "\x1B[33m",
|
|
316
|
-
blue: "\x1B[34m",
|
|
317
|
-
dim: "\x1B[2m"
|
|
318
|
-
};
|
|
319
|
-
async function runCheckChangeset(options = {}) {
|
|
320
|
-
if (options.skip || process.env["NO_CHANGESET"] === "1") {
|
|
321
|
-
process.stdout.write(`${COLORS2.dim}check-changeset skipped (NO_CHANGESET=1)${COLORS2.reset}
|
|
322
|
-
`);
|
|
323
|
-
process.exit(0);
|
|
324
|
-
}
|
|
325
|
-
if (process.env["GITHUB_EVENT_NAME"] === "push") {
|
|
326
|
-
process.stdout.write(`${COLORS2.dim}check-changeset skipped (push event)${COLORS2.reset}
|
|
327
|
-
`);
|
|
328
|
-
process.exit(0);
|
|
329
|
-
}
|
|
330
|
-
const cwd = process.cwd();
|
|
331
|
-
let changedFiles;
|
|
332
|
-
try {
|
|
333
|
-
const proc = Bun.spawnSync(["git", "diff", "--name-only", "origin/main...HEAD"], { cwd });
|
|
334
|
-
if (proc.exitCode !== 0) {
|
|
335
|
-
process.exit(0);
|
|
336
|
-
}
|
|
337
|
-
changedFiles = proc.stdout.toString().trim().split(`
|
|
338
|
-
`).filter((line) => line.length > 0);
|
|
339
|
-
} catch {
|
|
340
|
-
process.exit(0);
|
|
341
|
-
}
|
|
342
|
-
const changedPackages = getChangedPackagePaths(changedFiles);
|
|
343
|
-
if (changedPackages.length === 0) {
|
|
344
|
-
process.stdout.write(`${COLORS2.green}No package source changes detected.${COLORS2.reset}
|
|
345
|
-
`);
|
|
346
|
-
process.exit(0);
|
|
347
|
-
}
|
|
348
|
-
const changesetFiles = getChangedChangesetFiles(changedFiles);
|
|
349
|
-
const check = checkChangesetRequired(changedPackages, changesetFiles);
|
|
350
|
-
if (!check.ok) {
|
|
351
|
-
process.stderr.write(`${COLORS2.yellow}No changeset found.${COLORS2.reset} ` + "Consider adding one with `bun run changeset` for a custom changelog entry.\n\n");
|
|
352
|
-
process.stderr.write(`Packages with source changes:
|
|
353
|
-
|
|
354
|
-
`);
|
|
355
|
-
for (const pkg of check.missingFor) {
|
|
356
|
-
process.stderr.write(` ${COLORS2.yellow}@outfitter/${pkg}${COLORS2.reset}
|
|
357
|
-
`);
|
|
358
|
-
}
|
|
359
|
-
process.stderr.write(`
|
|
360
|
-
Run ${COLORS2.blue}bun run changeset${COLORS2.reset} for a custom changelog entry, ` + `or add ${COLORS2.blue}release:none${COLORS2.reset} to skip.
|
|
361
|
-
`);
|
|
362
|
-
}
|
|
363
|
-
const ignoredReferences = getIgnoredReferencesForChangedChangesets(cwd, changesetFiles);
|
|
364
|
-
if (ignoredReferences.length > 0) {
|
|
365
|
-
process.stderr.write(`${COLORS2.red}Invalid changeset package reference(s).${COLORS2.reset}
|
|
366
|
-
|
|
367
|
-
`);
|
|
368
|
-
process.stderr.write(`Changesets must not reference packages listed in .changeset/config.json ignore:
|
|
369
|
-
|
|
370
|
-
`);
|
|
371
|
-
for (const reference of ignoredReferences) {
|
|
372
|
-
process.stderr.write(` ${COLORS2.yellow}${reference.file}${COLORS2.reset}
|
|
373
|
-
`);
|
|
374
|
-
for (const pkg of reference.packages) {
|
|
375
|
-
process.stderr.write(` - ${pkg}
|
|
376
|
-
`);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
process.stderr.write(`
|
|
380
|
-
Update the affected changeset files to remove ignored packages before merging.
|
|
381
|
-
`);
|
|
382
|
-
process.exit(1);
|
|
383
|
-
}
|
|
384
|
-
process.stdout.write(`${COLORS2.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS2.reset}
|
|
385
|
-
`);
|
|
386
|
-
process.exit(0);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// src/cli/check-clean-tree.ts
|
|
390
|
-
function parseGitDiff(diffOutput) {
|
|
391
|
-
return diffOutput.split(`
|
|
392
|
-
`).map((line) => line.trim()).filter(Boolean);
|
|
393
|
-
}
|
|
394
|
-
function parseUntrackedFiles(lsOutput) {
|
|
395
|
-
return lsOutput.split(`
|
|
396
|
-
`).map((line) => line.trim()).filter(Boolean);
|
|
397
|
-
}
|
|
398
|
-
var COLORS3 = {
|
|
399
|
-
reset: "\x1B[0m",
|
|
400
|
-
red: "\x1B[31m",
|
|
401
|
-
green: "\x1B[32m",
|
|
402
|
-
dim: "\x1B[2m"
|
|
403
|
-
};
|
|
404
|
-
async function runCheckCleanTree(options = {}) {
|
|
405
|
-
const pathArgs = options.paths ?? [];
|
|
406
|
-
const diffResult = Bun.spawnSync(["git", "diff", "HEAD", "--name-only", "--", ...pathArgs], { stderr: "pipe" });
|
|
407
|
-
if (diffResult.exitCode !== 0) {
|
|
408
|
-
process.stderr.write(`Failed to run git diff
|
|
409
|
-
`);
|
|
410
|
-
process.exit(1);
|
|
411
|
-
}
|
|
412
|
-
const modified = parseGitDiff(diffResult.stdout.toString());
|
|
413
|
-
const lsResult = Bun.spawnSync(["git", "ls-files", "--others", "--exclude-standard", "--", ...pathArgs], { stderr: "pipe" });
|
|
414
|
-
if (lsResult.exitCode !== 0) {
|
|
415
|
-
process.stderr.write(`Failed to run git ls-files
|
|
416
|
-
`);
|
|
417
|
-
process.exit(1);
|
|
418
|
-
}
|
|
419
|
-
const untracked = parseUntrackedFiles(lsResult.stdout.toString());
|
|
420
|
-
const clean = modified.length === 0 && untracked.length === 0;
|
|
421
|
-
const status = { clean, modified, untracked };
|
|
422
|
-
if (status.clean) {
|
|
423
|
-
process.stdout.write(`${COLORS3.green}Working tree is clean.${COLORS3.reset}
|
|
424
|
-
`);
|
|
425
|
-
process.exit(0);
|
|
426
|
-
}
|
|
427
|
-
process.stderr.write(`${COLORS3.red}Working tree is dirty after verification:${COLORS3.reset}
|
|
428
|
-
|
|
429
|
-
`);
|
|
430
|
-
if (modified.length > 0) {
|
|
431
|
-
process.stderr.write(`Modified files:
|
|
432
|
-
`);
|
|
433
|
-
for (const file of modified) {
|
|
434
|
-
process.stderr.write(` ${COLORS3.dim}M${COLORS3.reset} ${file}
|
|
435
|
-
`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
if (untracked.length > 0) {
|
|
439
|
-
process.stderr.write(`Untracked files:
|
|
440
|
-
`);
|
|
441
|
-
for (const file of untracked) {
|
|
442
|
-
process.stderr.write(` ${COLORS3.dim}?${COLORS3.reset} ${file}
|
|
443
|
-
`);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
process.stderr.write(`
|
|
447
|
-
This likely means a build step produced uncommitted changes.
|
|
448
|
-
`);
|
|
449
|
-
process.stderr.write(`Commit these changes or add them to .gitignore.
|
|
450
|
-
`);
|
|
451
|
-
process.exit(1);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// src/cli/check-exports.ts
|
|
455
|
-
import { resolve as resolve3 } from "node:path";
|
|
456
|
-
function entryToSubpath(entry) {
|
|
457
|
-
const stripped = entry.replace(/^src\//, "").replace(/\.[cm]?[jt]sx?$/, "");
|
|
458
|
-
if (stripped === "index") {
|
|
459
|
-
return ".";
|
|
460
|
-
}
|
|
461
|
-
if (stripped.endsWith("/index")) {
|
|
462
|
-
return `./${stripped.slice(0, -"/index".length)}`;
|
|
463
|
-
}
|
|
464
|
-
return `./${stripped}`;
|
|
465
|
-
}
|
|
466
|
-
function compareExports(input) {
|
|
467
|
-
const { name, actual, expected, path } = input;
|
|
468
|
-
const actualKeys = new Set(Object.keys(actual));
|
|
469
|
-
const expectedKeys = new Set(Object.keys(expected));
|
|
470
|
-
const added = [];
|
|
471
|
-
const removed = [];
|
|
472
|
-
const changed = [];
|
|
473
|
-
for (const key of expectedKeys) {
|
|
474
|
-
if (!actualKeys.has(key)) {
|
|
475
|
-
added.push(key);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
for (const key of actualKeys) {
|
|
479
|
-
if (!expectedKeys.has(key)) {
|
|
480
|
-
removed.push(key);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
for (const key of actualKeys) {
|
|
484
|
-
if (expectedKeys.has(key)) {
|
|
485
|
-
const actualValue = actual[key];
|
|
486
|
-
const expectedValue = expected[key];
|
|
487
|
-
if (JSON.stringify(actualValue) !== JSON.stringify(expectedValue)) {
|
|
488
|
-
changed.push({ key, expected: expectedValue, actual: actualValue });
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
added.sort();
|
|
493
|
-
removed.sort();
|
|
494
|
-
changed.sort((a, b) => a.key.localeCompare(b.key));
|
|
495
|
-
if (added.length === 0 && removed.length === 0 && changed.length === 0) {
|
|
496
|
-
return { name, status: "ok" };
|
|
497
|
-
}
|
|
498
|
-
return {
|
|
499
|
-
name,
|
|
500
|
-
status: "drift",
|
|
501
|
-
drift: {
|
|
502
|
-
package: name,
|
|
503
|
-
path: path ?? "",
|
|
504
|
-
added,
|
|
505
|
-
removed,
|
|
506
|
-
changed
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
function matchesExclude(subpath, excludes) {
|
|
511
|
-
return excludes.some((pattern) => new Bun.Glob(pattern).match(subpath));
|
|
512
|
-
}
|
|
513
|
-
var CLI_EXCLUSION_PATTERNS = [
|
|
514
|
-
"**/cli.ts",
|
|
515
|
-
"**/cli/index.ts",
|
|
516
|
-
"**/bin.ts",
|
|
517
|
-
"**/bin/index.ts"
|
|
518
|
-
];
|
|
519
|
-
function isCliEntrypoint(entry) {
|
|
520
|
-
return CLI_EXCLUSION_PATTERNS.some((pattern) => new Bun.Glob(pattern).match(entry));
|
|
521
|
-
}
|
|
522
|
-
function buildExportValue(entry) {
|
|
523
|
-
const distPath = entry.replace(/^src\//, "").replace(/\.[cm]?[jt]sx?$/, "");
|
|
524
|
-
return {
|
|
525
|
-
import: {
|
|
526
|
-
types: `./dist/${distPath}.d.ts`,
|
|
527
|
-
default: `./dist/${distPath}.js`
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
function discoverEntries(packageRoot) {
|
|
532
|
-
const glob = new Bun.Glob("src/**/*.ts");
|
|
533
|
-
const entries = [];
|
|
534
|
-
for (const match of glob.scanSync({ cwd: packageRoot, dot: false })) {
|
|
535
|
-
if (match.includes("__tests__") || match.endsWith(".test.ts")) {
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
entries.push(match);
|
|
539
|
-
}
|
|
540
|
-
return entries.sort();
|
|
541
|
-
}
|
|
542
|
-
function addConfigFileExports(expected, pkg) {
|
|
543
|
-
const CONFIG_RE = /\.(json|jsonc|yml|yaml|toml)$/;
|
|
544
|
-
const configFiles = (pkg.files ?? []).filter((file) => CONFIG_RE.test(file) && file !== "package.json");
|
|
545
|
-
for (const file of configFiles) {
|
|
546
|
-
expected[`./${file}`] = `./${file}`;
|
|
547
|
-
let base = file.replace(CONFIG_RE, "");
|
|
548
|
-
const match = base.match(/^(.+)\.preset(?:\.(.+))?$/);
|
|
549
|
-
if (match?.[1]) {
|
|
550
|
-
base = match[2] ? `${match[1]}-${match[2]}` : match[1];
|
|
551
|
-
}
|
|
552
|
-
if (base !== file) {
|
|
553
|
-
expected[`./${base}`] = `./${file}`;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
function computeExpectedExports(packageRoot, workspace, pkg) {
|
|
558
|
-
const entries = discoverEntries(packageRoot);
|
|
559
|
-
const exportsConfig = typeof workspace.config?.exports === "object" ? workspace.config.exports : undefined;
|
|
560
|
-
const excludes = exportsConfig?.exclude ?? [];
|
|
561
|
-
const customExports = exportsConfig?.customExports ?? {};
|
|
562
|
-
const expected = {};
|
|
563
|
-
const subpathEntries = new Map;
|
|
564
|
-
for (const entry of entries) {
|
|
565
|
-
if (isCliEntrypoint(entry))
|
|
566
|
-
continue;
|
|
567
|
-
const subpath = entryToSubpath(entry);
|
|
568
|
-
if (matchesExclude(subpath, excludes))
|
|
569
|
-
continue;
|
|
570
|
-
const existing = subpathEntries.get(subpath);
|
|
571
|
-
if (existing) {
|
|
572
|
-
if (!existing.endsWith("/index.ts") && entry.endsWith("/index.ts")) {
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
subpathEntries.set(subpath, entry);
|
|
577
|
-
}
|
|
578
|
-
for (const [subpath, entry] of subpathEntries) {
|
|
579
|
-
expected[subpath] = buildExportValue(entry);
|
|
580
|
-
}
|
|
581
|
-
for (const [key, value] of Object.entries(customExports)) {
|
|
582
|
-
expected[`./${key.replace(/^\.\//, "")}`] = value;
|
|
583
|
-
}
|
|
584
|
-
addConfigFileExports(expected, pkg);
|
|
585
|
-
expected["./package.json"] = "./package.json";
|
|
586
|
-
return expected;
|
|
587
|
-
}
|
|
588
|
-
var COLORS4 = {
|
|
589
|
-
reset: "\x1B[0m",
|
|
590
|
-
red: "\x1B[31m",
|
|
591
|
-
green: "\x1B[32m",
|
|
592
|
-
yellow: "\x1B[33m",
|
|
593
|
-
blue: "\x1B[34m",
|
|
594
|
-
dim: "\x1B[2m"
|
|
595
|
-
};
|
|
596
|
-
function resolveJsonMode(options = {}) {
|
|
597
|
-
return options.json ?? process.env["OUTFITTER_JSON"] === "1";
|
|
598
|
-
}
|
|
599
|
-
async function runCheckExports(options = {}) {
|
|
600
|
-
const cwd = process.cwd();
|
|
601
|
-
const configPath = resolve3(cwd, "bunup.config.ts");
|
|
602
|
-
let workspaces;
|
|
603
|
-
try {
|
|
604
|
-
const configModule = await import(configPath);
|
|
605
|
-
const rawConfig = configModule.default;
|
|
606
|
-
if (!Array.isArray(rawConfig)) {
|
|
607
|
-
process.stderr.write(`bunup.config.ts must export a workspace array
|
|
608
|
-
`);
|
|
609
|
-
process.exit(1);
|
|
610
|
-
}
|
|
611
|
-
workspaces = rawConfig;
|
|
612
|
-
} catch {
|
|
613
|
-
process.stderr.write(`Could not load bunup.config.ts from ${cwd}
|
|
614
|
-
`);
|
|
615
|
-
process.exit(1);
|
|
616
|
-
}
|
|
617
|
-
const results = [];
|
|
618
|
-
for (const workspace of workspaces) {
|
|
619
|
-
const packageRoot = resolve3(cwd, workspace.root);
|
|
620
|
-
const pkgPath = resolve3(packageRoot, "package.json");
|
|
621
|
-
let pkg;
|
|
622
|
-
try {
|
|
623
|
-
pkg = await Bun.file(pkgPath).json();
|
|
624
|
-
} catch {
|
|
625
|
-
results.push({ name: workspace.name, status: "ok" });
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
|
-
const actual = typeof pkg.exports === "object" && pkg.exports !== null ? pkg.exports : {};
|
|
629
|
-
const expected = computeExpectedExports(packageRoot, workspace, pkg);
|
|
630
|
-
results.push(compareExports({
|
|
631
|
-
name: workspace.name,
|
|
632
|
-
actual,
|
|
633
|
-
expected,
|
|
634
|
-
path: workspace.root
|
|
635
|
-
}));
|
|
636
|
-
}
|
|
637
|
-
const checkResult = {
|
|
638
|
-
ok: results.every((r) => r.status === "ok"),
|
|
639
|
-
packages: results
|
|
640
|
-
};
|
|
641
|
-
if (resolveJsonMode(options)) {
|
|
642
|
-
process.stdout.write(`${JSON.stringify(checkResult, null, 2)}
|
|
643
|
-
`);
|
|
644
|
-
} else {
|
|
645
|
-
const drifted = results.filter((r) => r.status === "drift");
|
|
646
|
-
if (drifted.length === 0) {
|
|
647
|
-
process.stdout.write(`${COLORS4.green}All ${results.length} packages have exports in sync.${COLORS4.reset}
|
|
648
|
-
`);
|
|
649
|
-
} else {
|
|
650
|
-
process.stderr.write(`${COLORS4.red}Export drift detected in ${drifted.length} package(s):${COLORS4.reset}
|
|
651
|
-
|
|
652
|
-
`);
|
|
653
|
-
for (const result of drifted) {
|
|
654
|
-
const drift = result.drift;
|
|
655
|
-
if (!drift)
|
|
656
|
-
continue;
|
|
657
|
-
process.stderr.write(` ${COLORS4.yellow}${result.name}${COLORS4.reset} ${COLORS4.dim}(${drift.path})${COLORS4.reset}
|
|
658
|
-
`);
|
|
659
|
-
for (const key of drift.added) {
|
|
660
|
-
process.stderr.write(` ${COLORS4.green}+ ${key}${COLORS4.reset} ${COLORS4.dim}(missing from package.json)${COLORS4.reset}
|
|
661
|
-
`);
|
|
662
|
-
}
|
|
663
|
-
for (const key of drift.removed) {
|
|
664
|
-
process.stderr.write(` ${COLORS4.red}- ${key}${COLORS4.reset} ${COLORS4.dim}(not in source)${COLORS4.reset}
|
|
665
|
-
`);
|
|
666
|
-
}
|
|
667
|
-
for (const entry of drift.changed) {
|
|
668
|
-
process.stderr.write(` ${COLORS4.yellow}~ ${entry.key}${COLORS4.reset} ${COLORS4.dim}(value mismatch)${COLORS4.reset}
|
|
669
|
-
`);
|
|
670
|
-
}
|
|
671
|
-
process.stderr.write(`
|
|
672
|
-
`);
|
|
673
|
-
}
|
|
674
|
-
process.stderr.write(`Run ${COLORS4.blue}bun run build${COLORS4.reset} to regenerate exports.
|
|
675
|
-
`);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
process.exit(checkResult.ok ? 0 : 1);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// src/cli/fix.ts
|
|
682
|
-
function buildFixCommand(options) {
|
|
683
|
-
const cmd = ["ultracite", "fix"];
|
|
684
|
-
if (options.paths && options.paths.length > 0) {
|
|
685
|
-
cmd.push(...options.paths);
|
|
686
|
-
}
|
|
687
|
-
return cmd;
|
|
688
|
-
}
|
|
689
|
-
async function runFix(paths = []) {
|
|
690
|
-
const cmd = buildFixCommand({ paths });
|
|
691
|
-
process.stdout.write(`Running: bun x ${cmd.join(" ")}
|
|
692
|
-
`);
|
|
693
|
-
const proc = Bun.spawn(["bun", "x", ...cmd], {
|
|
694
|
-
stdio: ["inherit", "inherit", "inherit"]
|
|
695
|
-
});
|
|
696
|
-
const exitCode = await proc.exited;
|
|
697
|
-
process.exit(exitCode);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// src/cli/init.ts
|
|
701
|
-
var FRAMEWORK_DETECTORS = {
|
|
702
|
-
react: ["react", "react-dom"],
|
|
703
|
-
next: ["next"],
|
|
704
|
-
vue: ["vue"],
|
|
705
|
-
nuxt: ["nuxt"],
|
|
706
|
-
svelte: ["svelte"],
|
|
707
|
-
angular: ["@angular/core"],
|
|
708
|
-
solid: ["solid-js"],
|
|
709
|
-
astro: ["astro"],
|
|
710
|
-
remix: ["@remix-run/react"],
|
|
711
|
-
qwik: ["@builder.io/qwik"]
|
|
712
|
-
};
|
|
713
|
-
function detectFrameworks(pkg) {
|
|
714
|
-
const allDeps = {
|
|
715
|
-
...pkg.dependencies,
|
|
716
|
-
...pkg.devDependencies
|
|
717
|
-
};
|
|
718
|
-
const detected = [];
|
|
719
|
-
for (const [framework, packages] of Object.entries(FRAMEWORK_DETECTORS)) {
|
|
720
|
-
if (packages.some((p) => (p in allDeps))) {
|
|
721
|
-
detected.push(framework);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
if (detected.length === 0) {
|
|
725
|
-
return [];
|
|
726
|
-
}
|
|
727
|
-
return ["--frameworks", ...detected];
|
|
728
|
-
}
|
|
729
|
-
function buildUltraciteCommand(options) {
|
|
730
|
-
const cmd = [
|
|
731
|
-
"ultracite",
|
|
732
|
-
"init",
|
|
733
|
-
"--linter",
|
|
734
|
-
"biome",
|
|
735
|
-
"--pm",
|
|
736
|
-
"bun",
|
|
737
|
-
"--quiet"
|
|
738
|
-
];
|
|
739
|
-
if (options.frameworks && options.frameworks.length > 0) {
|
|
740
|
-
cmd.push("--frameworks", ...options.frameworks);
|
|
741
|
-
}
|
|
742
|
-
return cmd;
|
|
743
|
-
}
|
|
744
|
-
async function runInit(cwd = process.cwd()) {
|
|
745
|
-
const pkgPath = `${cwd}/package.json`;
|
|
746
|
-
const pkgFile = Bun.file(pkgPath);
|
|
747
|
-
if (!await pkgFile.exists()) {
|
|
748
|
-
process.stderr.write(`No package.json found in current directory
|
|
749
|
-
`);
|
|
750
|
-
process.exit(1);
|
|
751
|
-
}
|
|
752
|
-
const pkg = await pkgFile.json();
|
|
753
|
-
const frameworkFlags = detectFrameworks(pkg);
|
|
754
|
-
const frameworks = frameworkFlags.length > 0 ? frameworkFlags.slice(1) : [];
|
|
755
|
-
const cmd = buildUltraciteCommand({ frameworks });
|
|
756
|
-
process.stdout.write(`Running: bun x ${cmd.join(" ")}
|
|
757
|
-
`);
|
|
758
|
-
const proc = Bun.spawn(["bun", "x", ...cmd], {
|
|
759
|
-
cwd,
|
|
760
|
-
stdio: ["inherit", "inherit", "inherit"]
|
|
761
|
-
});
|
|
762
|
-
const exitCode = await proc.exited;
|
|
763
|
-
process.exit(exitCode);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// src/cli/pre-push.ts
|
|
767
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
768
|
-
import { join as join2, resolve as resolve4 } from "node:path";
|
|
769
|
-
import ts from "typescript";
|
|
770
|
-
var COLORS5 = {
|
|
771
|
-
reset: "\x1B[0m",
|
|
772
|
-
red: "\x1B[31m",
|
|
773
|
-
green: "\x1B[32m",
|
|
774
|
-
yellow: "\x1B[33m",
|
|
775
|
-
blue: "\x1B[34m"
|
|
776
|
-
};
|
|
777
|
-
function log(msg) {
|
|
778
|
-
process.stdout.write(`${msg}
|
|
779
|
-
`);
|
|
780
|
-
}
|
|
781
|
-
function getCurrentBranch() {
|
|
782
|
-
const result = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
783
|
-
return result.stdout.toString().trim();
|
|
784
|
-
}
|
|
785
|
-
function runGit(args) {
|
|
786
|
-
try {
|
|
787
|
-
const result = Bun.spawnSync(["git", ...args], { stderr: "ignore" });
|
|
788
|
-
if (result.exitCode !== 0) {
|
|
789
|
-
return { ok: false, lines: [] };
|
|
790
|
-
}
|
|
791
|
-
return {
|
|
792
|
-
ok: true,
|
|
793
|
-
lines: result.stdout.toString().split(`
|
|
794
|
-
`).map((line) => line.trim()).filter(Boolean)
|
|
795
|
-
};
|
|
796
|
-
} catch {
|
|
797
|
-
return { ok: false, lines: [] };
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
function isRedPhaseBranch(branch) {
|
|
801
|
-
return branch.endsWith("-tests") || branch.endsWith("/tests") || branch.endsWith("_tests");
|
|
802
|
-
}
|
|
803
|
-
function isScaffoldBranch(branch) {
|
|
804
|
-
return branch.endsWith("-scaffold") || branch.endsWith("/scaffold") || branch.endsWith("_scaffold");
|
|
805
|
-
}
|
|
806
|
-
function isReleaseBranch(branch) {
|
|
807
|
-
return branch.startsWith("changeset-release/");
|
|
808
|
-
}
|
|
809
|
-
var TEST_PATH_PATTERNS = [
|
|
810
|
-
/(^|\/)__tests__\//,
|
|
811
|
-
/(^|\/)__snapshots__\//,
|
|
812
|
-
/\.(test|spec)\.[cm]?[jt]sx?$/,
|
|
813
|
-
/\.snap$/,
|
|
814
|
-
/(^|\/)(vitest|jest|bun)\.config\.[cm]?[jt]s$/,
|
|
815
|
-
/(^|\/)tsconfig\.test\.json$/,
|
|
816
|
-
/(^|\/)\.env\.test(\.|$)/
|
|
817
|
-
];
|
|
818
|
-
function isTestOnlyPath(path) {
|
|
819
|
-
const normalized = path.replaceAll("\\", "/");
|
|
820
|
-
return TEST_PATH_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
821
|
-
}
|
|
822
|
-
function areFilesTestOnly(paths) {
|
|
823
|
-
return paths.length > 0 && paths.every((path) => isTestOnlyPath(path));
|
|
824
|
-
}
|
|
825
|
-
function canBypassRedPhaseByChangedFiles(changedFiles) {
|
|
826
|
-
return changedFiles.deterministic && areFilesTestOnly(changedFiles.files);
|
|
827
|
-
}
|
|
828
|
-
function hasPackageSourceChanges(changedFiles) {
|
|
829
|
-
const packageSrcPattern = /^packages\/[^/]+\/src\//;
|
|
830
|
-
return changedFiles.files.some((f) => packageSrcPattern.test(f));
|
|
831
|
-
}
|
|
832
|
-
async function printTsdocSummary() {
|
|
833
|
-
const glob = new Bun.Glob("packages/*/src/index.ts");
|
|
834
|
-
const cwd = process.cwd();
|
|
835
|
-
const allDeclarations = [];
|
|
836
|
-
for (const entry of glob.scanSync({ cwd })) {
|
|
837
|
-
const filePath = resolve4(cwd, entry);
|
|
838
|
-
const content = await Bun.file(filePath).text();
|
|
839
|
-
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
840
|
-
allDeclarations.push(...analyzeSourceFile(sourceFile));
|
|
841
|
-
}
|
|
842
|
-
if (allDeclarations.length === 0)
|
|
843
|
-
return;
|
|
844
|
-
const coverage = calculateCoverage(allDeclarations);
|
|
845
|
-
const parts = [];
|
|
846
|
-
if (coverage.documented > 0)
|
|
847
|
-
parts.push(`${coverage.documented} documented`);
|
|
848
|
-
if (coverage.partial > 0)
|
|
849
|
-
parts.push(`${coverage.partial} partial`);
|
|
850
|
-
if (coverage.undocumented > 0)
|
|
851
|
-
parts.push(`${coverage.undocumented} undocumented`);
|
|
852
|
-
log(`${COLORS5.blue}TSDoc${COLORS5.reset}: ${coverage.percentage}% coverage (${parts.join(", ")} of ${coverage.total} total)`);
|
|
853
|
-
}
|
|
854
|
-
function resolveBaseRef() {
|
|
855
|
-
const candidates = [
|
|
856
|
-
"origin/main",
|
|
857
|
-
"main",
|
|
858
|
-
"origin/trunk",
|
|
859
|
-
"trunk",
|
|
860
|
-
"origin/master",
|
|
861
|
-
"master"
|
|
862
|
-
];
|
|
863
|
-
for (const candidate of candidates) {
|
|
864
|
-
const resolved = runGit(["rev-parse", "--verify", "--quiet", candidate]);
|
|
865
|
-
if (resolved.ok) {
|
|
866
|
-
return candidate;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
function changedFilesFromRange(range) {
|
|
872
|
-
const result = runGit(["diff", "--name-only", "--diff-filter=d", range]);
|
|
873
|
-
return {
|
|
874
|
-
ok: result.ok,
|
|
875
|
-
files: result.lines
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
function getChangedFilesForPush() {
|
|
879
|
-
const upstream = runGit([
|
|
880
|
-
"rev-parse",
|
|
881
|
-
"--abbrev-ref",
|
|
882
|
-
"--symbolic-full-name",
|
|
883
|
-
"@{upstream}"
|
|
884
|
-
]);
|
|
885
|
-
if (upstream.ok && upstream.lines[0]) {
|
|
886
|
-
const rangeResult = changedFilesFromRange(`${upstream.lines[0]}...HEAD`);
|
|
887
|
-
if (rangeResult.ok) {
|
|
888
|
-
return {
|
|
889
|
-
files: rangeResult.files,
|
|
890
|
-
deterministic: true,
|
|
891
|
-
source: "upstream"
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
const baseRef = resolveBaseRef();
|
|
896
|
-
if (baseRef) {
|
|
897
|
-
const rangeResult = changedFilesFromRange(`${baseRef}...HEAD`);
|
|
898
|
-
if (rangeResult.ok) {
|
|
899
|
-
return {
|
|
900
|
-
files: rangeResult.files,
|
|
901
|
-
deterministic: true,
|
|
902
|
-
source: "baseRef"
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
return {
|
|
907
|
-
files: [],
|
|
908
|
-
deterministic: false,
|
|
909
|
-
source: "undetermined"
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
function maybeSkipForRedPhase(reason, branch) {
|
|
913
|
-
const changedFiles = getChangedFilesForPush();
|
|
914
|
-
if (!changedFiles.deterministic) {
|
|
915
|
-
log(`${COLORS5.yellow}RED-phase bypass denied${COLORS5.reset}: could not determine full push diff range`);
|
|
916
|
-
log("Running strict verification.");
|
|
917
|
-
log("");
|
|
918
|
-
return false;
|
|
919
|
-
}
|
|
920
|
-
if (!canBypassRedPhaseByChangedFiles(changedFiles)) {
|
|
921
|
-
log(`${COLORS5.yellow}RED-phase bypass denied${COLORS5.reset}: changed files are not test-only`);
|
|
922
|
-
if (changedFiles.files.length > 0) {
|
|
923
|
-
log(`Changed files (${changedFiles.source}): ${changedFiles.files.join(", ")}`);
|
|
924
|
-
} else {
|
|
925
|
-
log(`No changed files detected in ${changedFiles.source} range. Running strict verification.`);
|
|
926
|
-
}
|
|
927
|
-
log("");
|
|
928
|
-
return false;
|
|
929
|
-
}
|
|
930
|
-
if (reason === "branch") {
|
|
931
|
-
log(`${COLORS5.yellow}TDD RED phase${COLORS5.reset} detected: ${COLORS5.blue}${branch}${COLORS5.reset}`);
|
|
932
|
-
} else {
|
|
933
|
-
log(`${COLORS5.yellow}Scaffold branch${COLORS5.reset} with RED phase branch in context: ${COLORS5.blue}${branch}${COLORS5.reset}`);
|
|
934
|
-
}
|
|
935
|
-
log(`${COLORS5.yellow}Skipping strict verification${COLORS5.reset} - changed files are test-only`);
|
|
936
|
-
log(`Diff source: ${changedFiles.source}`);
|
|
937
|
-
log("");
|
|
938
|
-
log("Remember: GREEN phase (implementation) must make these tests pass!");
|
|
939
|
-
return true;
|
|
940
|
-
}
|
|
941
|
-
function hasRedPhaseBranchInContext(currentBranch) {
|
|
942
|
-
let branches = [];
|
|
943
|
-
try {
|
|
944
|
-
const gtResult = Bun.spawnSync(["gt", "ls"], { stderr: "pipe" });
|
|
945
|
-
if (gtResult.exitCode === 0) {
|
|
946
|
-
branches = gtResult.stdout.toString().split(`
|
|
947
|
-
`).map((line) => line.replace(/^[│├└─◉◯ ]*/g, "").replace(/ \(.*/, "")).filter(Boolean);
|
|
948
|
-
}
|
|
949
|
-
} catch {}
|
|
950
|
-
if (branches.length === 0) {
|
|
951
|
-
const gitResult = Bun.spawnSync([
|
|
952
|
-
"git",
|
|
953
|
-
"branch",
|
|
954
|
-
"--list",
|
|
955
|
-
"cli/*",
|
|
956
|
-
"types/*",
|
|
957
|
-
"contracts/*"
|
|
958
|
-
]);
|
|
959
|
-
branches = gitResult.stdout.toString().split(`
|
|
960
|
-
`).map((line) => line.replace(/^[* ]+/, "")).filter(Boolean);
|
|
961
|
-
}
|
|
962
|
-
for (const branch of branches) {
|
|
963
|
-
if (branch === currentBranch)
|
|
964
|
-
continue;
|
|
965
|
-
if (isRedPhaseBranch(branch))
|
|
966
|
-
return true;
|
|
967
|
-
}
|
|
968
|
-
return false;
|
|
969
|
-
}
|
|
970
|
-
function createVerificationPlan(scripts) {
|
|
971
|
-
if (scripts["verify:ci"]) {
|
|
972
|
-
return { ok: true, scripts: ["verify:ci"], source: "verify:ci" };
|
|
973
|
-
}
|
|
974
|
-
const requiredScripts = ["typecheck", "build", "test"];
|
|
975
|
-
const missingRequired = requiredScripts.filter((name) => !scripts[name]);
|
|
976
|
-
const checkOrLint = scripts["check"] ? "check" : scripts["lint"] ? "lint" : undefined;
|
|
977
|
-
if (!checkOrLint || missingRequired.length > 0) {
|
|
978
|
-
const missing = checkOrLint ? missingRequired : [...missingRequired, "check|lint"];
|
|
979
|
-
return {
|
|
980
|
-
ok: false,
|
|
981
|
-
error: `Missing required scripts for strict pre-push verification: ${missing.join(", ")}`
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
return {
|
|
985
|
-
ok: true,
|
|
986
|
-
scripts: ["typecheck", checkOrLint, "build", "test"],
|
|
987
|
-
source: "fallback"
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
function readPackageScripts(cwd = process.cwd()) {
|
|
991
|
-
const packageJsonPath = join2(cwd, "package.json");
|
|
992
|
-
if (!existsSync2(packageJsonPath)) {
|
|
993
|
-
return {};
|
|
994
|
-
}
|
|
995
|
-
try {
|
|
996
|
-
const parsed = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
997
|
-
const scripts = parsed.scripts ?? {};
|
|
998
|
-
const normalized = {};
|
|
999
|
-
for (const [name, value] of Object.entries(scripts)) {
|
|
1000
|
-
if (typeof value === "string") {
|
|
1001
|
-
normalized[name] = value;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
return normalized;
|
|
1005
|
-
} catch {
|
|
1006
|
-
return {};
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
function runScript(scriptName) {
|
|
1010
|
-
log("");
|
|
1011
|
-
log(`Running: ${COLORS5.blue}bun run ${scriptName}${COLORS5.reset}`);
|
|
1012
|
-
const result = Bun.spawnSync(["bun", "run", scriptName], {
|
|
1013
|
-
stdio: ["inherit", "inherit", "inherit"]
|
|
1014
|
-
});
|
|
1015
|
-
return result.exitCode === 0;
|
|
1016
|
-
}
|
|
1017
|
-
async function runPrePush(options = {}) {
|
|
1018
|
-
log(`${COLORS5.blue}Pre-push verify${COLORS5.reset} (TDD-aware)`);
|
|
1019
|
-
log("");
|
|
1020
|
-
const branch = getCurrentBranch();
|
|
1021
|
-
if (isReleaseBranch(branch)) {
|
|
1022
|
-
log(`${COLORS5.yellow}Release branch detected${COLORS5.reset}: ${COLORS5.blue}${branch}${COLORS5.reset}`);
|
|
1023
|
-
log(`${COLORS5.yellow}Skipping strict verification${COLORS5.reset} for automated changeset release push`);
|
|
1024
|
-
process.exit(0);
|
|
1025
|
-
}
|
|
1026
|
-
if (isRedPhaseBranch(branch)) {
|
|
1027
|
-
if (maybeSkipForRedPhase("branch", branch)) {
|
|
1028
|
-
process.exit(0);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
if (isScaffoldBranch(branch)) {
|
|
1032
|
-
if (hasRedPhaseBranchInContext(branch)) {
|
|
1033
|
-
if (maybeSkipForRedPhase("context", branch)) {
|
|
1034
|
-
process.exit(0);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
if (options.force) {
|
|
1039
|
-
log(`${COLORS5.yellow}Force flag set${COLORS5.reset} - skipping strict verification`);
|
|
1040
|
-
process.exit(0);
|
|
1041
|
-
}
|
|
1042
|
-
const plan = createVerificationPlan(readPackageScripts());
|
|
1043
|
-
if (!plan.ok) {
|
|
1044
|
-
log(`${COLORS5.red}Strict pre-push verification is not configured${COLORS5.reset}`);
|
|
1045
|
-
log(plan.error);
|
|
1046
|
-
log("");
|
|
1047
|
-
log("Add one of:");
|
|
1048
|
-
log(" - verify:ci");
|
|
1049
|
-
log(" - typecheck + (check or lint) + build + test");
|
|
1050
|
-
process.exit(1);
|
|
1051
|
-
}
|
|
1052
|
-
log(`Running strict verification for branch: ${COLORS5.blue}${branch}${COLORS5.reset}`);
|
|
1053
|
-
if (plan.source === "verify:ci") {
|
|
1054
|
-
log("Using `verify:ci` script.");
|
|
1055
|
-
} else {
|
|
1056
|
-
log(`Using fallback scripts: ${plan.scripts.join(" -> ")}`);
|
|
1057
|
-
}
|
|
1058
|
-
for (const scriptName of plan.scripts) {
|
|
1059
|
-
if (runScript(scriptName)) {
|
|
1060
|
-
continue;
|
|
1061
|
-
}
|
|
1062
|
-
log("");
|
|
1063
|
-
log(`${COLORS5.red}Verification failed${COLORS5.reset} on script: ${scriptName}`);
|
|
1064
|
-
log("");
|
|
1065
|
-
log("If this is intentional TDD RED phase work, name your branch:");
|
|
1066
|
-
log(" - feature-tests");
|
|
1067
|
-
log(" - feature/tests");
|
|
1068
|
-
log(" - feature_tests");
|
|
1069
|
-
process.exit(1);
|
|
1070
|
-
}
|
|
1071
|
-
const changedFiles = getChangedFilesForPush();
|
|
1072
|
-
if (hasPackageSourceChanges(changedFiles)) {
|
|
1073
|
-
try {
|
|
1074
|
-
await printTsdocSummary();
|
|
1075
|
-
} catch {}
|
|
1076
|
-
}
|
|
1077
|
-
log("");
|
|
1078
|
-
log(`${COLORS5.green}Strict verification passed${COLORS5.reset}`);
|
|
1079
|
-
process.exit(0);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// src/cli/upgrade-bun.ts
|
|
1083
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
1084
|
-
import { join as join3 } from "node:path";
|
|
1085
|
-
var COLORS6 = {
|
|
1086
|
-
reset: "\x1B[0m",
|
|
1087
|
-
red: "\x1B[31m",
|
|
1088
|
-
green: "\x1B[32m",
|
|
1089
|
-
yellow: "\x1B[33m",
|
|
1090
|
-
blue: "\x1B[34m"
|
|
1091
|
-
};
|
|
1092
|
-
function log2(msg) {
|
|
1093
|
-
process.stdout.write(`${msg}
|
|
1094
|
-
`);
|
|
1095
|
-
}
|
|
1096
|
-
function info(msg) {
|
|
1097
|
-
process.stdout.write(`${COLORS6.blue}▸${COLORS6.reset} ${msg}
|
|
1098
|
-
`);
|
|
1099
|
-
}
|
|
1100
|
-
function success(msg) {
|
|
1101
|
-
process.stdout.write(`${COLORS6.green}✓${COLORS6.reset} ${msg}
|
|
1102
|
-
`);
|
|
1103
|
-
}
|
|
1104
|
-
function warn(msg) {
|
|
1105
|
-
process.stdout.write(`${COLORS6.yellow}!${COLORS6.reset} ${msg}
|
|
1106
|
-
`);
|
|
1107
|
-
}
|
|
1108
|
-
async function fetchLatestVersion() {
|
|
1109
|
-
const response = await fetch("https://api.github.com/repos/oven-sh/bun/releases/latest");
|
|
1110
|
-
const data = await response.json();
|
|
1111
|
-
const match = data.tag_name.match(/bun-v(.+)/);
|
|
1112
|
-
if (!match?.[1]) {
|
|
1113
|
-
throw new Error(`Could not parse version from tag: ${data.tag_name}`);
|
|
1114
|
-
}
|
|
1115
|
-
return match[1];
|
|
1116
|
-
}
|
|
1117
|
-
function findPackageJsonFiles(dir) {
|
|
1118
|
-
const results = [];
|
|
1119
|
-
const glob = new Bun.Glob("**/package.json");
|
|
1120
|
-
for (const path of glob.scanSync({ cwd: dir })) {
|
|
1121
|
-
if (!path.includes("node_modules")) {
|
|
1122
|
-
results.push(join3(dir, path));
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
return results;
|
|
1126
|
-
}
|
|
1127
|
-
function updateEnginesBun(filePath, version) {
|
|
1128
|
-
const content = readFileSync3(filePath, "utf-8");
|
|
1129
|
-
const pattern = /"bun":\s*">=[\d.]+"/;
|
|
1130
|
-
if (!pattern.test(content)) {
|
|
1131
|
-
return false;
|
|
1132
|
-
}
|
|
1133
|
-
const updated = content.replace(pattern, `"bun": ">=${version}"`);
|
|
1134
|
-
if (updated !== content) {
|
|
1135
|
-
writeFileSync(filePath, updated);
|
|
1136
|
-
return true;
|
|
1137
|
-
}
|
|
1138
|
-
return false;
|
|
1139
|
-
}
|
|
1140
|
-
function updateTypesBun(filePath, version) {
|
|
1141
|
-
const content = readFileSync3(filePath, "utf-8");
|
|
1142
|
-
const pattern = /"@types\/bun":\s*"\^[\d.]+"/;
|
|
1143
|
-
if (!pattern.test(content)) {
|
|
1144
|
-
return false;
|
|
1145
|
-
}
|
|
1146
|
-
const updated = content.replace(pattern, `"@types/bun": "^${version}"`);
|
|
1147
|
-
if (updated !== content) {
|
|
1148
|
-
writeFileSync(filePath, updated);
|
|
1149
|
-
return true;
|
|
1150
|
-
}
|
|
1151
|
-
return false;
|
|
1152
|
-
}
|
|
1153
|
-
async function runUpgradeBun(targetVersion, options = {}) {
|
|
1154
|
-
const cwd = process.cwd();
|
|
1155
|
-
const bunVersionFile = join3(cwd, ".bun-version");
|
|
1156
|
-
let version = targetVersion;
|
|
1157
|
-
if (!version) {
|
|
1158
|
-
info("Fetching latest Bun version...");
|
|
1159
|
-
version = await fetchLatestVersion();
|
|
1160
|
-
log2(`Latest version: ${version}`);
|
|
1161
|
-
}
|
|
1162
|
-
const currentVersion = existsSync3(bunVersionFile) ? readFileSync3(bunVersionFile, "utf-8").trim() : "unknown";
|
|
1163
|
-
log2(`Current version: ${currentVersion}`);
|
|
1164
|
-
if (currentVersion === version) {
|
|
1165
|
-
success(`Already on version ${version}`);
|
|
1166
|
-
return;
|
|
1167
|
-
}
|
|
1168
|
-
log2("");
|
|
1169
|
-
info(`Upgrading Bun: ${currentVersion} → ${version}`);
|
|
1170
|
-
log2("");
|
|
1171
|
-
writeFileSync(bunVersionFile, `${version}
|
|
1172
|
-
`);
|
|
1173
|
-
success("Updated .bun-version");
|
|
1174
|
-
const packageFiles = findPackageJsonFiles(cwd);
|
|
1175
|
-
info("Updating engines.bun...");
|
|
1176
|
-
for (const file of packageFiles) {
|
|
1177
|
-
if (updateEnginesBun(file, version)) {
|
|
1178
|
-
log2(` ${file.replace(`${cwd}/`, "")}`);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
info("Updating @types/bun...");
|
|
1182
|
-
for (const file of packageFiles) {
|
|
1183
|
-
if (updateTypesBun(file, version)) {
|
|
1184
|
-
log2(` ${file.replace(`${cwd}/`, "")}`);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
if (options.install !== false) {
|
|
1188
|
-
log2("");
|
|
1189
|
-
info(`Installing Bun ${version}...`);
|
|
1190
|
-
const installResult = Bun.spawnSync([
|
|
1191
|
-
"bash",
|
|
1192
|
-
"-c",
|
|
1193
|
-
`curl -fsSL https://bun.sh/install | bash -s "bun-v${version}"`
|
|
1194
|
-
]);
|
|
1195
|
-
if (installResult.exitCode !== 0) {
|
|
1196
|
-
warn("Could not install Bun automatically");
|
|
1197
|
-
log2("Install manually: curl -fsSL https://bun.sh/install | bash");
|
|
1198
|
-
} else {
|
|
1199
|
-
success(`Bun ${version} installed`);
|
|
1200
|
-
log2("");
|
|
1201
|
-
info("Updating lockfile...");
|
|
1202
|
-
const bunInstall = Bun.spawnSync(["bun", "install"], {
|
|
1203
|
-
cwd,
|
|
1204
|
-
env: {
|
|
1205
|
-
...process.env,
|
|
1206
|
-
BUN_INSTALL: `${process.env["HOME"]}/.bun`,
|
|
1207
|
-
PATH: `${process.env["HOME"]}/.bun/bin:${process.env["PATH"]}`
|
|
1208
|
-
}
|
|
1209
|
-
});
|
|
1210
|
-
if (bunInstall.exitCode === 0) {
|
|
1211
|
-
success("Lockfile updated");
|
|
1212
|
-
} else {
|
|
1213
|
-
warn("Could not update lockfile - run 'bun install' manually");
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
log2("");
|
|
1218
|
-
success("Done! Changes ready to commit:");
|
|
1219
|
-
log2(" - .bun-version");
|
|
1220
|
-
log2(" - package.json files (engines.bun, @types/bun)");
|
|
1221
|
-
log2(" - bun.lock");
|
|
1222
|
-
log2("");
|
|
1223
|
-
log2(`Commit with: git add -A && git commit -m 'chore: upgrade Bun to ${version}'`);
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
// src/cli/index.ts
|
|
1227
60
|
var cli = createCLI({
|
|
1228
61
|
name: "tooling",
|
|
1229
62
|
version: VERSION,
|
|
@@ -1271,10 +104,20 @@ register(new Command("check-tsdoc").description("Check TSDoc coverage on exporte
|
|
|
1271
104
|
register(new Command("check-clean-tree").description("Assert working tree is clean (no modified or untracked files)").option("--paths <paths...>", "Limit check to specific paths").action(async (options) => {
|
|
1272
105
|
await runCheckCleanTree(options);
|
|
1273
106
|
}));
|
|
107
|
+
register(new Command("check-home-paths").description("Check staged files for hardcoded home directory paths").argument("[paths...]", "Specific files to scan").action((paths) => {
|
|
108
|
+
runCheckHomePaths(paths);
|
|
109
|
+
}));
|
|
1274
110
|
register(new Command("check-readme-imports").description("Validate README import examples match package exports").option("--json", "Output results as JSON").action(async (options) => {
|
|
1275
|
-
const { runCheckReadmeImports } = await import("
|
|
111
|
+
const { runCheckReadmeImports } = await import("./check-readme-imports.js");
|
|
1276
112
|
await runCheckReadmeImports(options);
|
|
1277
113
|
}));
|
|
114
|
+
register(new Command("check-markdown-links").description("Validate relative links in markdown files resolve to existing files").argument("[patterns...]", "Glob patterns to scan").action(async (patterns) => {
|
|
115
|
+
const cwd = process.cwd();
|
|
116
|
+
const exitCode = await runCheckMarkdownLinks(cwd, patterns.length > 0 ? patterns : undefined);
|
|
117
|
+
if (exitCode !== 0) {
|
|
118
|
+
process.exitCode = exitCode;
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
1278
121
|
register(new Command("check-boundary-invocations").description("Validate root/app scripts do not execute packages/*/src entrypoints directly").action(async () => {
|
|
1279
122
|
await runCheckBoundaryInvocations();
|
|
1280
123
|
}));
|