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
@@ -0,0 +1,404 @@
1
+ // @bun
2
+ import {
3
+ analyzeUpdates
4
+ } from "./outfitter-qfgj5xpq.js";
5
+ import {
6
+ applyUpdatesToWorkspace,
7
+ getInstalledPackagesFromWorkspace,
8
+ runInstall
9
+ } from "./outfitter-6a4bq054.js";
10
+ import {
11
+ resolveStructuredOutputMode
12
+ } from "./outfitter-7r12fj7f.js";
13
+
14
+ // apps/outfitter/src/commands/update.ts
15
+ import { existsSync, readFileSync } from "fs";
16
+ import { join, resolve } from "path";
17
+ import { output } from "@outfitter/cli/output";
18
+ import { InternalError, Result } from "@outfitter/contracts";
19
+ import { createTheme } from "@outfitter/tui/render";
20
+ async function getLatestVersion(name) {
21
+ try {
22
+ const proc = Bun.spawn(["npm", "view", name, "version"], {
23
+ stdout: "pipe",
24
+ stderr: "pipe"
25
+ });
26
+ const stdout = await new Response(proc.stdout).text();
27
+ const exitCode = await proc.exited;
28
+ if (exitCode !== 0)
29
+ return null;
30
+ return stdout.trim() || null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ var MIGRATION_DOC_PATHS = [
36
+ "plugins/outfitter/shared/migrations",
37
+ "node_modules/@outfitter/kit/shared/migrations"
38
+ ];
39
+ function findMigrationDocsDir(cwd, binaryDir) {
40
+ for (const relative of MIGRATION_DOC_PATHS) {
41
+ const dir = join(cwd, relative);
42
+ if (existsSync(dir))
43
+ return dir;
44
+ }
45
+ let current = resolve(cwd);
46
+ const root = resolve("/");
47
+ while (current !== root) {
48
+ const parent = resolve(current, "..");
49
+ if (parent === current)
50
+ break;
51
+ current = parent;
52
+ for (const relative of MIGRATION_DOC_PATHS) {
53
+ const dir = join(current, relative);
54
+ if (existsSync(dir))
55
+ return dir;
56
+ }
57
+ }
58
+ const resolvedBinaryDir = binaryDir ?? resolve(import.meta.dir, "../../../..");
59
+ for (const relative of MIGRATION_DOC_PATHS) {
60
+ const dir = join(resolvedBinaryDir, relative);
61
+ if (existsSync(dir))
62
+ return dir;
63
+ }
64
+ return null;
65
+ }
66
+ function readMigrationDocs(migrationsDir, shortName, fromVersion, toVersion) {
67
+ const glob = new Bun.Glob(`outfitter-${shortName}-*.md`);
68
+ const versionPattern = new RegExp(`^outfitter-${shortName}-(\\d+\\.\\d+\\.\\d+)\\.md$`);
69
+ const docs = [];
70
+ for (const entry of glob.scanSync({ cwd: migrationsDir })) {
71
+ const match = entry.match(versionPattern);
72
+ if (!match?.[1])
73
+ continue;
74
+ const docVersion = match[1];
75
+ if (Bun.semver.order(docVersion, fromVersion) <= 0)
76
+ continue;
77
+ if (Bun.semver.order(docVersion, toVersion) > 0)
78
+ continue;
79
+ const filePath = join(migrationsDir, entry);
80
+ let content;
81
+ try {
82
+ content = readFileSync(filePath, "utf-8");
83
+ } catch {
84
+ continue;
85
+ }
86
+ const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
87
+ if (body) {
88
+ docs.push({ version: docVersion, content: body });
89
+ }
90
+ }
91
+ docs.sort((a, b) => Bun.semver.order(a.version, b.version));
92
+ return docs.map((d) => d.content);
93
+ }
94
+ function readMigrationBreakingFlag(migrationsDir, shortName, version) {
95
+ const filePath = join(migrationsDir, `outfitter-${shortName}-${version}.md`);
96
+ if (!existsSync(filePath)) {
97
+ return;
98
+ }
99
+ let content;
100
+ try {
101
+ content = readFileSync(filePath, "utf-8");
102
+ } catch {
103
+ return;
104
+ }
105
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
106
+ if (!frontmatter?.[1]) {
107
+ return;
108
+ }
109
+ const breakingLine = frontmatter[1].split(/\r?\n/).find((line) => line.trimStart().startsWith("breaking:"));
110
+ if (breakingLine === undefined) {
111
+ return;
112
+ }
113
+ const rawValue = breakingLine.split(":").slice(1).join(":").trim();
114
+ if (rawValue === "true")
115
+ return true;
116
+ if (rawValue === "false")
117
+ return false;
118
+ return;
119
+ }
120
+ function buildMigrationGuides(packages, migrationsDir) {
121
+ const guides = [];
122
+ for (const pkg of packages) {
123
+ if (!pkg.updateAvailable || pkg.latest === null)
124
+ continue;
125
+ let steps = [];
126
+ if (migrationsDir !== null) {
127
+ const shortName = pkg.name.replace("@outfitter/", "");
128
+ const docs = readMigrationDocs(migrationsDir, shortName, pkg.current, pkg.latest);
129
+ steps = docs;
130
+ }
131
+ guides.push({
132
+ packageName: pkg.name,
133
+ fromVersion: pkg.current,
134
+ toVersion: pkg.latest,
135
+ breaking: pkg.breaking,
136
+ steps
137
+ });
138
+ }
139
+ return guides;
140
+ }
141
+ function getVersionPrefix(specifier) {
142
+ if (specifier.startsWith("workspace:")) {
143
+ const inner = specifier.slice("workspace:".length);
144
+ return `workspace:${getVersionPrefix(inner)}`;
145
+ }
146
+ const match = specifier.match(/^([\^~>=<]+)/);
147
+ return match?.[1] ?? "";
148
+ }
149
+ async function applyUpdates(cwd, updates) {
150
+ const pkgPath = join(cwd, "package.json");
151
+ let raw;
152
+ try {
153
+ raw = readFileSync(pkgPath, "utf-8");
154
+ } catch {
155
+ return Result.err(InternalError.create("Failed to read package.json for apply", { cwd }));
156
+ }
157
+ let pkg;
158
+ try {
159
+ pkg = JSON.parse(raw);
160
+ } catch {
161
+ return Result.err(InternalError.create("Invalid JSON in package.json", { cwd }));
162
+ }
163
+ const updateMap = new Map;
164
+ for (const u of updates) {
165
+ updateMap.set(u.name, u.latestVersion);
166
+ }
167
+ for (const section of ["dependencies", "devDependencies"]) {
168
+ const deps = pkg[section];
169
+ if (!deps)
170
+ continue;
171
+ for (const name of Object.keys(deps)) {
172
+ const newVersion = updateMap.get(name);
173
+ if (newVersion === undefined)
174
+ continue;
175
+ const currentSpecifier = deps[name];
176
+ if (currentSpecifier === undefined)
177
+ continue;
178
+ const prefix = getVersionPrefix(currentSpecifier);
179
+ deps[name] = `${prefix}${newVersion}`;
180
+ }
181
+ }
182
+ try {
183
+ const updated = `${JSON.stringify(pkg, null, 2)}
184
+ `;
185
+ await Bun.write(pkgPath, updated);
186
+ } catch {
187
+ return Result.err(InternalError.create("Failed to write updated package.json", { cwd }));
188
+ }
189
+ try {
190
+ const proc = Bun.spawn(["bun", "install"], {
191
+ cwd,
192
+ stdout: "pipe",
193
+ stderr: "pipe"
194
+ });
195
+ const exitCode = await proc.exited;
196
+ if (exitCode !== 0) {
197
+ const stderr = await new Response(proc.stderr).text();
198
+ return Result.err(InternalError.create("bun install failed", {
199
+ cwd,
200
+ exitCode,
201
+ stderr: stderr.trim()
202
+ }));
203
+ }
204
+ } catch {
205
+ return Result.err(InternalError.create("Failed to run bun install", { cwd }));
206
+ }
207
+ return Result.ok(undefined);
208
+ }
209
+ async function runUpdate(options) {
210
+ const cwd = resolve(options.cwd);
211
+ const migrationsDir = findMigrationDocsDir(cwd);
212
+ const migrationFlagsDir = findMigrationDocsDir(cwd, cwd);
213
+ const scanResult = getInstalledPackagesFromWorkspace(cwd);
214
+ if (scanResult.isErr())
215
+ return scanResult;
216
+ const scan = scanResult.value;
217
+ const installed = scan.packages;
218
+ const installRoot = scan.workspaceRoot ?? cwd;
219
+ if (installed.length === 0) {
220
+ return Result.ok({
221
+ packages: [],
222
+ total: 0,
223
+ updatesAvailable: 0,
224
+ hasBreaking: false,
225
+ applied: false,
226
+ appliedPackages: [],
227
+ skippedBreaking: []
228
+ });
229
+ }
230
+ const latestVersions = new Map;
231
+ const installedMap = new Map;
232
+ const npmFailures = new Set;
233
+ await Promise.all(installed.map(async (pkg) => {
234
+ installedMap.set(pkg.name, pkg.version);
235
+ const latest = await getLatestVersion(pkg.name);
236
+ if (latest !== null) {
237
+ const shortName = pkg.name.replace("@outfitter/", "");
238
+ const docBreaking = migrationFlagsDir !== null ? readMigrationBreakingFlag(migrationFlagsDir, shortName, latest) : undefined;
239
+ latestVersions.set(pkg.name, {
240
+ version: latest,
241
+ ...docBreaking !== undefined ? { breaking: docBreaking } : {}
242
+ });
243
+ } else {
244
+ npmFailures.add(pkg.name);
245
+ }
246
+ }));
247
+ const plan = analyzeUpdates(installedMap, latestVersions);
248
+ const packages = plan.packages.map((action) => ({
249
+ name: action.name,
250
+ current: action.currentVersion,
251
+ latest: npmFailures.has(action.name) ? null : action.latestVersion,
252
+ updateAvailable: action.classification === "upgradableNonBreaking" || action.classification === "upgradableBreaking",
253
+ breaking: action.breaking
254
+ }));
255
+ const updatesAvailable = packages.filter((p) => p.updateAvailable).length;
256
+ const hasBreaking = packages.some((p) => p.breaking);
257
+ const nonBreakingUpgradable = plan.packages.filter((a) => a.classification === "upgradableNonBreaking");
258
+ const breakingUpgradable = plan.packages.filter((a) => a.classification === "upgradableBreaking");
259
+ const includeBreaking = options.apply === true && options.breaking === true;
260
+ const packagesToApply = includeBreaking ? [...nonBreakingUpgradable, ...breakingUpgradable] : nonBreakingUpgradable;
261
+ const skippedBreaking = includeBreaking ? [] : breakingUpgradable.map((a) => a.name);
262
+ let applied = false;
263
+ const appliedPackages = [];
264
+ if (options.apply && packagesToApply.length > 0) {
265
+ if (scan.workspaceRoot !== null) {
266
+ const applyResult = await applyUpdatesToWorkspace(scan.manifestPaths, scan.manifestsByPackage, packagesToApply);
267
+ if (applyResult.isErr())
268
+ return applyResult;
269
+ const installResult = await runInstall(installRoot);
270
+ if (installResult.isErr())
271
+ return installResult;
272
+ } else {
273
+ const applyResult = await applyUpdates(cwd, packagesToApply);
274
+ if (applyResult.isErr())
275
+ return applyResult;
276
+ }
277
+ applied = true;
278
+ appliedPackages.push(...packagesToApply.map((a) => a.name));
279
+ }
280
+ let guidesData = options.guide === true ? buildMigrationGuides(packages, migrationsDir) : undefined;
281
+ if (guidesData !== undefined && options.guidePackages !== undefined && options.guidePackages.length > 0) {
282
+ const filterSet = new Set(options.guidePackages);
283
+ guidesData = guidesData.filter((g) => filterSet.has(g.packageName));
284
+ }
285
+ return Result.ok({
286
+ packages,
287
+ total: packages.length,
288
+ updatesAvailable,
289
+ hasBreaking,
290
+ applied,
291
+ appliedPackages,
292
+ skippedBreaking,
293
+ ...guidesData !== undefined ? { guides: guidesData } : {}
294
+ });
295
+ }
296
+ async function printUpdateResults(result, options) {
297
+ const structuredMode = resolveStructuredOutputMode(options?.mode);
298
+ if (structuredMode) {
299
+ await output(result, { mode: structuredMode });
300
+ return;
301
+ }
302
+ const theme = createTheme();
303
+ const lines = ["", "Outfitter Update", "", "=".repeat(60)];
304
+ if (result.packages.length === 0) {
305
+ lines.push("No @outfitter/* packages found in package.json.");
306
+ await output(lines, { mode: "human" });
307
+ return;
308
+ }
309
+ lines.push(` ${"Package".padEnd(28)} ${"Current".padEnd(10)} ${"Available".padEnd(10)} Migration`);
310
+ lines.push(` ${"\u2500".repeat(28)} ${"\u2500".repeat(10)} ${"\u2500".repeat(10)} ${"\u2500".repeat(20)}`);
311
+ for (const pkg of result.packages) {
312
+ const name = pkg.name.padEnd(28);
313
+ const current = pkg.current.padEnd(10);
314
+ const available = (pkg.latest ?? "unknown").padEnd(10);
315
+ let migration;
316
+ if (pkg.latest === null) {
317
+ migration = theme.muted("lookup failed");
318
+ } else if (!pkg.updateAvailable) {
319
+ migration = theme.muted("up to date");
320
+ } else if (pkg.breaking) {
321
+ migration = theme.error("breaking");
322
+ } else {
323
+ migration = theme.success("non-breaking");
324
+ }
325
+ lines.push(` ${name} ${current} ${available} ${migration}`);
326
+ }
327
+ lines.push("");
328
+ if (result.applied && result.appliedPackages.length > 0) {
329
+ const breakingApplied = result.appliedPackages.filter((name) => result.packages.some((p) => p.name === name && p.breaking));
330
+ const nonBreakingApplied = result.appliedPackages.filter((name) => !result.packages.some((p) => p.name === name && p.breaking));
331
+ if (nonBreakingApplied.length > 0) {
332
+ lines.push(theme.success(`Applied ${nonBreakingApplied.length} non-breaking update(s):`));
333
+ for (const name of nonBreakingApplied) {
334
+ lines.push(` - ${name}`);
335
+ }
336
+ lines.push("");
337
+ }
338
+ if (breakingApplied.length > 0) {
339
+ lines.push(theme.error(`Applied ${breakingApplied.length} breaking update(s):`));
340
+ for (const name of breakingApplied) {
341
+ const pkg = result.packages.find((p) => p.name === name);
342
+ lines.push(` - ${name} (${pkg?.current} -> ${pkg?.latest})`);
343
+ }
344
+ lines.push("", theme.muted("Review migration guides: 'outfitter update --guide'"));
345
+ lines.push("");
346
+ }
347
+ } else if (options?.applied !== undefined && options.applied === false) {
348
+ if (result.updatesAvailable === 0) {
349
+ lines.push(theme.success("All packages are up to date. Nothing to apply."));
350
+ } else if (result.appliedPackages.length === 0 && result.skippedBreaking.length > 0) {
351
+ lines.push(theme.muted("No non-breaking updates to apply. All available updates contain breaking changes."));
352
+ }
353
+ }
354
+ if (result.skippedBreaking.length > 0 && result.applied) {
355
+ lines.push(theme.error(`Skipped ${result.skippedBreaking.length} breaking update(s):`));
356
+ for (const name of result.skippedBreaking) {
357
+ lines.push(` - ${name}`);
358
+ }
359
+ lines.push("", theme.muted("Use 'outfitter update --apply --breaking' to include breaking updates."));
360
+ lines.push("");
361
+ }
362
+ if (!result.applied) {
363
+ if (result.updatesAvailable > 0) {
364
+ lines.push(theme.muted("Run 'outfitter update --guide' for migration instructions."));
365
+ } else {
366
+ lines.push(theme.success("All packages are up to date."));
367
+ }
368
+ }
369
+ if (options?.guide && result.guides && result.guides.length > 0) {
370
+ lines.push("", "=".repeat(60), "", "Migration Guide", "");
371
+ for (const guide of result.guides) {
372
+ const label = guide.breaking ? theme.error("BREAKING") : theme.success("non-breaking");
373
+ lines.push(`${theme.info(guide.packageName)} ${guide.fromVersion} -> ${guide.toVersion} [${label}]`);
374
+ if (guide.steps.length > 0) {
375
+ for (const step of guide.steps) {
376
+ lines.push(` ${step}`);
377
+ }
378
+ } else {
379
+ lines.push(` ${theme.muted("No migration steps available. Check release notes.")}`);
380
+ }
381
+ lines.push("");
382
+ }
383
+ } else if (options?.guide && result.updatesAvailable > 0 && !result.guides) {
384
+ const cwd = options.cwd ?? process.cwd();
385
+ const migrationsDir = findMigrationDocsDir(cwd);
386
+ if (migrationsDir) {
387
+ lines.push("", "=".repeat(60), "", "Migration Guide", "");
388
+ for (const pkg of result.packages) {
389
+ if (!(pkg.updateAvailable && pkg.latest))
390
+ continue;
391
+ const shortName = pkg.name.replace("@outfitter/", "");
392
+ const docs = readMigrationDocs(migrationsDir, shortName, pkg.current, pkg.latest);
393
+ for (const doc of docs) {
394
+ lines.push(doc, "", "---", "");
395
+ }
396
+ }
397
+ } else {
398
+ lines.push("", theme.muted("Migration docs not found locally. See https://github.com/outfitter-dev/outfitter for migration guides."));
399
+ }
400
+ }
401
+ await output(lines, { mode: "human" });
402
+ }
403
+
404
+ export { findMigrationDocsDir, readMigrationDocs, readMigrationBreakingFlag, buildMigrationGuides, runUpdate, printUpdateResults };
@@ -0,0 +1,21 @@
1
+ // @bun
2
+ // apps/outfitter/src/commands/shared-deps.ts
3
+ var SHARED_DEV_DEPS = {
4
+ "@biomejs/biome": "^2.3.12",
5
+ "@outfitter/tooling": "^0.2.1",
6
+ "@types/bun": "^1.3.7",
7
+ lefthook: "^2.0.16",
8
+ typescript: "^5.9.3",
9
+ ultracite: "^7.1.1"
10
+ };
11
+ var SHARED_SCRIPTS = {
12
+ check: "ultracite check",
13
+ "clean:artifacts": "rm -rf dist .turbo",
14
+ "verify:ci": "bun run typecheck && bun run check && bun run build && bun run test",
15
+ lint: "biome check .",
16
+ "lint:fix": "biome check . --write",
17
+ format: "biome format --write .",
18
+ typecheck: "tsc --noEmit"
19
+ };
20
+
21
+ export { SHARED_DEV_DEPS, SHARED_SCRIPTS };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Unique identifier for a scaffold target.
3
+ */
4
+ type TargetId = "minimal" | "cli" | "mcp" | "daemon" | "api" | "worker" | "web" | "lib";
5
+ /**
6
+ * Whether the target produces a runnable application or a library package.
7
+ */
8
+ type TargetCategory = "runnable" | "library";
9
+ /**
10
+ * Whether the target has a working template or is a planned placeholder.
11
+ */
12
+ type TargetStatus = "ready" | "stub";
13
+ /**
14
+ * Whether a target can be used with init, scaffold, or both.
15
+ */
16
+ type TargetScope = "init-only" | "scaffold-only" | "both";
17
+ /**
18
+ * Complete definition for a scaffold target.
19
+ */
20
+ interface TargetDefinition {
21
+ readonly id: TargetId;
22
+ readonly description: string;
23
+ readonly category: TargetCategory;
24
+ readonly placement: "apps" | "packages";
25
+ readonly templateDir: string;
26
+ readonly defaultBlocks: readonly string[];
27
+ readonly status: TargetStatus;
28
+ readonly scope: TargetScope;
29
+ }
30
+ export { TargetId, TargetCategory, TargetStatus, TargetScope, TargetDefinition };
@@ -0,0 +1,4 @@
1
+ import { CreateProjectInput, CreateProjectPlan } from "./outfitter-e2zz5wv7";
2
+ import { Result, ValidationError } from "@outfitter/contracts";
3
+ declare function planCreateProject(input: CreateProjectInput): Result<CreateProjectPlan, ValidationError>;
4
+ export { planCreateProject };
@@ -0,0 +1,4 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ export { __require };
@@ -0,0 +1,91 @@
1
+ // @bun
2
+ import {
3
+ __require
4
+ } from "./outfitter-mdt37hqm.js";
5
+
6
+ // apps/outfitter/src/manifest.ts
7
+ import { join } from "path";
8
+ import {
9
+ InternalError,
10
+ Result,
11
+ ValidationError
12
+ } from "@outfitter/contracts";
13
+ import { z } from "zod";
14
+ var OUTFITTER_DIR = ".outfitter";
15
+ var MANIFEST_FILE = "manifest.json";
16
+ var BlockEntrySchema = z.object({
17
+ installedFrom: z.string(),
18
+ installedAt: z.string().datetime()
19
+ });
20
+ var ManifestSchema = z.object({
21
+ version: z.literal(1),
22
+ blocks: z.record(z.string(), BlockEntrySchema)
23
+ });
24
+ function manifestPath(cwd) {
25
+ return join(cwd, OUTFITTER_DIR, MANIFEST_FILE);
26
+ }
27
+ async function readManifest(cwd) {
28
+ const path = manifestPath(cwd);
29
+ const file = Bun.file(path);
30
+ if (!await file.exists()) {
31
+ return Result.ok(null);
32
+ }
33
+ let raw;
34
+ try {
35
+ raw = await file.text();
36
+ } catch {
37
+ return Result.err(InternalError.create("Failed to read manifest file", { path }));
38
+ }
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(raw);
42
+ } catch {
43
+ return Result.err(ValidationError.create("manifest", "Invalid JSON in manifest file"));
44
+ }
45
+ const validated = ManifestSchema.safeParse(parsed);
46
+ if (!validated.success) {
47
+ return Result.err(ValidationError.create("manifest", "Manifest file does not match expected schema"));
48
+ }
49
+ return Result.ok(validated.data);
50
+ }
51
+ async function writeManifest(cwd, manifest) {
52
+ const dirPath = join(cwd, OUTFITTER_DIR);
53
+ const path = join(dirPath, MANIFEST_FILE);
54
+ try {
55
+ const { mkdirSync, existsSync, statSync } = await import("fs");
56
+ if (existsSync(dirPath)) {
57
+ if (!statSync(dirPath).isDirectory()) {
58
+ return Result.err(ValidationError.create("manifest", ".outfitter exists but is not a directory"));
59
+ }
60
+ } else {
61
+ mkdirSync(dirPath, { recursive: true });
62
+ }
63
+ const content = JSON.stringify(manifest, null, "\t");
64
+ await Bun.write(path, `${content}
65
+ `);
66
+ return Result.ok(undefined);
67
+ } catch {
68
+ return Result.err(InternalError.create("Failed to write manifest file", { path }));
69
+ }
70
+ }
71
+ async function stampBlock(cwd, blockName, toolingVersion) {
72
+ const readResult = await readManifest(cwd);
73
+ if (readResult.isErr()) {
74
+ return readResult;
75
+ }
76
+ const existing = readResult.value;
77
+ const manifest = existing ?? { version: 1, blocks: {} };
78
+ const updated = {
79
+ ...manifest,
80
+ blocks: {
81
+ ...manifest.blocks,
82
+ [blockName]: {
83
+ installedFrom: toolingVersion,
84
+ installedAt: new Date().toISOString()
85
+ }
86
+ }
87
+ };
88
+ return writeManifest(cwd, updated);
89
+ }
90
+
91
+ export { BlockEntrySchema, ManifestSchema, readManifest, writeManifest, stampBlock };
@@ -0,0 +1,131 @@
1
+ import { OutputMode } from "@outfitter/cli/types";
2
+ import { Command } from "commander";
3
+ /**
4
+ * Options for the doctor command.
5
+ */
6
+ interface DoctorOptions {
7
+ /** Working directory to check (defaults to cwd) */
8
+ readonly cwd: string;
9
+ }
10
+ /**
11
+ * Result of a single health check.
12
+ */
13
+ interface CheckResult {
14
+ /** Whether the check passed */
15
+ readonly passed: boolean;
16
+ /** Error message if check failed */
17
+ readonly error?: string;
18
+ }
19
+ /**
20
+ * Bun version check result.
21
+ */
22
+ interface BunVersionCheck extends CheckResult {
23
+ /** Current Bun version */
24
+ readonly version: string;
25
+ /** Required minimum version */
26
+ readonly required: string;
27
+ }
28
+ /**
29
+ * Package.json validation result.
30
+ */
31
+ interface PackageJsonCheck extends CheckResult {
32
+ /** Package name if found */
33
+ readonly name?: string;
34
+ /** Package version if found */
35
+ readonly version?: string;
36
+ }
37
+ /**
38
+ * Dependencies check result.
39
+ */
40
+ interface DependenciesCheck extends CheckResult {
41
+ /** Number of dependencies found */
42
+ readonly count?: number;
43
+ /** Missing dependencies if any */
44
+ readonly missing?: readonly string[];
45
+ }
46
+ /**
47
+ * Config files check result.
48
+ */
49
+ interface ConfigFilesCheck {
50
+ /** Whether tsconfig.json exists */
51
+ readonly tsconfig: boolean;
52
+ /** Whether biome.json exists */
53
+ readonly biome?: boolean;
54
+ }
55
+ /**
56
+ * Directories check result.
57
+ */
58
+ interface DirectoriesCheck {
59
+ /** Whether src directory exists */
60
+ readonly src: boolean;
61
+ /** Whether tests directory exists */
62
+ readonly tests?: boolean;
63
+ }
64
+ /**
65
+ * Summary of all checks.
66
+ */
67
+ interface DoctorSummary {
68
+ /** Number of passed checks */
69
+ readonly passed: number;
70
+ /** Number of failed checks */
71
+ readonly failed: number;
72
+ /** Total number of checks */
73
+ readonly total: number;
74
+ }
75
+ /**
76
+ * Complete doctor result.
77
+ */
78
+ interface DoctorResult {
79
+ /** Individual check results */
80
+ readonly checks: {
81
+ readonly bunVersion: BunVersionCheck;
82
+ readonly packageJson: PackageJsonCheck;
83
+ readonly dependencies: DependenciesCheck;
84
+ readonly configFiles: ConfigFilesCheck;
85
+ readonly directories: DirectoriesCheck;
86
+ };
87
+ /** Summary of all checks */
88
+ readonly summary: DoctorSummary;
89
+ /** Exit code (0 = all passed, 1 = some failed) */
90
+ readonly exitCode: number;
91
+ }
92
+ /**
93
+ * Runs the doctor command programmatically.
94
+ *
95
+ * @param options - Doctor options
96
+ * @returns Doctor result with all check results
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const result = await runDoctor({ cwd: process.cwd() });
101
+ *
102
+ * if (result.exitCode === 0) {
103
+ * console.log("All checks passed!");
104
+ * } else {
105
+ * console.log(`${result.summary.failed} checks failed`);
106
+ * }
107
+ * ```
108
+ */
109
+ declare function runDoctor(options: DoctorOptions): DoctorResult;
110
+ /**
111
+ * Formats and outputs doctor results.
112
+ */
113
+ declare function printDoctorResults(result: DoctorResult, options?: {
114
+ mode?: OutputMode;
115
+ }): Promise<void>;
116
+ /**
117
+ * Registers the doctor command with the CLI program.
118
+ *
119
+ * @param program - Commander program instance
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * import { Command } from "commander";
124
+ * import { doctorCommand } from "./commands/doctor.js";
125
+ *
126
+ * const program = new Command();
127
+ * doctorCommand(program);
128
+ * ```
129
+ */
130
+ declare function doctorCommand(program: Command): void;
131
+ export { DoctorOptions, CheckResult, BunVersionCheck, PackageJsonCheck, DependenciesCheck, ConfigFilesCheck, DirectoriesCheck, DoctorSummary, DoctorResult, runDoctor, printDoctorResults, doctorCommand };