outfitter 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 +215 -116
- package/dist/actions.d.ts +2 -0
- package/dist/actions.js +34 -0
- package/dist/cli.js +3 -1
- package/dist/commands/add.d.ts +54 -0
- package/dist/commands/add.js +16 -0
- package/dist/commands/check.d.ts +91 -0
- package/dist/commands/check.js +14 -0
- package/dist/commands/demo.d.ts +21 -0
- package/dist/commands/demo.js +8 -0
- package/dist/commands/docs-module-loader.d.ts +2 -0
- package/dist/commands/docs-module-loader.js +8 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +13 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +31 -0
- package/dist/commands/migrate-kit.d.ts +2 -0
- package/dist/commands/migrate-kit.js +15 -0
- package/dist/commands/repo.d.ts +3 -0
- package/dist/commands/repo.js +9 -0
- package/dist/commands/scaffold.d.ts +4 -0
- package/dist/commands/scaffold.js +31 -0
- package/dist/commands/shared-deps.d.ts +36 -0
- package/dist/commands/shared-deps.js +10 -0
- package/dist/commands/update-planner.d.ts +58 -0
- package/dist/commands/update-planner.js +8 -0
- package/dist/commands/update-workspace.d.ts +76 -0
- package/dist/commands/update-workspace.js +16 -0
- package/dist/commands/update.d.ts +113 -0
- package/dist/commands/update.js +21 -0
- package/dist/create/index.d.ts +5 -0
- package/dist/create/index.js +29 -0
- package/dist/create/planner.d.ts +3 -0
- package/dist/create/planner.js +21 -0
- package/dist/create/presets.d.ts +3 -0
- package/dist/create/presets.js +12 -0
- package/dist/create/types.d.ts +2 -0
- package/dist/create/types.js +1 -0
- package/dist/engine/blocks.d.ts +3 -0
- package/dist/engine/blocks.js +12 -0
- package/dist/engine/collector.d.ts +2 -0
- package/dist/engine/collector.js +8 -0
- package/dist/engine/config.d.ts +3 -0
- package/dist/engine/config.js +12 -0
- package/dist/engine/executor.d.ts +3 -0
- package/dist/engine/executor.js +16 -0
- package/dist/engine/index.d.ts +8 -0
- package/dist/engine/index.js +59 -0
- package/dist/engine/names.d.ts +2 -0
- package/dist/engine/names.js +16 -0
- package/dist/engine/post-scaffold.d.ts +3 -0
- package/dist/engine/post-scaffold.js +8 -0
- package/dist/engine/render-plan.d.ts +7 -0
- package/dist/engine/render-plan.js +9 -0
- package/dist/engine/template.d.ts +3 -0
- package/dist/engine/template.js +17 -0
- package/dist/engine/types.d.ts +2 -0
- package/dist/engine/types.js +8 -0
- package/dist/engine/workspace.d.ts +3 -0
- package/dist/engine/workspace.js +13 -0
- package/dist/index.d.ts +228 -152
- package/dist/index.js +144 -14
- package/dist/manifest.d.ts +71 -0
- package/dist/manifest.js +16 -0
- package/dist/output-mode.d.ts +2 -0
- package/dist/output-mode.js +10 -0
- package/dist/shared/chunk-b0y0cwkr.js +5533 -0
- package/dist/shared/outfitter-193jvzg4.d.ts +5 -0
- package/dist/shared/outfitter-1dd0k853.js +194 -0
- package/dist/shared/outfitter-1h7k8xxt.js +29 -0
- package/dist/shared/outfitter-1qwpjt6w.js +125 -0
- package/dist/shared/outfitter-2ngep1h2.d.ts +5 -0
- package/dist/shared/outfitter-2np85etz.js +95 -0
- package/dist/shared/outfitter-33w361tc.d.ts +18 -0
- package/dist/shared/outfitter-344t1r38.js +1 -0
- package/dist/shared/outfitter-3weh61w7.d.ts +25 -0
- package/dist/shared/outfitter-4s9meh3j.js +221 -0
- package/dist/shared/outfitter-6a4bq054.js +322 -0
- package/dist/shared/outfitter-6bkqjk86.d.ts +3 -0
- package/dist/shared/outfitter-6gc3g5wk.js +98 -0
- package/dist/shared/outfitter-7cv5fg1m.js +61 -0
- package/dist/shared/outfitter-7ha7p61k.d.ts +6 -0
- package/dist/shared/outfitter-7r12fj7f.js +30 -0
- package/dist/shared/outfitter-8y2dfx6n.js +11 -0
- package/dist/shared/outfitter-9c8edfsn.js +715 -0
- package/dist/shared/outfitter-9x1brcmq.js +184 -0
- package/dist/shared/outfitter-a79xrm12.d.ts +17 -0
- package/dist/shared/outfitter-amc4jbs1.d.ts +50 -0
- package/dist/shared/outfitter-ara3djt0.js +73 -0
- package/dist/shared/outfitter-avhm5z6w.js +82 -0
- package/dist/shared/outfitter-b5nd42y4.d.ts +45 -0
- package/dist/shared/outfitter-dd0btgec.d.ts +40 -0
- package/dist/shared/outfitter-e2zz5wv7.d.ts +51 -0
- package/dist/shared/outfitter-ehp18x1n.js +1 -0
- package/dist/shared/outfitter-fnsmx3xg.js +750 -0
- package/dist/shared/outfitter-gdvm5c0b.d.ts +4 -0
- package/dist/shared/outfitter-gp4v5gkf.js +322 -0
- package/dist/shared/outfitter-h1mnzzd1.d.ts +14 -0
- package/dist/shared/outfitter-hpymx4m9.js +184 -0
- package/dist/shared/outfitter-hvsaxgcp.js +1 -0
- package/dist/shared/outfitter-j8yc7294.d.ts +22 -0
- package/dist/shared/outfitter-jyxwznk1.js +404 -0
- package/dist/shared/outfitter-k112c427.js +21 -0
- package/dist/shared/outfitter-k56rmt24.d.ts +30 -0
- package/dist/shared/outfitter-ksa1pp4t.d.ts +4 -0
- package/dist/shared/outfitter-mdt37hqm.js +4 -0
- package/dist/shared/outfitter-mtbpabf3.js +91 -0
- package/dist/shared/outfitter-nm4m0v6x.d.ts +131 -0
- package/dist/shared/outfitter-nmeecf1b.js +531 -0
- package/dist/shared/outfitter-npyfbdmc.d.ts +6 -0
- package/dist/shared/outfitter-pxt58tsq.js +582 -0
- package/dist/shared/outfitter-q9agarmb.js +42 -0
- package/dist/shared/outfitter-qfgj5xpq.js +70 -0
- package/dist/shared/outfitter-qfh36ddg.d.ts +66 -0
- package/dist/shared/outfitter-s6k8y2p4.js +269 -0
- package/dist/shared/outfitter-sftf1s26.js +199 -0
- package/dist/shared/outfitter-sg7ncy4a.d.ts +51 -0
- package/dist/shared/outfitter-sgtq57qr.d.ts +5 -0
- package/dist/shared/outfitter-txre6cdn.d.ts +60 -0
- package/dist/shared/outfitter-vh4xgb93.js +35 -0
- package/dist/shared/outfitter-ya44h1km.js +191 -0
- package/dist/shared/outfitter-zwyvewr1.js +36 -0
- package/dist/targets/index.d.ts +4 -0
- package/dist/targets/index.js +29 -0
- package/dist/targets/registry.d.ts +3 -0
- package/dist/targets/registry.js +28 -0
- package/dist/targets/types.d.ts +2 -0
- package/dist/targets/types.js +1 -0
- package/package.json +154 -37
- package/templates/minimal/.gitignore.template +30 -0
- package/templates/minimal/.lefthook.yml.template +26 -0
- package/templates/minimal/package.json.template +46 -0
- package/templates/minimal/src/index.ts.template +26 -0
- package/templates/minimal/tsconfig.json.template +34 -0
- package/dist/shared/chunk-sak1tt33.js +0 -3457
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { EngineOptions, ScaffoldError, ScaffoldPlan, ScaffoldResult } from "./outfitter-qfh36ddg";
|
|
2
|
+
import { Result } from "@outfitter/contracts";
|
|
3
|
+
declare function executePlan(plan: ScaffoldPlan, options: EngineOptions): Promise<Result<ScaffoldResult, ScaffoldError>>;
|
|
4
|
+
export { executePlan };
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
readManifest
|
|
4
|
+
} from "./outfitter-mtbpabf3.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveStructuredOutputMode
|
|
7
|
+
} from "./outfitter-7r12fj7f.js";
|
|
8
|
+
|
|
9
|
+
// apps/outfitter/src/commands/check.ts
|
|
10
|
+
import { existsSync, readFileSync } from "fs";
|
|
11
|
+
import { dirname, join, resolve } from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { output } from "@outfitter/cli/output";
|
|
14
|
+
import { Result } from "@outfitter/contracts";
|
|
15
|
+
import { RegistrySchema } from "@outfitter/tooling";
|
|
16
|
+
import { createTheme } from "@outfitter/tui/render";
|
|
17
|
+
class CheckError extends Error {
|
|
18
|
+
_tag = "CheckError";
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "CheckError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function getRegistryPath() {
|
|
25
|
+
let currentDir = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
for (let i = 0;i < 10; i++) {
|
|
27
|
+
const registryPath = join(currentDir, "node_modules/@outfitter/tooling/registry/registry.json");
|
|
28
|
+
if (existsSync(registryPath)) {
|
|
29
|
+
return registryPath;
|
|
30
|
+
}
|
|
31
|
+
const monoRepoPath = join(currentDir, "packages/tooling/registry/registry.json");
|
|
32
|
+
if (existsSync(monoRepoPath)) {
|
|
33
|
+
return monoRepoPath;
|
|
34
|
+
}
|
|
35
|
+
currentDir = dirname(currentDir);
|
|
36
|
+
}
|
|
37
|
+
throw new CheckError("Could not find registry.json. Ensure @outfitter/tooling is installed.");
|
|
38
|
+
}
|
|
39
|
+
function readToolingVersion(registryPath) {
|
|
40
|
+
try {
|
|
41
|
+
const toolingRoot = dirname(dirname(registryPath));
|
|
42
|
+
const pkgPath = join(toolingRoot, "package.json");
|
|
43
|
+
const content = readFileSync(pkgPath, "utf-8");
|
|
44
|
+
const pkg = JSON.parse(content);
|
|
45
|
+
return pkg.version ?? "unknown";
|
|
46
|
+
} catch {
|
|
47
|
+
return "unknown";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function loadRegistry() {
|
|
51
|
+
try {
|
|
52
|
+
const registryPath = getRegistryPath();
|
|
53
|
+
const content = readFileSync(registryPath, "utf-8");
|
|
54
|
+
const parsed = JSON.parse(content);
|
|
55
|
+
const registry = RegistrySchema.parse(parsed);
|
|
56
|
+
const toolingVersion = readToolingVersion(registryPath);
|
|
57
|
+
return Result.ok({ registry, toolingVersion });
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
60
|
+
return Result.err(new CheckError(`Failed to load registry: ${message}`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function resolveBlockFiles(registry, blockName, visited = new Set) {
|
|
64
|
+
if (visited.has(blockName)) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
visited.add(blockName);
|
|
68
|
+
const block = registry.blocks[blockName];
|
|
69
|
+
if (!block) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const files = [];
|
|
73
|
+
if (block.extends && block.extends.length > 0) {
|
|
74
|
+
for (const extendedName of block.extends) {
|
|
75
|
+
files.push(...resolveBlockFiles(registry, extendedName, visited));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (block.files) {
|
|
79
|
+
files.push(...block.files);
|
|
80
|
+
}
|
|
81
|
+
return files;
|
|
82
|
+
}
|
|
83
|
+
function deepEqual(a, b) {
|
|
84
|
+
if (a === b)
|
|
85
|
+
return true;
|
|
86
|
+
if (a === null || b === null)
|
|
87
|
+
return a === b;
|
|
88
|
+
if (typeof a !== typeof b)
|
|
89
|
+
return false;
|
|
90
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
91
|
+
if (a.length !== b.length)
|
|
92
|
+
return false;
|
|
93
|
+
for (let i = 0;i < a.length; i++) {
|
|
94
|
+
if (!deepEqual(a[i], b[i]))
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
100
|
+
const aObj = a;
|
|
101
|
+
const bObj = b;
|
|
102
|
+
const aKeys = Object.keys(aObj);
|
|
103
|
+
const bKeys = Object.keys(bObj);
|
|
104
|
+
if (aKeys.length !== bKeys.length)
|
|
105
|
+
return false;
|
|
106
|
+
for (const key of aKeys) {
|
|
107
|
+
if (!Object.hasOwn(bObj, key))
|
|
108
|
+
return false;
|
|
109
|
+
if (!deepEqual(aObj[key], bObj[key]))
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
function compareFileContent(filePath, localContent, registryContent) {
|
|
117
|
+
if (filePath.endsWith(".json") || filePath.endsWith(".jsonc")) {
|
|
118
|
+
try {
|
|
119
|
+
const cleanLocal = filePath.endsWith(".jsonc") ? stripJsoncComments(localContent) : localContent;
|
|
120
|
+
const cleanRegistry = filePath.endsWith(".jsonc") ? stripJsoncComments(registryContent) : registryContent;
|
|
121
|
+
const localParsed = JSON.parse(cleanLocal);
|
|
122
|
+
const registryParsed = JSON.parse(cleanRegistry);
|
|
123
|
+
return deepEqual(localParsed, registryParsed);
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
126
|
+
return localContent === registryContent;
|
|
127
|
+
}
|
|
128
|
+
function stripJsoncComments(content) {
|
|
129
|
+
return content.split(`
|
|
130
|
+
`).map((line) => {
|
|
131
|
+
let inString = false;
|
|
132
|
+
for (let i = 0;i < line.length; i++) {
|
|
133
|
+
const char = line[i];
|
|
134
|
+
if (char === '"' && (i === 0 || line[i - 1] !== "\\")) {
|
|
135
|
+
inString = !inString;
|
|
136
|
+
}
|
|
137
|
+
if (!inString && char === "/" && line[i + 1] === "/") {
|
|
138
|
+
return line.slice(0, i).trimEnd();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return line;
|
|
142
|
+
}).filter((line) => line.trim() !== "").join(`
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
var BLOCK_FILE_MARKERS = {
|
|
146
|
+
biome: ["biome.json"],
|
|
147
|
+
lefthook: [".lefthook.yml"],
|
|
148
|
+
claude: [".claude/settings.json"],
|
|
149
|
+
markdownlint: [".markdownlint-cli2.jsonc"],
|
|
150
|
+
bootstrap: ["scripts/bootstrap.sh"]
|
|
151
|
+
};
|
|
152
|
+
function detectBlocksByFilePresence(cwd) {
|
|
153
|
+
const detected = [];
|
|
154
|
+
for (const [blockName, files] of Object.entries(BLOCK_FILE_MARKERS)) {
|
|
155
|
+
const hasAnyFile = files.some((filePath) => existsSync(join(cwd, filePath)));
|
|
156
|
+
if (hasAnyFile) {
|
|
157
|
+
detected.push(blockName);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return detected;
|
|
161
|
+
}
|
|
162
|
+
function checkBlock(cwd, blockName, registry, toolingVersion, installedFrom, verbose) {
|
|
163
|
+
const versionFields = {
|
|
164
|
+
...installedFrom !== undefined ? { installedFrom } : {},
|
|
165
|
+
currentToolingVersion: toolingVersion
|
|
166
|
+
};
|
|
167
|
+
const files = resolveBlockFiles(registry, blockName);
|
|
168
|
+
if (files.length === 0 && !registry.blocks[blockName]) {
|
|
169
|
+
return {
|
|
170
|
+
name: blockName,
|
|
171
|
+
status: "missing",
|
|
172
|
+
...versionFields
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (files.length === 0) {
|
|
176
|
+
return {
|
|
177
|
+
name: blockName,
|
|
178
|
+
status: "current",
|
|
179
|
+
...versionFields
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
let allMissing = true;
|
|
183
|
+
let anyDrifted = false;
|
|
184
|
+
const driftedFiles = [];
|
|
185
|
+
for (const file of files) {
|
|
186
|
+
const localPath = join(cwd, file.path);
|
|
187
|
+
if (!existsSync(localPath)) {
|
|
188
|
+
anyDrifted = true;
|
|
189
|
+
if (verbose) {
|
|
190
|
+
driftedFiles.push({ path: file.path, reason: "missing" });
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
allMissing = false;
|
|
195
|
+
const localContent = readFileSync(localPath, "utf-8");
|
|
196
|
+
if (!compareFileContent(file.path, localContent, file.content)) {
|
|
197
|
+
anyDrifted = true;
|
|
198
|
+
if (verbose) {
|
|
199
|
+
driftedFiles.push({ path: file.path, reason: "modified" });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (allMissing) {
|
|
204
|
+
return {
|
|
205
|
+
name: blockName,
|
|
206
|
+
status: "missing",
|
|
207
|
+
...versionFields,
|
|
208
|
+
...verbose && driftedFiles.length > 0 ? { driftedFiles } : {}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (anyDrifted) {
|
|
212
|
+
return {
|
|
213
|
+
name: blockName,
|
|
214
|
+
status: "drifted",
|
|
215
|
+
...versionFields,
|
|
216
|
+
...verbose && driftedFiles.length > 0 ? { driftedFiles } : {}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
name: blockName,
|
|
221
|
+
status: "current",
|
|
222
|
+
...versionFields
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
async function runCheck(options) {
|
|
226
|
+
const { cwd: rawCwd, verbose = false, block: blockFilter } = options;
|
|
227
|
+
const cwd = resolve(rawCwd);
|
|
228
|
+
const registryResult = loadRegistry();
|
|
229
|
+
if (registryResult.isErr()) {
|
|
230
|
+
return registryResult;
|
|
231
|
+
}
|
|
232
|
+
const { registry, toolingVersion } = registryResult.value;
|
|
233
|
+
const manifestResult = await readManifest(cwd);
|
|
234
|
+
if (manifestResult.isErr()) {
|
|
235
|
+
return Result.err(new CheckError(`Failed to read manifest: ${manifestResult.error.message}`));
|
|
236
|
+
}
|
|
237
|
+
const manifest = manifestResult.value;
|
|
238
|
+
let blocksToCheck;
|
|
239
|
+
if (manifest) {
|
|
240
|
+
blocksToCheck = Object.entries(manifest.blocks).map(([name, entry]) => ({
|
|
241
|
+
name,
|
|
242
|
+
installedFrom: entry.installedFrom
|
|
243
|
+
}));
|
|
244
|
+
} else {
|
|
245
|
+
const detected = detectBlocksByFilePresence(cwd);
|
|
246
|
+
blocksToCheck = detected.map((name) => ({
|
|
247
|
+
name,
|
|
248
|
+
installedFrom: undefined
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
if (blockFilter) {
|
|
252
|
+
blocksToCheck = blocksToCheck.filter((b) => b.name === blockFilter);
|
|
253
|
+
}
|
|
254
|
+
const blocks = [];
|
|
255
|
+
for (const { name, installedFrom } of blocksToCheck) {
|
|
256
|
+
blocks.push(checkBlock(cwd, name, registry, toolingVersion, installedFrom, verbose));
|
|
257
|
+
}
|
|
258
|
+
const currentCount = blocks.filter((b) => b.status === "current").length;
|
|
259
|
+
const driftedCount = blocks.filter((b) => b.status === "drifted").length;
|
|
260
|
+
const missingCount = blocks.filter((b) => b.status === "missing").length;
|
|
261
|
+
return Result.ok({
|
|
262
|
+
blocks,
|
|
263
|
+
totalChecked: blocks.length,
|
|
264
|
+
currentCount,
|
|
265
|
+
driftedCount,
|
|
266
|
+
missingCount
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
async function printCheckResults(result, options) {
|
|
270
|
+
const structuredMode = resolveStructuredOutputMode(options?.mode);
|
|
271
|
+
if (structuredMode) {
|
|
272
|
+
await output(result, { mode: structuredMode });
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const theme = createTheme();
|
|
276
|
+
const lines = [];
|
|
277
|
+
lines.push("");
|
|
278
|
+
lines.push("Outfitter Check");
|
|
279
|
+
lines.push("=".repeat(50));
|
|
280
|
+
lines.push("");
|
|
281
|
+
for (const block of result.blocks) {
|
|
282
|
+
let statusIcon;
|
|
283
|
+
switch (block.status) {
|
|
284
|
+
case "current":
|
|
285
|
+
statusIcon = theme.success("[PASS]");
|
|
286
|
+
break;
|
|
287
|
+
case "drifted":
|
|
288
|
+
statusIcon = theme.error("[DRIFT]");
|
|
289
|
+
break;
|
|
290
|
+
case "missing":
|
|
291
|
+
statusIcon = theme.warning("[MISSING]");
|
|
292
|
+
break;
|
|
293
|
+
default:
|
|
294
|
+
statusIcon = "[?]";
|
|
295
|
+
}
|
|
296
|
+
const versionInfo = block.installedFrom ? ` (installed from ${block.installedFrom})` : "";
|
|
297
|
+
lines.push(`${statusIcon} ${block.name}${versionInfo}`);
|
|
298
|
+
if (options?.verbose && block.driftedFiles) {
|
|
299
|
+
for (const file of block.driftedFiles) {
|
|
300
|
+
const reason = file.reason === "missing" ? "file missing" : "modified";
|
|
301
|
+
lines.push(` ${theme.muted(`${file.path}: ${reason}`)}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
lines.push("");
|
|
306
|
+
lines.push("=".repeat(50));
|
|
307
|
+
const summaryColor = result.driftedCount === 0 && result.missingCount === 0 ? theme.success : theme.error;
|
|
308
|
+
lines.push(summaryColor(`${result.currentCount}/${result.totalChecked} blocks current`));
|
|
309
|
+
if (result.driftedCount > 0) {
|
|
310
|
+
lines.push(theme.muted(`${result.driftedCount} block(s) have drifted`));
|
|
311
|
+
}
|
|
312
|
+
if (result.missingCount > 0) {
|
|
313
|
+
lines.push(theme.muted(`${result.missingCount} block(s) have missing files`));
|
|
314
|
+
}
|
|
315
|
+
if (result.driftedCount > 0 || result.missingCount > 0) {
|
|
316
|
+
lines.push("");
|
|
317
|
+
lines.push(theme.muted("Run 'outfitter add <block> --force' to restore registry defaults."));
|
|
318
|
+
}
|
|
319
|
+
await output(lines, { mode: "human" });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export { CheckError, runCheck, printCheckResults };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EngineOptions, PlaceholderValues, ScaffoldError } from "./outfitter-qfh36ddg";
|
|
2
|
+
import { Result } from "@outfitter/contracts";
|
|
3
|
+
declare function getTemplatesDir(): string;
|
|
4
|
+
declare function getOutputFilename(templateFilename: string): string;
|
|
5
|
+
declare function isBinaryFile(filename: string): boolean;
|
|
6
|
+
declare function replacePlaceholders(content: string, values: PlaceholderValues): string;
|
|
7
|
+
declare function copyTemplateFiles(templateDir: string, targetDir: string, values: PlaceholderValues, options: EngineOptions, copyOptions?: {
|
|
8
|
+
readonly allowOverwrite?: boolean;
|
|
9
|
+
readonly overwritablePaths?: ReadonlySet<string>;
|
|
10
|
+
readonly writtenPaths?: Set<string>;
|
|
11
|
+
readonly skipFilter?: (relativePath: string) => boolean;
|
|
12
|
+
readonly relativePrefix?: string;
|
|
13
|
+
}): Result<void, ScaffoldError>;
|
|
14
|
+
export { getTemplatesDir, getOutputFilename, isBinaryFile, replacePlaceholders, copyTemplateFiles };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
loadDocsModule
|
|
4
|
+
} from "./outfitter-zwyvewr1.js";
|
|
5
|
+
|
|
6
|
+
// apps/outfitter/src/commands/repo.ts
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
import { dirname, join, resolve } from "path";
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
var require2 = createRequire(import.meta.url);
|
|
12
|
+
function getIo(options) {
|
|
13
|
+
return {
|
|
14
|
+
out: options?.io?.out ?? ((line) => process.stdout.write(`${line}
|
|
15
|
+
`)),
|
|
16
|
+
err: options?.io?.err ?? ((line) => process.stderr.write(`${line}
|
|
17
|
+
`))
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function resolveCwdOption(inputCwd) {
|
|
21
|
+
if (typeof inputCwd !== "string" || inputCwd.length === 0) {
|
|
22
|
+
return process.cwd();
|
|
23
|
+
}
|
|
24
|
+
return resolve(process.cwd(), inputCwd);
|
|
25
|
+
}
|
|
26
|
+
function resolveToolingCliEntrypoint() {
|
|
27
|
+
const packageJsonPath = require2.resolve("@outfitter/tooling/package.json");
|
|
28
|
+
const packageRoot = dirname(packageJsonPath);
|
|
29
|
+
const srcEntrypoint = join(packageRoot, "src", "cli", "index.ts");
|
|
30
|
+
if (existsSync(srcEntrypoint)) {
|
|
31
|
+
return srcEntrypoint;
|
|
32
|
+
}
|
|
33
|
+
const distEntrypoint = join(packageRoot, "dist", "cli", "index.js");
|
|
34
|
+
if (existsSync(distEntrypoint)) {
|
|
35
|
+
return distEntrypoint;
|
|
36
|
+
}
|
|
37
|
+
throw new Error("Unable to resolve @outfitter/tooling CLI entrypoint (expected dist/cli/index.js or src/cli/index.ts).");
|
|
38
|
+
}
|
|
39
|
+
async function runDocsCheckDefault(options, io) {
|
|
40
|
+
const docsModule = await loadDocsModule();
|
|
41
|
+
return await docsModule.executeCheckCommand(options, io);
|
|
42
|
+
}
|
|
43
|
+
async function runDocsSyncDefault(options, io) {
|
|
44
|
+
const docsModule = await loadDocsModule();
|
|
45
|
+
return await docsModule.executeSyncCommand(options, io);
|
|
46
|
+
}
|
|
47
|
+
async function runDocsExportDefault(options, io) {
|
|
48
|
+
const docsModule = await loadDocsModule();
|
|
49
|
+
return await docsModule.executeExportCommand(options, io);
|
|
50
|
+
}
|
|
51
|
+
async function runToolingCommandDefault(input) {
|
|
52
|
+
const toolingCliEntrypoint = resolveToolingCliEntrypoint();
|
|
53
|
+
const child = Bun.spawn([process.execPath, toolingCliEntrypoint, input.command, ...input.args], {
|
|
54
|
+
cwd: input.cwd,
|
|
55
|
+
stdin: "inherit",
|
|
56
|
+
stdout: "inherit",
|
|
57
|
+
stderr: "inherit"
|
|
58
|
+
});
|
|
59
|
+
return await child.exited;
|
|
60
|
+
}
|
|
61
|
+
function applyExitCode(code) {
|
|
62
|
+
if (code !== 0) {
|
|
63
|
+
process.exitCode = code;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function addDocsCheckSubcommand(command, options) {
|
|
67
|
+
command.command("docs").description("Check whether assembled package docs are in sync").option("--cwd <path>", "Workspace root to operate in").option("--packages-dir <path>", "Packages directory relative to workspace").option("--output-dir <path>", "Output directory relative to workspace").option("--mdx-mode <mode>", "MDX handling mode: strict or lossy").action(async (cmdOptions) => {
|
|
68
|
+
const code = await options.runDocsCheck({
|
|
69
|
+
...cmdOptions,
|
|
70
|
+
...cmdOptions.cwd ? { cwd: resolveCwdOption(cmdOptions.cwd) } : {}
|
|
71
|
+
}, options.io);
|
|
72
|
+
applyExitCode(code);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function addDocsSyncSubcommand(command, options) {
|
|
76
|
+
command.command("docs").description("Assemble package docs into docs/packages").option("--cwd <path>", "Workspace root to operate in").option("--packages-dir <path>", "Packages directory relative to workspace").option("--output-dir <path>", "Output directory relative to workspace").option("--mdx-mode <mode>", "MDX handling mode: strict or lossy").action(async (cmdOptions) => {
|
|
77
|
+
const code = await options.runDocsSync({
|
|
78
|
+
...cmdOptions,
|
|
79
|
+
...cmdOptions.cwd ? { cwd: resolveCwdOption(cmdOptions.cwd) } : {}
|
|
80
|
+
}, options.io);
|
|
81
|
+
applyExitCode(code);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function addDocsExportSubcommand(command, options) {
|
|
85
|
+
command.command("docs").description("Export docs artifacts for packages and LLM targets").option("--cwd <path>", "Workspace root to operate in").option("--packages-dir <path>", "Packages directory relative to workspace").option("--output-dir <path>", "Output directory relative to workspace").option("--mdx-mode <mode>", "MDX handling mode: strict or lossy").option("--llms-file <path>", "llms.txt output path relative to workspace").option("--llms-full-file <path>", "llms-full.txt output path relative to workspace").option("--target <target>", "Export target: packages, llms, llms-full, all", "all").action(async (cmdOptions) => {
|
|
86
|
+
const code = await options.runDocsExport({
|
|
87
|
+
...cmdOptions,
|
|
88
|
+
...cmdOptions.cwd ? { cwd: resolveCwdOption(cmdOptions.cwd) } : {}
|
|
89
|
+
}, options.io);
|
|
90
|
+
applyExitCode(code);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function addToolingCheckSubcommands(command, runToolingCommand) {
|
|
94
|
+
command.command("exports").description("Validate package.json exports match source entry points").option("--json", "Output results as JSON").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
|
|
95
|
+
const args = [];
|
|
96
|
+
if (cmdOptions.json) {
|
|
97
|
+
args.push("--json");
|
|
98
|
+
}
|
|
99
|
+
const code = await runToolingCommand({
|
|
100
|
+
command: "check-exports",
|
|
101
|
+
args,
|
|
102
|
+
cwd: resolveCwdOption(cmdOptions.cwd)
|
|
103
|
+
});
|
|
104
|
+
applyExitCode(code);
|
|
105
|
+
});
|
|
106
|
+
command.command("readme").description("Validate README import examples match package exports").option("--json", "Output results as JSON").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
|
|
107
|
+
const args = [];
|
|
108
|
+
if (cmdOptions.json) {
|
|
109
|
+
args.push("--json");
|
|
110
|
+
}
|
|
111
|
+
const code = await runToolingCommand({
|
|
112
|
+
command: "check-readme-imports",
|
|
113
|
+
args,
|
|
114
|
+
cwd: resolveCwdOption(cmdOptions.cwd)
|
|
115
|
+
});
|
|
116
|
+
applyExitCode(code);
|
|
117
|
+
});
|
|
118
|
+
command.command("registry").description("Validate packages with bunup --filter are registered in bunup.config.ts").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
|
|
119
|
+
const code = await runToolingCommand({
|
|
120
|
+
command: "check-bunup-registry",
|
|
121
|
+
args: [],
|
|
122
|
+
cwd: resolveCwdOption(cmdOptions.cwd)
|
|
123
|
+
});
|
|
124
|
+
applyExitCode(code);
|
|
125
|
+
});
|
|
126
|
+
command.command("changeset").description("Validate PRs touching package source include a changeset").option("-s, --skip", "Skip changeset check").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
|
|
127
|
+
const args = [];
|
|
128
|
+
if (cmdOptions.skip) {
|
|
129
|
+
args.push("--skip");
|
|
130
|
+
}
|
|
131
|
+
const code = await runToolingCommand({
|
|
132
|
+
command: "check-changeset",
|
|
133
|
+
args,
|
|
134
|
+
cwd: resolveCwdOption(cmdOptions.cwd)
|
|
135
|
+
});
|
|
136
|
+
applyExitCode(code);
|
|
137
|
+
});
|
|
138
|
+
command.command("tree").description("Assert working tree is clean (no modified or untracked files)").option("--paths <paths...>", "Limit check to specific paths").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
|
|
139
|
+
const args = [];
|
|
140
|
+
if (Array.isArray(cmdOptions.paths) && cmdOptions.paths.length > 0) {
|
|
141
|
+
args.push("--paths", ...cmdOptions.paths);
|
|
142
|
+
}
|
|
143
|
+
const code = await runToolingCommand({
|
|
144
|
+
command: "check-clean-tree",
|
|
145
|
+
args,
|
|
146
|
+
cwd: resolveCwdOption(cmdOptions.cwd)
|
|
147
|
+
});
|
|
148
|
+
applyExitCode(code);
|
|
149
|
+
});
|
|
150
|
+
command.command("boundary-invocations").description("Validate root/app scripts do not execute packages/*/src entrypoints directly").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
|
|
151
|
+
const code = await runToolingCommand({
|
|
152
|
+
command: "check-boundary-invocations",
|
|
153
|
+
args: [],
|
|
154
|
+
cwd: resolveCwdOption(cmdOptions.cwd)
|
|
155
|
+
});
|
|
156
|
+
applyExitCode(code);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function createRepoCommand(options) {
|
|
160
|
+
const io = getIo(options);
|
|
161
|
+
const runDocsCheck = options?.runDocsCheck ?? runDocsCheckDefault;
|
|
162
|
+
const runDocsSync = options?.runDocsSync ?? runDocsSyncDefault;
|
|
163
|
+
const runDocsExport = options?.runDocsExport ?? runDocsExportDefault;
|
|
164
|
+
const runToolingCommand = options?.runToolingCommand ?? runToolingCommandDefault;
|
|
165
|
+
const command = new Command(options?.commandName ?? "repo");
|
|
166
|
+
command.description("Repository maintenance commands");
|
|
167
|
+
const checkCommand = command.command("check").description("Run repository checks by subject");
|
|
168
|
+
addDocsCheckSubcommand(checkCommand, { io, runDocsCheck });
|
|
169
|
+
addToolingCheckSubcommands(checkCommand, runToolingCommand);
|
|
170
|
+
const syncCommand = command.command("sync").description("Synchronize repository artifacts by subject");
|
|
171
|
+
addDocsSyncSubcommand(syncCommand, { io, runDocsSync });
|
|
172
|
+
const exportCommand = command.command("export").description("Export repository artifacts by subject");
|
|
173
|
+
addDocsExportSubcommand(exportCommand, { io, runDocsExport });
|
|
174
|
+
return command;
|
|
175
|
+
}
|
|
176
|
+
async function main() {
|
|
177
|
+
const command = createRepoCommand({ commandName: "repo" });
|
|
178
|
+
await command.parseAsync(process.argv, { from: "node" });
|
|
179
|
+
}
|
|
180
|
+
if (import.meta.main) {
|
|
181
|
+
main();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export { createRepoCommand };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// @bun
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ExecuteCheckCommandOptions, ExecuteExportCommandOptions, ExecuteSyncCommandOptions } from "./outfitter-dd0btgec";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
type RepoCheckSubject = "docs" | "exports" | "readme" | "registry" | "changeset" | "tree" | "boundary-invocations";
|
|
4
|
+
interface RepoCommandIo {
|
|
5
|
+
readonly out?: (line: string) => void;
|
|
6
|
+
readonly err?: (line: string) => void;
|
|
7
|
+
}
|
|
8
|
+
interface RepoToolingInvocation {
|
|
9
|
+
readonly command: "check-exports" | "check-readme-imports" | "check-bunup-registry" | "check-changeset" | "check-clean-tree" | "check-boundary-invocations";
|
|
10
|
+
readonly args: readonly string[];
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
}
|
|
13
|
+
interface CreateRepoCommandOptions {
|
|
14
|
+
readonly commandName?: string;
|
|
15
|
+
readonly io?: RepoCommandIo;
|
|
16
|
+
readonly runDocsCheck?: (options: ExecuteCheckCommandOptions, io: Required<RepoCommandIo>) => Promise<number>;
|
|
17
|
+
readonly runDocsSync?: (options: ExecuteSyncCommandOptions, io: Required<RepoCommandIo>) => Promise<number>;
|
|
18
|
+
readonly runDocsExport?: (options: ExecuteExportCommandOptions, io: Required<RepoCommandIo>) => Promise<number>;
|
|
19
|
+
readonly runToolingCommand?: (input: RepoToolingInvocation) => Promise<number>;
|
|
20
|
+
}
|
|
21
|
+
declare function createRepoCommand(options?: CreateRepoCommandOptions): Command;
|
|
22
|
+
export { RepoCheckSubject, RepoCommandIo, RepoToolingInvocation, CreateRepoCommandOptions, createRepoCommand };
|