outfitter 0.2.7 → 0.3.2

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 (203) hide show
  1. package/README.md +1 -2
  2. package/dist/cli.js +1 -1
  3. package/dist/index.d.ts +5 -6
  4. package/dist/index.js +8 -7
  5. package/dist/shared/{chunk-x6644tk8.js → chunk-3pwh8ys4.js} +124 -154
  6. package/package.json +15 -11
  7. package/dist/actions.d.ts +0 -2
  8. package/dist/actions.js +0 -35
  9. package/dist/commands/add.d.ts +0 -54
  10. package/dist/commands/add.js +0 -16
  11. package/dist/commands/check-tsdoc.d.ts +0 -22
  12. package/dist/commands/check-tsdoc.js +0 -8
  13. package/dist/commands/check.d.ts +0 -91
  14. package/dist/commands/check.js +0 -14
  15. package/dist/commands/demo.d.ts +0 -21
  16. package/dist/commands/demo.js +0 -8
  17. package/dist/commands/docs-module-loader.d.ts +0 -2
  18. package/dist/commands/docs-module-loader.js +0 -8
  19. package/dist/commands/doctor.d.ts +0 -2
  20. package/dist/commands/doctor.js +0 -25
  21. package/dist/commands/init.d.ts +0 -7
  22. package/dist/commands/init.js +0 -32
  23. package/dist/commands/repo.d.ts +0 -3
  24. package/dist/commands/repo.js +0 -9
  25. package/dist/commands/scaffold.d.ts +0 -4
  26. package/dist/commands/scaffold.js +0 -32
  27. package/dist/commands/shared-deps.d.ts +0 -21
  28. package/dist/commands/shared-deps.js +0 -11
  29. package/dist/commands/upgrade-codemods.d.ts +0 -42
  30. package/dist/commands/upgrade-codemods.js +0 -15
  31. package/dist/commands/upgrade-planner.d.ts +0 -58
  32. package/dist/commands/upgrade-planner.js +0 -8
  33. package/dist/commands/upgrade-workspace.d.ts +0 -2
  34. package/dist/commands/upgrade-workspace.js +0 -16
  35. package/dist/commands/upgrade.d.ts +0 -221
  36. package/dist/commands/upgrade.js +0 -25
  37. package/dist/create/index.d.ts +0 -5
  38. package/dist/create/index.js +0 -30
  39. package/dist/create/planner.d.ts +0 -3
  40. package/dist/create/planner.js +0 -22
  41. package/dist/create/presets.d.ts +0 -3
  42. package/dist/create/presets.js +0 -12
  43. package/dist/create/types.d.ts +0 -2
  44. package/dist/create/types.js +0 -1
  45. package/dist/engine/blocks.d.ts +0 -3
  46. package/dist/engine/blocks.js +0 -12
  47. package/dist/engine/collector.d.ts +0 -2
  48. package/dist/engine/collector.js +0 -8
  49. package/dist/engine/config.d.ts +0 -3
  50. package/dist/engine/config.js +0 -15
  51. package/dist/engine/dependency-versions.d.ts +0 -12
  52. package/dist/engine/dependency-versions.js +0 -12
  53. package/dist/engine/executor.d.ts +0 -3
  54. package/dist/engine/executor.js +0 -19
  55. package/dist/engine/index.d.ts +0 -8
  56. package/dist/engine/index.js +0 -68
  57. package/dist/engine/names.d.ts +0 -2
  58. package/dist/engine/names.js +0 -24
  59. package/dist/engine/post-scaffold.d.ts +0 -3
  60. package/dist/engine/post-scaffold.js +0 -8
  61. package/dist/engine/render-plan.d.ts +0 -7
  62. package/dist/engine/render-plan.js +0 -9
  63. package/dist/engine/template.d.ts +0 -3
  64. package/dist/engine/template.js +0 -17
  65. package/dist/engine/types.d.ts +0 -2
  66. package/dist/engine/types.js +0 -8
  67. package/dist/engine/workspace.d.ts +0 -3
  68. package/dist/engine/workspace.js +0 -20
  69. package/dist/manifest.d.ts +0 -71
  70. package/dist/manifest.js +0 -16
  71. package/dist/output-mode.d.ts +0 -2
  72. package/dist/output-mode.js +0 -10
  73. package/dist/shared/outfitter-109s75x0.d.ts +0 -76
  74. package/dist/shared/outfitter-1fy7byz5.js +0 -170
  75. package/dist/shared/outfitter-1h7k8xxt.js +0 -29
  76. package/dist/shared/outfitter-20f6a2n4.js +0 -35
  77. package/dist/shared/outfitter-344t1r38.js +0 -1
  78. package/dist/shared/outfitter-4q1zfmvc.js +0 -154
  79. package/dist/shared/outfitter-4s9meh3j.js +0 -221
  80. package/dist/shared/outfitter-5akzvppx.js +0 -125
  81. package/dist/shared/outfitter-5y646xzk.js +0 -301
  82. package/dist/shared/outfitter-5yjr404v.d.ts +0 -22
  83. package/dist/shared/outfitter-63gse8fv.js +0 -316
  84. package/dist/shared/outfitter-6bkqjk86.d.ts +0 -3
  85. package/dist/shared/outfitter-6fgk6adm.d.ts +0 -40
  86. package/dist/shared/outfitter-79vfxt6y.js +0 -269
  87. package/dist/shared/outfitter-7ch26yq8.js +0 -885
  88. package/dist/shared/outfitter-7r12fj7f.js +0 -30
  89. package/dist/shared/outfitter-8y2dfx6n.js +0 -11
  90. package/dist/shared/outfitter-9x1brcmq.js +0 -184
  91. package/dist/shared/outfitter-a79xrm12.d.ts +0 -17
  92. package/dist/shared/outfitter-amc4jbs1.d.ts +0 -50
  93. package/dist/shared/outfitter-bn9c8p2e.js +0 -204
  94. package/dist/shared/outfitter-bpr28y54.js +0 -70
  95. package/dist/shared/outfitter-dpj9erew.d.ts +0 -4
  96. package/dist/shared/outfitter-e9rrfekb.d.ts +0 -51
  97. package/dist/shared/outfitter-ehp18x1n.js +0 -1
  98. package/dist/shared/outfitter-f9znfhkn.d.ts +0 -5
  99. package/dist/shared/outfitter-fhnjpjwc.d.ts +0 -18
  100. package/dist/shared/outfitter-fn20r49x.d.ts +0 -5
  101. package/dist/shared/outfitter-h3q6ae6d.d.ts +0 -48
  102. package/dist/shared/outfitter-ha89qf8q.js +0 -132
  103. package/dist/shared/outfitter-hsp8vy5m.d.ts +0 -146
  104. package/dist/shared/outfitter-hvsaxgcp.js +0 -1
  105. package/dist/shared/outfitter-j833sxws.js +0 -61
  106. package/dist/shared/outfitter-ksyvwmb5.js +0 -191
  107. package/dist/shared/outfitter-m3ehh37q.d.ts +0 -22
  108. package/dist/shared/outfitter-m44n0qzw.js +0 -161
  109. package/dist/shared/outfitter-mdt37hqm.js +0 -4
  110. package/dist/shared/outfitter-mt7d1ek2.js +0 -698
  111. package/dist/shared/outfitter-mtbpabf3.js +0 -91
  112. package/dist/shared/outfitter-n9g1zk4x.d.ts +0 -66
  113. package/dist/shared/outfitter-p71qb0f0.js +0 -82
  114. package/dist/shared/outfitter-pcj9gg2g.js +0 -909
  115. package/dist/shared/outfitter-pj9vp00r.js +0 -601
  116. package/dist/shared/outfitter-qakwgrrh.d.ts +0 -4
  117. package/dist/shared/outfitter-r419zfgs.d.ts +0 -30
  118. package/dist/shared/outfitter-s7jetkge.d.ts +0 -18
  119. package/dist/shared/outfitter-ttjr95y9.js +0 -98
  120. package/dist/shared/outfitter-vh4xgb93.js +0 -35
  121. package/dist/shared/outfitter-w1j80j1r.js +0 -326
  122. package/dist/shared/outfitter-xe5mzgdc.js +0 -208
  123. package/dist/shared/outfitter-ybbazsxq.d.ts +0 -14
  124. package/dist/shared/outfitter-yraebrmw.d.ts +0 -5
  125. package/dist/shared/outfitter-yvksv5qb.js +0 -322
  126. package/dist/shared/outfitter-z0we32cp.d.ts +0 -63
  127. package/dist/shared/outfitter-z5sx06qe.d.ts +0 -25
  128. package/dist/shared/outfitter-zwyvewr1.js +0 -36
  129. package/dist/targets/index.d.ts +0 -4
  130. package/dist/targets/index.js +0 -29
  131. package/dist/targets/registry.d.ts +0 -3
  132. package/dist/targets/registry.js +0 -28
  133. package/dist/targets/types.d.ts +0 -2
  134. package/dist/targets/types.js +0 -1
  135. package/template-versions.json +0 -22
  136. package/templates/.gitkeep +0 -0
  137. package/templates/basic/.gitignore.template +0 -30
  138. package/templates/basic/.lefthook.yml.template +0 -26
  139. package/templates/basic/package.json.template +0 -46
  140. package/templates/basic/src/index.ts.template +0 -26
  141. package/templates/basic/tsconfig.json.template +0 -34
  142. package/templates/cli/.gitignore.template +0 -4
  143. package/templates/cli/.lefthook.yml.template +0 -26
  144. package/templates/cli/README.md.template +0 -35
  145. package/templates/cli/biome.json.template +0 -4
  146. package/templates/cli/package.json.template +0 -53
  147. package/templates/cli/src/cli.ts.template +0 -8
  148. package/templates/cli/src/index.ts.template +0 -7
  149. package/templates/cli/src/program.ts.template +0 -31
  150. package/templates/cli/tsconfig.json.template +0 -34
  151. package/templates/daemon/.gitignore.template +0 -4
  152. package/templates/daemon/.lefthook.yml.template +0 -26
  153. package/templates/daemon/README.md.template +0 -67
  154. package/templates/daemon/biome.json.template +0 -4
  155. package/templates/daemon/package.json.template +0 -56
  156. package/templates/daemon/src/cli.ts.template +0 -96
  157. package/templates/daemon/src/daemon-main.ts.template +0 -79
  158. package/templates/daemon/src/daemon.ts.template +0 -11
  159. package/templates/daemon/src/index.ts.template +0 -7
  160. package/templates/daemon/tsconfig.json.template +0 -23
  161. package/templates/full-stack/.gitignore.template +0 -30
  162. package/templates/full-stack/README.md.template +0 -30
  163. package/templates/full-stack/apps/cli/package.json.template +0 -39
  164. package/templates/full-stack/apps/cli/src/cli.ts.template +0 -24
  165. package/templates/full-stack/apps/cli/src/index.test.ts.template +0 -18
  166. package/templates/full-stack/apps/cli/src/index.ts.template +0 -5
  167. package/templates/full-stack/apps/cli/tsconfig.json.template +0 -37
  168. package/templates/full-stack/apps/mcp/package.json.template +0 -40
  169. package/templates/full-stack/apps/mcp/src/index.test.ts.template +0 -18
  170. package/templates/full-stack/apps/mcp/src/index.ts.template +0 -6
  171. package/templates/full-stack/apps/mcp/src/mcp.ts.template +0 -22
  172. package/templates/full-stack/apps/mcp/src/server.ts.template +0 -10
  173. package/templates/full-stack/apps/mcp/tsconfig.json.template +0 -37
  174. package/templates/full-stack/package.json.template +0 -16
  175. package/templates/full-stack/packages/core/package.json.template +0 -36
  176. package/templates/full-stack/packages/core/src/handlers.ts.template +0 -31
  177. package/templates/full-stack/packages/core/src/index.test.ts.template +0 -30
  178. package/templates/full-stack/packages/core/src/index.ts.template +0 -8
  179. package/templates/full-stack/packages/core/src/types.ts.template +0 -13
  180. package/templates/full-stack/packages/core/tsconfig.json.template +0 -34
  181. package/templates/library/.gitignore.template +0 -30
  182. package/templates/library/README.md.template +0 -29
  183. package/templates/library/bunup.config.ts.template +0 -20
  184. package/templates/library/package.json.template +0 -55
  185. package/templates/library/src/handlers.ts.template +0 -31
  186. package/templates/library/src/index.test.ts.template +0 -35
  187. package/templates/library/src/index.ts.template +0 -8
  188. package/templates/library/src/types.ts.template +0 -13
  189. package/templates/library/tsconfig.json.template +0 -34
  190. package/templates/mcp/.gitignore.template +0 -4
  191. package/templates/mcp/.lefthook.yml.template +0 -26
  192. package/templates/mcp/README.md.template +0 -54
  193. package/templates/mcp/biome.json.template +0 -4
  194. package/templates/mcp/package.json.template +0 -53
  195. package/templates/mcp/src/index.ts.template +0 -7
  196. package/templates/mcp/src/mcp.ts.template +0 -33
  197. package/templates/mcp/src/server.ts.template +0 -8
  198. package/templates/mcp/tsconfig.json.template +0 -23
  199. package/templates/minimal/.gitignore.template +0 -30
  200. package/templates/minimal/.lefthook.yml.template +0 -26
  201. package/templates/minimal/package.json.template +0 -53
  202. package/templates/minimal/src/index.ts.template +0 -26
  203. package/templates/minimal/tsconfig.json.template +0 -34
