outfitter 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +8 -5
  2. package/dist/actions.d.ts +2 -0
  3. package/dist/actions.js +34 -0
  4. package/dist/cli.js +1 -1
  5. package/dist/commands/add.d.ts +54 -0
  6. package/dist/commands/add.js +16 -0
  7. package/dist/commands/check.d.ts +91 -0
  8. package/dist/commands/check.js +14 -0
  9. package/dist/commands/demo.d.ts +21 -0
  10. package/dist/commands/demo.js +8 -0
  11. package/dist/commands/docs-module-loader.d.ts +2 -0
  12. package/dist/commands/docs-module-loader.js +8 -0
  13. package/dist/commands/doctor.d.ts +2 -0
  14. package/dist/commands/doctor.js +13 -0
  15. package/dist/commands/init.d.ts +7 -0
  16. package/dist/commands/init.js +31 -0
  17. package/dist/commands/migrate-kit.d.ts +2 -0
  18. package/dist/commands/migrate-kit.js +15 -0
  19. package/dist/commands/repo.d.ts +3 -0
  20. package/dist/commands/repo.js +9 -0
  21. package/dist/commands/scaffold.d.ts +4 -0
  22. package/dist/commands/scaffold.js +31 -0
  23. package/dist/commands/shared-deps.d.ts +36 -0
  24. package/dist/commands/shared-deps.js +10 -0
  25. package/dist/commands/upgrade-codemods.d.ts +42 -0
  26. package/dist/commands/upgrade-codemods.js +15 -0
  27. package/dist/commands/upgrade-planner.d.ts +58 -0
  28. package/dist/commands/upgrade-planner.js +8 -0
  29. package/dist/commands/upgrade-workspace.d.ts +76 -0
  30. package/dist/commands/upgrade-workspace.js +16 -0
  31. package/dist/commands/upgrade.d.ts +214 -0
  32. package/dist/commands/upgrade.js +25 -0
  33. package/dist/create/index.d.ts +5 -0
  34. package/dist/create/index.js +29 -0
  35. package/dist/create/planner.d.ts +3 -0
  36. package/dist/create/planner.js +21 -0
  37. package/dist/create/presets.d.ts +3 -0
  38. package/dist/create/presets.js +12 -0
  39. package/dist/create/types.d.ts +2 -0
  40. package/dist/create/types.js +1 -0
  41. package/dist/engine/blocks.d.ts +3 -0
  42. package/dist/engine/blocks.js +12 -0
  43. package/dist/engine/collector.d.ts +2 -0
  44. package/dist/engine/collector.js +8 -0
  45. package/dist/engine/config.d.ts +3 -0
  46. package/dist/engine/config.js +12 -0
  47. package/dist/engine/executor.d.ts +3 -0
  48. package/dist/engine/executor.js +16 -0
  49. package/dist/engine/index.d.ts +8 -0
  50. package/dist/engine/index.js +59 -0
  51. package/dist/engine/names.d.ts +2 -0
  52. package/dist/engine/names.js +16 -0
  53. package/dist/engine/post-scaffold.d.ts +3 -0
  54. package/dist/engine/post-scaffold.js +8 -0
  55. package/dist/engine/render-plan.d.ts +7 -0
  56. package/dist/engine/render-plan.js +9 -0
  57. package/dist/engine/template.d.ts +3 -0
  58. package/dist/engine/template.js +17 -0
  59. package/dist/engine/types.d.ts +2 -0
  60. package/dist/engine/types.js +8 -0
  61. package/dist/engine/workspace.d.ts +3 -0
  62. package/dist/engine/workspace.js +13 -0
  63. package/dist/index.d.ts +10 -0
  64. package/dist/index.js +1 -1
  65. package/dist/manifest.d.ts +71 -0
  66. package/dist/manifest.js +16 -0
  67. package/dist/output-mode.d.ts +2 -0
  68. package/dist/output-mode.js +10 -0
  69. package/dist/shared/{chunk-tpwtpa74.js → chunk-k59f60cp.js} +858 -396
  70. package/dist/shared/outfitter-193jvzg4.d.ts +5 -0
  71. package/dist/shared/outfitter-1dd0k853.js +194 -0
  72. package/dist/shared/outfitter-1dvma85c.js +322 -0
  73. package/dist/shared/outfitter-1h7k8xxt.js +29 -0
  74. package/dist/shared/outfitter-2ngep1h2.d.ts +5 -0
  75. package/dist/shared/outfitter-2np85etz.js +95 -0
  76. package/dist/shared/outfitter-33w361tc.d.ts +18 -0
  77. package/dist/shared/outfitter-344t1r38.js +1 -0
  78. package/dist/shared/outfitter-3weh61w7.d.ts +25 -0
  79. package/dist/shared/outfitter-4s9meh3j.js +221 -0
  80. package/dist/shared/outfitter-66b25bj8.js +125 -0
  81. package/dist/shared/outfitter-6bkqjk86.d.ts +3 -0
  82. package/dist/shared/outfitter-79vfxt6y.js +269 -0
  83. package/dist/shared/outfitter-7ha7p61k.d.ts +6 -0
  84. package/dist/shared/outfitter-7r12fj7f.js +30 -0
  85. package/dist/shared/outfitter-8y2dfx6n.js +11 -0
  86. package/dist/shared/outfitter-9x1brcmq.js +184 -0
  87. package/dist/shared/outfitter-9zqc2njf.js +859 -0
  88. package/dist/shared/outfitter-a79xrm12.d.ts +17 -0
  89. package/dist/shared/outfitter-amc4jbs1.d.ts +50 -0
  90. package/dist/shared/outfitter-ara3djt0.js +73 -0
  91. package/dist/shared/outfitter-avhm5z6w.js +82 -0
  92. package/dist/shared/outfitter-bkwpbkr9.d.ts +63 -0
  93. package/dist/shared/outfitter-bn9c8p2e.js +204 -0
  94. package/dist/shared/outfitter-bpr28y54.js +70 -0
  95. package/dist/shared/outfitter-cwq39bv4.d.ts +48 -0
  96. package/dist/shared/outfitter-d7pq7d0k.js +196 -0
  97. package/dist/shared/outfitter-dd0btgec.d.ts +40 -0
  98. package/dist/shared/outfitter-e2zz5wv7.d.ts +51 -0
  99. package/dist/shared/outfitter-ehp18x1n.js +1 -0
  100. package/dist/shared/outfitter-gdvm5c0b.d.ts +4 -0
  101. package/dist/shared/outfitter-h1mnzzd1.d.ts +14 -0
  102. package/dist/shared/outfitter-hvsaxgcp.js +1 -0
  103. package/dist/shared/outfitter-hws10ze7.js +532 -0
  104. package/dist/shared/outfitter-j833sxws.js +61 -0
  105. package/dist/shared/outfitter-j8yc7294.d.ts +22 -0
  106. package/dist/shared/outfitter-k112c427.js +21 -0
  107. package/dist/shared/outfitter-k56rmt24.d.ts +30 -0
  108. package/dist/shared/outfitter-ksa1pp4t.d.ts +4 -0
  109. package/dist/shared/outfitter-ksyvwmb5.js +191 -0
  110. package/dist/shared/outfitter-mdt37hqm.js +4 -0
  111. package/dist/shared/outfitter-mtbpabf3.js +91 -0
  112. package/dist/shared/outfitter-mxz69pgy.js +713 -0
  113. package/dist/shared/outfitter-npemy7ta.d.ts +53 -0
  114. package/dist/shared/outfitter-npyfbdmc.d.ts +6 -0
  115. package/dist/shared/outfitter-pyy1zkfh.d.ts +133 -0
  116. package/dist/shared/outfitter-q9agarmb.js +42 -0
  117. package/dist/shared/outfitter-qfh36ddg.d.ts +66 -0
  118. package/dist/shared/outfitter-qn864k6h.js +581 -0
  119. package/dist/shared/outfitter-rdc5v5ms.js +746 -0
  120. package/dist/shared/outfitter-sgtq57qr.d.ts +5 -0
  121. package/dist/shared/outfitter-ttjr95y9.js +98 -0
  122. package/dist/shared/outfitter-vh4xgb93.js +35 -0
  123. package/dist/shared/outfitter-yvksv5qb.js +322 -0
  124. package/dist/shared/outfitter-zwyvewr1.js +36 -0
  125. package/dist/targets/index.d.ts +4 -0
  126. package/dist/targets/index.js +29 -0
  127. package/dist/targets/registry.d.ts +3 -0
  128. package/dist/targets/registry.js +28 -0
  129. package/dist/targets/types.d.ts +2 -0
  130. package/dist/targets/types.js +1 -0
  131. package/package.json +19 -12
