outfitter 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/actions.d.ts +2 -0
- package/dist/actions.js +34 -0
- package/dist/cli.js +1 -1
- package/dist/commands/add.d.ts +54 -0
- package/dist/commands/add.js +16 -0
- package/dist/commands/check.d.ts +91 -0
- package/dist/commands/check.js +14 -0
- package/dist/commands/demo.d.ts +21 -0
- package/dist/commands/demo.js +8 -0
- package/dist/commands/docs-module-loader.d.ts +2 -0
- package/dist/commands/docs-module-loader.js +8 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +13 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +31 -0
- package/dist/commands/migrate-kit.d.ts +2 -0
- package/dist/commands/migrate-kit.js +15 -0
- package/dist/commands/repo.d.ts +3 -0
- package/dist/commands/repo.js +9 -0
- package/dist/commands/scaffold.d.ts +4 -0
- package/dist/commands/scaffold.js +31 -0
- package/dist/commands/shared-deps.d.ts +36 -0
- package/dist/commands/shared-deps.js +10 -0
- package/dist/commands/upgrade-codemods.d.ts +42 -0
- package/dist/commands/upgrade-codemods.js +15 -0
- package/dist/commands/upgrade-planner.d.ts +58 -0
- package/dist/commands/upgrade-planner.js +8 -0
- package/dist/commands/upgrade-workspace.d.ts +76 -0
- package/dist/commands/upgrade-workspace.js +16 -0
- package/dist/commands/upgrade.d.ts +214 -0
- package/dist/commands/upgrade.js +25 -0
- package/dist/create/index.d.ts +5 -0
- package/dist/create/index.js +29 -0
- package/dist/create/planner.d.ts +3 -0
- package/dist/create/planner.js +21 -0
- package/dist/create/presets.d.ts +3 -0
- package/dist/create/presets.js +12 -0
- package/dist/create/types.d.ts +2 -0
- package/dist/create/types.js +1 -0
- package/dist/engine/blocks.d.ts +3 -0
- package/dist/engine/blocks.js +12 -0
- package/dist/engine/collector.d.ts +2 -0
- package/dist/engine/collector.js +8 -0
- package/dist/engine/config.d.ts +3 -0
- package/dist/engine/config.js +12 -0
- package/dist/engine/executor.d.ts +3 -0
- package/dist/engine/executor.js +16 -0
- package/dist/engine/index.d.ts +8 -0
- package/dist/engine/index.js +59 -0
- package/dist/engine/names.d.ts +2 -0
- package/dist/engine/names.js +16 -0
- package/dist/engine/post-scaffold.d.ts +3 -0
- package/dist/engine/post-scaffold.js +8 -0
- package/dist/engine/render-plan.d.ts +7 -0
- package/dist/engine/render-plan.js +9 -0
- package/dist/engine/template.d.ts +3 -0
- package/dist/engine/template.js +17 -0
- package/dist/engine/types.d.ts +2 -0
- package/dist/engine/types.js +8 -0
- package/dist/engine/workspace.d.ts +3 -0
- package/dist/engine/workspace.js +13 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1 -1
- package/dist/manifest.d.ts +71 -0
- package/dist/manifest.js +16 -0
- package/dist/output-mode.d.ts +2 -0
- package/dist/output-mode.js +10 -0
- package/dist/shared/{chunk-tpwtpa74.js → chunk-k59f60cp.js} +858 -396
- package/dist/shared/outfitter-193jvzg4.d.ts +5 -0
- package/dist/shared/outfitter-1dd0k853.js +194 -0
- package/dist/shared/outfitter-1dvma85c.js +322 -0
- package/dist/shared/outfitter-1h7k8xxt.js +29 -0
- package/dist/shared/outfitter-2ngep1h2.d.ts +5 -0
- package/dist/shared/outfitter-2np85etz.js +95 -0
- package/dist/shared/outfitter-33w361tc.d.ts +18 -0
- package/dist/shared/outfitter-344t1r38.js +1 -0
- package/dist/shared/outfitter-3weh61w7.d.ts +25 -0
- package/dist/shared/outfitter-4s9meh3j.js +221 -0
- package/dist/shared/outfitter-66b25bj8.js +125 -0
- package/dist/shared/outfitter-6bkqjk86.d.ts +3 -0
- package/dist/shared/outfitter-79vfxt6y.js +269 -0
- package/dist/shared/outfitter-7ha7p61k.d.ts +6 -0
- package/dist/shared/outfitter-7r12fj7f.js +30 -0
- package/dist/shared/outfitter-8y2dfx6n.js +11 -0
- package/dist/shared/outfitter-9x1brcmq.js +184 -0
- package/dist/shared/outfitter-9zqc2njf.js +859 -0
- package/dist/shared/outfitter-a79xrm12.d.ts +17 -0
- package/dist/shared/outfitter-amc4jbs1.d.ts +50 -0
- package/dist/shared/outfitter-ara3djt0.js +73 -0
- package/dist/shared/outfitter-avhm5z6w.js +82 -0
- package/dist/shared/outfitter-bkwpbkr9.d.ts +63 -0
- package/dist/shared/outfitter-bn9c8p2e.js +204 -0
- package/dist/shared/outfitter-bpr28y54.js +70 -0
- package/dist/shared/outfitter-cwq39bv4.d.ts +48 -0
- package/dist/shared/outfitter-d7pq7d0k.js +196 -0
- package/dist/shared/outfitter-dd0btgec.d.ts +40 -0
- package/dist/shared/outfitter-e2zz5wv7.d.ts +51 -0
- package/dist/shared/outfitter-ehp18x1n.js +1 -0
- package/dist/shared/outfitter-gdvm5c0b.d.ts +4 -0
- package/dist/shared/outfitter-h1mnzzd1.d.ts +14 -0
- package/dist/shared/outfitter-hvsaxgcp.js +1 -0
- package/dist/shared/outfitter-hws10ze7.js +532 -0
- package/dist/shared/outfitter-j833sxws.js +61 -0
- package/dist/shared/outfitter-j8yc7294.d.ts +22 -0
- package/dist/shared/outfitter-k112c427.js +21 -0
- package/dist/shared/outfitter-k56rmt24.d.ts +30 -0
- package/dist/shared/outfitter-ksa1pp4t.d.ts +4 -0
- package/dist/shared/outfitter-ksyvwmb5.js +191 -0
- package/dist/shared/outfitter-mdt37hqm.js +4 -0
- package/dist/shared/outfitter-mtbpabf3.js +91 -0
- package/dist/shared/outfitter-mxz69pgy.js +713 -0
- package/dist/shared/outfitter-npemy7ta.d.ts +53 -0
- package/dist/shared/outfitter-npyfbdmc.d.ts +6 -0
- package/dist/shared/outfitter-pyy1zkfh.d.ts +133 -0
- package/dist/shared/outfitter-q9agarmb.js +42 -0
- package/dist/shared/outfitter-qfh36ddg.d.ts +66 -0
- package/dist/shared/outfitter-qn864k6h.js +581 -0
- package/dist/shared/outfitter-rdc5v5ms.js +746 -0
- package/dist/shared/outfitter-sgtq57qr.d.ts +5 -0
- package/dist/shared/outfitter-ttjr95y9.js +98 -0
- package/dist/shared/outfitter-vh4xgb93.js +35 -0
- package/dist/shared/outfitter-yvksv5qb.js +322 -0
- package/dist/shared/outfitter-zwyvewr1.js +36 -0
- package/dist/targets/index.d.ts +4 -0
- package/dist/targets/index.js +29 -0
- package/dist/targets/registry.d.ts +3 -0
- package/dist/targets/registry.js +28 -0
- package/dist/targets/types.d.ts +2 -0
- package/dist/targets/types.js +1 -0
- package/package.json +19 -12
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
INIT_TARGET_IDS,
|
|
4
|
+
TARGET_REGISTRY,
|
|
5
|
+
getInitTarget
|
|
6
|
+
} from "./outfitter-1dd0k853.js";
|
|
7
|
+
import {
|
|
8
|
+
runPostScaffold
|
|
9
|
+
} from "./outfitter-4s9meh3j.js";
|
|
10
|
+
import {
|
|
11
|
+
scaffoldWorkspaceRoot
|
|
12
|
+
} from "./outfitter-2np85etz.js";
|
|
13
|
+
import {
|
|
14
|
+
deriveBinName,
|
|
15
|
+
deriveProjectName,
|
|
16
|
+
resolveAuthor,
|
|
17
|
+
resolvePackageName,
|
|
18
|
+
resolveYear
|
|
19
|
+
} from "./outfitter-q9agarmb.js";
|
|
20
|
+
import {
|
|
21
|
+
executePlan
|
|
22
|
+
} from "./outfitter-66b25bj8.js";
|
|
23
|
+
import {
|
|
24
|
+
renderOperationPlan
|
|
25
|
+
} from "./outfitter-ttjr95y9.js";
|
|
26
|
+
import {
|
|
27
|
+
resolveStructuredOutputMode
|
|
28
|
+
} from "./outfitter-7r12fj7f.js";
|
|
29
|
+
import {
|
|
30
|
+
OperationCollector
|
|
31
|
+
} from "./outfitter-1h7k8xxt.js";
|
|
32
|
+
|
|
33
|
+
// apps/outfitter/src/commands/init.ts
|
|
34
|
+
import { existsSync } from "fs";
|
|
35
|
+
import { basename, join, resolve } from "path";
|
|
36
|
+
import {
|
|
37
|
+
cancel,
|
|
38
|
+
confirm,
|
|
39
|
+
intro,
|
|
40
|
+
isCancel,
|
|
41
|
+
outro,
|
|
42
|
+
select,
|
|
43
|
+
text
|
|
44
|
+
} from "@clack/prompts";
|
|
45
|
+
import { exitWithError, output } from "@outfitter/cli";
|
|
46
|
+
import { Result } from "@outfitter/contracts";
|
|
47
|
+
class InitError extends Error {
|
|
48
|
+
_tag = "InitError";
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "InitError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function parseBlocks(withFlag) {
|
|
55
|
+
if (!withFlag) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const blocks = withFlag.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
59
|
+
return blocks.length > 0 ? blocks : undefined;
|
|
60
|
+
}
|
|
61
|
+
function isBinaryPreset(preset) {
|
|
62
|
+
return preset === "cli" || preset === "daemon";
|
|
63
|
+
}
|
|
64
|
+
function isValidInitPreset(value) {
|
|
65
|
+
return value === "minimal" || value === "cli" || value === "mcp" || value === "daemon";
|
|
66
|
+
}
|
|
67
|
+
function resolvePresetFromFlags(options) {
|
|
68
|
+
const presetFromFlag = options.preset;
|
|
69
|
+
if (presetFromFlag) {
|
|
70
|
+
if (!isValidInitPreset(presetFromFlag)) {
|
|
71
|
+
return Result.err(new InitError(`Unknown preset '${presetFromFlag}'. Available presets: ${INIT_TARGET_IDS.join(", ")}`));
|
|
72
|
+
}
|
|
73
|
+
return Result.ok(presetFromFlag);
|
|
74
|
+
}
|
|
75
|
+
if (options.template) {
|
|
76
|
+
const mapped = options.template === "basic" ? "minimal" : options.template;
|
|
77
|
+
process.stderr.write(`Warning: --template is deprecated and will be removed in the next major version.
|
|
78
|
+
` + ` Use --preset instead: outfitter init --preset ${mapped}
|
|
79
|
+
` + (options.template === "basic" ? ` Note: "basic" has been renamed to "minimal".
|
|
80
|
+
` : ""));
|
|
81
|
+
if (isValidInitPreset(mapped)) {
|
|
82
|
+
return Result.ok(mapped);
|
|
83
|
+
}
|
|
84
|
+
return Result.err(new InitError(`Unknown template '${options.template}'. Available presets: ${INIT_TARGET_IDS.join(", ")}`));
|
|
85
|
+
}
|
|
86
|
+
return Result.ok(undefined);
|
|
87
|
+
}
|
|
88
|
+
async function resolveInitInput(options, presetOverride) {
|
|
89
|
+
const rootDir = resolve(options.targetDir);
|
|
90
|
+
const defaultName = basename(rootDir);
|
|
91
|
+
const presetFromFlagsResult = resolvePresetFromFlags(options);
|
|
92
|
+
if (presetFromFlagsResult.isErr()) {
|
|
93
|
+
return presetFromFlagsResult;
|
|
94
|
+
}
|
|
95
|
+
const presetFromFlags = presetFromFlagsResult.value;
|
|
96
|
+
if (options.yes || !process.stdout.isTTY) {
|
|
97
|
+
const packageName2 = resolvePackageName(rootDir, options.name).trim();
|
|
98
|
+
if (packageName2.length === 0) {
|
|
99
|
+
return Result.err(new InitError("Project name must not be empty"));
|
|
100
|
+
}
|
|
101
|
+
const preset = presetOverride ?? presetFromFlags ?? "minimal";
|
|
102
|
+
const structure = options.structure ?? "single";
|
|
103
|
+
const blocksOverride2 = parseBlocks(options.with);
|
|
104
|
+
const workspaceName2 = structure === "workspace" ? (options.workspaceName ?? defaultName).trim() || defaultName : undefined;
|
|
105
|
+
return Result.ok({
|
|
106
|
+
rootDir,
|
|
107
|
+
packageName: packageName2,
|
|
108
|
+
preset,
|
|
109
|
+
structure,
|
|
110
|
+
includeTooling: !(options.noTooling ?? false),
|
|
111
|
+
local: Boolean(options.local),
|
|
112
|
+
...blocksOverride2 ? { blocksOverride: blocksOverride2 } : {},
|
|
113
|
+
...workspaceName2 ? { workspaceName: workspaceName2 } : {},
|
|
114
|
+
...options.bin ? { binName: options.bin } : {}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
intro("Outfitter init");
|
|
118
|
+
const packageNameValue = options.name ?? await text({
|
|
119
|
+
message: "Project package name",
|
|
120
|
+
placeholder: defaultName,
|
|
121
|
+
initialValue: defaultName,
|
|
122
|
+
validate: (value) => value.trim().length === 0 ? "Project name is required" : undefined
|
|
123
|
+
});
|
|
124
|
+
if (isCancel(packageNameValue)) {
|
|
125
|
+
cancel("Init cancelled.");
|
|
126
|
+
return Result.err(new InitError("Init cancelled"));
|
|
127
|
+
}
|
|
128
|
+
const presetValue = presetOverride ?? presetFromFlags ?? await select({
|
|
129
|
+
message: "Select a preset",
|
|
130
|
+
options: INIT_TARGET_IDS.map((id) => {
|
|
131
|
+
const target = TARGET_REGISTRY.get(id);
|
|
132
|
+
return {
|
|
133
|
+
value: id,
|
|
134
|
+
label: id,
|
|
135
|
+
hint: target?.description ?? ""
|
|
136
|
+
};
|
|
137
|
+
}),
|
|
138
|
+
initialValue: "minimal"
|
|
139
|
+
});
|
|
140
|
+
if (isCancel(presetValue)) {
|
|
141
|
+
cancel("Init cancelled.");
|
|
142
|
+
return Result.err(new InitError("Init cancelled"));
|
|
143
|
+
}
|
|
144
|
+
const structureValue = options.structure ?? await select({
|
|
145
|
+
message: "Project structure",
|
|
146
|
+
options: [
|
|
147
|
+
{
|
|
148
|
+
value: "single",
|
|
149
|
+
label: "Single package",
|
|
150
|
+
hint: "One package in the target directory"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
value: "workspace",
|
|
154
|
+
label: "Workspace",
|
|
155
|
+
hint: "Root workspace with project under apps/ or packages/"
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
initialValue: "single"
|
|
159
|
+
});
|
|
160
|
+
if (isCancel(structureValue)) {
|
|
161
|
+
cancel("Init cancelled.");
|
|
162
|
+
return Result.err(new InitError("Init cancelled"));
|
|
163
|
+
}
|
|
164
|
+
let binName;
|
|
165
|
+
if (isBinaryPreset(presetValue) && process.stdout.isTTY) {
|
|
166
|
+
const defaultBin = deriveBinName(deriveProjectName(packageNameValue.trim()));
|
|
167
|
+
const binValue = options.bin ?? await text({
|
|
168
|
+
message: "Binary name",
|
|
169
|
+
placeholder: defaultBin,
|
|
170
|
+
initialValue: defaultBin
|
|
171
|
+
});
|
|
172
|
+
if (isCancel(binValue)) {
|
|
173
|
+
cancel("Init cancelled.");
|
|
174
|
+
return Result.err(new InitError("Init cancelled"));
|
|
175
|
+
}
|
|
176
|
+
binName = binValue.trim();
|
|
177
|
+
}
|
|
178
|
+
let includeTooling;
|
|
179
|
+
if (options.noTooling !== undefined) {
|
|
180
|
+
includeTooling = !options.noTooling;
|
|
181
|
+
} else if (options.with !== undefined) {
|
|
182
|
+
includeTooling = true;
|
|
183
|
+
} else {
|
|
184
|
+
includeTooling = await confirm({
|
|
185
|
+
message: "Add default tooling blocks?",
|
|
186
|
+
initialValue: true
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (isCancel(includeTooling)) {
|
|
190
|
+
cancel("Init cancelled.");
|
|
191
|
+
return Result.err(new InitError("Init cancelled"));
|
|
192
|
+
}
|
|
193
|
+
const localValue = options.local !== undefined ? options.local : await confirm({
|
|
194
|
+
message: "Use workspace:* for @outfitter dependencies?",
|
|
195
|
+
initialValue: false
|
|
196
|
+
});
|
|
197
|
+
if (isCancel(localValue)) {
|
|
198
|
+
cancel("Init cancelled.");
|
|
199
|
+
return Result.err(new InitError("Init cancelled"));
|
|
200
|
+
}
|
|
201
|
+
let workspaceName;
|
|
202
|
+
if (structureValue === "workspace") {
|
|
203
|
+
const workspaceNameValue = options.workspaceName ?? await text({
|
|
204
|
+
message: "Workspace package name",
|
|
205
|
+
placeholder: defaultName,
|
|
206
|
+
initialValue: defaultName,
|
|
207
|
+
validate: (value) => value.trim().length === 0 ? "Workspace name is required" : undefined
|
|
208
|
+
});
|
|
209
|
+
if (isCancel(workspaceNameValue)) {
|
|
210
|
+
cancel("Init cancelled.");
|
|
211
|
+
return Result.err(new InitError("Init cancelled"));
|
|
212
|
+
}
|
|
213
|
+
workspaceName = workspaceNameValue.trim();
|
|
214
|
+
}
|
|
215
|
+
outro("Scaffolding project...");
|
|
216
|
+
const packageName = packageNameValue.trim();
|
|
217
|
+
if (packageName.length === 0) {
|
|
218
|
+
return Result.err(new InitError("Project name must not be empty"));
|
|
219
|
+
}
|
|
220
|
+
const blocksOverride = parseBlocks(options.with);
|
|
221
|
+
return Result.ok({
|
|
222
|
+
rootDir,
|
|
223
|
+
packageName,
|
|
224
|
+
preset: presetValue,
|
|
225
|
+
structure: structureValue,
|
|
226
|
+
includeTooling,
|
|
227
|
+
local: Boolean(localValue),
|
|
228
|
+
...blocksOverride ? { blocksOverride } : {},
|
|
229
|
+
...workspaceName ? { workspaceName } : {},
|
|
230
|
+
...binName ? { binName } : {}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function toInitError(error) {
|
|
234
|
+
if (error instanceof InitError) {
|
|
235
|
+
return error;
|
|
236
|
+
}
|
|
237
|
+
if (error instanceof Error) {
|
|
238
|
+
return new InitError(error.message);
|
|
239
|
+
}
|
|
240
|
+
return new InitError("Unknown init error");
|
|
241
|
+
}
|
|
242
|
+
function buildInitPlan(target, input, projectDir, resolvedBinName) {
|
|
243
|
+
const blocks = input.includeTooling ? input.blocksOverride ?? [...target.defaultBlocks] : [];
|
|
244
|
+
return {
|
|
245
|
+
values: {
|
|
246
|
+
name: deriveProjectName(input.packageName),
|
|
247
|
+
projectName: deriveProjectName(input.packageName),
|
|
248
|
+
packageName: input.packageName,
|
|
249
|
+
binName: resolvedBinName,
|
|
250
|
+
version: "0.1.0",
|
|
251
|
+
description: "A new project created with Outfitter",
|
|
252
|
+
author: resolveAuthor(),
|
|
253
|
+
year: resolveYear()
|
|
254
|
+
},
|
|
255
|
+
changes: [
|
|
256
|
+
{
|
|
257
|
+
type: "copy-template",
|
|
258
|
+
template: target.templateDir,
|
|
259
|
+
targetDir: projectDir,
|
|
260
|
+
overlayBaseTemplate: true
|
|
261
|
+
},
|
|
262
|
+
{ type: "inject-shared-config" },
|
|
263
|
+
...input.local ? [{ type: "rewrite-local-dependencies", mode: "workspace" }] : [],
|
|
264
|
+
...blocks.length > 0 ? [{ type: "add-blocks", blocks }] : []
|
|
265
|
+
]
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
async function runInit(options, presetOverride) {
|
|
269
|
+
const inputResult = await resolveInitInput(options, presetOverride);
|
|
270
|
+
if (inputResult.isErr()) {
|
|
271
|
+
return inputResult;
|
|
272
|
+
}
|
|
273
|
+
const input = inputResult.value;
|
|
274
|
+
const targetResult = getInitTarget(input.preset);
|
|
275
|
+
if (targetResult.isErr()) {
|
|
276
|
+
return Result.err(new InitError(targetResult.error.message));
|
|
277
|
+
}
|
|
278
|
+
const target = targetResult.value;
|
|
279
|
+
const dryRun = Boolean(options.dryRun);
|
|
280
|
+
const collector = dryRun ? new OperationCollector : undefined;
|
|
281
|
+
const projectDir = input.structure === "workspace" ? join(input.rootDir, target.placement, deriveProjectName(input.packageName)) : input.rootDir;
|
|
282
|
+
if (input.structure === "single") {
|
|
283
|
+
if (existsSync(join(input.rootDir, "package.json")) && !options.force) {
|
|
284
|
+
return Result.err(new InitError(`Directory '${input.rootDir}' already has a package.json. ` + `Use --force to overwrite, or use 'outfitter add' for existing projects.`));
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
const workspaceName = input.workspaceName ?? basename(input.rootDir);
|
|
288
|
+
const workspacePackageJsonPath = join(input.rootDir, "package.json");
|
|
289
|
+
if (dryRun) {
|
|
290
|
+
if (existsSync(workspacePackageJsonPath) && !options.force) {
|
|
291
|
+
return Result.err(new InitError(`Directory '${input.rootDir}' already has a package.json. Use --force to overwrite.`));
|
|
292
|
+
}
|
|
293
|
+
collector?.add({
|
|
294
|
+
type: "dir-create",
|
|
295
|
+
path: join(input.rootDir, "apps")
|
|
296
|
+
});
|
|
297
|
+
collector?.add({
|
|
298
|
+
type: "dir-create",
|
|
299
|
+
path: join(input.rootDir, "packages")
|
|
300
|
+
});
|
|
301
|
+
collector?.add(existsSync(workspacePackageJsonPath) ? {
|
|
302
|
+
type: "file-overwrite",
|
|
303
|
+
path: workspacePackageJsonPath,
|
|
304
|
+
source: "generated"
|
|
305
|
+
} : {
|
|
306
|
+
type: "file-create",
|
|
307
|
+
path: workspacePackageJsonPath,
|
|
308
|
+
source: "generated"
|
|
309
|
+
});
|
|
310
|
+
const gitignorePath = join(input.rootDir, ".gitignore");
|
|
311
|
+
if (options.force || !existsSync(gitignorePath)) {
|
|
312
|
+
collector?.add(existsSync(gitignorePath) ? {
|
|
313
|
+
type: "file-overwrite",
|
|
314
|
+
path: gitignorePath,
|
|
315
|
+
source: "generated"
|
|
316
|
+
} : {
|
|
317
|
+
type: "file-create",
|
|
318
|
+
path: gitignorePath,
|
|
319
|
+
source: "generated"
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
const workspaceResult = scaffoldWorkspaceRoot(input.rootDir, workspaceName, options.force);
|
|
324
|
+
if (workspaceResult.isErr()) {
|
|
325
|
+
return Result.err(new InitError(workspaceResult.error.message));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const resolvedBinName = input.binName ?? deriveBinName(deriveProjectName(input.packageName));
|
|
330
|
+
const plan = buildInitPlan(target, input, projectDir, resolvedBinName);
|
|
331
|
+
const executeResult = await executePlan(plan, {
|
|
332
|
+
force: options.force,
|
|
333
|
+
...collector ? { collector } : {}
|
|
334
|
+
});
|
|
335
|
+
if (executeResult.isErr()) {
|
|
336
|
+
return Result.err(toInitError(executeResult.error));
|
|
337
|
+
}
|
|
338
|
+
const postScaffoldResult = await runPostScaffold({
|
|
339
|
+
rootDir: input.rootDir,
|
|
340
|
+
projectDir,
|
|
341
|
+
origin: "init",
|
|
342
|
+
target: input.preset,
|
|
343
|
+
structure: input.structure,
|
|
344
|
+
skipInstall: Boolean(options.skipInstall),
|
|
345
|
+
skipGit: Boolean(options.skipGit),
|
|
346
|
+
skipCommit: Boolean(options.skipCommit),
|
|
347
|
+
dryRun,
|
|
348
|
+
installTimeoutMs: options.installTimeout ?? 60000
|
|
349
|
+
}, collector);
|
|
350
|
+
if (postScaffoldResult.isErr()) {
|
|
351
|
+
return Result.err(new InitError("Post-scaffold step failed"));
|
|
352
|
+
}
|
|
353
|
+
const result = {
|
|
354
|
+
structure: input.structure,
|
|
355
|
+
rootDir: input.rootDir,
|
|
356
|
+
projectDir,
|
|
357
|
+
preset: input.preset,
|
|
358
|
+
packageName: input.packageName,
|
|
359
|
+
blocksAdded: executeResult.value.blocksAdded,
|
|
360
|
+
postScaffold: postScaffoldResult.value,
|
|
361
|
+
...collector ? { dryRunPlan: collector.toJSON() } : {}
|
|
362
|
+
};
|
|
363
|
+
return Result.ok(result);
|
|
364
|
+
}
|
|
365
|
+
async function printInitResults(result, options) {
|
|
366
|
+
const structuredMode = resolveStructuredOutputMode(options?.mode);
|
|
367
|
+
if (result.dryRunPlan) {
|
|
368
|
+
if (structuredMode) {
|
|
369
|
+
await output({
|
|
370
|
+
rootDir: result.rootDir,
|
|
371
|
+
projectDir: result.projectDir,
|
|
372
|
+
structure: result.structure,
|
|
373
|
+
preset: result.preset,
|
|
374
|
+
packageName: result.packageName,
|
|
375
|
+
...result.dryRunPlan
|
|
376
|
+
}, { mode: structuredMode });
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const collector = new OperationCollector;
|
|
380
|
+
for (const op of result.dryRunPlan.operations) {
|
|
381
|
+
collector.add(op);
|
|
382
|
+
}
|
|
383
|
+
await renderOperationPlan(collector, { rootDir: result.rootDir });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (structuredMode) {
|
|
387
|
+
await output({
|
|
388
|
+
structure: result.structure,
|
|
389
|
+
rootDir: result.rootDir,
|
|
390
|
+
projectDir: result.projectDir,
|
|
391
|
+
preset: result.preset,
|
|
392
|
+
packageName: result.packageName,
|
|
393
|
+
blocksAdded: result.blocksAdded ?? null,
|
|
394
|
+
postScaffold: result.postScaffold,
|
|
395
|
+
nextSteps: result.postScaffold.nextSteps
|
|
396
|
+
}, { mode: structuredMode });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const lines = [
|
|
400
|
+
`Project initialized successfully in ${result.rootDir}`,
|
|
401
|
+
`Structure: ${result.structure}`,
|
|
402
|
+
`Preset: ${result.preset}`
|
|
403
|
+
];
|
|
404
|
+
if (result.structure === "workspace") {
|
|
405
|
+
lines.push(`Workspace project path: ${result.projectDir}`);
|
|
406
|
+
}
|
|
407
|
+
if (result.blocksAdded) {
|
|
408
|
+
const { created, skipped, dependencies, devDependencies } = result.blocksAdded;
|
|
409
|
+
if (created.length > 0) {
|
|
410
|
+
lines.push("", `Added ${created.length} tooling file(s):`);
|
|
411
|
+
for (const file of created) {
|
|
412
|
+
lines.push(` \u2713 ${file}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (skipped.length > 0) {
|
|
416
|
+
lines.push("", `Skipped ${skipped.length} existing file(s):`);
|
|
417
|
+
for (const file of skipped) {
|
|
418
|
+
lines.push(` - ${file}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const depCount = Object.keys(dependencies).length + Object.keys(devDependencies).length;
|
|
422
|
+
if (depCount > 0) {
|
|
423
|
+
lines.push("", `Added ${depCount} package(s) to package.json:`);
|
|
424
|
+
for (const [name, version] of Object.entries(dependencies)) {
|
|
425
|
+
lines.push(` + ${name}@${version}`);
|
|
426
|
+
}
|
|
427
|
+
for (const [name, version] of Object.entries(devDependencies)) {
|
|
428
|
+
lines.push(` + ${name}@${version} (dev)`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (result.postScaffold.installResult === "failed") {
|
|
433
|
+
lines.push("", `Warning: bun install failed: ${result.postScaffold.installError ?? "unknown"}`);
|
|
434
|
+
}
|
|
435
|
+
if (result.postScaffold.gitInitResult === "failed") {
|
|
436
|
+
lines.push("", `Warning: git setup failed: ${result.postScaffold.gitError ?? "unknown"}`);
|
|
437
|
+
}
|
|
438
|
+
lines.push("", "Next steps:");
|
|
439
|
+
for (const step of result.postScaffold.nextSteps) {
|
|
440
|
+
lines.push(` ${step}`);
|
|
441
|
+
}
|
|
442
|
+
await output(lines, { mode: "human" });
|
|
443
|
+
}
|
|
444
|
+
function initCommand(program) {
|
|
445
|
+
const init = program.command("init").description("Create a new Outfitter project");
|
|
446
|
+
const resolveFlags = (flags, command) => {
|
|
447
|
+
if (command) {
|
|
448
|
+
return command.optsWithGlobals();
|
|
449
|
+
}
|
|
450
|
+
return typeof flags.opts === "function" ? flags.opts() : flags;
|
|
451
|
+
};
|
|
452
|
+
const resolveLocal = (flags) => {
|
|
453
|
+
if (flags.local === true || flags.workspace === true) {
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
};
|
|
458
|
+
const resolveOutputMode = (flags) => {
|
|
459
|
+
if (flags.json) {
|
|
460
|
+
return "json";
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
};
|
|
464
|
+
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)").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");
|
|
465
|
+
withCommonOptions(init.argument("[directory]").option("-t, --template <template>", "Template to use (deprecated)")).action(async (directory, flags, command) => {
|
|
466
|
+
const targetDir = directory ?? process.cwd();
|
|
467
|
+
const resolvedFlags = resolveFlags(flags, command);
|
|
468
|
+
const mode = resolveOutputMode(resolvedFlags);
|
|
469
|
+
const outputOptions = mode ? { mode } : undefined;
|
|
470
|
+
const result = await runInit({
|
|
471
|
+
targetDir,
|
|
472
|
+
name: resolvedFlags.name,
|
|
473
|
+
bin: resolvedFlags.bin,
|
|
474
|
+
preset: resolvedFlags.preset,
|
|
475
|
+
template: resolvedFlags.template,
|
|
476
|
+
structure: resolvedFlags.structure,
|
|
477
|
+
workspaceName: resolvedFlags.workspaceName,
|
|
478
|
+
local: resolveLocal(resolvedFlags),
|
|
479
|
+
force: resolvedFlags.force ?? false,
|
|
480
|
+
with: resolvedFlags.with,
|
|
481
|
+
noTooling: resolvedFlags.noTooling,
|
|
482
|
+
yes: resolvedFlags.yes,
|
|
483
|
+
dryRun: Boolean(resolvedFlags.dryRun),
|
|
484
|
+
skipInstall: Boolean(resolvedFlags.skipInstall),
|
|
485
|
+
skipGit: Boolean(resolvedFlags.skipGit),
|
|
486
|
+
skipCommit: Boolean(resolvedFlags.skipCommit),
|
|
487
|
+
...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
|
|
488
|
+
});
|
|
489
|
+
if (result.isErr()) {
|
|
490
|
+
exitWithError(result.error, outputOptions);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
await printInitResults(result.value, outputOptions);
|
|
494
|
+
});
|
|
495
|
+
withCommonOptions(init.command("cli [directory]").description("Create a new CLI project")).action(async (directory, flags, command) => {
|
|
496
|
+
const targetDir = directory ?? process.cwd();
|
|
497
|
+
const resolvedFlags = resolveFlags(flags, command);
|
|
498
|
+
const mode = resolveOutputMode(resolvedFlags);
|
|
499
|
+
const outputOptions = mode ? { mode } : undefined;
|
|
500
|
+
const result = await runInit({
|
|
501
|
+
targetDir,
|
|
502
|
+
name: resolvedFlags.name,
|
|
503
|
+
bin: resolvedFlags.bin,
|
|
504
|
+
structure: resolvedFlags.structure,
|
|
505
|
+
workspaceName: resolvedFlags.workspaceName,
|
|
506
|
+
local: resolveLocal(resolvedFlags),
|
|
507
|
+
force: resolvedFlags.force ?? false,
|
|
508
|
+
with: resolvedFlags.with,
|
|
509
|
+
noTooling: resolvedFlags.noTooling,
|
|
510
|
+
yes: resolvedFlags.yes,
|
|
511
|
+
dryRun: Boolean(resolvedFlags.dryRun),
|
|
512
|
+
skipInstall: Boolean(resolvedFlags.skipInstall),
|
|
513
|
+
skipGit: Boolean(resolvedFlags.skipGit),
|
|
514
|
+
skipCommit: Boolean(resolvedFlags.skipCommit),
|
|
515
|
+
...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
|
|
516
|
+
}, "cli");
|
|
517
|
+
if (result.isErr()) {
|
|
518
|
+
exitWithError(result.error, outputOptions);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
await printInitResults(result.value, outputOptions);
|
|
522
|
+
});
|
|
523
|
+
withCommonOptions(init.command("mcp [directory]").description("Create a new MCP server")).action(async (directory, flags, command) => {
|
|
524
|
+
const targetDir = directory ?? process.cwd();
|
|
525
|
+
const resolvedFlags = resolveFlags(flags, command);
|
|
526
|
+
const mode = resolveOutputMode(resolvedFlags);
|
|
527
|
+
const outputOptions = mode ? { mode } : undefined;
|
|
528
|
+
const result = await runInit({
|
|
529
|
+
targetDir,
|
|
530
|
+
name: resolvedFlags.name,
|
|
531
|
+
bin: resolvedFlags.bin,
|
|
532
|
+
structure: resolvedFlags.structure,
|
|
533
|
+
workspaceName: resolvedFlags.workspaceName,
|
|
534
|
+
local: resolveLocal(resolvedFlags),
|
|
535
|
+
force: resolvedFlags.force ?? false,
|
|
536
|
+
with: resolvedFlags.with,
|
|
537
|
+
noTooling: resolvedFlags.noTooling,
|
|
538
|
+
yes: resolvedFlags.yes,
|
|
539
|
+
dryRun: Boolean(resolvedFlags.dryRun),
|
|
540
|
+
skipInstall: Boolean(resolvedFlags.skipInstall),
|
|
541
|
+
skipGit: Boolean(resolvedFlags.skipGit),
|
|
542
|
+
skipCommit: Boolean(resolvedFlags.skipCommit),
|
|
543
|
+
...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
|
|
544
|
+
}, "mcp");
|
|
545
|
+
if (result.isErr()) {
|
|
546
|
+
exitWithError(result.error, outputOptions);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
await printInitResults(result.value, outputOptions);
|
|
550
|
+
});
|
|
551
|
+
withCommonOptions(init.command("daemon [directory]").description("Create a new daemon project")).action(async (directory, flags, command) => {
|
|
552
|
+
const targetDir = directory ?? process.cwd();
|
|
553
|
+
const resolvedFlags = resolveFlags(flags, command);
|
|
554
|
+
const mode = resolveOutputMode(resolvedFlags);
|
|
555
|
+
const outputOptions = mode ? { mode } : undefined;
|
|
556
|
+
const result = await runInit({
|
|
557
|
+
targetDir,
|
|
558
|
+
name: resolvedFlags.name,
|
|
559
|
+
bin: resolvedFlags.bin,
|
|
560
|
+
structure: resolvedFlags.structure,
|
|
561
|
+
workspaceName: resolvedFlags.workspaceName,
|
|
562
|
+
local: resolveLocal(resolvedFlags),
|
|
563
|
+
force: resolvedFlags.force ?? false,
|
|
564
|
+
with: resolvedFlags.with,
|
|
565
|
+
noTooling: resolvedFlags.noTooling,
|
|
566
|
+
yes: resolvedFlags.yes,
|
|
567
|
+
dryRun: Boolean(resolvedFlags.dryRun),
|
|
568
|
+
skipInstall: Boolean(resolvedFlags.skipInstall),
|
|
569
|
+
skipGit: Boolean(resolvedFlags.skipGit),
|
|
570
|
+
skipCommit: Boolean(resolvedFlags.skipCommit),
|
|
571
|
+
...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
|
|
572
|
+
}, "daemon");
|
|
573
|
+
if (result.isErr()) {
|
|
574
|
+
exitWithError(result.error, outputOptions);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
await printInitResults(result.value, outputOptions);
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export { InitError, runInit, printInitResults, initCommand };
|