@@ -1,698 +0,0 @@
1
- // @bun
2
- import {
3
- runPostScaffold
4
- } from "./outfitter-4s9meh3j.js";
5
- import {
6
- INIT_TARGET_IDS,
7
- TARGET_REGISTRY,
8
- getInitTarget
9
- } from "./outfitter-xe5mzgdc.js";
10
- import {
11
- OperationCollector
12
- } from "./outfitter-1h7k8xxt.js";
13
- import {
14
- renderOperationPlan
15
- } from "./outfitter-ttjr95y9.js";
16
- import {
17
- executePlan
18
- } from "./outfitter-5akzvppx.js";
19
- import {
20
- scaffoldWorkspaceRoot
21
- } from "./outfitter-1fy7byz5.js";
22
- import {
23
- deriveBinName,
24
- deriveProjectName,
25
- isPathWithin,
26
- resolveAuthor,
27
- resolvePackageName,
28
- resolveYear,
29
- sanitizePackageName,
30
- validatePackageName,
31
- validateProjectDirectoryName
32
- } from "./outfitter-4q1zfmvc.js";
33
- import {
34
- resolveStructuredOutputMode
35
- } from "./outfitter-7r12fj7f.js";
36
-
37
- // apps/outfitter/src/commands/init.ts
38
- import { existsSync } from "fs";
39
- import { realpath } from "fs/promises";
40
- import { basename, join, resolve } from "path";
41
- import {
42
- cancel,
43
- confirm,
44
- intro,
45
- isCancel,
46
- outro,
47
- select,
48
- text
49
- } from "@clack/prompts";
50
- import { exitWithError, output } from "@outfitter/cli";
51
- import { Result } from "@outfitter/contracts";
52
- class InitError extends Error {
53
- _tag = "InitError";
54
- constructor(message) {
55
- super(message);
56
- this.name = "InitError";
57
- }
58
- }
59
- function parseBlocks(withFlag) {
60
- if (!withFlag) {
61
- return;
62
- }
63
- const blocks = withFlag.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
64
- return blocks.length > 0 ? blocks : undefined;
65
- }
66
- function isBinaryPreset(preset) {
67
- return preset === "cli" || preset === "daemon";
68
- }
69
- function isValidInitPreset(value) {
70
- return value === "minimal" || value === "cli" || value === "mcp" || value === "daemon" || value === "library" || value === "full-stack";
71
- }
72
- function resolvePresetFromFlags(options) {
73
- const presetFromFlag = options.preset;
74
- if (presetFromFlag) {
75
- if (!isValidInitPreset(presetFromFlag)) {
76
- return Result.err(new InitError(`Unknown preset '${presetFromFlag}'. Available presets: ${INIT_TARGET_IDS.join(", ")}`));
77
- }
78
- return Result.ok(presetFromFlag);
79
- }
80
- if (options.template) {
81
- const mapped = options.template === "basic" ? "minimal" : options.template;
82
- process.stderr.write(`Warning: --template is deprecated and will be removed in the next major version.
83
- ` + ` Use --preset instead: outfitter init --preset ${mapped}
84
- ` + (options.template === "basic" ? ` Note: "basic" has been renamed to "minimal".
85
- ` : ""));
86
- if (isValidInitPreset(mapped)) {
87
- return Result.ok(mapped);
88
- }
89
- return Result.err(new InitError(`Unknown template '${options.template}'. Available presets: ${INIT_TARGET_IDS.join(", ")}`));
90
- }
91
- return Result.ok(undefined);
92
- }
93
- async function resolveInitInput(options, presetOverride) {
94
- const rootDir = resolve(options.targetDir);
95
- const defaultName = basename(rootDir);
96
- const defaultPackageName = sanitizePackageName(defaultName) || defaultName;
97
- const presetFromFlagsResult = resolvePresetFromFlags(options);
98
- if (presetFromFlagsResult.isErr()) {
99
- return presetFromFlagsResult;
100
- }
101
- const presetFromFlags = presetFromFlagsResult.value;
102
- if (options.yes || !process.stdout.isTTY) {
103
- const packageNameRaw = resolvePackageName(rootDir, options.name).trim();
104
- const packageName2 = options.name === undefined ? sanitizePackageName(packageNameRaw) : packageNameRaw;
105
- if (packageName2.length === 0) {
106
- return Result.err(new InitError("Project name must not be empty"));
107
- }
108
- const invalidPackageName2 = validatePackageName(packageName2);
109
- if (invalidPackageName2) {
110
- const suggested = sanitizePackageName(packageNameRaw);
111
- const suggestion = suggested.length > 0 && suggested !== packageNameRaw ? ` Try '${suggested}'.` : "";
112
- return Result.err(new InitError(`Invalid package name '${packageNameRaw}': ${invalidPackageName2}.${suggestion}`));
113
- }
114
- const preset = presetOverride ?? presetFromFlags ?? "minimal";
115
- const structure = preset === "full-stack" ? "single" : options.structure ?? "single";
116
- const blocksOverride2 = parseBlocks(options.with);
117
- const workspaceName2 = structure === "workspace" ? (options.workspaceName ?? defaultPackageName).trim() || defaultPackageName : undefined;
118
- if (workspaceName2) {
119
- const invalidWorkspaceName = validatePackageName(workspaceName2);
120
- if (invalidWorkspaceName) {
121
- return Result.err(new InitError(`Invalid workspace package name '${workspaceName2}': ${invalidWorkspaceName}`));
122
- }
123
- }
124
- return Result.ok({
125
- rootDir,
126
- packageName: packageName2,
127
- preset,
128
- structure,
129
- includeTooling: !(options.noTooling ?? false),
130
- local: Boolean(options.local),
131
- ...blocksOverride2 ? { blocksOverride: blocksOverride2 } : {},
132
- ...workspaceName2 ? { workspaceName: workspaceName2 } : {},
133
- ...options.bin ? { binName: options.bin } : {}
134
- });
135
- }
136
- intro("Outfitter init");
137
- const packageNameValue = options.name ?? await text({
138
- message: "Project package name",
139
- placeholder: defaultPackageName,
140
- initialValue: defaultPackageName,
141
- validate: (value) => (value ?? "").trim().length === 0 ? "Project name is required" : undefined
142
- });
143
- if (isCancel(packageNameValue)) {
144
- cancel("Init cancelled.");
145
- return Result.err(new InitError("Init cancelled"));
146
- }
147
- const presetValue = presetOverride ?? presetFromFlags ?? await select({
148
- message: "Select a preset",
149
- options: INIT_TARGET_IDS.map((id) => {
150
- const target = TARGET_REGISTRY.get(id);
151
- return {
152
- value: id,
153
- label: id,
154
- hint: target?.description ?? ""
155
- };
156
- }),
157
- initialValue: "minimal"
158
- });
159
- if (isCancel(presetValue)) {
160
- cancel("Init cancelled.");
161
- return Result.err(new InitError("Init cancelled"));
162
- }
163
- const structureValue = presetValue === "full-stack" ? "single" : options.structure ?? await select({
164
- message: "Project structure",
165
- options: [
166
- {
167
- value: "single",
168
- label: "Single package",
169
- hint: "One package in the target directory"
170
- },
171
- {
172
- value: "workspace",
173
- label: "Workspace",
174
- hint: "Root workspace with project under apps/ or packages/"
175
- }
176
- ],
177
- initialValue: "single"
178
- });
179
- if (isCancel(structureValue)) {
180
- cancel("Init cancelled.");
181
- return Result.err(new InitError("Init cancelled"));
182
- }
183
- let binName;
184
- if (isBinaryPreset(presetValue) && process.stdout.isTTY) {
185
- const defaultBin = deriveBinName(deriveProjectName(packageNameValue.trim()));
186
- const binValue = options.bin ?? await text({
187
- message: "Binary name",
188
- placeholder: defaultBin,
189
- initialValue: defaultBin
190
- });
191
- if (isCancel(binValue)) {
192
- cancel("Init cancelled.");
193
- return Result.err(new InitError("Init cancelled"));
194
- }
195
- binName = binValue.trim();
196
- }
197
- let includeTooling;
198
- if (options.noTooling !== undefined) {
199
- includeTooling = !options.noTooling;
200
- } else if (options.with !== undefined) {
201
- includeTooling = true;
202
- } else {
203
- includeTooling = await confirm({
204
- message: "Add default tooling blocks?",
205
- initialValue: true
206
- });
207
- }
208
- if (isCancel(includeTooling)) {
209
- cancel("Init cancelled.");
210
- return Result.err(new InitError("Init cancelled"));
211
- }
212
- const localValue = options.local !== undefined ? options.local : await confirm({
213
- message: "Use workspace:* for @outfitter dependencies?",
214
- initialValue: false
215
- });
216
- if (isCancel(localValue)) {
217
- cancel("Init cancelled.");
218
- return Result.err(new InitError("Init cancelled"));
219
- }
220
- let workspaceName;
221
- if (structureValue === "workspace") {
222
- const workspaceNameValue = options.workspaceName ?? await text({
223
- message: "Workspace package name",
224
- placeholder: defaultPackageName,
225
- initialValue: defaultPackageName,
226
- validate: (value) => (value ?? "").trim().length === 0 ? "Workspace name is required" : undefined
227
- });
228
- if (isCancel(workspaceNameValue)) {
229
- cancel("Init cancelled.");
230
- return Result.err(new InitError("Init cancelled"));
231
- }
232
- workspaceName = workspaceNameValue.trim();
233
- }
234
- outro("Scaffolding project...");
235
- const packageName = packageNameValue.trim();
236
- if (packageName.length === 0) {
237
- return Result.err(new InitError("Project name must not be empty"));
238
- }
239
- const invalidPackageName = validatePackageName(packageName);
240
- if (invalidPackageName) {
241
- const suggested = sanitizePackageName(packageName);
242
- const suggestion = suggested.length > 0 && suggested !== packageName ? ` Try '${suggested}'.` : "";
243
- return Result.err(new InitError(`Invalid package name '${packageName}': ${invalidPackageName}.${suggestion}`));
244
- }
245
- if (workspaceName) {
246
- const invalidWorkspaceName = validatePackageName(workspaceName);
247
- if (invalidWorkspaceName) {
248
- return Result.err(new InitError(`Invalid workspace package name '${workspaceName}': ${invalidWorkspaceName}`));
249
- }
250
- }
251
- const blocksOverride = parseBlocks(options.with);
252
- return Result.ok({
253
- rootDir,
254
- packageName,
255
- preset: presetValue,
256
- structure: structureValue,
257
- includeTooling,
258
- local: Boolean(localValue),
259
- ...blocksOverride ? { blocksOverride } : {},
260
- ...workspaceName ? { workspaceName } : {},
261
- ...binName ? { binName } : {}
262
- });
263
- }
264
- function toInitError(error) {
265
- if (error instanceof InitError) {
266
- return error;
267
- }
268
- if (error instanceof Error) {
269
- return new InitError(error.message);
270
- }
271
- return new InitError("Unknown init error");
272
- }
273
- function buildInitPlan(target, input, projectDir, resolvedBinName) {
274
- const blocks = input.includeTooling ? input.blocksOverride ?? [...target.defaultBlocks] : [];
275
- return {
276
- values: {
277
- name: deriveProjectName(input.packageName),
278
- projectName: deriveProjectName(input.packageName),
279
- packageName: input.packageName,
280
- binName: resolvedBinName,
281
- version: "0.1.0",
282
- description: "A new project created with Outfitter",
283
- author: resolveAuthor(),
284
- year: resolveYear()
285
- },
286
- changes: [
287
- {
288
- type: "copy-template",
289
- template: target.templateDir,
290
- targetDir: projectDir,
291
- overlayBaseTemplate: true
292
- },
293
- { type: "inject-shared-config" },
294
- ...input.local ? [{ type: "rewrite-local-dependencies", mode: "workspace" }] : [],
295
- ...blocks.length > 0 ? [{ type: "add-blocks", blocks }] : []
296
- ]
297
- };
298
- }
299
- async function runInit(options, presetOverride) {
300
- const inputResult = await resolveInitInput(options, presetOverride);
301
- if (inputResult.isErr()) {
302
- return inputResult;
303
- }
304
- const input = inputResult.value;
305
- const targetResult = getInitTarget(input.preset);
306
- if (targetResult.isErr()) {
307
- return Result.err(new InitError(targetResult.error.message));
308
- }
309
- const target = targetResult.value;
310
- const projectName = deriveProjectName(input.packageName);
311
- if (input.structure === "workspace") {
312
- const invalidProjectName = validateProjectDirectoryName(projectName);
313
- if (invalidProjectName) {
314
- return Result.err(new InitError(`Invalid workspace project name '${projectName}': ${invalidProjectName}`));
315
- }
316
- }
317
- const dryRun = Boolean(options.dryRun);
318
- const collector = dryRun ? new OperationCollector : undefined;
319
- const projectBaseDir = resolve(input.rootDir, target.placement);
320
- const resolvedProjectDir = resolve(projectBaseDir, projectName);
321
- if (input.structure === "workspace" && !isPathWithin(projectBaseDir, resolvedProjectDir)) {
322
- return Result.err(new InitError(`Invalid workspace project name '${projectName}': path escapes '${projectBaseDir}'`));
323
- }
324
- const projectDir = input.structure === "workspace" ? resolvedProjectDir : input.rootDir;
325
- if (input.structure === "single") {
326
- if (existsSync(join(input.rootDir, "package.json")) && !options.force) {
327
- return Result.err(new InitError(`Directory '${input.rootDir}' already has a package.json. ` + `Use --force to overwrite, or use 'outfitter add' for existing projects.`));
328
- }
329
- } else {
330
- const workspaceName = input.workspaceName ?? basename(input.rootDir);
331
- const workspacePackageJsonPath = join(input.rootDir, "package.json");
332
- if (dryRun) {
333
- if (existsSync(workspacePackageJsonPath) && !options.force) {
334
- return Result.err(new InitError(`Directory '${input.rootDir}' already has a package.json. Use --force to overwrite.`));
335
- }
336
- collector?.add({
337
- type: "dir-create",
338
- path: join(input.rootDir, "apps")
339
- });
340
- collector?.add({
341
- type: "dir-create",
342
- path: join(input.rootDir, "packages")
343
- });
344
- collector?.add(existsSync(workspacePackageJsonPath) ? {
345
- type: "file-overwrite",
346
- path: workspacePackageJsonPath,
347
- source: "generated"
348
- } : {
349
- type: "file-create",
350
- path: workspacePackageJsonPath,
351
- source: "generated"
352
- });
353
- const readmePath = join(input.rootDir, "README.md");
354
- if (options.force || !existsSync(readmePath)) {
355
- collector?.add(existsSync(readmePath) ? {
356
- type: "file-overwrite",
357
- path: readmePath,
358
- source: "generated"
359
- } : {
360
- type: "file-create",
361
- path: readmePath,
362
- source: "generated"
363
- });
364
- }
365
- const gitignorePath = join(input.rootDir, ".gitignore");
366
- if (options.force || !existsSync(gitignorePath)) {
367
- collector?.add(existsSync(gitignorePath) ? {
368
- type: "file-overwrite",
369
- path: gitignorePath,
370
- source: "generated"
371
- } : {
372
- type: "file-create",
373
- path: gitignorePath,
374
- source: "generated"
375
- });
376
- }
377
- } else {
378
- const workspaceResult = scaffoldWorkspaceRoot(input.rootDir, workspaceName, options.force);
379
- if (workspaceResult.isErr()) {
380
- return Result.err(new InitError(workspaceResult.error.message));
381
- }
382
- }
383
- }
384
- const resolvedBinName = input.binName ?? deriveBinName(deriveProjectName(input.packageName));
385
- const plan = buildInitPlan(target, input, projectDir, resolvedBinName);
386
- const executeResult = await executePlan(plan, {
387
- force: options.force,
388
- ...collector ? { collector } : {}
389
- });
390
- if (executeResult.isErr()) {
391
- return Result.err(toInitError(executeResult.error));
392
- }
393
- const postScaffoldResult = await runPostScaffold({
394
- rootDir: input.rootDir,
395
- projectDir,
396
- origin: "init",
397
- target: input.preset,
398
- structure: input.structure,
399
- skipInstall: Boolean(options.skipInstall),
400
- skipGit: Boolean(options.skipGit),
401
- skipCommit: Boolean(options.skipCommit),
402
- dryRun,
403
- installTimeoutMs: options.installTimeout ?? 60000
404
- }, collector);
405
- if (postScaffoldResult.isErr()) {
406
- return Result.err(new InitError("Post-scaffold step failed"));
407
- }
408
- const result = {
409
- structure: input.structure,
410
- rootDir: input.rootDir,
411
- projectDir,
412
- preset: input.preset,
413
- packageName: input.packageName,
414
- blocksAdded: executeResult.value.blocksAdded,
415
- postScaffold: postScaffoldResult.value,
416
- ...collector ? { dryRunPlan: collector.toJSON() } : {}
417
- };
418
- return Result.ok(result);
419
- }
420
- async function printInitResults(result, options) {
421
- let rootDir = result.rootDir;
422
- let projectDir = result.projectDir;
423
- try {
424
- rootDir = await realpath(rootDir);
425
- projectDir = await realpath(projectDir);
426
- } catch {}
427
- const structuredMode = resolveStructuredOutputMode(options?.mode);
428
- if (result.dryRunPlan) {
429
- if (structuredMode) {
430
- await output({
431
- rootDir,
432
- projectDir,
433
- structure: result.structure,
434
- preset: result.preset,
435
- packageName: result.packageName,
436
- ...result.dryRunPlan
437
- }, { mode: structuredMode });
438
- return;
439
- }
440
- const collector = new OperationCollector;
441
- for (const op of result.dryRunPlan.operations) {
442
- collector.add(op);
443
- }
444
- await renderOperationPlan(collector, { rootDir });
445
- return;
446
- }
447
- if (structuredMode) {
448
- await output({
449
- structure: result.structure,
450
- rootDir,
451
- projectDir,
452
- preset: result.preset,
453
- packageName: result.packageName,
454
- blocksAdded: result.blocksAdded ?? null,
455
- postScaffold: result.postScaffold,
456
- nextSteps: result.postScaffold.nextSteps
457
- }, { mode: structuredMode });
458
- return;
459
- }
460
- const lines = [
461
- `Project initialized successfully in ${rootDir}`,
462
- `Structure: ${result.structure}`,
463
- `Preset: ${result.preset}`
464
- ];
465
- if (result.structure === "workspace") {
466
- lines.push(`Workspace project path: ${projectDir}`);
467
- }
468
- if (result.blocksAdded) {
469
- const { created, skipped, dependencies, devDependencies } = result.blocksAdded;
470
- if (created.length > 0) {
471
- lines.push("", `Added ${created.length} tooling file(s):`);
472
- for (const file of created) {
473
- lines.push(` \u2713 ${file}`);
474
- }
475
- }
476
- if (skipped.length > 0) {
477
- lines.push("", `Skipped ${skipped.length} existing file(s):`);
478
- for (const file of skipped) {
479
- lines.push(` - ${file}`);
480
- }
481
- }
482
- const depCount = Object.keys(dependencies).length + Object.keys(devDependencies).length;
483
- if (depCount > 0) {
484
- lines.push("", `Added ${depCount} package(s) to package.json:`);
485
- for (const [name, version] of Object.entries(dependencies)) {
486
- lines.push(` + ${name}@${version}`);
487
- }
488
- for (const [name, version] of Object.entries(devDependencies)) {
489
- lines.push(` + ${name}@${version} (dev)`);
490
- }
491
- }
492
- }
493
- if (result.postScaffold.installResult === "failed") {
494
- lines.push("", `Warning: bun install failed: ${result.postScaffold.installError ?? "unknown"}`);
495
- }
496
- if (result.postScaffold.gitInitResult === "failed") {
497
- lines.push("", `Warning: git setup failed: ${result.postScaffold.gitError ?? "unknown"}`);
498
- }
499
- lines.push("", "Next steps:");
500
- for (const step of result.postScaffold.nextSteps) {
501
- lines.push(` ${step}`);
502
- }
503
- await output(lines, { mode: "human" });
504
- }
505
- function initCommand(program) {
506
- const init = program.command("init").description("Create a new Outfitter project");
507
- const resolveFlags = (flags, command) => {
508
- if (command) {
509
- return command.optsWithGlobals();
510
- }
511
- return typeof flags.opts === "function" ? flags.opts() : flags;
512
- };
513
- const resolveLocal = (flags) => {
514
- if (flags.local === true || flags.workspace === true) {
515
- return true;
516
- }
517
- return;
518
- };
519
- const resolveOutputMode = (flags) => {
520
- if (flags.json) {
521
- return "json";
522
- }
523
- return;
524
- };
525
- 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("-p, --preset <preset>", "Preset to use (minimal|cli|mcp|daemon|library|full-stack)").option("-s, --structure <mode>", "Project structure (single|workspace)").option("--workspace-name <name>", "Workspace root package name").option("-f, --force", "Overwrite existing files", false).option("--local", "Use workspace:* for @outfitter dependencies").option("--workspace", "Alias for --local").option("--with <blocks>", "Comma-separated tooling blocks to add").option("--no-tooling", "Skip default tooling blocks").option("-y, --yes", "Skip prompts and use defaults", false).option("--dry-run", "Preview changes without writing files", false).option("--skip-install", "Skip bun install", false).option("--skip-git", "Skip git init and initial commit", false).option("--skip-commit", "Skip initial commit only", false).option("--install-timeout <ms>", "bun install timeout in ms");
526
- withCommonOptions(init.argument("[directory]").option("-t, --template <template>", "Template to use (deprecated)")).action(async (directory, flags, command) => {
527
- const targetDir = directory ?? process.cwd();
528
- const resolvedFlags = resolveFlags(flags, command);
529
- const mode = resolveOutputMode(resolvedFlags);
530
- const outputOptions = mode ? { mode } : undefined;
531
- const result = await runInit({
532
- targetDir,
533
- name: resolvedFlags.name,
534
- bin: resolvedFlags.bin,
535
- preset: resolvedFlags.preset,
536
- template: resolvedFlags.template,
537
- structure: resolvedFlags.structure,
538
- workspaceName: resolvedFlags.workspaceName,
539
- local: resolveLocal(resolvedFlags),
540
- force: resolvedFlags.force ?? false,
541
- with: resolvedFlags.with,
542
- noTooling: resolvedFlags.noTooling,
543
- yes: resolvedFlags.yes,
544
- dryRun: Boolean(resolvedFlags.dryRun),
545
- skipInstall: Boolean(resolvedFlags.skipInstall),
546
- skipGit: Boolean(resolvedFlags.skipGit),
547
- skipCommit: Boolean(resolvedFlags.skipCommit),
548
- ...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
549
- });
550
- if (result.isErr()) {
551
- exitWithError(result.error, outputOptions);
552
- return;
553
- }
554
- await printInitResults(result.value, outputOptions);
555
- });
556
- withCommonOptions(init.command("cli [directory]").description("Create a new CLI project")).action(async (directory, flags, command) => {
557
- const targetDir = directory ?? process.cwd();
558
- const resolvedFlags = resolveFlags(flags, command);
559
- const mode = resolveOutputMode(resolvedFlags);
560
- const outputOptions = mode ? { mode } : undefined;
561
- const result = await runInit({
562
- targetDir,
563
- name: resolvedFlags.name,
564
- bin: resolvedFlags.bin,
565
- structure: resolvedFlags.structure,
566
- workspaceName: resolvedFlags.workspaceName,
567
- local: resolveLocal(resolvedFlags),
568
- force: resolvedFlags.force ?? false,
569
- with: resolvedFlags.with,
570
- noTooling: resolvedFlags.noTooling,
571
- yes: resolvedFlags.yes,
572
- dryRun: Boolean(resolvedFlags.dryRun),
573
- skipInstall: Boolean(resolvedFlags.skipInstall),
574
- skipGit: Boolean(resolvedFlags.skipGit),
575
- skipCommit: Boolean(resolvedFlags.skipCommit),
576
- ...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
577
- }, "cli");
578
- if (result.isErr()) {
579
- exitWithError(result.error, outputOptions);
580
- return;
581
- }
582
- await printInitResults(result.value, outputOptions);
583
- });
584
- withCommonOptions(init.command("mcp [directory]").description("Create a new MCP server")).action(async (directory, flags, command) => {
585
- const targetDir = directory ?? process.cwd();
586
- const resolvedFlags = resolveFlags(flags, command);
587
- const mode = resolveOutputMode(resolvedFlags);
588
- const outputOptions = mode ? { mode } : undefined;
589
- const result = await runInit({
590
- targetDir,
591
- name: resolvedFlags.name,
592
- bin: resolvedFlags.bin,
593
- structure: resolvedFlags.structure,
594
- workspaceName: resolvedFlags.workspaceName,
595
- local: resolveLocal(resolvedFlags),
596
- force: resolvedFlags.force ?? false,
597
- with: resolvedFlags.with,
598
- noTooling: resolvedFlags.noTooling,
599
- yes: resolvedFlags.yes,
600
- dryRun: Boolean(resolvedFlags.dryRun),
601
- skipInstall: Boolean(resolvedFlags.skipInstall),
602
- skipGit: Boolean(resolvedFlags.skipGit),
603
- skipCommit: Boolean(resolvedFlags.skipCommit),
604
- ...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
605
- }, "mcp");
606
- if (result.isErr()) {
607
- exitWithError(result.error, outputOptions);
608
- return;
609
- }
610
- await printInitResults(result.value, outputOptions);
611
- });
612
- withCommonOptions(init.command("daemon [directory]").description("Create a new daemon project")).action(async (directory, flags, command) => {
613
- const targetDir = directory ?? process.cwd();
614
- const resolvedFlags = resolveFlags(flags, command);
615
- const mode = resolveOutputMode(resolvedFlags);
616
- const outputOptions = mode ? { mode } : undefined;
617
- const result = await runInit({
618
- targetDir,
619
- name: resolvedFlags.name,
620
- bin: resolvedFlags.bin,
621
- structure: resolvedFlags.structure,
622
- workspaceName: resolvedFlags.workspaceName,
623
- local: resolveLocal(resolvedFlags),
624
- force: resolvedFlags.force ?? false,
625
- with: resolvedFlags.with,
626
- noTooling: resolvedFlags.noTooling,
627
- yes: resolvedFlags.yes,
628
- dryRun: Boolean(resolvedFlags.dryRun),
629
- skipInstall: Boolean(resolvedFlags.skipInstall),
630
- skipGit: Boolean(resolvedFlags.skipGit),
631
- skipCommit: Boolean(resolvedFlags.skipCommit),
632
- ...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
633
- }, "daemon");
634
- if (result.isErr()) {
635
- exitWithError(result.error, outputOptions);
636
- return;
637
- }
638
- await printInitResults(result.value, outputOptions);
639
- });
640
- withCommonOptions(init.command("library [directory]").description("Create a new library")).action(async (directory, flags, command) => {
641
- const targetDir = directory ?? process.cwd();
642
- const resolvedFlags = resolveFlags(flags, command);
643
- const mode = resolveOutputMode(resolvedFlags);
644
- const outputOptions = mode ? { mode } : undefined;
645
- const result = await runInit({
646
- targetDir,
647
- name: resolvedFlags.name,
648
- bin: resolvedFlags.bin,
649
- structure: resolvedFlags.structure,
650
- workspaceName: resolvedFlags.workspaceName,
651
- local: resolveLocal(resolvedFlags),
652
- force: resolvedFlags.force ?? false,
653
- with: resolvedFlags.with,
654
- noTooling: resolvedFlags.noTooling,
655
- yes: resolvedFlags.yes,
656
- dryRun: Boolean(resolvedFlags.dryRun),
657
- skipInstall: Boolean(resolvedFlags.skipInstall),
658
- skipGit: Boolean(resolvedFlags.skipGit),
659
- skipCommit: Boolean(resolvedFlags.skipCommit),
660
- ...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
661
- }, "library");
662
- if (result.isErr()) {
663
- exitWithError(result.error, outputOptions);
664
- return;
665
- }
666
- await printInitResults(result.value, outputOptions);
667
- });
668
- withCommonOptions(init.command("full-stack [directory]").description("Create a full-stack workspace")).action(async (directory, flags, command) => {
669
- const targetDir = directory ?? process.cwd();
670
- const resolvedFlags = resolveFlags(flags, command);
671
- const mode = resolveOutputMode(resolvedFlags);
672
- const outputOptions = mode ? { mode } : undefined;
673
- const result = await runInit({
674
- targetDir,
675
- name: resolvedFlags.name,
676
- bin: resolvedFlags.bin,
677
- structure: resolvedFlags.structure,
678
- workspaceName: resolvedFlags.workspaceName,
679
- local: resolveLocal(resolvedFlags),
680
- force: resolvedFlags.force ?? false,
681
- with: resolvedFlags.with,
682
- noTooling: resolvedFlags.noTooling,
683
- yes: resolvedFlags.yes,
684
- dryRun: Boolean(resolvedFlags.dryRun),
685
- skipInstall: Boolean(resolvedFlags.skipInstall),
686
- skipGit: Boolean(resolvedFlags.skipGit),
687
- skipCommit: Boolean(resolvedFlags.skipCommit),
688
- ...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
689
- }, "full-stack");
690
- if (result.isErr()) {
691
- exitWithError(result.error, outputOptions);
692
- return;
693
- }
694
- await printInitResults(result.value, outputOptions);
695
- });
696
- }
697
-
698
- export { InitError, runInit, printInitResults, initCommand };