@@ -0,0 +1,5 @@
1
+ import { EngineOptions, ScaffoldError } from "./outfitter-qfh36ddg";
2
+ import { Result } from "@outfitter/contracts";
3
+ import { AddBlockResult } from "@outfitter/tooling";
4
+ declare function addBlocks(targetDir: string, blocks: readonly string[], options: EngineOptions): Promise<Result<AddBlockResult, ScaffoldError>>;
5
+ export { addBlocks };
@@ -0,0 +1,194 @@
1
+ // @bun
2
+ // apps/outfitter/src/targets/registry.ts
3
+ import { NotFoundError, Result, ValidationError } from "@outfitter/contracts";
4
+ var TARGET_ALIASES = new Map([
5
+ ["basic", "minimal"]
6
+ ]);
7
+ var TARGET_REGISTRY = new Map([
8
+ [
9
+ "minimal",
10
+ {
11
+ id: "minimal",
12
+ description: "Minimal Bun + TypeScript project",
13
+ category: "library",
14
+ placement: "packages",
15
+ templateDir: "minimal",
16
+ defaultBlocks: ["scaffolding"],
17
+ status: "ready",
18
+ scope: "init-only"
19
+ }
20
+ ],
21
+ [
22
+ "cli",
23
+ {
24
+ id: "cli",
25
+ description: "CLI application with Outfitter command ergonomics",
26
+ category: "runnable",
27
+ placement: "apps",
28
+ templateDir: "cli",
29
+ defaultBlocks: ["scaffolding"],
30
+ status: "ready",
31
+ scope: "both"
32
+ }
33
+ ],
34
+ [
35
+ "mcp",
36
+ {
37
+ id: "mcp",
38
+ description: "MCP server with typed tools and action registry",
39
+ category: "runnable",
40
+ placement: "apps",
41
+ templateDir: "mcp",
42
+ defaultBlocks: ["scaffolding"],
43
+ status: "ready",
44
+ scope: "both"
45
+ }
46
+ ],
47
+ [
48
+ "daemon",
49
+ {
50
+ id: "daemon",
51
+ description: "Background daemon with control CLI",
52
+ category: "runnable",
53
+ placement: "apps",
54
+ templateDir: "daemon",
55
+ defaultBlocks: ["scaffolding"],
56
+ status: "ready",
57
+ scope: "both"
58
+ }
59
+ ],
60
+ [
61
+ "api",
62
+ {
63
+ id: "api",
64
+ description: "HTTP API server (Hono)",
65
+ category: "runnable",
66
+ placement: "apps",
67
+ templateDir: "api",
68
+ defaultBlocks: ["scaffolding"],
69
+ status: "stub",
70
+ scope: "both"
71
+ }
72
+ ],
73
+ [
74
+ "worker",
75
+ {
76
+ id: "worker",
77
+ description: "Background job worker",
78
+ category: "runnable",
79
+ placement: "apps",
80
+ templateDir: "worker",
81
+ defaultBlocks: ["scaffolding"],
82
+ status: "stub",
83
+ scope: "both"
84
+ }
85
+ ],
86
+ [
87
+ "web",
88
+ {
89
+ id: "web",
90
+ description: "Web application (TanStack Start)",
91
+ category: "runnable",
92
+ placement: "apps",
93
+ templateDir: "web",
94
+ defaultBlocks: ["scaffolding"],
95
+ status: "stub",
96
+ scope: "both"
97
+ }
98
+ ],
99
+ [
100
+ "lib",
101
+ {
102
+ id: "lib",
103
+ description: "Shared library package",
104
+ category: "library",
105
+ placement: "packages",
106
+ templateDir: "lib",
107
+ defaultBlocks: ["scaffolding"],
108
+ status: "stub",
109
+ scope: "both"
110
+ }
111
+ ]
112
+ ]);
113
+ var TARGET_IDS = [...TARGET_REGISTRY.keys()];
114
+ var READY_TARGET_IDS = TARGET_IDS.filter((id) => TARGET_REGISTRY.get(id)?.status === "ready");
115
+ var INIT_TARGET_IDS = TARGET_IDS.filter((id) => {
116
+ const target = TARGET_REGISTRY.get(id);
117
+ return target?.status === "ready" && target.scope !== "scaffold-only";
118
+ });
119
+ var SCAFFOLD_TARGET_IDS = TARGET_IDS.filter((id) => {
120
+ const target = TARGET_REGISTRY.get(id);
121
+ return target?.status === "ready" && target.scope !== "init-only";
122
+ });
123
+ function getTarget(id) {
124
+ const resolvedId = TARGET_ALIASES.get(id) ?? id;
125
+ const target = TARGET_REGISTRY.get(resolvedId);
126
+ if (!target) {
127
+ return Result.err(new NotFoundError({
128
+ message: `Unknown target '${id}'. Available targets: ${TARGET_IDS.join(", ")}`,
129
+ resourceType: "target",
130
+ resourceId: id
131
+ }));
132
+ }
133
+ return Result.ok(target);
134
+ }
135
+ function getReadyTarget(id) {
136
+ const targetResult = getTarget(id);
137
+ if (targetResult.isErr()) {
138
+ return targetResult;
139
+ }
140
+ const target = targetResult.value;
141
+ if (target.status === "stub") {
142
+ return Result.err(new ValidationError({
143
+ message: `Target '${id}' is not yet available. ` + "It is planned but the template has not been implemented. " + `Ready targets: ${READY_TARGET_IDS.join(", ")}`,
144
+ field: "target"
145
+ }));
146
+ }
147
+ return Result.ok(target);
148
+ }
149
+ function getInitTarget(id) {
150
+ const targetResult = getReadyTarget(id);
151
+ if (targetResult.isErr()) {
152
+ return targetResult;
153
+ }
154
+ const target = targetResult.value;
155
+ if (target.scope === "scaffold-only") {
156
+ return Result.err(new ValidationError({
157
+ message: `Target '${id}' cannot be used with init. Use 'outfitter scaffold ${id}' instead.`,
158
+ field: "target"
159
+ }));
160
+ }
161
+ return Result.ok(target);
162
+ }
163
+ function getScaffoldTarget(id) {
164
+ const targetResult = getReadyTarget(id);
165
+ if (targetResult.isErr()) {
166
+ return targetResult;
167
+ }
168
+ const target = targetResult.value;
169
+ if (target.scope === "init-only") {
170
+ return Result.err(new ValidationError({
171
+ message: `Target '${id}' cannot be scaffolded into an existing project. ` + `It is only available for new project creation via 'outfitter init'.`,
172
+ field: "target"
173
+ }));
174
+ }
175
+ return Result.ok(target);
176
+ }
177
+ function resolvePlacement(target) {
178
+ return target.placement;
179
+ }
180
+ function listTargets(filter) {
181
+ let targets = [...TARGET_REGISTRY.values()];
182
+ if (filter?.status) {
183
+ targets = targets.filter((target) => target.status === filter.status);
184
+ }
185
+ if (filter?.scope) {
186
+ targets = targets.filter((target) => target.scope === filter.scope);
187
+ }
188
+ if (filter?.category) {
189
+ targets = targets.filter((target) => target.category === filter.category);
190
+ }
191
+ return targets;
192
+ }
193
+
194
+ export { TARGET_REGISTRY, TARGET_IDS, READY_TARGET_IDS, INIT_TARGET_IDS, SCAFFOLD_TARGET_IDS, getTarget, getReadyTarget, getInitTarget, getScaffoldTarget, resolvePlacement, listTargets };
@@ -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";
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,29 @@
1
+ // @bun
2
+ // apps/outfitter/src/engine/collector.ts
3
+ class OperationCollector {
4
+ operations = [];
5
+ add(op) {
6
+ this.operations.push(op);
7
+ }
8
+ getOperations() {
9
+ return this.operations;
10
+ }
11
+ countByType() {
12
+ const counts = {};
13
+ for (const op of this.operations) {
14
+ counts[op.type] = (counts[op.type] ?? 0) + 1;
15
+ }
16
+ return counts;
17
+ }
18
+ isEmpty() {
19
+ return this.operations.length === 0;
20
+ }
21
+ toJSON() {
22
+ return {
23
+ operations: this.operations,
24
+ summary: this.countByType()
25
+ };
26
+ }
27
+ }
28
+
29
+ export { OperationCollector };
@@ -0,0 +1,5 @@
1
+ import { CreatePresetDefinition, CreatePresetId } from "./outfitter-e2zz5wv7";
2
+ declare const CREATE_PRESETS: Readonly<Record<CreatePresetId, CreatePresetDefinition>>;
3
+ declare const CREATE_PRESET_IDS: CreatePresetId[];
4
+ declare function getCreatePreset(id: string): CreatePresetDefinition | undefined;
5
+ export { CREATE_PRESETS, CREATE_PRESET_IDS, getCreatePreset };
@@ -0,0 +1,95 @@
1
+ // @bun
2
+ import {
3
+ ScaffoldError
4
+ } from "./outfitter-8y2dfx6n.js";
5
+
6
+ // apps/outfitter/src/engine/workspace.ts
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { dirname, join, resolve } from "path";
9
+ import { Result } from "@outfitter/contracts";
10
+ function buildWorkspaceRootPackageJson(workspaceName) {
11
+ const workspacePackage = {
12
+ name: workspaceName,
13
+ private: true,
14
+ version: "0.1.0",
15
+ workspaces: ["apps/*", "packages/*"],
16
+ scripts: {
17
+ build: "bun run --filter '*' build",
18
+ dev: "bun run --filter '*' dev",
19
+ test: "bun run --filter '*' test",
20
+ typecheck: "bun run --filter '*' typecheck",
21
+ lint: "bun run --filter '*' lint",
22
+ "lint:fix": "bun run --filter '*' lint:fix",
23
+ format: "bun run --filter '*' format"
24
+ }
25
+ };
26
+ return `${JSON.stringify(workspacePackage, null, 2)}
27
+ `;
28
+ }
29
+ function scaffoldWorkspaceRoot(rootDir, workspaceName, force) {
30
+ const packageJsonPath = join(rootDir, "package.json");
31
+ if (existsSync(packageJsonPath) && !force) {
32
+ return Result.err(new ScaffoldError(`Directory '${rootDir}' already has a package.json. Use --force to overwrite.`));
33
+ }
34
+ try {
35
+ if (!existsSync(rootDir)) {
36
+ mkdirSync(rootDir, { recursive: true });
37
+ }
38
+ mkdirSync(join(rootDir, "apps"), { recursive: true });
39
+ mkdirSync(join(rootDir, "packages"), { recursive: true });
40
+ writeFileSync(packageJsonPath, buildWorkspaceRootPackageJson(workspaceName), "utf-8");
41
+ const gitignorePath = join(rootDir, ".gitignore");
42
+ if (force || !existsSync(gitignorePath)) {
43
+ writeFileSync(gitignorePath, `node_modules
44
+ **/dist
45
+ `, "utf-8");
46
+ }
47
+ return Result.ok(undefined);
48
+ } catch (error) {
49
+ const message = error instanceof Error ? error.message : "Unknown error";
50
+ return Result.err(new ScaffoldError(`Failed to scaffold workspace root: ${message}`));
51
+ }
52
+ }
53
+ function hasWorkspacesField(pkg) {
54
+ const workspaces = pkg.workspaces;
55
+ if (Array.isArray(workspaces) && workspaces.length > 0) {
56
+ return true;
57
+ }
58
+ if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
59
+ const packages = workspaces.packages;
60
+ if (Array.isArray(packages) && packages.length > 0) {
61
+ return true;
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+ function detectWorkspaceRoot(cwd) {
67
+ let current = resolve(cwd);
68
+ const root = resolve("/");
69
+ while (true) {
70
+ if (existsSync(join(current, "pnpm-workspace.yaml"))) {
71
+ return Result.ok(current);
72
+ }
73
+ const pkgPath = join(current, "package.json");
74
+ if (existsSync(pkgPath)) {
75
+ try {
76
+ const raw = readFileSync(pkgPath, "utf-8");
77
+ const pkg = JSON.parse(raw);
78
+ if (hasWorkspacesField(pkg)) {
79
+ return Result.ok(current);
80
+ }
81
+ } catch {}
82
+ }
83
+ if (current === root) {
84
+ break;
85
+ }
86
+ const parent = dirname(current);
87
+ if (parent === current) {
88
+ break;
89
+ }
90
+ current = parent;
91
+ }
92
+ return Result.ok(null);
93
+ }
94
+
95
+ export { buildWorkspaceRootPackageJson, scaffoldWorkspaceRoot, detectWorkspaceRoot };
@@ -0,0 +1,18 @@
1
+ import { TargetCategory, TargetDefinition, TargetId, TargetScope, TargetStatus } from "./outfitter-k56rmt24";
2
+ import { NotFoundError, Result, ValidationError } from "@outfitter/contracts";
3
+ declare const TARGET_REGISTRY: ReadonlyMap<TargetId, TargetDefinition>;
4
+ declare const TARGET_IDS: readonly TargetId[];
5
+ declare const READY_TARGET_IDS: readonly TargetId[];
6
+ declare const INIT_TARGET_IDS: readonly TargetId[];
7
+ declare const SCAFFOLD_TARGET_IDS: readonly TargetId[];
8
+ declare function getTarget(id: string): Result<TargetDefinition, NotFoundError>;
9
+ declare function getReadyTarget(id: string): Result<TargetDefinition, NotFoundError | ValidationError>;
10
+ declare function getInitTarget(id: string): Result<TargetDefinition, NotFoundError | ValidationError>;
11
+ declare function getScaffoldTarget(id: string): Result<TargetDefinition, NotFoundError | ValidationError>;
12
+ declare function resolvePlacement(target: TargetDefinition): "apps" | "packages";
13
+ declare function listTargets(filter?: {
14
+ readonly status?: TargetStatus;
15
+ readonly scope?: TargetScope;
16
+ readonly category?: TargetCategory;
17
+ }): readonly TargetDefinition[];
18
+ export { TARGET_REGISTRY, TARGET_IDS, READY_TARGET_IDS, INIT_TARGET_IDS, SCAFFOLD_TARGET_IDS, getTarget, getReadyTarget, getInitTarget, getScaffoldTarget, resolvePlacement, listTargets };
@@ -0,0 +1 @@
1
+ // @bun
@@ -0,0 +1,25 @@
1
+ import { OperationCollector } from "./outfitter-amc4jbs1";
2
+ import { Result } from "@outfitter/contracts";
3
+ type ScaffoldOrigin = "init" | "scaffold";
4
+ interface PostScaffoldOptions {
5
+ readonly rootDir: string;
6
+ readonly projectDir: string;
7
+ readonly origin: ScaffoldOrigin;
8
+ readonly target: string;
9
+ readonly structure: "single" | "workspace";
10
+ readonly skipInstall: boolean;
11
+ readonly skipGit: boolean;
12
+ readonly skipCommit: boolean;
13
+ readonly dryRun: boolean;
14
+ readonly installTimeoutMs: number;
15
+ }
16
+ interface PostScaffoldResult {
17
+ readonly installResult: "success" | "failed" | "skipped";
18
+ readonly installError?: string | undefined;
19
+ readonly gitInitResult: "success" | "failed" | "skipped" | "already-repo";
20
+ readonly gitCommitResult: "success" | "failed" | "skipped";
21
+ readonly gitError?: string | undefined;
22
+ readonly nextSteps: readonly string[];
23
+ }
24
+ declare function runPostScaffold(options: PostScaffoldOptions, collector?: OperationCollector): Promise<Result<PostScaffoldResult, never>>;
25
+ export { PostScaffoldOptions, PostScaffoldResult, runPostScaffold };