outfitter 0.2.1 → 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.
Files changed (146) hide show
  1. package/README.md +215 -116
  2. package/dist/actions.d.ts +2 -0
  3. package/dist/actions.js +34 -0
  4. package/dist/cli.js +29 -5
  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/update-planner.d.ts +58 -0
  26. package/dist/commands/update-planner.js +8 -0
  27. package/dist/commands/update-workspace.d.ts +76 -0
  28. package/dist/commands/update-workspace.js +16 -0
  29. package/dist/commands/update.d.ts +113 -0
  30. package/dist/commands/update.js +21 -0
  31. package/dist/create/index.d.ts +5 -0
  32. package/dist/create/index.js +29 -0
  33. package/dist/create/planner.d.ts +3 -0
  34. package/dist/create/planner.js +21 -0
  35. package/dist/create/presets.d.ts +3 -0
  36. package/dist/create/presets.js +12 -0
  37. package/dist/create/types.d.ts +2 -0
  38. package/dist/create/types.js +1 -0
  39. package/dist/engine/blocks.d.ts +3 -0
  40. package/dist/engine/blocks.js +12 -0
  41. package/dist/engine/collector.d.ts +2 -0
  42. package/dist/engine/collector.js +8 -0
  43. package/dist/engine/config.d.ts +3 -0
  44. package/dist/engine/config.js +12 -0
  45. package/dist/engine/executor.d.ts +3 -0
  46. package/dist/engine/executor.js +16 -0
  47. package/dist/engine/index.d.ts +8 -0
  48. package/dist/engine/index.js +59 -0
  49. package/dist/engine/names.d.ts +2 -0
  50. package/dist/engine/names.js +16 -0
  51. package/dist/engine/post-scaffold.d.ts +3 -0
  52. package/dist/engine/post-scaffold.js +8 -0
  53. package/dist/engine/render-plan.d.ts +7 -0
  54. package/dist/engine/render-plan.js +9 -0
  55. package/dist/engine/template.d.ts +3 -0
  56. package/dist/engine/template.js +17 -0
  57. package/dist/engine/types.d.ts +2 -0
  58. package/dist/engine/types.js +8 -0
  59. package/dist/engine/workspace.d.ts +3 -0
  60. package/dist/engine/workspace.js +13 -0
  61. package/dist/index.d.ts +265 -50
  62. package/dist/index.js +157 -3
  63. package/dist/manifest.d.ts +71 -0
  64. package/dist/manifest.js +16 -0
  65. package/dist/output-mode.d.ts +2 -0
  66. package/dist/output-mode.js +10 -0
  67. package/dist/shared/chunk-b0y0cwkr.js +5533 -0
  68. package/dist/shared/outfitter-193jvzg4.d.ts +5 -0
  69. package/dist/shared/outfitter-1dd0k853.js +194 -0
  70. package/dist/shared/outfitter-1h7k8xxt.js +29 -0
  71. package/dist/shared/outfitter-1qwpjt6w.js +125 -0
  72. package/dist/shared/outfitter-2ngep1h2.d.ts +5 -0
  73. package/dist/shared/outfitter-2np85etz.js +95 -0
  74. package/dist/shared/outfitter-33w361tc.d.ts +18 -0
  75. package/dist/shared/outfitter-344t1r38.js +1 -0
  76. package/dist/shared/outfitter-3weh61w7.d.ts +25 -0
  77. package/dist/shared/outfitter-4s9meh3j.js +221 -0
  78. package/dist/shared/outfitter-6a4bq054.js +322 -0
  79. package/dist/shared/outfitter-6bkqjk86.d.ts +3 -0
  80. package/dist/shared/outfitter-6gc3g5wk.js +98 -0
  81. package/dist/shared/outfitter-7cv5fg1m.js +61 -0
  82. package/dist/shared/outfitter-7ha7p61k.d.ts +6 -0
  83. package/dist/shared/outfitter-7r12fj7f.js +30 -0
  84. package/dist/shared/outfitter-8y2dfx6n.js +11 -0
  85. package/dist/shared/outfitter-9c8edfsn.js +715 -0
  86. package/dist/shared/outfitter-9x1brcmq.js +184 -0
  87. package/dist/shared/outfitter-a79xrm12.d.ts +17 -0
  88. package/dist/shared/outfitter-amc4jbs1.d.ts +50 -0
  89. package/dist/shared/outfitter-ara3djt0.js +73 -0
  90. package/dist/shared/outfitter-avhm5z6w.js +82 -0
  91. package/dist/shared/outfitter-b5nd42y4.d.ts +45 -0
  92. package/dist/shared/outfitter-dd0btgec.d.ts +40 -0
  93. package/dist/shared/outfitter-e2zz5wv7.d.ts +51 -0
  94. package/dist/shared/outfitter-ehp18x1n.js +1 -0
  95. package/dist/shared/outfitter-fnsmx3xg.js +750 -0
  96. package/dist/shared/outfitter-gdvm5c0b.d.ts +4 -0
  97. package/dist/shared/outfitter-gp4v5gkf.js +322 -0
  98. package/dist/shared/outfitter-h1mnzzd1.d.ts +14 -0
  99. package/dist/shared/outfitter-hpymx4m9.js +184 -0
  100. package/dist/shared/outfitter-hvsaxgcp.js +1 -0
  101. package/dist/shared/outfitter-j8yc7294.d.ts +22 -0
  102. package/dist/shared/outfitter-jyxwznk1.js +404 -0
  103. package/dist/shared/outfitter-k112c427.js +21 -0
  104. package/dist/shared/outfitter-k56rmt24.d.ts +30 -0
  105. package/dist/shared/outfitter-ksa1pp4t.d.ts +4 -0
  106. package/dist/shared/outfitter-mdt37hqm.js +4 -0
  107. package/dist/shared/outfitter-mtbpabf3.js +91 -0
  108. package/dist/shared/outfitter-nm4m0v6x.d.ts +131 -0
  109. package/dist/shared/outfitter-nmeecf1b.js +531 -0
  110. package/dist/shared/outfitter-npyfbdmc.d.ts +6 -0
  111. package/dist/shared/outfitter-pxt58tsq.js +582 -0
  112. package/dist/shared/outfitter-q9agarmb.js +42 -0
  113. package/dist/shared/outfitter-qfgj5xpq.js +70 -0
  114. package/dist/shared/outfitter-qfh36ddg.d.ts +66 -0
  115. package/dist/shared/outfitter-s6k8y2p4.js +269 -0
  116. package/dist/shared/outfitter-sftf1s26.js +199 -0
  117. package/dist/shared/outfitter-sg7ncy4a.d.ts +51 -0
  118. package/dist/shared/outfitter-sgtq57qr.d.ts +5 -0
  119. package/dist/shared/outfitter-txre6cdn.d.ts +60 -0
  120. package/dist/shared/outfitter-vh4xgb93.js +35 -0
  121. package/dist/shared/outfitter-ya44h1km.js +191 -0
  122. package/dist/shared/outfitter-zwyvewr1.js +36 -0
  123. package/dist/targets/index.d.ts +4 -0
  124. package/dist/targets/index.js +29 -0
  125. package/dist/targets/registry.d.ts +3 -0
  126. package/dist/targets/registry.js +28 -0
  127. package/dist/targets/types.d.ts +2 -0
  128. package/dist/targets/types.js +1 -0
  129. package/package.json +182 -29
  130. package/templates/basic/.lefthook.yml.template +4 -5
  131. package/templates/basic/package.json.template +10 -5
  132. package/templates/cli/.lefthook.yml.template +4 -5
  133. package/templates/cli/biome.json.template +2 -15
  134. package/templates/cli/package.json.template +11 -7
  135. package/templates/daemon/.lefthook.yml.template +4 -5
  136. package/templates/daemon/biome.json.template +2 -15
  137. package/templates/daemon/package.json.template +11 -7
  138. package/templates/mcp/.lefthook.yml.template +4 -5
  139. package/templates/mcp/biome.json.template +2 -15
  140. package/templates/mcp/package.json.template +11 -7
  141. package/templates/minimal/.gitignore.template +30 -0
  142. package/templates/minimal/.lefthook.yml.template +26 -0
  143. package/templates/minimal/package.json.template +46 -0
  144. package/templates/minimal/src/index.ts.template +26 -0
  145. package/templates/minimal/tsconfig.json.template +34 -0
  146. package/dist/shared/chunk-f3sch043.js +0 -1858
