outfitter 0.2.2 → 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.
- package/README.md +215 -116
- package/dist/actions.d.ts +2 -0
- package/dist/actions.js +34 -0
- package/dist/cli.js +3 -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/update-planner.d.ts +58 -0
- package/dist/commands/update-planner.js +8 -0
- package/dist/commands/update-workspace.d.ts +76 -0
- package/dist/commands/update-workspace.js +16 -0
- package/dist/commands/update.d.ts +113 -0
- package/dist/commands/update.js +21 -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 +228 -152
- package/dist/index.js +144 -14
- 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-b0y0cwkr.js +5533 -0
- package/dist/shared/outfitter-193jvzg4.d.ts +5 -0
- package/dist/shared/outfitter-1dd0k853.js +194 -0
- package/dist/shared/outfitter-1h7k8xxt.js +29 -0
- package/dist/shared/outfitter-1qwpjt6w.js +125 -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-6a4bq054.js +322 -0
- package/dist/shared/outfitter-6bkqjk86.d.ts +3 -0
- package/dist/shared/outfitter-6gc3g5wk.js +98 -0
- package/dist/shared/outfitter-7cv5fg1m.js +61 -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-9c8edfsn.js +715 -0
- package/dist/shared/outfitter-9x1brcmq.js +184 -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-b5nd42y4.d.ts +45 -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-fnsmx3xg.js +750 -0
- package/dist/shared/outfitter-gdvm5c0b.d.ts +4 -0
- package/dist/shared/outfitter-gp4v5gkf.js +322 -0
- package/dist/shared/outfitter-h1mnzzd1.d.ts +14 -0
- package/dist/shared/outfitter-hpymx4m9.js +184 -0
- package/dist/shared/outfitter-hvsaxgcp.js +1 -0
- package/dist/shared/outfitter-j8yc7294.d.ts +22 -0
- package/dist/shared/outfitter-jyxwznk1.js +404 -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-mdt37hqm.js +4 -0
- package/dist/shared/outfitter-mtbpabf3.js +91 -0
- package/dist/shared/outfitter-nm4m0v6x.d.ts +131 -0
- package/dist/shared/outfitter-nmeecf1b.js +531 -0
- package/dist/shared/outfitter-npyfbdmc.d.ts +6 -0
- package/dist/shared/outfitter-pxt58tsq.js +582 -0
- package/dist/shared/outfitter-q9agarmb.js +42 -0
- package/dist/shared/outfitter-qfgj5xpq.js +70 -0
- package/dist/shared/outfitter-qfh36ddg.d.ts +66 -0
- package/dist/shared/outfitter-s6k8y2p4.js +269 -0
- package/dist/shared/outfitter-sftf1s26.js +199 -0
- package/dist/shared/outfitter-sg7ncy4a.d.ts +51 -0
- package/dist/shared/outfitter-sgtq57qr.d.ts +5 -0
- package/dist/shared/outfitter-txre6cdn.d.ts +60 -0
- package/dist/shared/outfitter-vh4xgb93.js +35 -0
- package/dist/shared/outfitter-ya44h1km.js +191 -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 +154 -37
- package/templates/minimal/.gitignore.template +30 -0
- package/templates/minimal/.lefthook.yml.template +26 -0
- package/templates/minimal/package.json.template +46 -0
- package/templates/minimal/src/index.ts.template +26 -0
- package/templates/minimal/tsconfig.json.template +34 -0
- package/dist/shared/chunk-sak1tt33.js +0 -3457
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
getScaffoldTarget
|
|
4
|
+
} from "./outfitter-1dd0k853.js";
|
|
5
|
+
import {
|
|
6
|
+
runPostScaffold
|
|
7
|
+
} from "./outfitter-4s9meh3j.js";
|
|
8
|
+
import {
|
|
9
|
+
buildWorkspaceRootPackageJson,
|
|
10
|
+
detectWorkspaceRoot,
|
|
11
|
+
scaffoldWorkspaceRoot
|
|
12
|
+
} from "./outfitter-2np85etz.js";
|
|
13
|
+
import {
|
|
14
|
+
deriveBinName,
|
|
15
|
+
deriveProjectName,
|
|
16
|
+
resolveAuthor,
|
|
17
|
+
resolveYear
|
|
18
|
+
} from "./outfitter-q9agarmb.js";
|
|
19
|
+
import {
|
|
20
|
+
executePlan
|
|
21
|
+
} from "./outfitter-1qwpjt6w.js";
|
|
22
|
+
import {
|
|
23
|
+
renderOperationPlan
|
|
24
|
+
} from "./outfitter-6gc3g5wk.js";
|
|
25
|
+
import {
|
|
26
|
+
resolveStructuredOutputMode
|
|
27
|
+
} from "./outfitter-7r12fj7f.js";
|
|
28
|
+
import {
|
|
29
|
+
OperationCollector
|
|
30
|
+
} from "./outfitter-1h7k8xxt.js";
|
|
31
|
+
|
|
32
|
+
// apps/outfitter/src/commands/scaffold.ts
|
|
33
|
+
import {
|
|
34
|
+
cpSync,
|
|
35
|
+
existsSync,
|
|
36
|
+
mkdirSync,
|
|
37
|
+
readdirSync,
|
|
38
|
+
readFileSync,
|
|
39
|
+
renameSync,
|
|
40
|
+
rmSync,
|
|
41
|
+
unlinkSync,
|
|
42
|
+
writeFileSync
|
|
43
|
+
} from "fs";
|
|
44
|
+
import { basename, dirname, join, resolve } from "path";
|
|
45
|
+
import { exitWithError, output } from "@outfitter/cli/output";
|
|
46
|
+
import { Result } from "@outfitter/contracts";
|
|
47
|
+
class ScaffoldCommandError extends Error {
|
|
48
|
+
_tag = "ScaffoldCommandError";
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "ScaffoldCommandError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function readPackageJson(path) {
|
|
55
|
+
if (!existsSync(path)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function hasWorkspacesField(pkg) {
|
|
65
|
+
const workspaces = pkg.workspaces;
|
|
66
|
+
if (Array.isArray(workspaces) && workspaces.length > 0) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
|
|
70
|
+
const packages = workspaces.packages;
|
|
71
|
+
return Array.isArray(packages) && packages.length > 0;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
function extractWorkspacePatterns(pkg) {
|
|
76
|
+
const workspaces = pkg.workspaces;
|
|
77
|
+
if (Array.isArray(workspaces)) {
|
|
78
|
+
return workspaces.filter((entry) => typeof entry === "string");
|
|
79
|
+
}
|
|
80
|
+
if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
|
|
81
|
+
const packages = workspaces.packages;
|
|
82
|
+
if (Array.isArray(packages)) {
|
|
83
|
+
return packages.filter((entry) => typeof entry === "string");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
function detectProjectStructure(cwd) {
|
|
89
|
+
const resolvedCwd = resolve(cwd);
|
|
90
|
+
const cwdPackageJsonPath = join(resolvedCwd, "package.json");
|
|
91
|
+
const cwdPkg = readPackageJson(cwdPackageJsonPath);
|
|
92
|
+
if (cwdPkg) {
|
|
93
|
+
if (hasWorkspacesField(cwdPkg)) {
|
|
94
|
+
return Result.ok({
|
|
95
|
+
kind: "workspace",
|
|
96
|
+
rootDir: resolvedCwd,
|
|
97
|
+
workspacePatterns: extractWorkspacePatterns(cwdPkg)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const wsResult2 = detectWorkspaceRoot(resolvedCwd);
|
|
101
|
+
if (wsResult2.isErr()) {
|
|
102
|
+
return Result.err(new ScaffoldCommandError(wsResult2.error.message));
|
|
103
|
+
}
|
|
104
|
+
if (wsResult2.value) {
|
|
105
|
+
const rootPkg = readPackageJson(join(wsResult2.value, "package.json"));
|
|
106
|
+
if (rootPkg) {
|
|
107
|
+
return Result.ok({
|
|
108
|
+
kind: "workspace",
|
|
109
|
+
rootDir: wsResult2.value,
|
|
110
|
+
workspacePatterns: extractWorkspacePatterns(rootPkg)
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return Result.ok({
|
|
115
|
+
kind: "single-package",
|
|
116
|
+
rootDir: resolvedCwd,
|
|
117
|
+
packageJson: cwdPkg
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const wsResult = detectWorkspaceRoot(resolvedCwd);
|
|
121
|
+
if (wsResult.isErr()) {
|
|
122
|
+
return Result.err(new ScaffoldCommandError(wsResult.error.message));
|
|
123
|
+
}
|
|
124
|
+
if (wsResult.value) {
|
|
125
|
+
const rootPkg = readPackageJson(join(wsResult.value, "package.json"));
|
|
126
|
+
if (rootPkg) {
|
|
127
|
+
return Result.ok({
|
|
128
|
+
kind: "workspace",
|
|
129
|
+
rootDir: wsResult.value,
|
|
130
|
+
workspacePatterns: extractWorkspacePatterns(rootPkg)
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return Result.ok({ kind: "none", rootDir: resolvedCwd });
|
|
135
|
+
}
|
|
136
|
+
function detectExistingCategory(pkg) {
|
|
137
|
+
if (pkg.bin) {
|
|
138
|
+
if (typeof pkg.bin === "string") {
|
|
139
|
+
return "runnable";
|
|
140
|
+
}
|
|
141
|
+
if (typeof pkg.bin === "object" && Object.keys(pkg.bin).length > 0) {
|
|
142
|
+
return "runnable";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const deps = {
|
|
146
|
+
...pkg.dependencies ?? {},
|
|
147
|
+
...pkg.devDependencies ?? {}
|
|
148
|
+
};
|
|
149
|
+
if (deps["@modelcontextprotocol/sdk"]) {
|
|
150
|
+
return "runnable";
|
|
151
|
+
}
|
|
152
|
+
return "library";
|
|
153
|
+
}
|
|
154
|
+
function ensureWorkspacePattern(rootDir, placement, dryRun, collector) {
|
|
155
|
+
const packageJsonPath = join(rootDir, "package.json");
|
|
156
|
+
const pkg = readPackageJson(packageJsonPath);
|
|
157
|
+
if (!pkg) {
|
|
158
|
+
if (dryRun) {
|
|
159
|
+
collector?.add({
|
|
160
|
+
type: "config-inject",
|
|
161
|
+
target: packageJsonPath,
|
|
162
|
+
description: `Ensure workspace pattern '${placement}/*'`
|
|
163
|
+
});
|
|
164
|
+
return Result.ok(true);
|
|
165
|
+
}
|
|
166
|
+
return Result.err(new ScaffoldCommandError("Failed to read workspace package.json"));
|
|
167
|
+
}
|
|
168
|
+
const pattern = `${placement}/*`;
|
|
169
|
+
const patterns = [...extractWorkspacePatterns(pkg)];
|
|
170
|
+
if (patterns.includes(pattern)) {
|
|
171
|
+
return Result.ok(false);
|
|
172
|
+
}
|
|
173
|
+
if (dryRun) {
|
|
174
|
+
collector?.add({
|
|
175
|
+
type: "config-inject",
|
|
176
|
+
target: packageJsonPath,
|
|
177
|
+
description: `Add workspace pattern '${pattern}'`
|
|
178
|
+
});
|
|
179
|
+
return Result.ok(true);
|
|
180
|
+
}
|
|
181
|
+
const workspaces = pkg.workspaces;
|
|
182
|
+
const nextPatterns = [...patterns, pattern];
|
|
183
|
+
const nextPkg = { ...pkg };
|
|
184
|
+
if (Array.isArray(workspaces)) {
|
|
185
|
+
nextPkg["workspaces"] = nextPatterns;
|
|
186
|
+
} else if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
|
|
187
|
+
nextPkg["workspaces"] = {
|
|
188
|
+
...workspaces,
|
|
189
|
+
packages: nextPatterns
|
|
190
|
+
};
|
|
191
|
+
} else {
|
|
192
|
+
nextPkg["workspaces"] = nextPatterns;
|
|
193
|
+
}
|
|
194
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(nextPkg, null, 2)}
|
|
195
|
+
`, "utf-8");
|
|
196
|
+
return Result.ok(true);
|
|
197
|
+
}
|
|
198
|
+
function movePath(source, destination) {
|
|
199
|
+
try {
|
|
200
|
+
renameSync(source, destination);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "EXDEV") {
|
|
203
|
+
cpSync(source, destination, { recursive: true });
|
|
204
|
+
rmSync(source, { recursive: true, force: true });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function convertToWorkspace(rootDir, existingPkg, dryRun, collector) {
|
|
211
|
+
const parentWorkspace = detectWorkspaceRoot(dirname(rootDir));
|
|
212
|
+
if (parentWorkspace.isErr()) {
|
|
213
|
+
return Result.err(new ScaffoldCommandError(parentWorkspace.error.message));
|
|
214
|
+
}
|
|
215
|
+
if (parentWorkspace.value && parentWorkspace.value !== rootDir) {
|
|
216
|
+
return Result.err(new ScaffoldCommandError(`Cannot convert to workspace: already inside workspace at '${parentWorkspace.value}'`));
|
|
217
|
+
}
|
|
218
|
+
const category = detectExistingCategory(existingPkg);
|
|
219
|
+
const placement = category === "runnable" ? "apps" : "packages";
|
|
220
|
+
const existingName = deriveProjectName(existingPkg.name ?? basename(rootDir));
|
|
221
|
+
const destinationDir = join(rootDir, placement, existingName);
|
|
222
|
+
const entries = readdirSync(rootDir);
|
|
223
|
+
const preserve = new Set([".git", "node_modules", ".outfitter", "bun.lock"]);
|
|
224
|
+
const toMove = entries.filter((entry) => !preserve.has(entry));
|
|
225
|
+
if (dryRun) {
|
|
226
|
+
collector?.add({
|
|
227
|
+
type: "dir-create",
|
|
228
|
+
path: join(rootDir, "apps")
|
|
229
|
+
});
|
|
230
|
+
collector?.add({
|
|
231
|
+
type: "dir-create",
|
|
232
|
+
path: join(rootDir, "packages")
|
|
233
|
+
});
|
|
234
|
+
for (const entry of toMove) {
|
|
235
|
+
collector?.add({
|
|
236
|
+
type: "file-overwrite",
|
|
237
|
+
path: join(destinationDir, entry),
|
|
238
|
+
source: "generated"
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
collector?.add({
|
|
242
|
+
type: "file-overwrite",
|
|
243
|
+
path: join(rootDir, "package.json"),
|
|
244
|
+
source: "generated"
|
|
245
|
+
});
|
|
246
|
+
return Result.ok({
|
|
247
|
+
movedExisting: {
|
|
248
|
+
from: rootDir,
|
|
249
|
+
to: destinationDir,
|
|
250
|
+
name: existingName
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const stagingDir = join(rootDir, `.outfitter-staging-${Date.now()}`);
|
|
255
|
+
try {
|
|
256
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
257
|
+
for (const entry of toMove) {
|
|
258
|
+
movePath(join(rootDir, entry), join(stagingDir, entry));
|
|
259
|
+
}
|
|
260
|
+
mkdirSync(join(rootDir, "apps"), { recursive: true });
|
|
261
|
+
mkdirSync(join(rootDir, "packages"), { recursive: true });
|
|
262
|
+
mkdirSync(destinationDir, { recursive: true });
|
|
263
|
+
for (const entry of toMove) {
|
|
264
|
+
movePath(join(stagingDir, entry), join(destinationDir, entry));
|
|
265
|
+
}
|
|
266
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
267
|
+
writeFileSync(join(rootDir, "package.json"), buildWorkspaceRootPackageJson(`${existingName}-workspace`), "utf-8");
|
|
268
|
+
const gitignorePath = join(rootDir, ".gitignore");
|
|
269
|
+
if (!existsSync(gitignorePath)) {
|
|
270
|
+
writeFileSync(gitignorePath, `node_modules
|
|
271
|
+
**/dist
|
|
272
|
+
.outfitter-staging-*
|
|
273
|
+
`, "utf-8");
|
|
274
|
+
}
|
|
275
|
+
const bunLockPath = join(rootDir, "bun.lock");
|
|
276
|
+
if (existsSync(bunLockPath)) {
|
|
277
|
+
unlinkSync(bunLockPath);
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
try {
|
|
281
|
+
if (existsSync(stagingDir)) {
|
|
282
|
+
const stagedEntries = readdirSync(stagingDir);
|
|
283
|
+
for (const entry of stagedEntries) {
|
|
284
|
+
movePath(join(stagingDir, entry), join(rootDir, entry));
|
|
285
|
+
}
|
|
286
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
287
|
+
}
|
|
288
|
+
} catch {}
|
|
289
|
+
return Result.err(new ScaffoldCommandError(`Workspace conversion failed: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
290
|
+
}
|
|
291
|
+
return Result.ok({
|
|
292
|
+
movedExisting: {
|
|
293
|
+
from: rootDir,
|
|
294
|
+
to: destinationDir,
|
|
295
|
+
name: existingName
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function parseBlocks(withFlag) {
|
|
300
|
+
if (!withFlag) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const blocks = withFlag.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
304
|
+
return blocks.length > 0 ? blocks : undefined;
|
|
305
|
+
}
|
|
306
|
+
function buildScaffoldPlan(target, rootDir, targetName, options) {
|
|
307
|
+
const targetDir = join(rootDir, target.placement, targetName);
|
|
308
|
+
const packageName = targetName;
|
|
309
|
+
const projectName = deriveProjectName(packageName);
|
|
310
|
+
const blocks = options.noTooling ? [] : parseBlocks(options.with) ?? [...target.defaultBlocks];
|
|
311
|
+
return {
|
|
312
|
+
values: {
|
|
313
|
+
name: projectName,
|
|
314
|
+
projectName,
|
|
315
|
+
packageName,
|
|
316
|
+
binName: deriveBinName(projectName),
|
|
317
|
+
version: "0.1.0",
|
|
318
|
+
description: `${target.description} scaffolded with Outfitter`,
|
|
319
|
+
author: resolveAuthor(),
|
|
320
|
+
year: resolveYear()
|
|
321
|
+
},
|
|
322
|
+
changes: [
|
|
323
|
+
{
|
|
324
|
+
type: "copy-template",
|
|
325
|
+
template: target.templateDir,
|
|
326
|
+
targetDir,
|
|
327
|
+
overlayBaseTemplate: true
|
|
328
|
+
},
|
|
329
|
+
{ type: "inject-shared-config" },
|
|
330
|
+
...options.local ? [{ type: "rewrite-local-dependencies", mode: "workspace" }] : [],
|
|
331
|
+
...blocks.length > 0 ? [{ type: "add-blocks", blocks }] : []
|
|
332
|
+
]
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
async function runScaffold(options) {
|
|
336
|
+
const targetResult = getScaffoldTarget(options.target);
|
|
337
|
+
if (targetResult.isErr()) {
|
|
338
|
+
return Result.err(new ScaffoldCommandError(targetResult.error.message));
|
|
339
|
+
}
|
|
340
|
+
const target = targetResult.value;
|
|
341
|
+
const targetName = deriveProjectName(options.name ?? target.id);
|
|
342
|
+
const structureResult = detectProjectStructure(options.cwd);
|
|
343
|
+
if (structureResult.isErr()) {
|
|
344
|
+
return structureResult;
|
|
345
|
+
}
|
|
346
|
+
const dryRun = options.dryRun;
|
|
347
|
+
const collector = dryRun ? new OperationCollector : undefined;
|
|
348
|
+
let rootDir = resolve(options.cwd);
|
|
349
|
+
let converted = false;
|
|
350
|
+
let movedExisting;
|
|
351
|
+
let workspacePatternsUpdated = false;
|
|
352
|
+
if (structureResult.value.kind === "workspace") {
|
|
353
|
+
rootDir = structureResult.value.rootDir;
|
|
354
|
+
const patternResult = ensureWorkspacePattern(rootDir, target.placement, dryRun, collector);
|
|
355
|
+
if (patternResult.isErr()) {
|
|
356
|
+
return patternResult;
|
|
357
|
+
}
|
|
358
|
+
workspacePatternsUpdated = patternResult.value;
|
|
359
|
+
} else if (structureResult.value.kind === "single-package") {
|
|
360
|
+
const conversionResult = convertToWorkspace(structureResult.value.rootDir, structureResult.value.packageJson, dryRun, collector);
|
|
361
|
+
if (conversionResult.isErr()) {
|
|
362
|
+
return conversionResult;
|
|
363
|
+
}
|
|
364
|
+
rootDir = structureResult.value.rootDir;
|
|
365
|
+
converted = true;
|
|
366
|
+
movedExisting = conversionResult.value.movedExisting;
|
|
367
|
+
} else {
|
|
368
|
+
const workspaceName = `${basename(rootDir)}-workspace`;
|
|
369
|
+
if (dryRun) {
|
|
370
|
+
const packageJsonPath = join(rootDir, "package.json");
|
|
371
|
+
if (existsSync(packageJsonPath) && !options.force) {
|
|
372
|
+
return Result.err(new ScaffoldCommandError(`Directory '${rootDir}' already has a package.json. Use --force to overwrite.`));
|
|
373
|
+
}
|
|
374
|
+
collector?.add({
|
|
375
|
+
type: "dir-create",
|
|
376
|
+
path: join(rootDir, "apps")
|
|
377
|
+
});
|
|
378
|
+
collector?.add({
|
|
379
|
+
type: "dir-create",
|
|
380
|
+
path: join(rootDir, "packages")
|
|
381
|
+
});
|
|
382
|
+
collector?.add(existsSync(packageJsonPath) ? {
|
|
383
|
+
type: "file-overwrite",
|
|
384
|
+
path: packageJsonPath,
|
|
385
|
+
source: "generated"
|
|
386
|
+
} : {
|
|
387
|
+
type: "file-create",
|
|
388
|
+
path: packageJsonPath,
|
|
389
|
+
source: "generated"
|
|
390
|
+
});
|
|
391
|
+
const gitignorePath = join(rootDir, ".gitignore");
|
|
392
|
+
if (options.force || !existsSync(gitignorePath)) {
|
|
393
|
+
collector?.add(existsSync(gitignorePath) ? {
|
|
394
|
+
type: "file-overwrite",
|
|
395
|
+
path: gitignorePath,
|
|
396
|
+
source: "generated"
|
|
397
|
+
} : {
|
|
398
|
+
type: "file-create",
|
|
399
|
+
path: gitignorePath,
|
|
400
|
+
source: "generated"
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
const workspaceResult = scaffoldWorkspaceRoot(rootDir, workspaceName, options.force);
|
|
405
|
+
if (workspaceResult.isErr()) {
|
|
406
|
+
return Result.err(new ScaffoldCommandError(workspaceResult.error.message));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
converted = true;
|
|
410
|
+
}
|
|
411
|
+
const targetDir = join(rootDir, target.placement, targetName);
|
|
412
|
+
if (existsSync(targetDir) && !options.force && !dryRun) {
|
|
413
|
+
return Result.err(new ScaffoldCommandError(`'${target.placement}/${targetName}/' already exists. Use --force to overwrite.`));
|
|
414
|
+
}
|
|
415
|
+
const plan = buildScaffoldPlan(target, rootDir, targetName, options);
|
|
416
|
+
const executeResult = await executePlan(plan, {
|
|
417
|
+
force: options.force,
|
|
418
|
+
...collector ? { collector } : {}
|
|
419
|
+
});
|
|
420
|
+
if (executeResult.isErr()) {
|
|
421
|
+
return Result.err(new ScaffoldCommandError(executeResult.error.message));
|
|
422
|
+
}
|
|
423
|
+
const postScaffoldResult = await runPostScaffold({
|
|
424
|
+
rootDir,
|
|
425
|
+
projectDir: targetDir,
|
|
426
|
+
origin: "scaffold",
|
|
427
|
+
target: target.id,
|
|
428
|
+
structure: "workspace",
|
|
429
|
+
skipInstall: options.skipInstall,
|
|
430
|
+
skipGit: true,
|
|
431
|
+
skipCommit: true,
|
|
432
|
+
dryRun,
|
|
433
|
+
installTimeoutMs: options.installTimeout ?? 60000
|
|
434
|
+
}, collector);
|
|
435
|
+
if (postScaffoldResult.isErr()) {
|
|
436
|
+
return Result.err(new ScaffoldCommandError("Post-scaffold step failed"));
|
|
437
|
+
}
|
|
438
|
+
return Result.ok({
|
|
439
|
+
target: target.id,
|
|
440
|
+
rootDir,
|
|
441
|
+
targetDir,
|
|
442
|
+
converted,
|
|
443
|
+
movedExisting,
|
|
444
|
+
workspacePatternsUpdated,
|
|
445
|
+
blocksAdded: executeResult.value.blocksAdded,
|
|
446
|
+
postScaffold: postScaffoldResult.value,
|
|
447
|
+
...collector ? { dryRunPlan: collector.toJSON() } : {}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
async function printScaffoldResults(result, options) {
|
|
451
|
+
const structuredMode = resolveStructuredOutputMode(options?.mode);
|
|
452
|
+
if (result.dryRunPlan) {
|
|
453
|
+
if (structuredMode) {
|
|
454
|
+
await output({
|
|
455
|
+
target: result.target,
|
|
456
|
+
rootDir: result.rootDir,
|
|
457
|
+
targetDir: result.targetDir,
|
|
458
|
+
converted: result.converted,
|
|
459
|
+
movedExisting: result.movedExisting ?? null,
|
|
460
|
+
...result.dryRunPlan
|
|
461
|
+
}, { mode: structuredMode });
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const collector = new OperationCollector;
|
|
465
|
+
for (const op of result.dryRunPlan.operations) {
|
|
466
|
+
collector.add(op);
|
|
467
|
+
}
|
|
468
|
+
await renderOperationPlan(collector, { rootDir: result.rootDir });
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (structuredMode) {
|
|
472
|
+
await output({
|
|
473
|
+
target: result.target,
|
|
474
|
+
rootDir: result.rootDir,
|
|
475
|
+
targetDir: result.targetDir,
|
|
476
|
+
converted: result.converted,
|
|
477
|
+
movedExisting: result.movedExisting ?? null,
|
|
478
|
+
workspacePatternsUpdated: result.workspacePatternsUpdated,
|
|
479
|
+
blocksAdded: result.blocksAdded ?? null,
|
|
480
|
+
postScaffold: result.postScaffold,
|
|
481
|
+
nextSteps: result.postScaffold.nextSteps
|
|
482
|
+
}, { mode: structuredMode });
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const lines = [];
|
|
486
|
+
if (result.converted) {
|
|
487
|
+
lines.push("Converted to workspace structure:");
|
|
488
|
+
if (result.movedExisting) {
|
|
489
|
+
lines.push(` Moved existing package -> ${result.movedExisting.to}`);
|
|
490
|
+
}
|
|
491
|
+
lines.push(" Created workspace root package.json");
|
|
492
|
+
lines.push("");
|
|
493
|
+
}
|
|
494
|
+
lines.push(`Scaffolded ${result.targetDir}`);
|
|
495
|
+
if (result.blocksAdded && result.blocksAdded.created.length > 0) {
|
|
496
|
+
lines.push(`Added ${result.blocksAdded.created.length} tooling file(s):`);
|
|
497
|
+
for (const created of result.blocksAdded.created) {
|
|
498
|
+
lines.push(` + ${created}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
lines.push("", "Next steps:");
|
|
502
|
+
for (const step of result.postScaffold.nextSteps) {
|
|
503
|
+
lines.push(` ${step}`);
|
|
504
|
+
}
|
|
505
|
+
await output(lines, { mode: "human" });
|
|
506
|
+
}
|
|
507
|
+
function scaffoldCommand(program) {
|
|
508
|
+
program.command("scaffold <target> [name]").description("Add a capability to an existing project").option("-f, --force", "Overwrite existing files", false).option("--skip-install", "Skip bun install", false).option("--dry-run", "Preview changes without executing", false).option("--with <blocks>", "Comma-separated tooling blocks to add").option("--no-tooling", "Skip default tooling blocks").option("--local", "Use workspace:* for @outfitter dependencies").option("--install-timeout <ms>", "bun install timeout in ms").option("--json", "Output as JSON", false).action(async (target, name, flags) => {
|
|
509
|
+
const mode = flags.json ? "json" : undefined;
|
|
510
|
+
const outputOptions = mode ? { mode } : undefined;
|
|
511
|
+
const result = await runScaffold({
|
|
512
|
+
target,
|
|
513
|
+
name,
|
|
514
|
+
force: Boolean(flags.force),
|
|
515
|
+
skipInstall: Boolean(flags.skipInstall),
|
|
516
|
+
dryRun: Boolean(flags.dryRun),
|
|
517
|
+
with: flags.with,
|
|
518
|
+
noTooling: flags.noTooling,
|
|
519
|
+
local: flags.local,
|
|
520
|
+
cwd: process.cwd(),
|
|
521
|
+
...flags.installTimeout !== undefined ? { installTimeout: flags.installTimeout } : {}
|
|
522
|
+
});
|
|
523
|
+
if (result.isErr()) {
|
|
524
|
+
exitWithError(result.error, outputOptions);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
await printScaffoldResults(result.value, outputOptions);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export { ScaffoldCommandError, runScaffold, printScaffoldResults, scaffoldCommand };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
declare function deriveProjectName(packageName: string): string;
|
|
2
|
+
declare function deriveBinName(projectName: string): string;
|
|
3
|
+
declare function resolveAuthor(): string;
|
|
4
|
+
declare function resolveYear(): string;
|
|
5
|
+
declare function resolvePackageName(targetDir: string, name?: string): string;
|
|
6
|
+
export { deriveProjectName, deriveBinName, resolveAuthor, resolveYear, resolvePackageName };
|