@outfitter/tooling 0.2.2 → 0.2.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 +23 -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 +64 -0
- package/dist/cli/check-changeset.js +14 -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 +2 -0
- package/dist/cli/check-exports.js +12 -0
- package/dist/cli/check-readme-imports.d.ts +60 -0
- package/dist/cli/check-readme-imports.js +194 -0
- package/dist/cli/check.js +1 -0
- package/dist/cli/fix.js +1 -0
- package/dist/cli/index.js +598 -18
- package/dist/cli/init.js +1 -0
- package/dist/cli/pre-push.js +1 -0
- package/dist/cli/upgrade-bun.js +1 -0
- package/dist/index.d.ts +2 -33
- package/dist/index.js +5 -2
- package/dist/registry/build.js +1 -0
- package/dist/registry/index.js +1 -0
- package/dist/registry/schema.js +1 -0
- package/dist/shared/@outfitter/tooling-1y8w5ahg.js +70 -0
- package/dist/shared/@outfitter/tooling-3w8vr2w3.js +94 -0
- package/dist/shared/@outfitter/tooling-9vs606gq.d.ts +3 -0
- package/dist/shared/@outfitter/tooling-ctmgnap5.js +19 -0
- package/dist/shared/@outfitter/tooling-dvwh9qve.js +4 -0
- package/dist/shared/@outfitter/tooling-q0d60xb3.d.ts +58 -0
- package/dist/shared/@outfitter/tooling-r9976n43.js +100 -0
- package/dist/shared/@outfitter/tooling-t17gnh9b.js +78 -0
- package/dist/shared/@outfitter/tooling-tf22zt9p.js +226 -0
- package/dist/shared/chunk-3s189drz.js +4 -0
- package/dist/shared/chunk-6a7tjcgm.js +193 -0
- package/dist/shared/chunk-8aenrm6f.js +18 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +8 -0
- package/package.json +22 -21
- package/registry/registry.json +1 -1
package/dist/cli/init.js
CHANGED
package/dist/cli/pre-push.js
CHANGED
package/dist/cli/upgrade-bun.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -106,37 +106,6 @@ interface AddBlockOptions {
|
|
|
106
106
|
/** Working directory (defaults to cwd) */
|
|
107
107
|
cwd?: string;
|
|
108
108
|
}
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
*
|
|
112
|
-
* Dev tooling configuration presets for Outfitter projects.
|
|
113
|
-
* Provides standardized biome, TypeScript, lefthook, and markdownlint configurations.
|
|
114
|
-
*
|
|
115
|
-
* @example
|
|
116
|
-
* ```json
|
|
117
|
-
* // biome.json
|
|
118
|
-
* {
|
|
119
|
-
* "extends": ["ultracite/biome/core", "@outfitter/tooling/biome.json"]
|
|
120
|
-
* }
|
|
121
|
-
* ```
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```json
|
|
125
|
-
* // tsconfig.json
|
|
126
|
-
* {
|
|
127
|
-
* "extends": "@outfitter/tooling/tsconfig.preset.bun.json"
|
|
128
|
-
* }
|
|
129
|
-
* ```
|
|
130
|
-
*
|
|
131
|
-
* @example
|
|
132
|
-
* ```yaml
|
|
133
|
-
* # .lefthook.yml
|
|
134
|
-
* extends:
|
|
135
|
-
* - node_modules/@outfitter/tooling/lefthook.yml
|
|
136
|
-
* ```
|
|
137
|
-
*
|
|
138
|
-
* @packageDocumentation
|
|
139
|
-
*/
|
|
140
|
-
/** Package version */
|
|
141
|
-
declare const VERSION = "0.1.0-rc.1";
|
|
109
|
+
/** Package version, read from package.json at load time. */
|
|
110
|
+
declare const VERSION: string;
|
|
142
111
|
export { VERSION, RegistrySchema, RegistryBuildConfig, Registry, FileEntrySchema, FileEntry, BlockSchema, BlockDefinition, Block, AddBlockResult, AddBlockOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VERSION
|
|
3
|
+
} from "./shared/chunk-8aenrm6f.js";
|
|
4
|
+
import"./shared/chunk-3s189drz.js";
|
|
5
|
+
|
|
1
6
|
// src/registry/schema.ts
|
|
2
7
|
import { z } from "zod";
|
|
3
8
|
var FileEntrySchema = z.object({
|
|
@@ -18,8 +23,6 @@ var RegistrySchema = z.object({
|
|
|
18
23
|
version: z.string(),
|
|
19
24
|
blocks: z.record(BlockSchema)
|
|
20
25
|
});
|
|
21
|
-
// src/index.ts
|
|
22
|
-
var VERSION = "0.1.0-rc.1";
|
|
23
26
|
export {
|
|
24
27
|
VERSION,
|
|
25
28
|
RegistrySchema,
|
package/dist/registry/build.js
CHANGED
package/dist/registry/index.js
CHANGED
package/dist/registry/schema.js
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
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 };
|
|
@@ -0,0 +1,94 @@
|
|
|
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 };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/version.ts
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
var DEFAULT_VERSION = "0.0.0";
|
|
6
|
+
function readPackageVersion() {
|
|
7
|
+
try {
|
|
8
|
+
const require2 = createRequire(import.meta.url);
|
|
9
|
+
const pkgPath = require2.resolve("@outfitter/tooling/package.json");
|
|
10
|
+
const packageJson = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
11
|
+
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
12
|
+
return packageJson.version;
|
|
13
|
+
}
|
|
14
|
+
} catch {}
|
|
15
|
+
return DEFAULT_VERSION;
|
|
16
|
+
}
|
|
17
|
+
var VERSION = readPackageVersion();
|
|
18
|
+
|
|
19
|
+
export { VERSION };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** A package.json map: keys are subpaths, values are conditions or strings */
|
|
2
|
+
type ExportMap = Record<string, unknown>;
|
|
3
|
+
/** Describes drift between expected and actual exports for a single package */
|
|
4
|
+
interface ExportDrift {
|
|
5
|
+
readonly package: string;
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly added: string[];
|
|
8
|
+
readonly removed: string[];
|
|
9
|
+
readonly changed: Array<{
|
|
10
|
+
readonly key: string;
|
|
11
|
+
readonly expected: unknown;
|
|
12
|
+
readonly actual: unknown;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
/** Per-package comparison result */
|
|
16
|
+
interface PackageResult {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly status: "ok" | "drift";
|
|
19
|
+
readonly drift?: ExportDrift;
|
|
20
|
+
}
|
|
21
|
+
/** Aggregated result across all checked packages */
|
|
22
|
+
interface CheckResult {
|
|
23
|
+
readonly ok: boolean;
|
|
24
|
+
readonly packages: PackageResult[];
|
|
25
|
+
}
|
|
26
|
+
/** Input for comparing a single package's exports */
|
|
27
|
+
interface CompareInput {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly actual: ExportMap;
|
|
30
|
+
readonly expected: ExportMap;
|
|
31
|
+
readonly path?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Convert a source entry file path to its subpath.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* entryToSubpath("src/index.ts") // "."
|
|
38
|
+
* entryToSubpath("src/branded.ts") // "./branded"
|
|
39
|
+
* entryToSubpath("src/cli/index.ts") // "./cli"
|
|
40
|
+
* entryToSubpath("src/cli/check.ts") // "./cli/check"
|
|
41
|
+
*/
|
|
42
|
+
declare function entryToSubpath(entry: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Compare actual vs expected exports for a single package.
|
|
45
|
+
*
|
|
46
|
+
* Returns a PackageResult with status "ok" or "drift" and detailed diff.
|
|
47
|
+
*/
|
|
48
|
+
declare function compareExports(input: CompareInput): PackageResult;
|
|
49
|
+
interface CheckExportsOptions {
|
|
50
|
+
readonly json?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Run check-exports across all workspace packages.
|
|
54
|
+
*
|
|
55
|
+
* Reads the bunup workspace config to discover packages and their * settings, then compares expected vs actual exports in each package.json.
|
|
56
|
+
*/
|
|
57
|
+
declare function runCheckExports(options?: CheckExportsOptions): Promise<void>;
|
|
58
|
+
export { ExportMap, ExportDrift, PackageResult, CheckResult, CompareInput, entryToSubpath, compareExports, CheckExportsOptions, runCheckExports };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/check-boundary-invocations.ts
|
|
3
|
+
import { relative, resolve } from "path";
|
|
4
|
+
var ROOT_RUNS_PACKAGE_SRC = /\bbun(?:x)?\s+(?:run\s+)?(?:\.\.\/|\.\/)?packages\/[^/\s]+\/src\/\S+/;
|
|
5
|
+
var CD_PACKAGE_THEN_RUNS_SRC = /\bcd\s+(?:\.\.\/|\.\/)?packages\/[^/\s]+\s*&&\s*bun(?:x)?\s+(?:run\s+)?(?:\.\.\/|\.\/)?src\/\S+/;
|
|
6
|
+
function detectBoundaryViolation(location) {
|
|
7
|
+
if (ROOT_RUNS_PACKAGE_SRC.test(location.command)) {
|
|
8
|
+
return { ...location, rule: "root-runs-package-src" };
|
|
9
|
+
}
|
|
10
|
+
if (CD_PACKAGE_THEN_RUNS_SRC.test(location.command)) {
|
|
11
|
+
return { ...location, rule: "cd-package-then-runs-src" };
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function findBoundaryViolations(entries) {
|
|
16
|
+
const violations = [];
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
for (const [scriptName, command] of Object.entries(entry.scripts)) {
|
|
19
|
+
const violation = detectBoundaryViolation({
|
|
20
|
+
file: entry.file,
|
|
21
|
+
scriptName,
|
|
22
|
+
command
|
|
23
|
+
});
|
|
24
|
+
if (violation) {
|
|
25
|
+
violations.push(violation);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return violations.sort((a, b) => {
|
|
30
|
+
const fileCompare = a.file.localeCompare(b.file);
|
|
31
|
+
if (fileCompare !== 0) {
|
|
32
|
+
return fileCompare;
|
|
33
|
+
}
|
|
34
|
+
return a.scriptName.localeCompare(b.scriptName);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function readScriptEntries(cwd, options = {}) {
|
|
38
|
+
const entries = [];
|
|
39
|
+
const rootPackagePath = resolve(cwd, "package.json");
|
|
40
|
+
const candidatePaths = [rootPackagePath];
|
|
41
|
+
if (options.appManifestRelativePaths) {
|
|
42
|
+
for (const file of options.appManifestRelativePaths) {
|
|
43
|
+
candidatePaths.push(resolve(cwd, file));
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
const appPackageGlob = new Bun.Glob("apps/*/package.json");
|
|
47
|
+
for (const match of appPackageGlob.scanSync({ cwd })) {
|
|
48
|
+
candidatePaths.push(resolve(cwd, match));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const readPackageJson = options.readPackageJson ?? (async (filePath) => await Bun.file(filePath).json());
|
|
52
|
+
for (const filePath of candidatePaths) {
|
|
53
|
+
const isRootManifest = filePath === rootPackagePath;
|
|
54
|
+
try {
|
|
55
|
+
const pkg = await readPackageJson(filePath);
|
|
56
|
+
if (!pkg.scripts) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
entries.push({
|
|
60
|
+
file: relative(cwd, filePath),
|
|
61
|
+
scripts: pkg.scripts
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (isRootManifest) {
|
|
65
|
+
const message = error instanceof Error ? error.message : "unknown parse error";
|
|
66
|
+
throw new Error(`Failed to read root package manifest (${filePath}): ${message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return entries;
|
|
71
|
+
}
|
|
72
|
+
async function runCheckBoundaryInvocations() {
|
|
73
|
+
const cwd = process.cwd();
|
|
74
|
+
let entries;
|
|
75
|
+
try {
|
|
76
|
+
entries = await readScriptEntries(cwd);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const message = error instanceof Error ? error.message : "unknown read failure";
|
|
79
|
+
process.stderr.write(`Boundary invocation check failed before evaluation: ${message}
|
|
80
|
+
`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const violations = findBoundaryViolations(entries);
|
|
84
|
+
if (violations.length === 0) {
|
|
85
|
+
process.stdout.write(`No boundary invocation violations detected in root/apps scripts.
|
|
86
|
+
`);
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
process.stderr.write(`Boundary invocation violations detected:
|
|
90
|
+
|
|
91
|
+
`);
|
|
92
|
+
for (const violation of violations) {
|
|
93
|
+
process.stderr.write(`- ${violation.file}#${violation.scriptName}: ${violation.command}
|
|
94
|
+
`);
|
|
95
|
+
}
|
|
96
|
+
process.stderr.write("\nUse canonical command surfaces (e.g. `outfitter repo ...` or package bins) instead of executing packages/*/src directly.\n");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { detectBoundaryViolation, findBoundaryViolations, readScriptEntries, runCheckBoundaryInvocations };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/check-bunup-registry.ts
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
function extractBunupFilterName(script) {
|
|
5
|
+
const match = script.match(/bunup\s+--filter\s+(\S+)/);
|
|
6
|
+
return match?.[1] ?? null;
|
|
7
|
+
}
|
|
8
|
+
function findUnregisteredPackages(packagesWithFilter, registeredNames) {
|
|
9
|
+
const registered = new Set(registeredNames);
|
|
10
|
+
const missing = packagesWithFilter.filter((name) => !registered.has(name)).sort();
|
|
11
|
+
return {
|
|
12
|
+
ok: missing.length === 0,
|
|
13
|
+
missing
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
var COLORS = {
|
|
17
|
+
reset: "\x1B[0m",
|
|
18
|
+
red: "\x1B[31m",
|
|
19
|
+
green: "\x1B[32m",
|
|
20
|
+
yellow: "\x1B[33m",
|
|
21
|
+
blue: "\x1B[34m",
|
|
22
|
+
dim: "\x1B[2m"
|
|
23
|
+
};
|
|
24
|
+
async function runCheckBunupRegistry() {
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
const configPath = resolve(cwd, "bunup.config.ts");
|
|
27
|
+
let registeredNames;
|
|
28
|
+
try {
|
|
29
|
+
const configModule = await import(configPath);
|
|
30
|
+
const rawConfig = configModule.default;
|
|
31
|
+
if (!Array.isArray(rawConfig)) {
|
|
32
|
+
process.stderr.write(`bunup.config.ts must export a workspace array
|
|
33
|
+
`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
registeredNames = rawConfig.map((entry) => entry.name);
|
|
37
|
+
} catch {
|
|
38
|
+
process.stderr.write(`Could not load bunup.config.ts from ${cwd}
|
|
39
|
+
`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const packagesWithFilter = [];
|
|
43
|
+
const glob = new Bun.Glob("{packages,apps}/*/package.json");
|
|
44
|
+
for (const match of glob.scanSync({ cwd })) {
|
|
45
|
+
const pkgPath = resolve(cwd, match);
|
|
46
|
+
try {
|
|
47
|
+
const pkg = await Bun.file(pkgPath).json();
|
|
48
|
+
const buildScript = pkg.scripts?.["build"];
|
|
49
|
+
if (!buildScript)
|
|
50
|
+
continue;
|
|
51
|
+
const filterName = extractBunupFilterName(buildScript);
|
|
52
|
+
if (filterName) {
|
|
53
|
+
packagesWithFilter.push(filterName);
|
|
54
|
+
}
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
const result = findUnregisteredPackages(packagesWithFilter, registeredNames);
|
|
58
|
+
if (result.ok) {
|
|
59
|
+
process.stdout.write(`${COLORS.green}All ${packagesWithFilter.length} packages with bunup --filter are registered in bunup.config.ts.${COLORS.reset}
|
|
60
|
+
`);
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
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}
|
|
64
|
+
|
|
65
|
+
`);
|
|
66
|
+
for (const name of result.missing) {
|
|
67
|
+
process.stderr.write(` ${COLORS.yellow}${name}${COLORS.reset} ${COLORS.dim}(missing from workspace array)${COLORS.reset}
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
process.stderr.write(`
|
|
71
|
+
Add the missing entries to ${COLORS.blue}bunup.config.ts${COLORS.reset} defineWorkspace array.
|
|
72
|
+
`);
|
|
73
|
+
process.stderr.write(`Without registration, ${COLORS.dim}bunup --filter <name>${COLORS.reset} silently exits with no output.
|
|
74
|
+
`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { extractBunupFilterName, findUnregisteredPackages, runCheckBunupRegistry };
|