@outfitter/tooling 0.2.4 → 0.3.3
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/README.md +69 -1
- package/biome.json +1 -1
- package/dist/cli/index.js +163 -24
- package/dist/index.d.ts +79 -4
- package/dist/index.js +17 -6
- package/dist/shared/chunk-cmde0fwx.js +421 -0
- package/package.json +14 -7
- package/registry/registry.json +6 -6
- package/dist/cli/check-boundary-invocations.d.ts +0 -34
- package/dist/cli/check-boundary-invocations.js +0 -14
- package/dist/cli/check-bunup-registry.d.ts +0 -36
- package/dist/cli/check-bunup-registry.js +0 -12
- package/dist/cli/check-changeset.d.ts +0 -64
- package/dist/cli/check-changeset.js +0 -14
- package/dist/cli/check-clean-tree.d.ts +0 -36
- package/dist/cli/check-clean-tree.js +0 -14
- package/dist/cli/check-exports.d.ts +0 -2
- package/dist/cli/check-exports.js +0 -14
- package/dist/cli/check-readme-imports.d.ts +0 -61
- package/dist/cli/check-readme-imports.js +0 -198
- package/dist/cli/check.d.ts +0 -19
- package/dist/cli/check.js +0 -10
- package/dist/cli/fix.d.ts +0 -19
- package/dist/cli/fix.js +0 -10
- package/dist/cli/init.d.ts +0 -31
- package/dist/cli/init.js +0 -12
- package/dist/cli/pre-push.d.ts +0 -41
- package/dist/cli/pre-push.js +0 -20
- package/dist/cli/upgrade-bun.d.ts +0 -8
- package/dist/cli/upgrade-bun.js +0 -8
- package/dist/registry/build.d.ts +0 -6
- package/dist/registry/build.js +0 -147
- package/dist/registry/index.d.ts +0 -3
- package/dist/registry/index.js +0 -13
- package/dist/registry/schema.d.ts +0 -2
- package/dist/registry/schema.js +0 -12
- package/dist/shared/@outfitter/tooling-0x5q15ec.js +0 -21
- package/dist/shared/@outfitter/tooling-1y8w5ahg.js +0 -70
- package/dist/shared/@outfitter/tooling-3w8vr2w3.js +0 -94
- package/dist/shared/@outfitter/tooling-8sd32ts6.js +0 -277
- package/dist/shared/@outfitter/tooling-9errkcvk.js +0 -21
- package/dist/shared/@outfitter/tooling-9vs606gq.d.ts +0 -3
- package/dist/shared/@outfitter/tooling-9yzd08v1.js +0 -146
- package/dist/shared/@outfitter/tooling-ctmgnap5.js +0 -19
- package/dist/shared/@outfitter/tooling-dvwh9qve.js +0 -4
- package/dist/shared/@outfitter/tooling-enjcenja.js +0 -229
- package/dist/shared/@outfitter/tooling-g83d0kjv.js +0 -23
- package/dist/shared/@outfitter/tooling-kcvs6mys.js +0 -1
- package/dist/shared/@outfitter/tooling-mxwc1n8w.js +0 -68
- package/dist/shared/@outfitter/tooling-r9976n43.js +0 -100
- package/dist/shared/@outfitter/tooling-sjm8nebx.d.ts +0 -109
- package/dist/shared/@outfitter/tooling-t17gnh9b.js +0 -78
- package/dist/shared/@outfitter/tooling-wesswf21.d.ts +0 -59
- package/dist/shared/chunk-8aenrm6f.js +0 -18
- package/dist/version.d.ts +0 -2
- package/dist/version.js +0 -8
package/dist/registry/build.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
// @bun
|
|
3
|
-
import"../shared/@outfitter/tooling-dvwh9qve.js";
|
|
4
|
-
|
|
5
|
-
// packages/tooling/src/registry/build.ts
|
|
6
|
-
import {
|
|
7
|
-
existsSync,
|
|
8
|
-
mkdirSync,
|
|
9
|
-
readFileSync,
|
|
10
|
-
statSync,
|
|
11
|
-
writeFileSync
|
|
12
|
-
} from "fs";
|
|
13
|
-
import { dirname, join } from "path";
|
|
14
|
-
function log(message) {
|
|
15
|
-
process.stdout.write(`${message}
|
|
16
|
-
`);
|
|
17
|
-
}
|
|
18
|
-
function findRepoRoot(startDir) {
|
|
19
|
-
let dir = startDir;
|
|
20
|
-
while (dir !== "/") {
|
|
21
|
-
const pkgPath = join(dir, "package.json");
|
|
22
|
-
if (existsSync(pkgPath)) {
|
|
23
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
24
|
-
if (pkg.workspaces) {
|
|
25
|
-
return dir;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
dir = dirname(dir);
|
|
29
|
-
}
|
|
30
|
-
throw new Error("Could not find repository root");
|
|
31
|
-
}
|
|
32
|
-
function isExecutable(filePath) {
|
|
33
|
-
try {
|
|
34
|
-
const stats = statSync(filePath);
|
|
35
|
-
return (stats.mode & 64) !== 0;
|
|
36
|
-
} catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function readFileEntry(repoRoot, sourcePath, destPath) {
|
|
41
|
-
const fullPath = join(repoRoot, sourcePath);
|
|
42
|
-
if (!existsSync(fullPath)) {
|
|
43
|
-
throw new Error(`Source file not found: ${fullPath}`);
|
|
44
|
-
}
|
|
45
|
-
const content = readFileSync(fullPath, "utf-8");
|
|
46
|
-
const executable = isExecutable(fullPath);
|
|
47
|
-
return {
|
|
48
|
-
path: destPath,
|
|
49
|
-
content,
|
|
50
|
-
...executable && { executable: true }
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function buildBlock(repoRoot, name, def) {
|
|
54
|
-
const block = {
|
|
55
|
-
name,
|
|
56
|
-
description: def.description
|
|
57
|
-
};
|
|
58
|
-
if (def.files && def.files.length > 0) {
|
|
59
|
-
block.files = def.files.map((sourcePath) => {
|
|
60
|
-
const destPath = def.remap?.[sourcePath] ?? sourcePath;
|
|
61
|
-
return readFileEntry(repoRoot, sourcePath, destPath);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
if (def.dependencies && Object.keys(def.dependencies).length > 0) {
|
|
65
|
-
block.dependencies = def.dependencies;
|
|
66
|
-
}
|
|
67
|
-
if (def.devDependencies && Object.keys(def.devDependencies).length > 0) {
|
|
68
|
-
block.devDependencies = def.devDependencies;
|
|
69
|
-
}
|
|
70
|
-
if (def.extends && def.extends.length > 0) {
|
|
71
|
-
block.extends = def.extends;
|
|
72
|
-
}
|
|
73
|
-
return block;
|
|
74
|
-
}
|
|
75
|
-
function buildRegistry(config, repoRoot) {
|
|
76
|
-
const blocks = {};
|
|
77
|
-
for (const [name, def] of Object.entries(config.blocks)) {
|
|
78
|
-
blocks[name] = buildBlock(repoRoot, name, def);
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
version: config.version,
|
|
82
|
-
blocks
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
var REGISTRY_CONFIG = {
|
|
86
|
-
version: "1.0.0",
|
|
87
|
-
blocks: {
|
|
88
|
-
claude: {
|
|
89
|
-
description: "Claude Code settings and hooks for automated formatting",
|
|
90
|
-
files: [".claude/settings.json", ".claude/hooks/format-code-on-stop.sh"]
|
|
91
|
-
},
|
|
92
|
-
biome: {
|
|
93
|
-
description: "Biome linter/formatter configuration via Ultracite",
|
|
94
|
-
files: ["packages/tooling/biome.json"],
|
|
95
|
-
remap: { "packages/tooling/biome.json": "biome.json" },
|
|
96
|
-
devDependencies: { ultracite: "^7.1.1" }
|
|
97
|
-
},
|
|
98
|
-
lefthook: {
|
|
99
|
-
description: "Git hooks via Lefthook for pre-commit and pre-push",
|
|
100
|
-
files: ["packages/tooling/lefthook.yml"],
|
|
101
|
-
remap: { "packages/tooling/lefthook.yml": ".lefthook.yml" },
|
|
102
|
-
devDependencies: {
|
|
103
|
-
"@outfitter/tooling": "^0.2.1",
|
|
104
|
-
lefthook: "^2.0.16",
|
|
105
|
-
ultracite: "^7.1.1"
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
markdownlint: {
|
|
109
|
-
description: "Markdown linting configuration via markdownlint-cli2",
|
|
110
|
-
files: ["packages/tooling/.markdownlint-cli2.jsonc"],
|
|
111
|
-
remap: {
|
|
112
|
-
"packages/tooling/.markdownlint-cli2.jsonc": ".markdownlint-cli2.jsonc"
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
bootstrap: {
|
|
116
|
-
description: "Project bootstrap script for installing tools and dependencies",
|
|
117
|
-
files: ["scripts/bootstrap.sh"]
|
|
118
|
-
},
|
|
119
|
-
scaffolding: {
|
|
120
|
-
description: "Full starter kit: Claude settings, Biome, Lefthook, markdownlint, and bootstrap script",
|
|
121
|
-
extends: ["claude", "biome", "lefthook", "markdownlint", "bootstrap"]
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
function main() {
|
|
126
|
-
const scriptDir = dirname(new URL(import.meta.url).pathname);
|
|
127
|
-
const repoRoot = findRepoRoot(scriptDir);
|
|
128
|
-
const outputDir = join(repoRoot, "packages/tooling/registry");
|
|
129
|
-
const outputPath = join(outputDir, "registry.json");
|
|
130
|
-
log(`Building registry from: ${repoRoot}`);
|
|
131
|
-
if (!existsSync(outputDir)) {
|
|
132
|
-
mkdirSync(outputDir, { recursive: true });
|
|
133
|
-
}
|
|
134
|
-
const registry = buildRegistry(REGISTRY_CONFIG, repoRoot);
|
|
135
|
-
writeFileSync(outputPath, `${JSON.stringify(registry, null, "\t")}
|
|
136
|
-
`);
|
|
137
|
-
const blockCount = Object.keys(registry.blocks).length;
|
|
138
|
-
const fileCount = Object.values(registry.blocks).flatMap((b) => b.files ?? []).length;
|
|
139
|
-
log(`\u2713 Generated ${outputPath}`);
|
|
140
|
-
log(` ${blockCount} blocks, ${fileCount} files embedded`);
|
|
141
|
-
}
|
|
142
|
-
if (import.meta.main) {
|
|
143
|
-
main();
|
|
144
|
-
}
|
|
145
|
-
export {
|
|
146
|
-
REGISTRY_CONFIG
|
|
147
|
-
};
|
package/dist/registry/index.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import "../shared/@outfitter/tooling-xqwn46sx";
|
|
2
|
-
import { AddBlockOptions, AddBlockResult, Block, BlockDefinition, BlockSchema, FileEntry, FileEntrySchema, Registry, RegistryBuildConfig, RegistrySchema } from "../shared/@outfitter/tooling-sjm8nebx";
|
|
3
|
-
export { RegistrySchema, RegistryBuildConfig, Registry, FileEntrySchema, FileEntry, BlockSchema, BlockDefinition, Block, AddBlockResult, AddBlockOptions };
|
package/dist/registry/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import"../shared/@outfitter/tooling-kcvs6mys.js";
|
|
3
|
-
import {
|
|
4
|
-
BlockSchema,
|
|
5
|
-
FileEntrySchema,
|
|
6
|
-
RegistrySchema
|
|
7
|
-
} from "../shared/@outfitter/tooling-g83d0kjv.js";
|
|
8
|
-
import"../shared/@outfitter/tooling-dvwh9qve.js";
|
|
9
|
-
export {
|
|
10
|
-
RegistrySchema,
|
|
11
|
-
FileEntrySchema,
|
|
12
|
-
BlockSchema
|
|
13
|
-
};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { AddBlockOptions, AddBlockResult, Block, BlockDefinition, BlockSchema, FileEntry, FileEntrySchema, Registry, RegistryBuildConfig, RegistrySchema } from "../shared/@outfitter/tooling-sjm8nebx";
|
|
2
|
-
export { RegistrySchema, RegistryBuildConfig, Registry, FileEntrySchema, FileEntry, BlockSchema, BlockDefinition, Block, AddBlockResult, AddBlockOptions };
|
package/dist/registry/schema.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/tooling/src/cli/check.ts
|
|
3
|
-
function buildCheckCommand(options) {
|
|
4
|
-
const cmd = ["ultracite", "check"];
|
|
5
|
-
if (options.paths && options.paths.length > 0) {
|
|
6
|
-
cmd.push(...options.paths);
|
|
7
|
-
}
|
|
8
|
-
return cmd;
|
|
9
|
-
}
|
|
10
|
-
async function runCheck(paths = []) {
|
|
11
|
-
const cmd = buildCheckCommand({ paths });
|
|
12
|
-
process.stdout.write(`Running: bun x ${cmd.join(" ")}
|
|
13
|
-
`);
|
|
14
|
-
const proc = Bun.spawn(["bun", "x", ...cmd], {
|
|
15
|
-
stdio: ["inherit", "inherit", "inherit"]
|
|
16
|
-
});
|
|
17
|
-
const exitCode = await proc.exited;
|
|
18
|
-
process.exit(exitCode);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export { buildCheckCommand, runCheck };
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/tooling/src/cli/check-clean-tree.ts
|
|
3
|
-
function parseGitDiff(diffOutput) {
|
|
4
|
-
return diffOutput.split(`
|
|
5
|
-
`).map((line) => line.trim()).filter(Boolean);
|
|
6
|
-
}
|
|
7
|
-
function parseUntrackedFiles(lsOutput) {
|
|
8
|
-
return lsOutput.split(`
|
|
9
|
-
`).map((line) => line.trim()).filter(Boolean);
|
|
10
|
-
}
|
|
11
|
-
function isCleanTree(status) {
|
|
12
|
-
return status.modified.length === 0 && status.untracked.length === 0;
|
|
13
|
-
}
|
|
14
|
-
var COLORS = {
|
|
15
|
-
reset: "\x1B[0m",
|
|
16
|
-
red: "\x1B[31m",
|
|
17
|
-
green: "\x1B[32m",
|
|
18
|
-
dim: "\x1B[2m"
|
|
19
|
-
};
|
|
20
|
-
async function runCheckCleanTree(options = {}) {
|
|
21
|
-
const pathArgs = options.paths ?? [];
|
|
22
|
-
const diffResult = Bun.spawnSync(["git", "diff", "HEAD", "--name-only", "--", ...pathArgs], { stderr: "pipe" });
|
|
23
|
-
if (diffResult.exitCode !== 0) {
|
|
24
|
-
process.stderr.write(`Failed to run git diff
|
|
25
|
-
`);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
const modified = parseGitDiff(diffResult.stdout.toString());
|
|
29
|
-
const lsResult = Bun.spawnSync(["git", "ls-files", "--others", "--exclude-standard", "--", ...pathArgs], { stderr: "pipe" });
|
|
30
|
-
if (lsResult.exitCode !== 0) {
|
|
31
|
-
process.stderr.write(`Failed to run git ls-files
|
|
32
|
-
`);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
const untracked = parseUntrackedFiles(lsResult.stdout.toString());
|
|
36
|
-
const clean = modified.length === 0 && untracked.length === 0;
|
|
37
|
-
const status = { clean, modified, untracked };
|
|
38
|
-
if (status.clean) {
|
|
39
|
-
process.stdout.write(`${COLORS.green}Working tree is clean.${COLORS.reset}
|
|
40
|
-
`);
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
process.stderr.write(`${COLORS.red}Working tree is dirty after verification:${COLORS.reset}
|
|
44
|
-
|
|
45
|
-
`);
|
|
46
|
-
if (modified.length > 0) {
|
|
47
|
-
process.stderr.write(`Modified files:
|
|
48
|
-
`);
|
|
49
|
-
for (const file of modified) {
|
|
50
|
-
process.stderr.write(` ${COLORS.dim}M${COLORS.reset} ${file}
|
|
51
|
-
`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (untracked.length > 0) {
|
|
55
|
-
process.stderr.write(`Untracked files:
|
|
56
|
-
`);
|
|
57
|
-
for (const file of untracked) {
|
|
58
|
-
process.stderr.write(` ${COLORS.dim}?${COLORS.reset} ${file}
|
|
59
|
-
`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
process.stderr.write(`
|
|
63
|
-
This likely means a build step produced uncommitted changes.
|
|
64
|
-
`);
|
|
65
|
-
process.stderr.write(`Commit these changes or add them to .gitignore.
|
|
66
|
-
`);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export { parseGitDiff, parseUntrackedFiles, isCleanTree, runCheckCleanTree };
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/tooling/src/cli/check-changeset.ts
|
|
3
|
-
function getChangedPackagePaths(files) {
|
|
4
|
-
const packageNames = new Set;
|
|
5
|
-
const pattern = /^packages\/([^/]+)\/src\//;
|
|
6
|
-
for (const file of files) {
|
|
7
|
-
const match = pattern.exec(file);
|
|
8
|
-
if (match?.[1]) {
|
|
9
|
-
packageNames.add(match[1]);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
return [...packageNames].sort();
|
|
13
|
-
}
|
|
14
|
-
function getChangedChangesetFiles(files) {
|
|
15
|
-
const pattern = /^\.changeset\/([^/]+\.md)$/;
|
|
16
|
-
const results = [];
|
|
17
|
-
for (const file of files) {
|
|
18
|
-
const match = pattern.exec(file);
|
|
19
|
-
if (match?.[1] && match[1] !== "README.md") {
|
|
20
|
-
results.push(match[1]);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return results.sort();
|
|
24
|
-
}
|
|
25
|
-
function checkChangesetRequired(changedPackages, changesetFiles) {
|
|
26
|
-
if (changedPackages.length === 0) {
|
|
27
|
-
return { ok: true, missingFor: [] };
|
|
28
|
-
}
|
|
29
|
-
if (changesetFiles.length > 0) {
|
|
30
|
-
return { ok: true, missingFor: [] };
|
|
31
|
-
}
|
|
32
|
-
return { ok: false, missingFor: changedPackages };
|
|
33
|
-
}
|
|
34
|
-
var COLORS = {
|
|
35
|
-
reset: "\x1B[0m",
|
|
36
|
-
red: "\x1B[31m",
|
|
37
|
-
green: "\x1B[32m",
|
|
38
|
-
yellow: "\x1B[33m",
|
|
39
|
-
blue: "\x1B[34m",
|
|
40
|
-
dim: "\x1B[2m"
|
|
41
|
-
};
|
|
42
|
-
async function runCheckChangeset(options = {}) {
|
|
43
|
-
if (options.skip || process.env["NO_CHANGESET"] === "1") {
|
|
44
|
-
process.stdout.write(`${COLORS.dim}check-changeset skipped (NO_CHANGESET=1)${COLORS.reset}
|
|
45
|
-
`);
|
|
46
|
-
process.exit(0);
|
|
47
|
-
}
|
|
48
|
-
if (process.env["GITHUB_EVENT_NAME"] === "push") {
|
|
49
|
-
process.stdout.write(`${COLORS.dim}check-changeset skipped (push event)${COLORS.reset}
|
|
50
|
-
`);
|
|
51
|
-
process.exit(0);
|
|
52
|
-
}
|
|
53
|
-
const cwd = process.cwd();
|
|
54
|
-
let changedFiles;
|
|
55
|
-
try {
|
|
56
|
-
const proc = Bun.spawnSync(["git", "diff", "--name-only", "origin/main...HEAD"], { cwd });
|
|
57
|
-
if (proc.exitCode !== 0) {
|
|
58
|
-
process.exit(0);
|
|
59
|
-
}
|
|
60
|
-
changedFiles = proc.stdout.toString().trim().split(`
|
|
61
|
-
`).filter((line) => line.length > 0);
|
|
62
|
-
} catch {
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
const changedPackages = getChangedPackagePaths(changedFiles);
|
|
66
|
-
if (changedPackages.length === 0) {
|
|
67
|
-
process.stdout.write(`${COLORS.green}No package source changes detected.${COLORS.reset}
|
|
68
|
-
`);
|
|
69
|
-
process.exit(0);
|
|
70
|
-
}
|
|
71
|
-
const changesetFiles = getChangedChangesetFiles(changedFiles);
|
|
72
|
-
const check = checkChangesetRequired(changedPackages, changesetFiles);
|
|
73
|
-
if (check.ok) {
|
|
74
|
-
process.stdout.write(`${COLORS.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS.reset}
|
|
75
|
-
`);
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
78
|
-
process.stderr.write(`${COLORS.red}Missing changeset!${COLORS.reset}
|
|
79
|
-
|
|
80
|
-
`);
|
|
81
|
-
process.stderr.write(`The following packages have source changes but no changeset:
|
|
82
|
-
|
|
83
|
-
`);
|
|
84
|
-
for (const pkg of check.missingFor) {
|
|
85
|
-
process.stderr.write(` ${COLORS.yellow}@outfitter/${pkg}${COLORS.reset}
|
|
86
|
-
`);
|
|
87
|
-
}
|
|
88
|
-
process.stderr.write(`
|
|
89
|
-
Run ${COLORS.blue}bun run changeset${COLORS.reset} to add a changeset, ` + `or add the ${COLORS.blue}no-changeset${COLORS.reset} label to skip.
|
|
90
|
-
`);
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export { getChangedPackagePaths, getChangedChangesetFiles, checkChangesetRequired, runCheckChangeset };
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/tooling/src/cli/pre-push.ts
|
|
3
|
-
import { existsSync, readFileSync } from "fs";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
var COLORS = {
|
|
6
|
-
reset: "\x1B[0m",
|
|
7
|
-
red: "\x1B[31m",
|
|
8
|
-
green: "\x1B[32m",
|
|
9
|
-
yellow: "\x1B[33m",
|
|
10
|
-
blue: "\x1B[34m"
|
|
11
|
-
};
|
|
12
|
-
function log(msg) {
|
|
13
|
-
process.stdout.write(`${msg}
|
|
14
|
-
`);
|
|
15
|
-
}
|
|
16
|
-
function getCurrentBranch() {
|
|
17
|
-
const result = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
18
|
-
return result.stdout.toString().trim();
|
|
19
|
-
}
|
|
20
|
-
function runGit(args) {
|
|
21
|
-
try {
|
|
22
|
-
const result = Bun.spawnSync(["git", ...args], { stderr: "ignore" });
|
|
23
|
-
if (result.exitCode !== 0) {
|
|
24
|
-
return { ok: false, lines: [] };
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
ok: true,
|
|
28
|
-
lines: result.stdout.toString().split(`
|
|
29
|
-
`).map((line) => line.trim()).filter(Boolean)
|
|
30
|
-
};
|
|
31
|
-
} catch {
|
|
32
|
-
return { ok: false, lines: [] };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function isRedPhaseBranch(branch) {
|
|
36
|
-
return branch.endsWith("-tests") || branch.endsWith("/tests") || branch.endsWith("_tests");
|
|
37
|
-
}
|
|
38
|
-
function isScaffoldBranch(branch) {
|
|
39
|
-
return branch.endsWith("-scaffold") || branch.endsWith("/scaffold") || branch.endsWith("_scaffold");
|
|
40
|
-
}
|
|
41
|
-
var TEST_PATH_PATTERNS = [
|
|
42
|
-
/(^|\/)__tests__\//,
|
|
43
|
-
/(^|\/)__snapshots__\//,
|
|
44
|
-
/\.(test|spec)\.[cm]?[jt]sx?$/,
|
|
45
|
-
/\.snap$/,
|
|
46
|
-
/(^|\/)(vitest|jest|bun)\.config\.[cm]?[jt]s$/,
|
|
47
|
-
/(^|\/)tsconfig\.test\.json$/,
|
|
48
|
-
/(^|\/)\.env\.test(\.|$)/
|
|
49
|
-
];
|
|
50
|
-
function isTestOnlyPath(path) {
|
|
51
|
-
const normalized = path.replaceAll("\\", "/");
|
|
52
|
-
return TEST_PATH_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
53
|
-
}
|
|
54
|
-
function areFilesTestOnly(paths) {
|
|
55
|
-
return paths.length > 0 && paths.every((path) => isTestOnlyPath(path));
|
|
56
|
-
}
|
|
57
|
-
function canBypassRedPhaseByChangedFiles(changedFiles) {
|
|
58
|
-
return changedFiles.deterministic && areFilesTestOnly(changedFiles.files);
|
|
59
|
-
}
|
|
60
|
-
function resolveBaseRef() {
|
|
61
|
-
const candidates = [
|
|
62
|
-
"origin/main",
|
|
63
|
-
"main",
|
|
64
|
-
"origin/trunk",
|
|
65
|
-
"trunk",
|
|
66
|
-
"origin/master",
|
|
67
|
-
"master"
|
|
68
|
-
];
|
|
69
|
-
for (const candidate of candidates) {
|
|
70
|
-
const resolved = runGit(["rev-parse", "--verify", "--quiet", candidate]);
|
|
71
|
-
if (resolved.ok) {
|
|
72
|
-
return candidate;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
function changedFilesFromRange(range) {
|
|
78
|
-
const result = runGit(["diff", "--name-only", "--diff-filter=d", range]);
|
|
79
|
-
return {
|
|
80
|
-
ok: result.ok,
|
|
81
|
-
files: result.lines
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
function getChangedFilesForPush() {
|
|
85
|
-
const upstream = runGit([
|
|
86
|
-
"rev-parse",
|
|
87
|
-
"--abbrev-ref",
|
|
88
|
-
"--symbolic-full-name",
|
|
89
|
-
"@{upstream}"
|
|
90
|
-
]);
|
|
91
|
-
if (upstream.ok && upstream.lines[0]) {
|
|
92
|
-
const rangeResult = changedFilesFromRange(`${upstream.lines[0]}...HEAD`);
|
|
93
|
-
if (rangeResult.ok) {
|
|
94
|
-
return {
|
|
95
|
-
files: rangeResult.files,
|
|
96
|
-
deterministic: true,
|
|
97
|
-
source: "upstream"
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const baseRef = resolveBaseRef();
|
|
102
|
-
if (baseRef) {
|
|
103
|
-
const rangeResult = changedFilesFromRange(`${baseRef}...HEAD`);
|
|
104
|
-
if (rangeResult.ok) {
|
|
105
|
-
return {
|
|
106
|
-
files: rangeResult.files,
|
|
107
|
-
deterministic: true,
|
|
108
|
-
source: "baseRef"
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
files: [],
|
|
114
|
-
deterministic: false,
|
|
115
|
-
source: "undetermined"
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function maybeSkipForRedPhase(reason, branch) {
|
|
119
|
-
const changedFiles = getChangedFilesForPush();
|
|
120
|
-
if (!changedFiles.deterministic) {
|
|
121
|
-
log(`${COLORS.yellow}RED-phase bypass denied${COLORS.reset}: could not determine full push diff range`);
|
|
122
|
-
log("Running strict verification.");
|
|
123
|
-
log("");
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
if (!canBypassRedPhaseByChangedFiles(changedFiles)) {
|
|
127
|
-
log(`${COLORS.yellow}RED-phase bypass denied${COLORS.reset}: changed files are not test-only`);
|
|
128
|
-
if (changedFiles.files.length > 0) {
|
|
129
|
-
log(`Changed files (${changedFiles.source}): ${changedFiles.files.join(", ")}`);
|
|
130
|
-
} else {
|
|
131
|
-
log(`No changed files detected in ${changedFiles.source} range. Running strict verification.`);
|
|
132
|
-
}
|
|
133
|
-
log("");
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
if (reason === "branch") {
|
|
137
|
-
log(`${COLORS.yellow}TDD RED phase${COLORS.reset} detected: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
138
|
-
} else {
|
|
139
|
-
log(`${COLORS.yellow}Scaffold branch${COLORS.reset} with RED phase branch in context: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
140
|
-
}
|
|
141
|
-
log(`${COLORS.yellow}Skipping strict verification${COLORS.reset} - changed files are test-only`);
|
|
142
|
-
log(`Diff source: ${changedFiles.source}`);
|
|
143
|
-
log("");
|
|
144
|
-
log("Remember: GREEN phase (implementation) must make these tests pass!");
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
function hasRedPhaseBranchInContext(currentBranch) {
|
|
148
|
-
let branches = [];
|
|
149
|
-
try {
|
|
150
|
-
const gtResult = Bun.spawnSync(["gt", "ls"], { stderr: "pipe" });
|
|
151
|
-
if (gtResult.exitCode === 0) {
|
|
152
|
-
branches = gtResult.stdout.toString().split(`
|
|
153
|
-
`).map((line) => line.replace(/^[\u2502\u251C\u2514\u2500\u25C9\u25EF ]*/g, "").replace(/ \(.*/, "")).filter(Boolean);
|
|
154
|
-
}
|
|
155
|
-
} catch {}
|
|
156
|
-
if (branches.length === 0) {
|
|
157
|
-
const gitResult = Bun.spawnSync([
|
|
158
|
-
"git",
|
|
159
|
-
"branch",
|
|
160
|
-
"--list",
|
|
161
|
-
"cli/*",
|
|
162
|
-
"types/*",
|
|
163
|
-
"contracts/*"
|
|
164
|
-
]);
|
|
165
|
-
branches = gitResult.stdout.toString().split(`
|
|
166
|
-
`).map((line) => line.replace(/^[* ]+/, "")).filter(Boolean);
|
|
167
|
-
}
|
|
168
|
-
for (const branch of branches) {
|
|
169
|
-
if (branch === currentBranch)
|
|
170
|
-
continue;
|
|
171
|
-
if (isRedPhaseBranch(branch))
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
function createVerificationPlan(scripts) {
|
|
177
|
-
if (scripts["verify:ci"]) {
|
|
178
|
-
return { ok: true, scripts: ["verify:ci"], source: "verify:ci" };
|
|
179
|
-
}
|
|
180
|
-
const requiredScripts = ["typecheck", "build", "test"];
|
|
181
|
-
const missingRequired = requiredScripts.filter((name) => !scripts[name]);
|
|
182
|
-
const checkOrLint = scripts["check"] ? "check" : scripts["lint"] ? "lint" : undefined;
|
|
183
|
-
if (!checkOrLint || missingRequired.length > 0) {
|
|
184
|
-
const missing = checkOrLint ? missingRequired : [...missingRequired, "check|lint"];
|
|
185
|
-
return {
|
|
186
|
-
ok: false,
|
|
187
|
-
error: `Missing required scripts for strict pre-push verification: ${missing.join(", ")}`
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
return {
|
|
191
|
-
ok: true,
|
|
192
|
-
scripts: ["typecheck", checkOrLint, "build", "test"],
|
|
193
|
-
source: "fallback"
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
function readPackageScripts(cwd = process.cwd()) {
|
|
197
|
-
const packageJsonPath = join(cwd, "package.json");
|
|
198
|
-
if (!existsSync(packageJsonPath)) {
|
|
199
|
-
return {};
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
203
|
-
const scripts = parsed.scripts ?? {};
|
|
204
|
-
const normalized = {};
|
|
205
|
-
for (const [name, value] of Object.entries(scripts)) {
|
|
206
|
-
if (typeof value === "string") {
|
|
207
|
-
normalized[name] = value;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return normalized;
|
|
211
|
-
} catch {
|
|
212
|
-
return {};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
function runScript(scriptName) {
|
|
216
|
-
log("");
|
|
217
|
-
log(`Running: ${COLORS.blue}bun run ${scriptName}${COLORS.reset}`);
|
|
218
|
-
const result = Bun.spawnSync(["bun", "run", scriptName], {
|
|
219
|
-
stdio: ["inherit", "inherit", "inherit"]
|
|
220
|
-
});
|
|
221
|
-
return result.exitCode === 0;
|
|
222
|
-
}
|
|
223
|
-
async function runPrePush(options = {}) {
|
|
224
|
-
log(`${COLORS.blue}Pre-push verify${COLORS.reset} (TDD-aware)`);
|
|
225
|
-
log("");
|
|
226
|
-
const branch = getCurrentBranch();
|
|
227
|
-
if (isRedPhaseBranch(branch)) {
|
|
228
|
-
if (maybeSkipForRedPhase("branch", branch)) {
|
|
229
|
-
process.exit(0);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (isScaffoldBranch(branch)) {
|
|
233
|
-
if (hasRedPhaseBranchInContext(branch)) {
|
|
234
|
-
if (maybeSkipForRedPhase("context", branch)) {
|
|
235
|
-
process.exit(0);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (options.force) {
|
|
240
|
-
log(`${COLORS.yellow}Force flag set${COLORS.reset} - skipping strict verification`);
|
|
241
|
-
process.exit(0);
|
|
242
|
-
}
|
|
243
|
-
const plan = createVerificationPlan(readPackageScripts());
|
|
244
|
-
if (!plan.ok) {
|
|
245
|
-
log(`${COLORS.red}Strict pre-push verification is not configured${COLORS.reset}`);
|
|
246
|
-
log(plan.error);
|
|
247
|
-
log("");
|
|
248
|
-
log("Add one of:");
|
|
249
|
-
log(" - verify:ci");
|
|
250
|
-
log(" - typecheck + (check or lint) + build + test");
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
log(`Running strict verification for branch: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
254
|
-
if (plan.source === "verify:ci") {
|
|
255
|
-
log("Using `verify:ci` script.");
|
|
256
|
-
} else {
|
|
257
|
-
log(`Using fallback scripts: ${plan.scripts.join(" -> ")}`);
|
|
258
|
-
}
|
|
259
|
-
for (const scriptName of plan.scripts) {
|
|
260
|
-
if (runScript(scriptName)) {
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
log("");
|
|
264
|
-
log(`${COLORS.red}Verification failed${COLORS.reset} on script: ${scriptName}`);
|
|
265
|
-
log("");
|
|
266
|
-
log("If this is intentional TDD RED phase work, name your branch:");
|
|
267
|
-
log(" - feature-tests");
|
|
268
|
-
log(" - feature/tests");
|
|
269
|
-
log(" - feature_tests");
|
|
270
|
-
process.exit(1);
|
|
271
|
-
}
|
|
272
|
-
log("");
|
|
273
|
-
log(`${COLORS.green}Strict verification passed${COLORS.reset}`);
|
|
274
|
-
process.exit(0);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export { isRedPhaseBranch, isScaffoldBranch, isTestOnlyPath, areFilesTestOnly, canBypassRedPhaseByChangedFiles, createVerificationPlan, runPrePush };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/tooling/src/cli/fix.ts
|
|
3
|
-
function buildFixCommand(options) {
|
|
4
|
-
const cmd = ["ultracite", "fix"];
|
|
5
|
-
if (options.paths && options.paths.length > 0) {
|
|
6
|
-
cmd.push(...options.paths);
|
|
7
|
-
}
|
|
8
|
-
return cmd;
|
|
9
|
-
}
|
|
10
|
-
async function runFix(paths = []) {
|
|
11
|
-
const cmd = buildFixCommand({ paths });
|
|
12
|
-
process.stdout.write(`Running: bun x ${cmd.join(" ")}
|
|
13
|
-
`);
|
|
14
|
-
const proc = Bun.spawn(["bun", "x", ...cmd], {
|
|
15
|
-
stdio: ["inherit", "inherit", "inherit"]
|
|
16
|
-
});
|
|
17
|
-
const exitCode = await proc.exited;
|
|
18
|
-
process.exit(exitCode);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export { buildFixCommand, runFix };
|