@@ -1,1858 +0,0 @@
1
- // src/commands/doctor.ts
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { join, resolve } from "node:path";
4
- import { output } from "@outfitter/cli/output";
5
- import { createTheme } from "@outfitter/cli/render";
6
- var MIN_BUN_VERSION = "1.3.6";
7
- function checkBunVersion() {
8
- const required = MIN_BUN_VERSION;
9
- const current = Bun.version;
10
- const passed = Bun.semver.satisfies(current, `>=${required}`);
11
- return {
12
- passed,
13
- version: current,
14
- required,
15
- ...passed ? {} : {
16
- error: `Bun version ${current} is below minimum required ${required}`
17
- }
18
- };
19
- }
20
- function checkPackageJson(cwd) {
21
- const packageJsonPath = join(cwd, "package.json");
22
- if (!existsSync(packageJsonPath)) {
23
- return {
24
- passed: false,
25
- error: "package.json not found in current directory"
26
- };
27
- }
28
- try {
29
- const content = readFileSync(packageJsonPath, "utf-8");
30
- const parsed = JSON.parse(content);
31
- if (typeof parsed["name"] !== "string" || parsed["name"].length === 0) {
32
- return {
33
- passed: false,
34
- error: "package.json is missing required 'name' field"
35
- };
36
- }
37
- if (typeof parsed["version"] !== "string" || parsed["version"].length === 0) {
38
- return {
39
- passed: false,
40
- error: "package.json is missing required 'version' field"
41
- };
42
- }
43
- return {
44
- passed: true,
45
- name: parsed["name"],
46
- version: parsed["version"]
47
- };
48
- } catch (error) {
49
- const message = error instanceof Error ? error.message : "Unknown error";
50
- return {
51
- passed: false,
52
- error: `package.json is invalid JSON: ${message}`
53
- };
54
- }
55
- }
56
- function checkDependencies(cwd) {
57
- const packageJsonPath = join(cwd, "package.json");
58
- if (!existsSync(packageJsonPath)) {
59
- return { passed: true, count: 0 };
60
- }
61
- try {
62
- const content = readFileSync(packageJsonPath, "utf-8");
63
- const parsed = JSON.parse(content);
64
- const dependencies = parsed["dependencies"];
65
- const devDependencies = parsed["devDependencies"];
66
- const allDeps = [
67
- ...Object.keys(dependencies ?? {}),
68
- ...Object.keys(devDependencies ?? {})
69
- ];
70
- if (allDeps.length === 0) {
71
- return { passed: true, count: 0 };
72
- }
73
- const nodeModulesPath = join(cwd, "node_modules");
74
- if (!existsSync(nodeModulesPath)) {
75
- return {
76
- passed: false,
77
- count: allDeps.length,
78
- error: "node_modules not found. Run 'bun install' to install dependencies."
79
- };
80
- }
81
- const missing = [];
82
- for (const dep of allDeps) {
83
- const depPath = join(nodeModulesPath, dep);
84
- if (!existsSync(depPath)) {
85
- missing.push(dep);
86
- }
87
- }
88
- if (missing.length > 0) {
89
- return {
90
- passed: false,
91
- count: allDeps.length,
92
- missing,
93
- error: `Missing dependencies: ${missing.join(", ")}. Run 'bun install' to install.`
94
- };
95
- }
96
- return { passed: true, count: allDeps.length };
97
- } catch {
98
- return { passed: true, count: 0 };
99
- }
100
- }
101
- function checkConfigFiles(cwd) {
102
- return {
103
- tsconfig: existsSync(join(cwd, "tsconfig.json")),
104
- biome: existsSync(join(cwd, "biome.json"))
105
- };
106
- }
107
- function checkDirectories(cwd) {
108
- return {
109
- src: existsSync(join(cwd, "src")),
110
- tests: existsSync(join(cwd, "src", "__tests__")) || existsSync(join(cwd, "tests"))
111
- };
112
- }
113
- function runDoctor(options) {
114
- const cwd = resolve(options.cwd);
115
- const bunVersion = checkBunVersion();
116
- const packageJson = checkPackageJson(cwd);
117
- const dependencies = checkDependencies(cwd);
118
- const configFiles = checkConfigFiles(cwd);
119
- const directories = checkDirectories(cwd);
120
- const checkResults = [
121
- bunVersion.passed,
122
- packageJson.passed,
123
- dependencies.passed,
124
- configFiles.tsconfig,
125
- directories.src
126
- ];
127
- const passed = checkResults.filter(Boolean).length;
128
- const failed = checkResults.length - passed;
129
- const total = checkResults.length;
130
- return {
131
- checks: {
132
- bunVersion,
133
- packageJson,
134
- dependencies,
135
- configFiles,
136
- directories
137
- },
138
- summary: { passed, failed, total },
139
- exitCode: failed > 0 ? 1 : 0
140
- };
141
- }
142
- async function printDoctorResults(result, options) {
143
- const mode = options?.mode;
144
- if (mode === "json" || mode === "jsonl") {
145
- await output(result, { mode });
146
- return;
147
- }
148
- const theme = createTheme();
149
- const lines = ["", "Outfitter Doctor", "", "=".repeat(50)];
150
- const bunIcon = result.checks.bunVersion.passed ? theme.success("[PASS]") : theme.error("[FAIL]");
151
- lines.push(`${bunIcon} Bun Version: ${result.checks.bunVersion.version} (requires ${result.checks.bunVersion.required})`);
152
- if (result.checks.bunVersion.error) {
153
- lines.push(` ${theme.muted(result.checks.bunVersion.error)}`);
154
- }
155
- const pkgIcon = result.checks.packageJson.passed ? theme.success("[PASS]") : theme.error("[FAIL]");
156
- lines.push(`${pkgIcon} package.json`);
157
- if (result.checks.packageJson.error) {
158
- lines.push(` ${theme.muted(result.checks.packageJson.error)}`);
159
- } else if (result.checks.packageJson.name) {
160
- lines.push(` ${theme.muted(`${result.checks.packageJson.name}@${result.checks.packageJson.version}`)}`);
161
- }
162
- const depsIcon = result.checks.dependencies.passed ? theme.success("[PASS]") : theme.error("[FAIL]");
163
- lines.push(`${depsIcon} Dependencies`);
164
- if (result.checks.dependencies.error) {
165
- lines.push(` ${theme.muted(result.checks.dependencies.error)}`);
166
- } else if (result.checks.dependencies.count !== undefined) {
167
- lines.push(` ${theme.muted(`${result.checks.dependencies.count} dependencies installed`)}`);
168
- }
169
- const tsconfigIcon = result.checks.configFiles.tsconfig ? theme.success("[PASS]") : theme.warning("[WARN]");
170
- lines.push(`${tsconfigIcon} tsconfig.json`);
171
- const srcIcon = result.checks.directories.src ? theme.success("[PASS]") : theme.warning("[WARN]");
172
- lines.push(`${srcIcon} src/ directory`);
173
- lines.push("", "=".repeat(50));
174
- const summaryColor = result.exitCode === 0 ? theme.success : theme.error;
175
- lines.push(summaryColor(`${result.summary.passed}/${result.summary.total} checks passed`));
176
- if (result.exitCode !== 0) {
177
- lines.push("", theme.muted("Run 'outfitter doctor' after fixing issues to verify."));
178
- }
179
- await output(lines);
180
- }
181
- function doctorCommand(program) {
182
- program.command("doctor").description("Validate environment and dependencies").option("--json", "Output as JSON", false).action(async (_flags, command) => {
183
- const resolvedFlags = command.optsWithGlobals();
184
- const outputOptions = resolvedFlags.json ? { mode: "json" } : undefined;
185
- if (resolvedFlags.json) {
186
- process.env["OUTFITTER_JSON"] = "1";
187
- }
188
- const result = await runDoctor({ cwd: process.cwd() });
189
- await printDoctorResults(result, outputOptions);
190
- process.exit(result.exitCode);
191
- });
192
- }
193
-
194
- // src/commands/init.ts
195
- import {
196
- existsSync as existsSync3,
197
- mkdirSync as mkdirSync2,
198
- readdirSync,
199
- readFileSync as readFileSync3,
200
- statSync,
201
- writeFileSync as writeFileSync2
202
- } from "node:fs";
203
- import { basename, dirname as dirname2, extname, join as join3, resolve as resolve3 } from "node:path";
204
- import { fileURLToPath as fileURLToPath2 } from "node:url";
205
- import { exitWithError, output as output3 } from "@outfitter/cli/output";
206
- import { Result as Result2 } from "@outfitter/contracts";
207
-
208
- // src/commands/add.ts
209
- import {
210
- chmodSync,
211
- existsSync as existsSync2,
212
- mkdirSync,
213
- readFileSync as readFileSync2,
214
- writeFileSync
215
- } from "node:fs";
216
- import { dirname, join as join2, resolve as resolve2 } from "node:path";
217
- import { fileURLToPath } from "node:url";
218
- import { output as output2 } from "@outfitter/cli/output";
219
- import { Result } from "@outfitter/contracts";
220
- import { RegistrySchema } from "@outfitter/tooling";
221
-
222
- class AddError extends Error {
223
- _tag = "AddError";
224
- constructor(message) {
225
- super(message);
226
- this.name = "AddError";
227
- }
228
- }
229
- function getRegistryPath() {
230
- let currentDir = dirname(fileURLToPath(import.meta.url));
231
- for (let i = 0;i < 10; i++) {
232
- const registryPath = join2(currentDir, "node_modules/@outfitter/tooling/registry/registry.json");
233
- if (existsSync2(registryPath)) {
234
- return registryPath;
235
- }
236
- const monoRepoPath = join2(currentDir, "packages/tooling/registry/registry.json");
237
- if (existsSync2(monoRepoPath)) {
238
- return monoRepoPath;
239
- }
240
- currentDir = dirname(currentDir);
241
- }
242
- throw new AddError("Could not find registry.json. Ensure @outfitter/tooling is installed.");
243
- }
244
- function loadRegistry() {
245
- try {
246
- const registryPath = getRegistryPath();
247
- const content = readFileSync2(registryPath, "utf-8");
248
- const parsed = JSON.parse(content);
249
- const registry = RegistrySchema.parse(parsed);
250
- return Result.ok(registry);
251
- } catch (error) {
252
- const message = error instanceof Error ? error.message : "Unknown error";
253
- return Result.err(new AddError(`Failed to load registry: ${message}`));
254
- }
255
- }
256
- function resolveBlock(registry, blockName, visited = new Set) {
257
- if (visited.has(blockName)) {
258
- return Result.err(new AddError(`Circular dependency detected for block: ${blockName}`));
259
- }
260
- visited.add(blockName);
261
- const block = registry.blocks[blockName];
262
- if (!block) {
263
- const available = Object.keys(registry.blocks).join(", ");
264
- return Result.err(new AddError(`Block '${blockName}' not found. Available blocks: ${available}`));
265
- }
266
- if (block.extends && block.extends.length > 0) {
267
- const mergedFiles = [];
268
- const mergedDeps = {};
269
- const mergedDevDeps = {};
270
- for (const extendedName of block.extends) {
271
- const extendedResult = resolveBlock(registry, extendedName, visited);
272
- if (extendedResult.isErr()) {
273
- return extendedResult;
274
- }
275
- const extended = extendedResult.value;
276
- if (extended.files) {
277
- mergedFiles.push(...extended.files);
278
- }
279
- if (extended.dependencies) {
280
- Object.assign(mergedDeps, extended.dependencies);
281
- }
282
- if (extended.devDependencies) {
283
- Object.assign(mergedDevDeps, extended.devDependencies);
284
- }
285
- }
286
- if (block.files) {
287
- mergedFiles.push(...block.files);
288
- }
289
- if (block.dependencies) {
290
- Object.assign(mergedDeps, block.dependencies);
291
- }
292
- if (block.devDependencies) {
293
- Object.assign(mergedDevDeps, block.devDependencies);
294
- }
295
- return Result.ok({
296
- name: block.name,
297
- description: block.description,
298
- files: mergedFiles.length > 0 ? mergedFiles : undefined,
299
- dependencies: Object.keys(mergedDeps).length > 0 ? mergedDeps : undefined,
300
- devDependencies: Object.keys(mergedDevDeps).length > 0 ? mergedDevDeps : undefined
301
- });
302
- }
303
- return Result.ok(block);
304
- }
305
- function writeFile(filePath, content, executable) {
306
- const dir = dirname(filePath);
307
- if (!existsSync2(dir)) {
308
- mkdirSync(dir, { recursive: true });
309
- }
310
- writeFileSync(filePath, content, "utf-8");
311
- if (executable) {
312
- chmodSync(filePath, 493);
313
- }
314
- }
315
- function updatePackageJson(cwd, dependencies, devDependencies, dryRun) {
316
- const packageJsonPath = join2(cwd, "package.json");
317
- if (!existsSync2(packageJsonPath)) {
318
- return { dependencies, devDependencies };
319
- }
320
- const content = readFileSync2(packageJsonPath, "utf-8");
321
- const pkg = JSON.parse(content);
322
- const existingDeps = pkg["dependencies"] ?? {};
323
- const existingDevDeps = pkg["devDependencies"] ?? {};
324
- const addedDeps = {};
325
- const addedDevDeps = {};
326
- for (const [name, version] of Object.entries(dependencies)) {
327
- if (!existingDeps[name]) {
328
- existingDeps[name] = version;
329
- addedDeps[name] = version;
330
- }
331
- }
332
- for (const [name, version] of Object.entries(devDependencies)) {
333
- if (!(existingDevDeps[name] || existingDeps[name])) {
334
- existingDevDeps[name] = version;
335
- addedDevDeps[name] = version;
336
- }
337
- }
338
- if (!dryRun && (Object.keys(addedDeps).length > 0 || Object.keys(addedDevDeps).length > 0)) {
339
- if (Object.keys(existingDeps).length > 0) {
340
- pkg["dependencies"] = existingDeps;
341
- }
342
- if (Object.keys(existingDevDeps).length > 0) {
343
- pkg["devDependencies"] = existingDevDeps;
344
- }
345
- writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}
346
- `);
347
- }
348
- return { dependencies: addedDeps, devDependencies: addedDevDeps };
349
- }
350
- async function runAdd(input) {
351
- const { block: blockName, force, dryRun, cwd = process.cwd() } = input;
352
- const resolvedCwd = resolve2(cwd);
353
- const registryResult = loadRegistry();
354
- if (registryResult.isErr()) {
355
- return registryResult;
356
- }
357
- const registry = registryResult.value;
358
- const blockResult = resolveBlock(registry, blockName);
359
- if (blockResult.isErr()) {
360
- return blockResult;
361
- }
362
- const block = blockResult.value;
363
- const created = [];
364
- const skipped = [];
365
- const overwritten = [];
366
- if (block.files) {
367
- for (const file of block.files) {
368
- const targetPath = join2(resolvedCwd, file.path);
369
- const fileExists = existsSync2(targetPath);
370
- if (fileExists && !force) {
371
- skipped.push(file.path);
372
- continue;
373
- }
374
- if (!dryRun) {
375
- writeFile(targetPath, file.content, file.executable ?? false);
376
- }
377
- if (fileExists) {
378
- overwritten.push(file.path);
379
- } else {
380
- created.push(file.path);
381
- }
382
- }
383
- }
384
- const { dependencies, devDependencies } = updatePackageJson(resolvedCwd, block.dependencies ?? {}, block.devDependencies ?? {}, dryRun);
385
- return Result.ok({
386
- created,
387
- skipped,
388
- overwritten,
389
- dependencies,
390
- devDependencies
391
- });
392
- }
393
- async function printAddResults(result, dryRun, options) {
394
- const mode = options?.mode;
395
- if (mode === "json" || mode === "jsonl") {
396
- await output2({
397
- dryRun,
398
- created: result.created,
399
- overwritten: result.overwritten,
400
- skipped: result.skipped,
401
- dependencies: result.dependencies,
402
- devDependencies: result.devDependencies
403
- }, { mode });
404
- return;
405
- }
406
- const lines = [];
407
- const prefix = dryRun ? "[dry-run] Would " : "";
408
- if (result.created.length > 0) {
409
- lines.push(`${prefix}create ${result.created.length} file(s):`);
410
- for (const file of result.created) {
411
- lines.push(` ✓ ${file}`);
412
- }
413
- }
414
- if (result.overwritten.length > 0) {
415
- lines.push(`${prefix}overwrite ${result.overwritten.length} file(s):`);
416
- for (const file of result.overwritten) {
417
- lines.push(` ✓ ${file}`);
418
- }
419
- }
420
- if (result.skipped.length > 0) {
421
- lines.push(`Skipped ${result.skipped.length} existing file(s):`);
422
- for (const file of result.skipped) {
423
- lines.push(` - ${file} (use --force to overwrite)`);
424
- }
425
- }
426
- const depCount = Object.keys(result.dependencies).length + Object.keys(result.devDependencies).length;
427
- if (depCount > 0) {
428
- lines.push("", `${prefix}add ${depCount} package(s) to package.json:`);
429
- for (const [name, version] of Object.entries(result.dependencies)) {
430
- lines.push(` + ${name}@${version}`);
431
- }
432
- for (const [name, version] of Object.entries(result.devDependencies)) {
433
- lines.push(` + ${name}@${version} (dev)`);
434
- }
435
- if (!dryRun) {
436
- lines.push("", "Run `bun install` to install new dependencies.");
437
- }
438
- }
439
- await output2(lines);
440
- }
441
- function listBlocks() {
442
- const registryResult = loadRegistry();
443
- if (registryResult.isErr()) {
444
- return registryResult;
445
- }
446
- const blocks = Object.keys(registryResult.value.blocks);
447
- return Result.ok(blocks);
448
- }
449
-
450
- // src/commands/shared-deps.ts
451
- var SHARED_DEV_DEPS = {
452
- "@biomejs/biome": "^2.3.11",
453
- "@types/bun": "latest",
454
- lefthook: "^2.0.15",
455
- typescript: "^5.9.3",
456
- ultracite: "^7.0.12"
457
- };
458
- var SHARED_SCRIPTS = {
459
- lint: "biome check .",
460
- "lint:fix": "biome check . --write",
461
- format: "biome format --write .",
462
- typecheck: "tsc --noEmit"
463
- };
464
-
465
- // src/commands/init.ts
466
- class InitError extends Error {
467
- _tag = "InitError";
468
- constructor(message) {
469
- super(message);
470
- this.name = "InitError";
471
- }
472
- }
473
- var BINARY_EXTENSIONS = new Set([
474
- ".png",
475
- ".jpg",
476
- ".jpeg",
477
- ".gif",
478
- ".ico",
479
- ".webp",
480
- ".bmp",
481
- ".tiff",
482
- ".svg",
483
- ".woff",
484
- ".woff2",
485
- ".ttf",
486
- ".otf",
487
- ".eot",
488
- ".mp3",
489
- ".mp4",
490
- ".wav",
491
- ".ogg",
492
- ".webm",
493
- ".zip",
494
- ".tar",
495
- ".gz",
496
- ".bz2",
497
- ".7z",
498
- ".pdf",
499
- ".exe",
500
- ".dll",
501
- ".so",
502
- ".dylib",
503
- ".node",
504
- ".wasm",
505
- ".bin",
506
- ".dat",
507
- ".db",
508
- ".sqlite",
509
- ".sqlite3"
510
- ]);
511
- function isBinaryFile(filename) {
512
- const ext = extname(filename).toLowerCase();
513
- return BINARY_EXTENSIONS.has(ext);
514
- }
515
- function getTemplatesDir() {
516
- let currentDir = dirname2(fileURLToPath2(import.meta.url));
517
- for (let i = 0;i < 10; i++) {
518
- const templatesPath = join3(currentDir, "templates");
519
- if (existsSync3(templatesPath)) {
520
- return templatesPath;
521
- }
522
- currentDir = dirname2(currentDir);
523
- }
524
- return join3(process.cwd(), "templates");
525
- }
526
- function validateTemplate(templateName) {
527
- const templatesDir = getTemplatesDir();
528
- const templatePath = join3(templatesDir, templateName);
529
- if (!existsSync3(templatePath)) {
530
- return Result2.err(new InitError(`Template '${templateName}' not found. Available templates are in: ${templatesDir}`));
531
- }
532
- return Result2.ok(templatePath);
533
- }
534
- function replacePlaceholders(content, values) {
535
- return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
536
- if (Object.hasOwn(values, key)) {
537
- return values[key];
538
- }
539
- return match;
540
- });
541
- }
542
- function getOutputFilename(templateFilename) {
543
- if (templateFilename.endsWith(".template")) {
544
- return templateFilename.slice(0, -".template".length);
545
- }
546
- return templateFilename;
547
- }
548
- function hasPackageJson(targetDir) {
549
- return existsSync3(join3(targetDir, "package.json"));
550
- }
551
- function deriveProjectName(packageName) {
552
- if (packageName.startsWith("@")) {
553
- const parts = packageName.split("/");
554
- if (parts.length > 1 && parts[1]) {
555
- return parts[1];
556
- }
557
- }
558
- return packageName;
559
- }
560
- function resolvePackageName(options, resolvedTargetDir) {
561
- return options.name ?? basename(resolvedTargetDir);
562
- }
563
- function resolveBinName(options, projectName) {
564
- return options.bin ?? deriveProjectName(projectName);
565
- }
566
- function resolveTemplateName(options) {
567
- return options.template ?? "basic";
568
- }
569
- function resolveAuthor() {
570
- const fromEnv = process.env["GIT_AUTHOR_NAME"] ?? process.env["GIT_COMMITTER_NAME"] ?? process.env["AUTHOR"] ?? process.env["USER"] ?? process.env["USERNAME"];
571
- if (fromEnv) {
572
- return fromEnv;
573
- }
574
- try {
575
- const result = Bun.spawnSync(["git", "config", "--get", "user.name"], {
576
- stdout: "pipe",
577
- stderr: "ignore"
578
- });
579
- if (result.exitCode === 0) {
580
- const value = result.stdout.toString().trim();
581
- return value.length > 0 ? value : "";
582
- }
583
- } catch {}
584
- return "";
585
- }
586
- function resolveYear() {
587
- return String(new Date().getFullYear());
588
- }
589
- function resolveBlocks(options) {
590
- if (options.noTooling) {
591
- return;
592
- }
593
- if (options.with) {
594
- const blocks = options.with.split(",").map((b) => b.trim()).filter(Boolean);
595
- return blocks.length > 0 ? blocks : undefined;
596
- }
597
- return ["scaffolding"];
598
- }
599
- function copyTemplateFiles(templateDir, targetDir, values, force, allowOverwrite = false) {
600
- try {
601
- if (!existsSync3(targetDir)) {
602
- mkdirSync2(targetDir, { recursive: true });
603
- }
604
- const entries = readdirSync(templateDir);
605
- for (const entry of entries) {
606
- const sourcePath = join3(templateDir, entry);
607
- const stat = statSync(sourcePath);
608
- if (stat.isDirectory()) {
609
- const targetSubDir = join3(targetDir, entry);
610
- const result = copyTemplateFiles(sourcePath, targetSubDir, values, force, allowOverwrite);
611
- if (result.isErr()) {
612
- return result;
613
- }
614
- } else if (stat.isFile()) {
615
- const outputFilename = getOutputFilename(entry);
616
- const targetPath = join3(targetDir, outputFilename);
617
- if (existsSync3(targetPath) && !force && !allowOverwrite) {
618
- return Result2.err(new InitError(`File '${targetPath}' already exists. Use --force to overwrite.`));
619
- }
620
- if (isBinaryFile(outputFilename)) {
621
- const buffer = readFileSync3(sourcePath);
622
- writeFileSync2(targetPath, buffer);
623
- } else {
624
- const content = readFileSync3(sourcePath, "utf-8");
625
- const processedContent = replacePlaceholders(content, values);
626
- writeFileSync2(targetPath, processedContent, "utf-8");
627
- }
628
- }
629
- }
630
- return Result2.ok(undefined);
631
- } catch (error) {
632
- const message = error instanceof Error ? error.message : "Unknown error";
633
- return Result2.err(new InitError(`Failed to copy template files: ${message}`));
634
- }
635
- }
636
- var DEPENDENCY_SECTIONS = [
637
- "dependencies",
638
- "devDependencies",
639
- "peerDependencies",
640
- "optionalDependencies"
641
- ];
642
- function rewriteLocalDependencies(targetDir) {
643
- const packageJsonPath = join3(targetDir, "package.json");
644
- if (!existsSync3(packageJsonPath)) {
645
- return Result2.ok(undefined);
646
- }
647
- try {
648
- const content = readFileSync3(packageJsonPath, "utf-8");
649
- const parsed = JSON.parse(content);
650
- let updated = false;
651
- for (const section of DEPENDENCY_SECTIONS) {
652
- const deps = parsed[section];
653
- if (!deps || typeof deps !== "object" || Array.isArray(deps)) {
654
- continue;
655
- }
656
- const entries = deps;
657
- for (const [name, version] of Object.entries(entries)) {
658
- if (typeof version === "string" && name.startsWith("@outfitter/") && version !== "workspace:*") {
659
- entries[name] = "workspace:*";
660
- updated = true;
661
- }
662
- }
663
- }
664
- if (updated) {
665
- writeFileSync2(packageJsonPath, `${JSON.stringify(parsed, null, 2)}
666
- `, "utf-8");
667
- }
668
- return Result2.ok(undefined);
669
- } catch (error) {
670
- const message = error instanceof Error ? error.message : "Unknown error";
671
- return Result2.err(new InitError(`Failed to update local dependencies: ${message}`));
672
- }
673
- }
674
- function injectSharedConfig(targetDir) {
675
- const packageJsonPath = join3(targetDir, "package.json");
676
- if (!existsSync3(packageJsonPath)) {
677
- return Result2.ok(undefined);
678
- }
679
- try {
680
- const content = readFileSync3(packageJsonPath, "utf-8");
681
- const parsed = JSON.parse(content);
682
- const existingDevDeps = parsed["devDependencies"] ?? {};
683
- parsed["devDependencies"] = { ...SHARED_DEV_DEPS, ...existingDevDeps };
684
- const existingScripts = parsed["scripts"] ?? {};
685
- parsed["scripts"] = { ...SHARED_SCRIPTS, ...existingScripts };
686
- writeFileSync2(packageJsonPath, `${JSON.stringify(parsed, null, 2)}
687
- `, "utf-8");
688
- return Result2.ok(undefined);
689
- } catch (error) {
690
- const message = error instanceof Error ? error.message : "Unknown error";
691
- return Result2.err(new InitError(`Failed to inject shared config: ${message}`));
692
- }
693
- }
694
- async function runInit(options) {
695
- const { targetDir, force } = options;
696
- const resolvedTargetDir = resolve3(targetDir);
697
- if (hasPackageJson(resolvedTargetDir) && !force) {
698
- return Result2.err(new InitError(`Directory '${resolvedTargetDir}' already has a package.json. ` + `Use --force to overwrite, or use 'outfitter add' to add tooling to an existing project.`));
699
- }
700
- const templateName = resolveTemplateName(options);
701
- const templateResult = validateTemplate(templateName);
702
- if (templateResult.isErr()) {
703
- return templateResult;
704
- }
705
- const templatePath = templateResult.value;
706
- const packageName = resolvePackageName(options, resolvedTargetDir);
707
- const projectName = deriveProjectName(packageName);
708
- const binName = resolveBinName(options, projectName);
709
- const author = resolveAuthor();
710
- const year = resolveYear();
711
- const values = {
712
- name: projectName,
713
- projectName,
714
- packageName,
715
- binName,
716
- version: "0.1.0",
717
- description: "A new project created with Outfitter",
718
- author,
719
- year
720
- };
721
- try {
722
- if (!existsSync3(resolvedTargetDir)) {
723
- mkdirSync2(resolvedTargetDir, { recursive: true });
724
- }
725
- } catch (error) {
726
- const message = error instanceof Error ? error.message : "Unknown error";
727
- return Result2.err(new InitError(`Failed to create target directory: ${message}`));
728
- }
729
- const templatesDir = getTemplatesDir();
730
- const basePath = join3(templatesDir, "_base");
731
- if (existsSync3(basePath)) {
732
- const baseResult = copyTemplateFiles(basePath, resolvedTargetDir, values, force);
733
- if (baseResult.isErr()) {
734
- return baseResult;
735
- }
736
- }
737
- const copyResult = copyTemplateFiles(templatePath, resolvedTargetDir, values, force, true);
738
- if (copyResult.isErr()) {
739
- return copyResult;
740
- }
741
- const injectResult = injectSharedConfig(resolvedTargetDir);
742
- if (injectResult.isErr()) {
743
- return injectResult;
744
- }
745
- if (options.local) {
746
- const rewriteResult = rewriteLocalDependencies(resolvedTargetDir);
747
- if (rewriteResult.isErr()) {
748
- return rewriteResult;
749
- }
750
- }
751
- const blocks = resolveBlocks(options);
752
- let blocksAdded;
753
- if (blocks && blocks.length > 0) {
754
- const mergedResult = {
755
- created: [],
756
- skipped: [],
757
- overwritten: [],
758
- dependencies: {},
759
- devDependencies: {}
760
- };
761
- for (const blockName of blocks) {
762
- const addResult = await runAdd({
763
- block: blockName,
764
- force,
765
- dryRun: false,
766
- cwd: resolvedTargetDir
767
- });
768
- if (addResult.isErr()) {
769
- return Result2.err(new InitError(`Failed to add block '${blockName}': ${addResult.error.message}`));
770
- }
771
- const blockResult = addResult.value;
772
- mergedResult.created.push(...blockResult.created);
773
- mergedResult.skipped.push(...blockResult.skipped);
774
- mergedResult.overwritten.push(...blockResult.overwritten);
775
- Object.assign(mergedResult.dependencies, blockResult.dependencies);
776
- Object.assign(mergedResult.devDependencies, blockResult.devDependencies);
777
- }
778
- blocksAdded = mergedResult;
779
- }
780
- return Result2.ok({ blocksAdded });
781
- }
782
- async function printInitResults(targetDir, result, options) {
783
- const mode = options?.mode;
784
- if (mode === "json" || mode === "jsonl") {
785
- await output3({
786
- targetDir: resolve3(targetDir),
787
- blocksAdded: result.blocksAdded ?? null,
788
- nextSteps: ["bun install", "bun run dev"]
789
- }, { mode });
790
- return;
791
- }
792
- const lines = [
793
- `Project initialized successfully in ${resolve3(targetDir)}`
794
- ];
795
- if (result.blocksAdded) {
796
- const { created, skipped, dependencies, devDependencies } = result.blocksAdded;
797
- if (created.length > 0) {
798
- lines.push("", `Added ${created.length} tooling file(s):`);
799
- for (const file of created) {
800
- lines.push(` ✓ ${file}`);
801
- }
802
- }
803
- if (skipped.length > 0) {
804
- lines.push("", `Skipped ${skipped.length} existing file(s):`);
805
- for (const file of skipped) {
806
- lines.push(` - ${file}`);
807
- }
808
- }
809
- const depCount = Object.keys(dependencies).length + Object.keys(devDependencies).length;
810
- if (depCount > 0) {
811
- lines.push("", `Added ${depCount} package(s) to package.json:`);
812
- for (const [name, version] of Object.entries(dependencies)) {
813
- lines.push(` + ${name}@${version}`);
814
- }
815
- for (const [name, version] of Object.entries(devDependencies)) {
816
- lines.push(` + ${name}@${version} (dev)`);
817
- }
818
- }
819
- }
820
- lines.push("", "Next steps:", " bun install", " bun run dev");
821
- await output3(lines);
822
- }
823
- function initCommand(program) {
824
- const init = program.command("init").description("Scaffold a new Outfitter project");
825
- const resolveFlags = (flags, command) => {
826
- if (command) {
827
- return command.optsWithGlobals();
828
- }
829
- return typeof flags.opts === "function" ? flags.opts() : flags;
830
- };
831
- const resolveLocal = (flags) => Boolean(flags.local || flags.workspace);
832
- const withCommonOptions = (command) => command.option("-n, --name <name>", "Package name (defaults to directory name)").option("-b, --bin <name>", "Binary name (defaults to project name)").option("-f, --force", "Overwrite existing files", false).option("--local", "Use workspace:* for @outfitter dependencies", false).option("--workspace", "Alias for --local", false).option("--with <blocks>", "Tooling to add (comma-separated: scaffolding, claude, biome, lefthook, bootstrap)").option("--no-tooling", "Skip tooling setup").option("--json", "Output as JSON", false);
833
- const resolveOutputMode = (flags) => {
834
- if (flags.json) {
835
- process.env["OUTFITTER_JSON"] = "1";
836
- return "json";
837
- }
838
- return;
839
- };
840
- withCommonOptions(init.argument("[directory]").option("-t, --template <template>", "Template to use")).action(async (directory, flags, command) => {
841
- const targetDir = directory ?? process.cwd();
842
- const resolvedFlags = resolveFlags(flags, command);
843
- const mode = resolveOutputMode(resolvedFlags);
844
- const outputOptions = mode ? { mode } : undefined;
845
- const local = resolveLocal(resolvedFlags);
846
- const result = await runInit({
847
- targetDir,
848
- name: resolvedFlags.name,
849
- template: resolvedFlags.template,
850
- local,
851
- force: resolvedFlags.force ?? false,
852
- with: resolvedFlags.with,
853
- noTooling: resolvedFlags.noTooling,
854
- ...resolvedFlags.bin !== undefined ? { bin: resolvedFlags.bin } : {}
855
- });
856
- if (result.isErr()) {
857
- exitWithError(result.error, outputOptions);
858
- }
859
- await printInitResults(targetDir, result.value, outputOptions);
860
- });
861
- withCommonOptions(init.command("cli [directory]").description("Scaffold a new CLI project")).action(async (directory, flags, command) => {
862
- const targetDir = directory ?? process.cwd();
863
- const resolvedFlags = resolveFlags(flags, command);
864
- const mode = resolveOutputMode(resolvedFlags);
865
- const outputOptions = mode ? { mode } : undefined;
866
- const local = resolveLocal(resolvedFlags);
867
- const result = await runInit({
868
- targetDir,
869
- name: resolvedFlags.name,
870
- template: "cli",
871
- local,
872
- force: resolvedFlags.force ?? false,
873
- with: resolvedFlags.with,
874
- noTooling: resolvedFlags.noTooling,
875
- ...resolvedFlags.bin !== undefined ? { bin: resolvedFlags.bin } : {}
876
- });
877
- if (result.isErr()) {
878
- exitWithError(result.error, outputOptions);
879
- }
880
- await printInitResults(targetDir, result.value, outputOptions);
881
- });
882
- withCommonOptions(init.command("mcp [directory]").description("Scaffold a new MCP server")).action(async (directory, flags, command) => {
883
- const targetDir = directory ?? process.cwd();
884
- const resolvedFlags = resolveFlags(flags, command);
885
- const mode = resolveOutputMode(resolvedFlags);
886
- const outputOptions = mode ? { mode } : undefined;
887
- const local = resolveLocal(resolvedFlags);
888
- const result = await runInit({
889
- targetDir,
890
- name: resolvedFlags.name,
891
- template: "mcp",
892
- local,
893
- force: resolvedFlags.force ?? false,
894
- with: resolvedFlags.with,
895
- noTooling: resolvedFlags.noTooling,
896
- ...resolvedFlags.bin !== undefined ? { bin: resolvedFlags.bin } : {}
897
- });
898
- if (result.isErr()) {
899
- exitWithError(result.error, outputOptions);
900
- }
901
- await printInitResults(targetDir, result.value, outputOptions);
902
- });
903
- withCommonOptions(init.command("daemon [directory]").description("Scaffold a new daemon project")).action(async (directory, flags, command) => {
904
- const targetDir = directory ?? process.cwd();
905
- const resolvedFlags = resolveFlags(flags, command);
906
- const mode = resolveOutputMode(resolvedFlags);
907
- const outputOptions = mode ? { mode } : undefined;
908
- const local = resolveLocal(resolvedFlags);
909
- const result = await runInit({
910
- targetDir,
911
- name: resolvedFlags.name,
912
- template: "daemon",
913
- local,
914
- force: resolvedFlags.force ?? false,
915
- with: resolvedFlags.with,
916
- noTooling: resolvedFlags.noTooling,
917
- ...resolvedFlags.bin !== undefined ? { bin: resolvedFlags.bin } : {}
918
- });
919
- if (result.isErr()) {
920
- exitWithError(result.error, outputOptions);
921
- }
922
- await printInitResults(targetDir, result.value, outputOptions);
923
- });
924
- }
925
-
926
- // src/actions.ts
927
- import { resolve as resolve5 } from "node:path";
928
- import { output as output6 } from "@outfitter/cli/output";
929
- import {
930
- createActionRegistry,
931
- defineAction,
932
- InternalError as InternalError2,
933
- Result as Result4
934
- } from "@outfitter/contracts";
935
- import { z } from "zod";
936
-
937
- // src/commands/demo.ts
938
- import { isCancel, select } from "@clack/prompts";
939
- import { output as output4 } from "@outfitter/cli/output";
940
- import { createTheme as createTheme3, renderTable as renderTable2, SPINNERS } from "@outfitter/cli/render";
941
- import { ANSI } from "@outfitter/cli/streaming";
942
- import { isInteractive } from "@outfitter/cli/terminal";
943
-
944
- // src/commands/demo/index.ts
945
- import {
946
- getPrimitive,
947
- getPrimitiveIds,
948
- renderAllDemos,
949
- renderDemo
950
- } from "@outfitter/cli/demo";
951
- var appSections = [];
952
- var DEMO_CONFIG = {
953
- examples: {
954
- success: "Package published",
955
- error: "Init failed",
956
- warning: "Deprecated API",
957
- spinnerMessage: "Installing dependencies...",
958
- boxTitle: "Outfitter",
959
- boxContent: "CLI scaffolding tool"
960
- }
961
- };
962
- function registerSection(section) {
963
- appSections.push(section);
964
- }
965
- function getSectionIds() {
966
- const primitiveIds = getPrimitiveIds();
967
- const appIds = appSections.map((s) => s.id);
968
- return [...primitiveIds, ...appIds];
969
- }
970
- function getSections() {
971
- const primitiveSections = getPrimitiveIds().map((id) => {
972
- const meta = getPrimitive(id);
973
- return {
974
- id: meta.id,
975
- description: meta.description,
976
- run: () => renderDemo(id, DEMO_CONFIG)
977
- };
978
- });
979
- return [...primitiveSections, ...appSections];
980
- }
981
- function getSection(id) {
982
- const primitiveIds = getPrimitiveIds();
983
- if (primitiveIds.includes(id)) {
984
- const meta = getPrimitive(id);
985
- return {
986
- id: meta.id,
987
- description: meta.description,
988
- run: () => renderDemo(id, DEMO_CONFIG)
989
- };
990
- }
991
- return appSections.find((s) => s.id === id);
992
- }
993
- function runSection(id) {
994
- const section = getSection(id);
995
- if (!section) {
996
- return;
997
- }
998
- return section.run();
999
- }
1000
- function runAllSections() {
1001
- const outputs = [];
1002
- outputs.push(renderAllDemos(DEMO_CONFIG));
1003
- for (const section of appSections) {
1004
- outputs.push("");
1005
- outputs.push(section.run());
1006
- }
1007
- return outputs.join(`
1008
- `).trimEnd();
1009
- }
1010
-
1011
- // src/commands/demo/errors.ts
1012
- import { createTheme as createTheme2, renderTable } from "@outfitter/cli/render";
1013
- import {
1014
- AuthError,
1015
- exitCodeMap,
1016
- NotFoundError,
1017
- statusCodeMap,
1018
- ValidationError
1019
- } from "@outfitter/contracts";
1020
- function getErrorInfos() {
1021
- return [
1022
- { name: "ValidationError", category: "validation" },
1023
- { name: "NotFoundError", category: "not_found" },
1024
- { name: "ConflictError", category: "conflict" },
1025
- { name: "PermissionError", category: "permission" },
1026
- { name: "TimeoutError", category: "timeout" },
1027
- { name: "RateLimitError", category: "rate_limit" },
1028
- { name: "NetworkError", category: "network" },
1029
- { name: "InternalError", category: "internal" },
1030
- { name: "AuthError", category: "auth" },
1031
- { name: "CancelledError", category: "cancelled" }
1032
- ];
1033
- }
1034
- function formatErrorHuman(error) {
1035
- return `${error._tag}: ${error.message}`;
1036
- }
1037
- function formatErrorJson(error) {
1038
- const result = {
1039
- message: error.message,
1040
- _tag: error._tag,
1041
- category: error.category
1042
- };
1043
- if (error.context !== undefined) {
1044
- result["context"] = error.context;
1045
- }
1046
- return JSON.stringify(result);
1047
- }
1048
- function runErrorsDemo() {
1049
- const theme = createTheme2();
1050
- const lines = [];
1051
- lines.push("ERROR OUTPUT");
1052
- lines.push("============");
1053
- lines.push("");
1054
- lines.push('import { exitWithError } from "@outfitter/cli";');
1055
- lines.push('import { ValidationError, NotFoundError } from "@outfitter/contracts";');
1056
- lines.push("");
1057
- const validationError = new ValidationError({
1058
- message: "Invalid email format",
1059
- field: "email"
1060
- });
1061
- lines.push("// Human mode (TTY):");
1062
- lines.push('exitWithError(new ValidationError({ message: "Invalid email format" }))');
1063
- lines.push(`→ stderr: "${formatErrorHuman(validationError)}"`);
1064
- lines.push(`→ exit code: ${validationError.exitCode()}`);
1065
- lines.push("");
1066
- const notFoundError = new NotFoundError({
1067
- message: "User not found",
1068
- resourceType: "user",
1069
- resourceId: "abc123"
1070
- });
1071
- lines.push('exitWithError(new NotFoundError({ message: "User not found", ... }))');
1072
- lines.push(`→ stderr: "${formatErrorHuman(notFoundError)}"`);
1073
- lines.push(`→ exit code: ${notFoundError.exitCode()}`);
1074
- lines.push("");
1075
- lines.push("// JSON mode (pipe/CI):");
1076
- const validationWithContext = new ValidationError({
1077
- message: "Invalid email",
1078
- field: "email"
1079
- });
1080
- lines.push('exitWithError(new ValidationError({ message: "Invalid email", field: "email" }))');
1081
- lines.push(`→ stderr: ${formatErrorJson({
1082
- ...validationWithContext,
1083
- context: { field: "email" }
1084
- })}`);
1085
- lines.push(`→ exit code: ${validationWithContext.exitCode()}`);
1086
- lines.push("");
1087
- lines.push("ERROR TAXONOMY → EXIT CODES");
1088
- lines.push("===========================");
1089
- lines.push("");
1090
- const taxonomyData = getErrorInfos().map((info) => ({
1091
- Category: info.category,
1092
- "Exit Code": exitCodeMap[info.category],
1093
- "HTTP Status": statusCodeMap[info.category],
1094
- "Example Error": info.name
1095
- }));
1096
- lines.push(renderTable(taxonomyData));
1097
- lines.push("");
1098
- lines.push("CREATING ERRORS");
1099
- lines.push("===============");
1100
- lines.push("");
1101
- lines.push('import { ValidationError, NotFoundError, TimeoutError } from "@outfitter/contracts";');
1102
- lines.push("");
1103
- lines.push("// Input validation failed");
1104
- lines.push('new ValidationError({ message: "Email format invalid", field: "email" })');
1105
- lines.push("");
1106
- lines.push("// Resource not found");
1107
- lines.push('new NotFoundError({ message: "note not found: abc123", resourceType: "note", resourceId: "abc123" })');
1108
- lines.push("");
1109
- lines.push("// Operation timed out");
1110
- lines.push('new TimeoutError({ message: "Database query timed out", operation: "query", timeoutMs: 5000 })');
1111
- lines.push("");
1112
- lines.push("MODE DETECTION");
1113
- lines.push("==============");
1114
- lines.push("");
1115
- lines.push("Output mode auto-detected:");
1116
- lines.push("- TTY → human-readable format");
1117
- lines.push("- Pipe/CI → JSON format");
1118
- lines.push("- OUTFITTER_JSON=1 → force JSON");
1119
- lines.push("- OUTFITTER_JSONL=1 → force JSONL");
1120
- lines.push("");
1121
- lines.push("ERROR METHODS");
1122
- lines.push("=============");
1123
- lines.push("");
1124
- lines.push("All error classes provide:");
1125
- lines.push("- .exitCode() → CLI exit code (from exitCodeMap)");
1126
- lines.push("- .statusCode() → HTTP status code (from statusCodeMap)");
1127
- lines.push("- .category → error category string");
1128
- lines.push("- ._tag → error type name (e.g., 'ValidationError')");
1129
- lines.push("");
1130
- const authError = new AuthError({
1131
- message: "Invalid token",
1132
- reason: "expired"
1133
- });
1134
- lines.push(theme.muted("Example:"));
1135
- lines.push(`const err = new AuthError({ message: "Invalid token", reason: "expired" })`);
1136
- lines.push(`err.exitCode() // ${authError.exitCode()}`);
1137
- lines.push(`err.statusCode() // ${authError.statusCode()}`);
1138
- lines.push(`err.category // "${authError.category}"`);
1139
- lines.push(`err._tag // "${authError._tag}"`);
1140
- return lines.join(`
1141
- `);
1142
- }
1143
- registerSection({
1144
- id: "errors",
1145
- description: "Error taxonomy and output formatting",
1146
- run: runErrorsDemo
1147
- });
1148
-
1149
- // src/commands/demo.ts
1150
- async function runDemo(options) {
1151
- if (options.list) {
1152
- return listSections();
1153
- }
1154
- if (options.animate) {
1155
- await runAnimatedSpinnerDemo();
1156
- return { output: "", exitCode: 0 };
1157
- }
1158
- if (options.section) {
1159
- return runSectionByName(options.section);
1160
- }
1161
- if (isInteractive()) {
1162
- const selectedSection = await selectSection();
1163
- if (selectedSection === null) {
1164
- return { output: "", exitCode: 130 };
1165
- }
1166
- return runSectionByName(selectedSection);
1167
- }
1168
- const output5 = runAllSections();
1169
- return { output: output5, exitCode: 0 };
1170
- }
1171
- function runSectionByName(sectionName) {
1172
- if (sectionName === "all") {
1173
- const output6 = runAllSections();
1174
- return { output: output6, exitCode: 0 };
1175
- }
1176
- const output5 = runSection(sectionName);
1177
- if (output5 === undefined) {
1178
- return {
1179
- output: formatError(sectionName),
1180
- exitCode: 1
1181
- };
1182
- }
1183
- return { output: output5, exitCode: 0 };
1184
- }
1185
- async function selectSection() {
1186
- const sections = getSections();
1187
- const options = [
1188
- { value: "all", label: "All sections", hint: "Run all demos" },
1189
- ...sections.map((s) => ({
1190
- value: s.id,
1191
- label: s.id,
1192
- hint: s.description
1193
- }))
1194
- ];
1195
- const selection = await select({
1196
- message: "Select a demo section",
1197
- options
1198
- });
1199
- if (isCancel(selection)) {
1200
- return null;
1201
- }
1202
- return String(selection);
1203
- }
1204
- function listSections() {
1205
- const sections = getSections();
1206
- const theme = createTheme3();
1207
- if (sections.length === 0) {
1208
- return {
1209
- output: theme.muted("No demo sections available."),
1210
- exitCode: 0
1211
- };
1212
- }
1213
- const lines = [
1214
- "Available demo sections:",
1215
- "",
1216
- renderTable2(sections.map((s) => ({ section: s.id, description: s.description })), { headers: { section: "Section", description: "Description" } }),
1217
- "",
1218
- theme.muted('Run "outfitter demo [section]" to run a specific section.'),
1219
- theme.muted('Run "outfitter demo all" to run all sections.')
1220
- ];
1221
- return { output: lines.join(`
1222
- `), exitCode: 0 };
1223
- }
1224
- function formatError(sectionId) {
1225
- const theme = createTheme3();
1226
- const available = getSectionIds();
1227
- const lines = [
1228
- theme.error(`Unknown section: ${sectionId}`),
1229
- "",
1230
- `Available sections: ${available.length > 0 ? available.join(", ") : "(none)"}`,
1231
- "",
1232
- theme.muted('Run "outfitter demo --list" to see all sections.')
1233
- ];
1234
- return lines.join(`
1235
- `);
1236
- }
1237
- async function runAnimatedSpinnerDemo() {
1238
- const theme = createTheme3();
1239
- const styles = Object.keys(SPINNERS);
1240
- const intervalMs = 80;
1241
- const stream = process.stdout;
1242
- const isTTY = stream.isTTY ?? false;
1243
- const writeLine = (line) => {
1244
- stream.write(`${line}
1245
- `);
1246
- };
1247
- writeLine("");
1248
- writeLine(theme.bold("ANIMATED SPINNER DEMO"));
1249
- writeLine(theme.muted("All styles running simultaneously"));
1250
- writeLine(theme.muted("Press Ctrl+C to stop"));
1251
- writeLine("");
1252
- if (!isTTY) {
1253
- for (const style of styles) {
1254
- const spinner = SPINNERS[style];
1255
- writeLine(`${style.padEnd(10)} ${spinner.frames.join(" ")}`);
1256
- }
1257
- return;
1258
- }
1259
- stream.write(ANSI.hideCursor);
1260
- const numLines = styles.length;
1261
- for (const style of styles) {
1262
- const spinner = SPINNERS[style];
1263
- const frame = spinner.frames[0] ?? "";
1264
- stream.write(` ${frame} ${style.padEnd(10)} ${theme.muted(`(${spinner.interval}ms)`)}
1265
- `);
1266
- }
1267
- const frameIndices = styles.map(() => 0);
1268
- const animate = () => {
1269
- stream.write(ANSI.cursorUp(numLines));
1270
- for (let i = 0;i < styles.length; i++) {
1271
- const style = styles[i];
1272
- if (!style)
1273
- continue;
1274
- const spinner = SPINNERS[style];
1275
- const frameIndex = frameIndices[i] ?? 0;
1276
- const frame = spinner.frames[frameIndex % spinner.frames.length] ?? "";
1277
- stream.write(ANSI.clearLine);
1278
- stream.write(` ${frame} ${style.padEnd(10)} ${theme.muted(`(${spinner.interval}ms)`)}
1279
- `);
1280
- frameIndices[i] = frameIndex + 1;
1281
- }
1282
- };
1283
- const timer = setInterval(animate, intervalMs);
1284
- const cleanup = () => {
1285
- clearInterval(timer);
1286
- stream.write(ANSI.showCursor);
1287
- writeLine("");
1288
- writeLine(theme.muted("Use createSpinner() from @outfitter/cli/streaming"));
1289
- writeLine("");
1290
- process.exit(0);
1291
- };
1292
- process.on("SIGINT", cleanup);
1293
- await new Promise(() => {});
1294
- }
1295
- async function printDemoResults(result) {
1296
- if (result.output) {
1297
- await output4(result.output, { mode: "human" });
1298
- }
1299
- }
1300
-
1301
- // src/commands/update.ts
1302
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
1303
- import { join as join4, resolve as resolve4 } from "node:path";
1304
- import { output as output5 } from "@outfitter/cli/output";
1305
- import { createTheme as createTheme4 } from "@outfitter/cli/render";
1306
- import { InternalError, Result as Result3 } from "@outfitter/contracts";
1307
- function getInstalledPackages(cwd) {
1308
- const pkgPath = join4(cwd, "package.json");
1309
- if (!existsSync4(pkgPath)) {
1310
- return Result3.err(InternalError.create("No package.json found", { cwd }));
1311
- }
1312
- let raw;
1313
- try {
1314
- raw = readFileSync4(pkgPath, "utf-8");
1315
- } catch {
1316
- return Result3.err(InternalError.create("Failed to read package.json", { cwd }));
1317
- }
1318
- let pkg;
1319
- try {
1320
- pkg = JSON.parse(raw);
1321
- } catch {
1322
- return Result3.err(InternalError.create("Invalid JSON in package.json", { cwd }));
1323
- }
1324
- const deps = {
1325
- ...pkg.dependencies ?? {},
1326
- ...pkg.devDependencies ?? {}
1327
- };
1328
- const packages = [];
1329
- for (const [name, version] of Object.entries(deps)) {
1330
- if (!name.startsWith("@outfitter/"))
1331
- continue;
1332
- if (version.startsWith("workspace:")) {
1333
- const wsVersion = version.slice("workspace:".length);
1334
- if (wsVersion === "*" || wsVersion === "~" || wsVersion === "^") {
1335
- continue;
1336
- }
1337
- const wsClean = wsVersion.replace(/^[\^~>=<]+/, "");
1338
- try {
1339
- if (!Bun.semver.satisfies(wsClean, "*"))
1340
- continue;
1341
- } catch {
1342
- continue;
1343
- }
1344
- packages.push({ name, version: wsClean });
1345
- continue;
1346
- }
1347
- const cleaned = version.replace(/^[\^~>=<]+/, "");
1348
- try {
1349
- if (!Bun.semver.satisfies(cleaned, "*"))
1350
- continue;
1351
- } catch {
1352
- continue;
1353
- }
1354
- packages.push({ name, version: cleaned });
1355
- }
1356
- return Result3.ok(packages);
1357
- }
1358
- async function getLatestVersion(name) {
1359
- try {
1360
- const proc = Bun.spawn(["npm", "view", name, "version"], {
1361
- stdout: "pipe",
1362
- stderr: "pipe"
1363
- });
1364
- const stdout = await new Response(proc.stdout).text();
1365
- const exitCode = await proc.exited;
1366
- if (exitCode !== 0)
1367
- return null;
1368
- return stdout.trim() || null;
1369
- } catch {
1370
- return null;
1371
- }
1372
- }
1373
- var MIGRATION_DOC_PATHS = [
1374
- "plugins/kit/shared/migrations",
1375
- "node_modules/@outfitter/kit/shared/migrations"
1376
- ];
1377
- function findMigrationDocsDir(cwd, binaryDir) {
1378
- for (const relative of MIGRATION_DOC_PATHS) {
1379
- const dir = join4(cwd, relative);
1380
- if (existsSync4(dir))
1381
- return dir;
1382
- }
1383
- let current = resolve4(cwd);
1384
- const root = resolve4("/");
1385
- while (current !== root) {
1386
- const parent = resolve4(current, "..");
1387
- if (parent === current)
1388
- break;
1389
- current = parent;
1390
- for (const relative of MIGRATION_DOC_PATHS) {
1391
- const dir = join4(current, relative);
1392
- if (existsSync4(dir))
1393
- return dir;
1394
- }
1395
- }
1396
- const resolvedBinaryDir = binaryDir ?? resolve4(import.meta.dir, "../../../..");
1397
- for (const relative of MIGRATION_DOC_PATHS) {
1398
- const dir = join4(resolvedBinaryDir, relative);
1399
- if (existsSync4(dir))
1400
- return dir;
1401
- }
1402
- return null;
1403
- }
1404
- function readMigrationDocs(migrationsDir, shortName, fromVersion, toVersion) {
1405
- const glob = new Bun.Glob(`outfitter-${shortName}-*.md`);
1406
- const versionPattern = new RegExp(`^outfitter-${shortName}-(\\d+\\.\\d+\\.\\d+)\\.md$`);
1407
- const docs = [];
1408
- for (const entry of glob.scanSync({ cwd: migrationsDir })) {
1409
- const match = entry.match(versionPattern);
1410
- if (!match?.[1])
1411
- continue;
1412
- const docVersion = match[1];
1413
- if (Bun.semver.order(docVersion, fromVersion) <= 0)
1414
- continue;
1415
- if (Bun.semver.order(docVersion, toVersion) > 0)
1416
- continue;
1417
- const filePath = join4(migrationsDir, entry);
1418
- const content = readFileSync4(filePath, "utf-8");
1419
- const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
1420
- if (body) {
1421
- docs.push({ version: docVersion, content: body });
1422
- }
1423
- }
1424
- docs.sort((a, b) => Bun.semver.order(a.version, b.version));
1425
- return docs.map((d) => d.content);
1426
- }
1427
- async function runUpdate(options) {
1428
- const cwd = resolve4(options.cwd);
1429
- const installedResult = getInstalledPackages(cwd);
1430
- if (installedResult.isErr())
1431
- return installedResult;
1432
- const installed = installedResult.value;
1433
- if (installed.length === 0) {
1434
- return Result3.ok({
1435
- packages: [],
1436
- total: 0,
1437
- updatesAvailable: 0,
1438
- hasBreaking: false
1439
- });
1440
- }
1441
- const packages = await Promise.all(installed.map(async (pkg) => {
1442
- const latest = await getLatestVersion(pkg.name);
1443
- const updateAvailable = latest !== null && Bun.semver.order(latest, pkg.version) > 0;
1444
- const breaking = updateAvailable && latest !== null ? getMajor(latest) > getMajor(pkg.version) : false;
1445
- return {
1446
- name: pkg.name,
1447
- current: pkg.version,
1448
- latest,
1449
- updateAvailable,
1450
- breaking
1451
- };
1452
- }));
1453
- const updatesAvailable = packages.filter((p) => p.updateAvailable).length;
1454
- const hasBreaking = packages.some((p) => p.breaking);
1455
- return Result3.ok({
1456
- packages,
1457
- total: packages.length,
1458
- updatesAvailable,
1459
- hasBreaking
1460
- });
1461
- }
1462
- function getMajor(version) {
1463
- const parts = version.split(".");
1464
- return Number.parseInt(parts[0] ?? "0", 10);
1465
- }
1466
- async function printUpdateResults(result, options) {
1467
- const mode = options?.mode;
1468
- if (mode === "json" || mode === "jsonl") {
1469
- await output5(result, { mode });
1470
- return;
1471
- }
1472
- const theme = createTheme4();
1473
- const lines = ["", "Outfitter Update", "", "=".repeat(60)];
1474
- if (result.packages.length === 0) {
1475
- lines.push("No @outfitter/* packages found in package.json.");
1476
- await output5(lines);
1477
- return;
1478
- }
1479
- lines.push(` ${"Package".padEnd(28)} ${"Current".padEnd(10)} ${"Available".padEnd(10)} Migration`);
1480
- lines.push(` ${"─".repeat(28)} ${"─".repeat(10)} ${"─".repeat(10)} ${"─".repeat(20)}`);
1481
- for (const pkg of result.packages) {
1482
- const name = pkg.name.padEnd(28);
1483
- const current = pkg.current.padEnd(10);
1484
- const available = (pkg.latest ?? "unknown").padEnd(10);
1485
- let migration;
1486
- if (pkg.latest === null) {
1487
- migration = theme.muted("lookup failed");
1488
- } else if (!pkg.updateAvailable) {
1489
- migration = theme.muted("up to date");
1490
- } else if (pkg.breaking) {
1491
- migration = theme.error("major (breaking)");
1492
- } else {
1493
- migration = theme.success("minor (no breaking)");
1494
- }
1495
- lines.push(` ${name} ${current} ${available} ${migration}`);
1496
- }
1497
- lines.push("");
1498
- if (result.updatesAvailable > 0) {
1499
- lines.push(theme.muted("Run 'outfitter update --guide' for migration instructions."));
1500
- } else {
1501
- lines.push(theme.success("All packages are up to date."));
1502
- }
1503
- if (options?.guide && result.updatesAvailable > 0) {
1504
- const cwd = options.cwd ?? process.cwd();
1505
- const migrationsDir = findMigrationDocsDir(cwd);
1506
- if (migrationsDir) {
1507
- lines.push("", "=".repeat(60), "", "Migration Guide", "");
1508
- for (const pkg of result.packages) {
1509
- if (!(pkg.updateAvailable && pkg.latest))
1510
- continue;
1511
- const shortName = pkg.name.replace("@outfitter/", "");
1512
- const docs = readMigrationDocs(migrationsDir, shortName, pkg.current, pkg.latest);
1513
- for (const doc of docs) {
1514
- lines.push(doc, "", "---", "");
1515
- }
1516
- }
1517
- } else {
1518
- lines.push("", theme.muted("Migration docs not found locally. See https://github.com/outfitter-dev/outfitter for migration guides."));
1519
- }
1520
- }
1521
- await output5(lines);
1522
- }
1523
-
1524
- // src/actions.ts
1525
- var outputModeSchema = z.enum(["human", "json", "jsonl"]).optional();
1526
- var initInputSchema = z.object({
1527
- targetDir: z.string(),
1528
- name: z.string().optional(),
1529
- bin: z.string().optional(),
1530
- template: z.string().optional(),
1531
- local: z.boolean().optional(),
1532
- force: z.boolean(),
1533
- outputMode: outputModeSchema
1534
- });
1535
- var doctorInputSchema = z.object({
1536
- cwd: z.string(),
1537
- outputMode: outputModeSchema
1538
- });
1539
- function resolveStringFlag(value) {
1540
- return typeof value === "string" && value.length > 0 ? value : undefined;
1541
- }
1542
- function resolveOutputMode(flags) {
1543
- if (flags["json"]) {
1544
- process.env["OUTFITTER_JSON"] = "1";
1545
- return "json";
1546
- }
1547
- return;
1548
- }
1549
- function resolveInitOptions(context, templateOverride) {
1550
- const flags = context.flags;
1551
- const targetDir = context.args[0] ?? process.cwd();
1552
- const name = resolveStringFlag(flags.name);
1553
- const bin = resolveStringFlag(flags.bin);
1554
- const template = templateOverride ?? resolveStringFlag(flags.template);
1555
- const local = Boolean(flags.local || flags.workspace);
1556
- const force = Boolean(flags.force);
1557
- const outputMode = resolveOutputMode(context.flags);
1558
- return {
1559
- targetDir,
1560
- name,
1561
- template,
1562
- local,
1563
- force,
1564
- ...bin ? { bin } : {},
1565
- ...outputMode ? { outputMode } : {}
1566
- };
1567
- }
1568
- var commonInitOptions = [
1569
- {
1570
- flags: "-n, --name <name>",
1571
- description: "Package name (defaults to directory name)"
1572
- },
1573
- {
1574
- flags: "-b, --bin <name>",
1575
- description: "Binary name (defaults to project name)"
1576
- },
1577
- {
1578
- flags: "-f, --force",
1579
- description: "Overwrite existing files",
1580
- defaultValue: false
1581
- },
1582
- {
1583
- flags: "--local",
1584
- description: "Use workspace:* for @outfitter dependencies",
1585
- defaultValue: false
1586
- },
1587
- {
1588
- flags: "--workspace",
1589
- description: "Alias for --local",
1590
- defaultValue: false
1591
- }
1592
- ];
1593
- var templateOption = {
1594
- flags: "-t, --template <template>",
1595
- description: "Template to use"
1596
- };
1597
- function createInitAction(options) {
1598
- const initOptions = options.includeTemplateOption ? [...commonInitOptions, templateOption] : commonInitOptions;
1599
- return defineAction({
1600
- id: options.id,
1601
- description: options.description,
1602
- surfaces: ["cli"],
1603
- input: initInputSchema,
1604
- cli: {
1605
- group: "init",
1606
- command: options.command,
1607
- description: options.description,
1608
- options: initOptions,
1609
- mapInput: (context) => resolveInitOptions(context, options.templateOverride)
1610
- },
1611
- handler: async (input) => {
1612
- const { outputMode, ...initInput } = input;
1613
- const outputOptions = outputMode ? { mode: outputMode } : undefined;
1614
- const result = await runInit(initInput);
1615
- if (result.isErr()) {
1616
- return Result4.err(new InternalError2({
1617
- message: result.error.message,
1618
- context: { action: options.id }
1619
- }));
1620
- }
1621
- await printInitResults(initInput.targetDir, result.value, outputOptions);
1622
- return Result4.ok({ ok: true });
1623
- }
1624
- });
1625
- }
1626
- var demoInputSchema = z.object({
1627
- section: z.string().optional(),
1628
- list: z.boolean().optional(),
1629
- animate: z.boolean().optional()
1630
- });
1631
- var demoAction = defineAction({
1632
- id: "demo",
1633
- description: "Showcase @outfitter/cli rendering capabilities",
1634
- surfaces: ["cli"],
1635
- input: demoInputSchema,
1636
- cli: {
1637
- command: "demo [section]",
1638
- description: "Showcase @outfitter/cli rendering capabilities",
1639
- options: [
1640
- {
1641
- flags: "-l, --list",
1642
- description: "List available demo sections",
1643
- defaultValue: false
1644
- },
1645
- {
1646
- flags: "-a, --animate",
1647
- description: "Run animated demo (spinners only)",
1648
- defaultValue: false
1649
- }
1650
- ],
1651
- mapInput: (context) => {
1652
- resolveOutputMode(context.flags);
1653
- return {
1654
- section: context.args[0],
1655
- list: Boolean(context.flags["list"]),
1656
- animate: Boolean(context.flags["animate"])
1657
- };
1658
- }
1659
- },
1660
- handler: async (input) => {
1661
- const result = await runDemo(input);
1662
- await printDemoResults(result);
1663
- if (result.exitCode !== 0) {
1664
- process.exit(result.exitCode);
1665
- }
1666
- return Result4.ok(result);
1667
- }
1668
- });
1669
- var doctorAction = defineAction({
1670
- id: "doctor",
1671
- description: "Validate environment and dependencies",
1672
- surfaces: ["cli"],
1673
- input: doctorInputSchema,
1674
- cli: {
1675
- command: "doctor",
1676
- description: "Validate environment and dependencies",
1677
- mapInput: (context) => {
1678
- const outputMode = resolveOutputMode(context.flags);
1679
- return {
1680
- cwd: process.cwd(),
1681
- ...outputMode ? { outputMode } : {}
1682
- };
1683
- }
1684
- },
1685
- handler: async (input) => {
1686
- const { outputMode, ...doctorInput } = input;
1687
- const outputOptions = outputMode ? { mode: outputMode } : undefined;
1688
- const result = await runDoctor(doctorInput);
1689
- await printDoctorResults(result, outputOptions);
1690
- if (result.exitCode !== 0) {
1691
- process.exit(result.exitCode);
1692
- }
1693
- return Result4.ok(result);
1694
- }
1695
- });
1696
- var addInputSchema = z.object({
1697
- block: z.string(),
1698
- force: z.boolean(),
1699
- dryRun: z.boolean(),
1700
- cwd: z.string().optional(),
1701
- outputMode: outputModeSchema
1702
- });
1703
- var addAction = defineAction({
1704
- id: "add",
1705
- description: "Add a block from the registry to your project",
1706
- surfaces: ["cli"],
1707
- input: addInputSchema,
1708
- cli: {
1709
- group: "add",
1710
- command: "<block>",
1711
- description: "Add a block from the registry (claude, biome, lefthook, bootstrap, scaffolding)",
1712
- options: [
1713
- {
1714
- flags: "-f, --force",
1715
- description: "Overwrite existing files",
1716
- defaultValue: false
1717
- },
1718
- {
1719
- flags: "--dry-run",
1720
- description: "Show what would be added without making changes",
1721
- defaultValue: false
1722
- }
1723
- ],
1724
- mapInput: (context) => {
1725
- const outputMode = resolveOutputMode(context.flags);
1726
- return {
1727
- block: context.args[0],
1728
- force: Boolean(context.flags["force"]),
1729
- dryRun: Boolean(context.flags["dry-run"] ?? context.flags["dryRun"]),
1730
- cwd: process.cwd(),
1731
- ...outputMode ? { outputMode } : {}
1732
- };
1733
- }
1734
- },
1735
- handler: async (input) => {
1736
- const { outputMode, ...addInput } = input;
1737
- const outputOptions = outputMode ? { mode: outputMode } : undefined;
1738
- const result = await runAdd(addInput);
1739
- if (result.isErr()) {
1740
- return Result4.err(new InternalError2({
1741
- message: result.error.message,
1742
- context: { action: "add" }
1743
- }));
1744
- }
1745
- await printAddResults(result.value, addInput.dryRun, outputOptions);
1746
- return Result4.ok(result.value);
1747
- }
1748
- });
1749
- var listBlocksAction = defineAction({
1750
- id: "add.list",
1751
- description: "List available blocks",
1752
- surfaces: ["cli"],
1753
- input: z.object({ outputMode: outputModeSchema }),
1754
- cli: {
1755
- group: "add",
1756
- command: "list",
1757
- description: "List available blocks",
1758
- mapInput: (context) => {
1759
- const outputMode = resolveOutputMode(context.flags);
1760
- return {
1761
- ...outputMode ? { outputMode } : {}
1762
- };
1763
- }
1764
- },
1765
- handler: async (input) => {
1766
- const result = listBlocks();
1767
- if (result.isErr()) {
1768
- return Result4.err(new InternalError2({
1769
- message: result.error.message,
1770
- context: { action: "add.list" }
1771
- }));
1772
- }
1773
- if (input.outputMode === "json" || input.outputMode === "jsonl") {
1774
- await output6({ blocks: result.value }, { mode: input.outputMode });
1775
- } else {
1776
- const lines = [
1777
- "Available blocks:",
1778
- ...result.value.map((block) => ` - ${block}`)
1779
- ];
1780
- await output6(lines);
1781
- }
1782
- return Result4.ok({ blocks: result.value });
1783
- }
1784
- });
1785
- var updateInputSchema = z.object({
1786
- cwd: z.string(),
1787
- guide: z.boolean(),
1788
- outputMode: outputModeSchema
1789
- });
1790
- var updateAction = defineAction({
1791
- id: "update",
1792
- description: "Check for @outfitter/* package updates and migration guidance",
1793
- surfaces: ["cli"],
1794
- input: updateInputSchema,
1795
- cli: {
1796
- command: "update",
1797
- description: "Check for @outfitter/* package updates and migration guidance",
1798
- options: [
1799
- {
1800
- flags: "--guide",
1801
- description: "Show migration instructions for available updates",
1802
- defaultValue: false
1803
- },
1804
- {
1805
- flags: "--cwd <path>",
1806
- description: "Working directory (defaults to current directory)"
1807
- }
1808
- ],
1809
- mapInput: (context) => {
1810
- const outputMode = resolveOutputMode(context.flags);
1811
- const cwd = typeof context.flags["cwd"] === "string" ? resolve5(process.cwd(), context.flags["cwd"]) : process.cwd();
1812
- return {
1813
- cwd,
1814
- guide: Boolean(context.flags["guide"]),
1815
- ...outputMode ? { outputMode } : {}
1816
- };
1817
- }
1818
- },
1819
- handler: async (input) => {
1820
- const { outputMode, ...updateInput } = input;
1821
- const result = await runUpdate(updateInput);
1822
- if (result.isErr()) {
1823
- return Result4.err(new InternalError2({
1824
- message: result.error.message,
1825
- context: { action: "update" }
1826
- }));
1827
- }
1828
- await printUpdateResults(result.value, {
1829
- ...outputMode ? { mode: outputMode } : {},
1830
- guide: updateInput.guide,
1831
- cwd: updateInput.cwd
1832
- });
1833
- return Result4.ok(result.value);
1834
- }
1835
- });
1836
- var outfitterActions = createActionRegistry().add(createInitAction({
1837
- id: "init",
1838
- description: "Scaffold a new Outfitter project",
1839
- command: "[directory]",
1840
- includeTemplateOption: true
1841
- })).add(createInitAction({
1842
- id: "init.cli",
1843
- description: "Scaffold a new CLI project",
1844
- command: "cli [directory]",
1845
- templateOverride: "cli"
1846
- })).add(createInitAction({
1847
- id: "init.mcp",
1848
- description: "Scaffold a new MCP server",
1849
- command: "mcp [directory]",
1850
- templateOverride: "mcp"
1851
- })).add(createInitAction({
1852
- id: "init.daemon",
1853
- description: "Scaffold a new daemon project",
1854
- command: "daemon [directory]",
1855
- templateOverride: "daemon"
1856
- })).add(demoAction).add(doctorAction).add(addAction).add(listBlocksAction).add(updateAction);
1857
-
1858
- export { runDoctor, printDoctorResults, doctorCommand, InitError, runInit, initCommand, outfitterActions };