outfitter 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +215 -116
  2. package/dist/actions.d.ts +2 -0
  3. package/dist/actions.js +34 -0
  4. package/dist/cli.js +29 -5
  5. package/dist/commands/add.d.ts +54 -0
  6. package/dist/commands/add.js +16 -0
  7. package/dist/commands/check.d.ts +91 -0
  8. package/dist/commands/check.js +14 -0
  9. package/dist/commands/demo.d.ts +21 -0
  10. package/dist/commands/demo.js +8 -0
  11. package/dist/commands/docs-module-loader.d.ts +2 -0
  12. package/dist/commands/docs-module-loader.js +8 -0
  13. package/dist/commands/doctor.d.ts +2 -0
  14. package/dist/commands/doctor.js +13 -0
  15. package/dist/commands/init.d.ts +7 -0
  16. package/dist/commands/init.js +31 -0
  17. package/dist/commands/migrate-kit.d.ts +2 -0
  18. package/dist/commands/migrate-kit.js +15 -0
  19. package/dist/commands/repo.d.ts +3 -0
  20. package/dist/commands/repo.js +9 -0
  21. package/dist/commands/scaffold.d.ts +4 -0
  22. package/dist/commands/scaffold.js +31 -0
  23. package/dist/commands/shared-deps.d.ts +36 -0
  24. package/dist/commands/shared-deps.js +10 -0
  25. package/dist/commands/update-planner.d.ts +58 -0
  26. package/dist/commands/update-planner.js +8 -0
  27. package/dist/commands/update-workspace.d.ts +76 -0
  28. package/dist/commands/update-workspace.js +16 -0
  29. package/dist/commands/update.d.ts +113 -0
  30. package/dist/commands/update.js +21 -0
  31. package/dist/create/index.d.ts +5 -0
  32. package/dist/create/index.js +29 -0
  33. package/dist/create/planner.d.ts +3 -0
  34. package/dist/create/planner.js +21 -0
  35. package/dist/create/presets.d.ts +3 -0
  36. package/dist/create/presets.js +12 -0
  37. package/dist/create/types.d.ts +2 -0
  38. package/dist/create/types.js +1 -0
  39. package/dist/engine/blocks.d.ts +3 -0
  40. package/dist/engine/blocks.js +12 -0
  41. package/dist/engine/collector.d.ts +2 -0
  42. package/dist/engine/collector.js +8 -0
  43. package/dist/engine/config.d.ts +3 -0
  44. package/dist/engine/config.js +12 -0
  45. package/dist/engine/executor.d.ts +3 -0
  46. package/dist/engine/executor.js +16 -0
  47. package/dist/engine/index.d.ts +8 -0
  48. package/dist/engine/index.js +59 -0
  49. package/dist/engine/names.d.ts +2 -0
  50. package/dist/engine/names.js +16 -0
  51. package/dist/engine/post-scaffold.d.ts +3 -0
  52. package/dist/engine/post-scaffold.js +8 -0
  53. package/dist/engine/render-plan.d.ts +7 -0
  54. package/dist/engine/render-plan.js +9 -0
  55. package/dist/engine/template.d.ts +3 -0
  56. package/dist/engine/template.js +17 -0
  57. package/dist/engine/types.d.ts +2 -0
  58. package/dist/engine/types.js +8 -0
  59. package/dist/engine/workspace.d.ts +3 -0
  60. package/dist/engine/workspace.js +13 -0
  61. package/dist/index.d.ts +265 -50
  62. package/dist/index.js +157 -3
  63. package/dist/manifest.d.ts +71 -0
  64. package/dist/manifest.js +16 -0
  65. package/dist/output-mode.d.ts +2 -0
  66. package/dist/output-mode.js +10 -0
  67. package/dist/shared/chunk-b0y0cwkr.js +5533 -0
  68. package/dist/shared/outfitter-193jvzg4.d.ts +5 -0
  69. package/dist/shared/outfitter-1dd0k853.js +194 -0
  70. package/dist/shared/outfitter-1h7k8xxt.js +29 -0
  71. package/dist/shared/outfitter-1qwpjt6w.js +125 -0
  72. package/dist/shared/outfitter-2ngep1h2.d.ts +5 -0
  73. package/dist/shared/outfitter-2np85etz.js +95 -0
  74. package/dist/shared/outfitter-33w361tc.d.ts +18 -0
  75. package/dist/shared/outfitter-344t1r38.js +1 -0
  76. package/dist/shared/outfitter-3weh61w7.d.ts +25 -0
  77. package/dist/shared/outfitter-4s9meh3j.js +221 -0
  78. package/dist/shared/outfitter-6a4bq054.js +322 -0
  79. package/dist/shared/outfitter-6bkqjk86.d.ts +3 -0
  80. package/dist/shared/outfitter-6gc3g5wk.js +98 -0
  81. package/dist/shared/outfitter-7cv5fg1m.js +61 -0
  82. package/dist/shared/outfitter-7ha7p61k.d.ts +6 -0
  83. package/dist/shared/outfitter-7r12fj7f.js +30 -0
  84. package/dist/shared/outfitter-8y2dfx6n.js +11 -0
  85. package/dist/shared/outfitter-9c8edfsn.js +715 -0
  86. package/dist/shared/outfitter-9x1brcmq.js +184 -0
  87. package/dist/shared/outfitter-a79xrm12.d.ts +17 -0
  88. package/dist/shared/outfitter-amc4jbs1.d.ts +50 -0
  89. package/dist/shared/outfitter-ara3djt0.js +73 -0
  90. package/dist/shared/outfitter-avhm5z6w.js +82 -0
  91. package/dist/shared/outfitter-b5nd42y4.d.ts +45 -0
  92. package/dist/shared/outfitter-dd0btgec.d.ts +40 -0
  93. package/dist/shared/outfitter-e2zz5wv7.d.ts +51 -0
  94. package/dist/shared/outfitter-ehp18x1n.js +1 -0
  95. package/dist/shared/outfitter-fnsmx3xg.js +750 -0
  96. package/dist/shared/outfitter-gdvm5c0b.d.ts +4 -0
  97. package/dist/shared/outfitter-gp4v5gkf.js +322 -0
  98. package/dist/shared/outfitter-h1mnzzd1.d.ts +14 -0
  99. package/dist/shared/outfitter-hpymx4m9.js +184 -0
  100. package/dist/shared/outfitter-hvsaxgcp.js +1 -0
  101. package/dist/shared/outfitter-j8yc7294.d.ts +22 -0
  102. package/dist/shared/outfitter-jyxwznk1.js +404 -0
  103. package/dist/shared/outfitter-k112c427.js +21 -0
  104. package/dist/shared/outfitter-k56rmt24.d.ts +30 -0
  105. package/dist/shared/outfitter-ksa1pp4t.d.ts +4 -0
  106. package/dist/shared/outfitter-mdt37hqm.js +4 -0
  107. package/dist/shared/outfitter-mtbpabf3.js +91 -0
  108. package/dist/shared/outfitter-nm4m0v6x.d.ts +131 -0
  109. package/dist/shared/outfitter-nmeecf1b.js +531 -0
  110. package/dist/shared/outfitter-npyfbdmc.d.ts +6 -0
  111. package/dist/shared/outfitter-pxt58tsq.js +582 -0
  112. package/dist/shared/outfitter-q9agarmb.js +42 -0
  113. package/dist/shared/outfitter-qfgj5xpq.js +70 -0
  114. package/dist/shared/outfitter-qfh36ddg.d.ts +66 -0
  115. package/dist/shared/outfitter-s6k8y2p4.js +269 -0
  116. package/dist/shared/outfitter-sftf1s26.js +199 -0
  117. package/dist/shared/outfitter-sg7ncy4a.d.ts +51 -0
  118. package/dist/shared/outfitter-sgtq57qr.d.ts +5 -0
  119. package/dist/shared/outfitter-txre6cdn.d.ts +60 -0
  120. package/dist/shared/outfitter-vh4xgb93.js +35 -0
  121. package/dist/shared/outfitter-ya44h1km.js +191 -0
  122. package/dist/shared/outfitter-zwyvewr1.js +36 -0
  123. package/dist/targets/index.d.ts +4 -0
  124. package/dist/targets/index.js +29 -0
  125. package/dist/targets/registry.d.ts +3 -0
  126. package/dist/targets/registry.js +28 -0
  127. package/dist/targets/types.d.ts +2 -0
  128. package/dist/targets/types.js +1 -0
  129. package/package.json +182 -29
  130. package/templates/basic/.lefthook.yml.template +4 -5
  131. package/templates/basic/package.json.template +10 -5
  132. package/templates/cli/.lefthook.yml.template +4 -5
  133. package/templates/cli/biome.json.template +2 -15
  134. package/templates/cli/package.json.template +11 -7
  135. package/templates/daemon/.lefthook.yml.template +4 -5
  136. package/templates/daemon/biome.json.template +2 -15
  137. package/templates/daemon/package.json.template +11 -7
  138. package/templates/mcp/.lefthook.yml.template +4 -5
  139. package/templates/mcp/biome.json.template +2 -15
  140. package/templates/mcp/package.json.template +11 -7
  141. package/templates/minimal/.gitignore.template +30 -0
  142. package/templates/minimal/.lefthook.yml.template +26 -0
  143. package/templates/minimal/package.json.template +46 -0
  144. package/templates/minimal/src/index.ts.template +26 -0
  145. package/templates/minimal/tsconfig.json.template +34 -0
  146. package/dist/shared/chunk-f3sch043.js +0 -1858
@@ -0,0 +1,221 @@
1
+ // @bun
2
+ // apps/outfitter/src/engine/post-scaffold.ts
3
+ import { relative } from "path";
4
+ import { Result } from "@outfitter/contracts";
5
+ function detectGitState(cwd) {
6
+ try {
7
+ const result = Bun.spawnSync(["git", "rev-parse", "--show-toplevel"], {
8
+ cwd,
9
+ stdout: "pipe",
10
+ stderr: "ignore"
11
+ });
12
+ return { isRepo: result.exitCode === 0 };
13
+ } catch {
14
+ return { isRepo: false };
15
+ }
16
+ }
17
+ async function runBunInstall(cwd, timeoutMs) {
18
+ try {
19
+ const proc = Bun.spawn(["bun", "install"], {
20
+ cwd,
21
+ stdout: "pipe",
22
+ stderr: "pipe"
23
+ });
24
+ const timeoutPromise = new Promise((resolveTimeout) => {
25
+ const timer = setTimeout(() => resolveTimeout("timeout"), timeoutMs);
26
+ proc.exited.finally(() => clearTimeout(timer));
27
+ });
28
+ const race = await Promise.race([
29
+ proc.exited.then(() => "exit"),
30
+ timeoutPromise
31
+ ]);
32
+ if (race === "timeout") {
33
+ proc.kill();
34
+ return Result.err(`bun install timed out after ${timeoutMs}ms`);
35
+ }
36
+ const exitCode = await proc.exited;
37
+ if (exitCode !== 0) {
38
+ const stderr = await new Response(proc.stderr).text();
39
+ return Result.err(stderr.trim() || `bun install exited with code ${exitCode}`);
40
+ }
41
+ return Result.ok(undefined);
42
+ } catch (error) {
43
+ return Result.err(error instanceof Error ? error.message : "Unknown error");
44
+ }
45
+ }
46
+ function runGitInit(cwd) {
47
+ try {
48
+ const result = Bun.spawnSync(["git", "init"], {
49
+ cwd,
50
+ stdout: "pipe",
51
+ stderr: "pipe"
52
+ });
53
+ if (result.exitCode !== 0) {
54
+ return Result.err(result.stderr.toString().trim());
55
+ }
56
+ return Result.ok(undefined);
57
+ } catch (error) {
58
+ return Result.err(error instanceof Error ? error.message : "Unknown error");
59
+ }
60
+ }
61
+ function hasGitUserConfig(cwd) {
62
+ try {
63
+ const name = Bun.spawnSync(["git", "config", "--get", "user.name"], {
64
+ cwd,
65
+ stdout: "pipe",
66
+ stderr: "ignore"
67
+ });
68
+ if (name.exitCode !== 0 || name.stdout.toString().trim().length === 0) {
69
+ return false;
70
+ }
71
+ const email = Bun.spawnSync(["git", "config", "--get", "user.email"], {
72
+ cwd,
73
+ stdout: "pipe",
74
+ stderr: "ignore"
75
+ });
76
+ return email.exitCode === 0 && email.stdout.toString().trim().length > 0;
77
+ } catch {
78
+ return false;
79
+ }
80
+ }
81
+ function runGitCommit(cwd, message) {
82
+ try {
83
+ const addResult = Bun.spawnSync(["git", "add", "."], {
84
+ cwd,
85
+ stdout: "pipe",
86
+ stderr: "pipe"
87
+ });
88
+ if (addResult.exitCode !== 0) {
89
+ return Result.err(`git add failed: ${addResult.stderr.toString().trim()}`);
90
+ }
91
+ const commitResult = Bun.spawnSync(["git", "commit", "-m", message], {
92
+ cwd,
93
+ stdout: "pipe",
94
+ stderr: "pipe"
95
+ });
96
+ if (commitResult.exitCode !== 0) {
97
+ return Result.err(`git commit failed: ${commitResult.stderr.toString().trim()}`);
98
+ }
99
+ return Result.ok(undefined);
100
+ } catch (error) {
101
+ return Result.err(error instanceof Error ? error.message : "Unknown error");
102
+ }
103
+ }
104
+ function computeNextSteps(options, installResult) {
105
+ const steps = [];
106
+ if (options.origin === "init") {
107
+ steps.push(`cd ${JSON.stringify(options.rootDir)}`);
108
+ }
109
+ if (installResult !== "success") {
110
+ steps.push("bun install");
111
+ }
112
+ if (options.structure === "workspace") {
113
+ const relProject = relative(options.rootDir, options.projectDir) || ".";
114
+ steps.push(`bun run --cwd ${JSON.stringify(relProject)} dev`);
115
+ } else {
116
+ steps.push("bun run dev");
117
+ }
118
+ return steps;
119
+ }
120
+ async function runPostScaffold(options, collector) {
121
+ if (process.env["OUTFITTER_DISABLE_POST_SCAFFOLD"] === "1") {
122
+ return Result.ok({
123
+ installResult: "skipped",
124
+ gitInitResult: "skipped",
125
+ gitCommitResult: "skipped",
126
+ nextSteps: computeNextSteps(options, "skipped")
127
+ });
128
+ }
129
+ let installResult = "skipped";
130
+ let installError;
131
+ if (!options.skipInstall) {
132
+ if (options.dryRun) {
133
+ collector?.add({
134
+ type: "install",
135
+ command: "bun install",
136
+ cwd: options.rootDir
137
+ });
138
+ installResult = "skipped";
139
+ } else {
140
+ const result = await runBunInstall(options.rootDir, options.installTimeoutMs);
141
+ if (result.isErr()) {
142
+ installResult = "failed";
143
+ installError = result.error;
144
+ process.stderr.write(`Warning: bun install failed: ${result.error}
145
+ `);
146
+ } else {
147
+ installResult = "success";
148
+ }
149
+ }
150
+ }
151
+ let gitInitResult = "skipped";
152
+ let gitCommitResult = "skipped";
153
+ let gitError;
154
+ if (!options.skipGit && options.origin === "init") {
155
+ const gitState = detectGitState(options.rootDir);
156
+ if (options.dryRun) {
157
+ if (!gitState.isRepo) {
158
+ collector?.add({
159
+ type: "git",
160
+ action: "init",
161
+ cwd: options.rootDir
162
+ });
163
+ }
164
+ if (!options.skipCommit) {
165
+ collector?.add({
166
+ type: "git",
167
+ action: "add-all",
168
+ cwd: options.rootDir
169
+ });
170
+ collector?.add({
171
+ type: "git",
172
+ action: "commit",
173
+ cwd: options.rootDir,
174
+ message: "init: scaffold with outfitter"
175
+ });
176
+ }
177
+ gitInitResult = gitState.isRepo ? "already-repo" : "skipped";
178
+ } else {
179
+ if (gitState.isRepo) {
180
+ gitInitResult = "already-repo";
181
+ } else {
182
+ const result = runGitInit(options.rootDir);
183
+ if (result.isErr()) {
184
+ gitInitResult = "failed";
185
+ gitError = result.error;
186
+ process.stderr.write(`Warning: git init failed: ${result.error}
187
+ `);
188
+ } else {
189
+ gitInitResult = "success";
190
+ }
191
+ }
192
+ if (!options.skipCommit && (gitInitResult === "success" || gitInitResult === "already-repo")) {
193
+ if (hasGitUserConfig(options.rootDir)) {
194
+ const commitResult = runGitCommit(options.rootDir, "init: scaffold with outfitter");
195
+ if (commitResult.isErr()) {
196
+ gitCommitResult = "failed";
197
+ gitError = commitResult.error;
198
+ process.stderr.write(`Warning: ${commitResult.error}
199
+ `);
200
+ } else {
201
+ gitCommitResult = "success";
202
+ }
203
+ } else {
204
+ gitCommitResult = "skipped";
205
+ process.stderr.write(`Warning: git user.name/email not configured, skipping initial commit.
206
+ `);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ return Result.ok({
212
+ installResult,
213
+ installError,
214
+ gitInitResult,
215
+ gitCommitResult,
216
+ gitError,
217
+ nextSteps: computeNextSteps(options, installResult)
218
+ });
219
+ }
220
+
221
+ export { runPostScaffold };
@@ -0,0 +1,322 @@
1
+ // @bun
2
+ // apps/outfitter/src/commands/update-workspace.ts
3
+ import { existsSync, readFileSync } from "fs";
4
+ import { basename, dirname, join, resolve } from "path";
5
+ import { InternalError, Result } from "@outfitter/contracts";
6
+ function detectWorkspaceRoot(cwd) {
7
+ let current = resolve(cwd);
8
+ const root = resolve("/");
9
+ while (true) {
10
+ if (existsSync(join(current, "pnpm-workspace.yaml"))) {
11
+ return Result.ok(current);
12
+ }
13
+ const pkgPath = join(current, "package.json");
14
+ if (existsSync(pkgPath)) {
15
+ try {
16
+ const raw = readFileSync(pkgPath, "utf-8");
17
+ const pkg = JSON.parse(raw);
18
+ if (hasWorkspacesField(pkg)) {
19
+ return Result.ok(current);
20
+ }
21
+ } catch {}
22
+ }
23
+ if (current === root) {
24
+ break;
25
+ }
26
+ const parent = dirname(current);
27
+ if (parent === current) {
28
+ break;
29
+ }
30
+ current = parent;
31
+ }
32
+ return Result.ok(null);
33
+ }
34
+ function hasWorkspacesField(pkg) {
35
+ const workspaces = pkg.workspaces;
36
+ if (Array.isArray(workspaces) && workspaces.length > 0) {
37
+ return true;
38
+ }
39
+ if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
40
+ const packages = workspaces.packages;
41
+ if (Array.isArray(packages) && packages.length > 0) {
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ function resolveWorkspacePatterns(pkg) {
48
+ const workspaces = pkg.workspaces;
49
+ let patterns;
50
+ if (Array.isArray(workspaces)) {
51
+ patterns = workspaces;
52
+ } else if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
53
+ const packages = workspaces.packages;
54
+ if (Array.isArray(packages)) {
55
+ patterns = packages;
56
+ } else {
57
+ return [];
58
+ }
59
+ } else {
60
+ return [];
61
+ }
62
+ return patterns.filter((p) => typeof p === "string").map(normalizeWorkspacePattern);
63
+ }
64
+ function normalizeWorkspacePattern(pattern) {
65
+ let value = pattern.trim().replaceAll("\\", "/");
66
+ if (value.length === 0)
67
+ return value;
68
+ if (value.endsWith("/")) {
69
+ value = value.slice(0, -1);
70
+ }
71
+ if (value.endsWith("package.json")) {
72
+ return value;
73
+ }
74
+ return `${value}/package.json`;
75
+ }
76
+ function collectWorkspaceManifests(rootDir) {
77
+ const resolvedRoot = resolve(rootDir);
78
+ const rootPackageJson = join(resolvedRoot, "package.json");
79
+ if (!existsSync(rootPackageJson)) {
80
+ return Result.err(InternalError.create("No package.json found at workspace root", {
81
+ rootDir: resolvedRoot
82
+ }));
83
+ }
84
+ let pkg;
85
+ try {
86
+ const raw = readFileSync(rootPackageJson, "utf-8");
87
+ pkg = JSON.parse(raw);
88
+ } catch {
89
+ return Result.err(InternalError.create("Invalid JSON in root package.json", {
90
+ rootDir: resolvedRoot
91
+ }));
92
+ }
93
+ const workspacePatterns = resolveWorkspacePatterns(pkg);
94
+ const files = new Set([rootPackageJson]);
95
+ for (const pattern of workspacePatterns) {
96
+ if (pattern.length === 0)
97
+ continue;
98
+ const glob = new Bun.Glob(pattern);
99
+ for (const entry of glob.scanSync({ cwd: resolvedRoot })) {
100
+ const absolute = resolve(resolvedRoot, entry);
101
+ if (existsSync(absolute) && basename(absolute) === "package.json") {
102
+ files.add(absolute);
103
+ }
104
+ }
105
+ }
106
+ return Result.ok(Array.from(files).sort((a, b) => a.localeCompare(b)));
107
+ }
108
+ function extractOutfitterDeps(manifestPath) {
109
+ let pkg;
110
+ try {
111
+ const raw = readFileSync(manifestPath, "utf-8");
112
+ pkg = JSON.parse(raw);
113
+ } catch {
114
+ return Result.err(InternalError.create("Failed to parse package.json", {
115
+ path: manifestPath
116
+ }));
117
+ }
118
+ const deps = {
119
+ ...pkg.dependencies ?? {},
120
+ ...pkg.devDependencies ?? {}
121
+ };
122
+ const packages = [];
123
+ for (const [name, version] of Object.entries(deps)) {
124
+ if (!name.startsWith("@outfitter/"))
125
+ continue;
126
+ if (version.startsWith("workspace:")) {
127
+ const wsVersion = version.slice("workspace:".length);
128
+ if (wsVersion === "*" || wsVersion === "~" || wsVersion === "^") {
129
+ continue;
130
+ }
131
+ const wsClean = wsVersion.replace(/^[\^~>=<]+/, "");
132
+ try {
133
+ if (!Bun.semver.satisfies(wsClean, "*"))
134
+ continue;
135
+ } catch {
136
+ continue;
137
+ }
138
+ packages.push({ name, version: wsClean });
139
+ continue;
140
+ }
141
+ const cleaned = version.replace(/^[\^~>=<]+/, "");
142
+ try {
143
+ if (!Bun.semver.satisfies(cleaned, "*"))
144
+ continue;
145
+ } catch {
146
+ continue;
147
+ }
148
+ packages.push({ name, version: cleaned });
149
+ }
150
+ return Result.ok(packages);
151
+ }
152
+ function getInstalledPackagesFromWorkspace(rootDir) {
153
+ const resolvedRoot = resolve(rootDir);
154
+ const wsRootResult = detectWorkspaceRoot(resolvedRoot);
155
+ if (wsRootResult.isErr())
156
+ return wsRootResult;
157
+ const effectiveRoot = wsRootResult.value ?? resolvedRoot;
158
+ let manifestPaths;
159
+ if (wsRootResult.value !== null) {
160
+ const manifestsResult = collectWorkspaceManifests(effectiveRoot);
161
+ if (manifestsResult.isErr())
162
+ return manifestsResult;
163
+ manifestPaths = manifestsResult.value;
164
+ } else {
165
+ const rootPkg = join(resolvedRoot, "package.json");
166
+ if (!existsSync(rootPkg)) {
167
+ return Result.err(InternalError.create("No package.json found", { cwd: resolvedRoot }));
168
+ }
169
+ manifestPaths = [rootPkg];
170
+ }
171
+ const packageVersionMap = new Map;
172
+ const manifestsByPackage = new Map;
173
+ for (const manifestPath of manifestPaths) {
174
+ const depsResult = extractOutfitterDeps(manifestPath);
175
+ if (depsResult.isErr())
176
+ return depsResult;
177
+ const deps = depsResult.value;
178
+ for (const dep of deps) {
179
+ let versionMap = packageVersionMap.get(dep.name);
180
+ if (!versionMap) {
181
+ versionMap = new Map;
182
+ packageVersionMap.set(dep.name, versionMap);
183
+ }
184
+ let manifests = versionMap.get(dep.version);
185
+ if (!manifests) {
186
+ manifests = [];
187
+ versionMap.set(dep.version, manifests);
188
+ }
189
+ manifests.push(manifestPath);
190
+ let pkgManifests = manifestsByPackage.get(dep.name);
191
+ if (!pkgManifests) {
192
+ pkgManifests = [];
193
+ manifestsByPackage.set(dep.name, pkgManifests);
194
+ }
195
+ pkgManifests.push(manifestPath);
196
+ }
197
+ }
198
+ const packages = [];
199
+ const conflicts = [];
200
+ for (const [name, versionMap] of packageVersionMap) {
201
+ const versions = Array.from(versionMap.entries()).map(([version, manifests]) => ({ version, manifests })).sort((a, b) => {
202
+ try {
203
+ return Bun.semver.order(a.version, b.version);
204
+ } catch {
205
+ return a.version.localeCompare(b.version);
206
+ }
207
+ });
208
+ if (versions.length > 1) {
209
+ conflicts.push({ name, versions });
210
+ }
211
+ const lowest = versions[0];
212
+ if (lowest) {
213
+ packages.push({ name, version: lowest.version });
214
+ }
215
+ }
216
+ packages.sort((a, b) => a.name.localeCompare(b.name));
217
+ return Result.ok({
218
+ packages,
219
+ conflicts,
220
+ manifestsByPackage,
221
+ manifestPaths,
222
+ workspaceRoot: wsRootResult.value
223
+ });
224
+ }
225
+ async function applyUpdatesToWorkspace(manifestPaths, manifestsByPackage, updates) {
226
+ const updateMap = new Map;
227
+ for (const u of updates) {
228
+ updateMap.set(u.name, u.latestVersion);
229
+ }
230
+ const manifestsToUpdate = new Set;
231
+ for (const u of updates) {
232
+ const manifests = manifestsByPackage.get(u.name);
233
+ if (manifests) {
234
+ for (const m of manifests) {
235
+ manifestsToUpdate.add(m);
236
+ }
237
+ }
238
+ }
239
+ for (const manifestPath of manifestPaths) {
240
+ if (!manifestsToUpdate.has(manifestPath))
241
+ continue;
242
+ let raw;
243
+ try {
244
+ raw = readFileSync(manifestPath, "utf-8");
245
+ } catch {
246
+ return Result.err(InternalError.create("Failed to read package.json for apply", {
247
+ path: manifestPath
248
+ }));
249
+ }
250
+ let pkg;
251
+ try {
252
+ pkg = JSON.parse(raw);
253
+ } catch {
254
+ return Result.err(InternalError.create("Invalid JSON in package.json", {
255
+ path: manifestPath
256
+ }));
257
+ }
258
+ let changed = false;
259
+ for (const section of ["dependencies", "devDependencies"]) {
260
+ const deps = pkg[section];
261
+ if (!deps)
262
+ continue;
263
+ for (const name of Object.keys(deps)) {
264
+ const newVersion = updateMap.get(name);
265
+ if (newVersion === undefined)
266
+ continue;
267
+ const currentSpecifier = deps[name];
268
+ if (currentSpecifier === undefined)
269
+ continue;
270
+ if (currentSpecifier.startsWith("workspace:") && ["*", "~", "^"].includes(currentSpecifier.slice("workspace:".length))) {
271
+ continue;
272
+ }
273
+ const prefix = getVersionPrefix(currentSpecifier);
274
+ deps[name] = `${prefix}${newVersion}`;
275
+ changed = true;
276
+ }
277
+ }
278
+ if (changed) {
279
+ try {
280
+ const updated = `${JSON.stringify(pkg, null, 2)}
281
+ `;
282
+ await Bun.write(manifestPath, updated);
283
+ } catch {
284
+ return Result.err(InternalError.create("Failed to write updated package.json", {
285
+ path: manifestPath
286
+ }));
287
+ }
288
+ }
289
+ }
290
+ return Result.ok(undefined);
291
+ }
292
+ function getVersionPrefix(specifier) {
293
+ if (specifier.startsWith("workspace:")) {
294
+ const inner = specifier.slice("workspace:".length);
295
+ return `workspace:${getVersionPrefix(inner)}`;
296
+ }
297
+ const match = specifier.match(/^([\^~>=<]+)/);
298
+ return match?.[1] ?? "";
299
+ }
300
+ async function runInstall(cwd) {
301
+ try {
302
+ const proc = Bun.spawn(["bun", "install"], {
303
+ cwd,
304
+ stdout: "pipe",
305
+ stderr: "pipe"
306
+ });
307
+ const exitCode = await proc.exited;
308
+ if (exitCode !== 0) {
309
+ const stderr = await new Response(proc.stderr).text();
310
+ return Result.err(InternalError.create("bun install failed", {
311
+ cwd,
312
+ exitCode,
313
+ stderr: stderr.trim()
314
+ }));
315
+ }
316
+ } catch {
317
+ return Result.err(InternalError.create("Failed to run bun install", { cwd }));
318
+ }
319
+ return Result.ok(undefined);
320
+ }
321
+
322
+ export { detectWorkspaceRoot, collectWorkspaceManifests, getInstalledPackagesFromWorkspace, applyUpdatesToWorkspace, runInstall };
@@ -0,0 +1,3 @@
1
+ import { ActionRegistry } from "@outfitter/contracts";
2
+ declare const outfitterActions: ActionRegistry;
3
+ export { outfitterActions };
@@ -0,0 +1,98 @@
1
+ // @bun
2
+ import {
3
+ resolveStructuredOutputMode
4
+ } from "./outfitter-7r12fj7f.js";
5
+
6
+ // apps/outfitter/src/engine/render-plan.ts
7
+ import { relative } from "path";
8
+ import { output } from "@outfitter/cli/output";
9
+ async function renderOperationPlan(collector, options) {
10
+ const structuredMode = resolveStructuredOutputMode(options?.mode);
11
+ if (structuredMode) {
12
+ await output(collector.toJSON(), { mode: structuredMode });
13
+ return;
14
+ }
15
+ const rootDir = options?.rootDir ?? process.cwd();
16
+ const operations = collector.getOperations();
17
+ const lines = ["[dry-run] Operation plan:", ""];
18
+ const fileCreates = operations.filter((op) => op.type === "file-create");
19
+ const fileOverwrites = operations.filter((op) => op.type === "file-overwrite");
20
+ const fileSkips = operations.filter((op) => op.type === "file-skip");
21
+ const dirCreates = operations.filter((op) => op.type === "dir-create");
22
+ const blockAdds = operations.filter((op) => op.type === "block-add");
23
+ const depAdds = operations.filter((op) => op.type === "dependency-add");
24
+ const configInjects = operations.filter((op) => op.type === "config-inject");
25
+ const installOps = operations.filter((op) => op.type === "install");
26
+ const gitOps = operations.filter((op) => op.type === "git");
27
+ if (fileCreates.length > 0) {
28
+ lines.push(`Create ${fileCreates.length} file(s):`);
29
+ for (const op of fileCreates) {
30
+ lines.push(` + ${relative(rootDir, op.path)}`);
31
+ }
32
+ lines.push("");
33
+ }
34
+ if (fileOverwrites.length > 0) {
35
+ lines.push(`Overwrite ${fileOverwrites.length} file(s):`);
36
+ for (const op of fileOverwrites) {
37
+ lines.push(` ~ ${relative(rootDir, op.path)}`);
38
+ }
39
+ lines.push("");
40
+ }
41
+ if (fileSkips.length > 0) {
42
+ lines.push(`Skip ${fileSkips.length} file(s):`);
43
+ for (const op of fileSkips) {
44
+ lines.push(` - ${relative(rootDir, op.path)} (${op.reason})`);
45
+ }
46
+ lines.push("");
47
+ }
48
+ if (dirCreates.length > 0) {
49
+ lines.push(`Create ${dirCreates.length} directory(ies):`);
50
+ for (const op of dirCreates) {
51
+ lines.push(` + ${relative(rootDir, op.path)}/`);
52
+ }
53
+ lines.push("");
54
+ }
55
+ if (depAdds.length > 0) {
56
+ lines.push(`Add ${depAdds.length} dependency(ies):`);
57
+ for (const dep of depAdds) {
58
+ const suffix = dep.section === "devDependencies" ? " (dev)" : "";
59
+ lines.push(` + ${dep.name}@${dep.version}${suffix}`);
60
+ }
61
+ lines.push("");
62
+ }
63
+ if (blockAdds.length > 0) {
64
+ lines.push(`Add ${blockAdds.length} block(s):`);
65
+ for (const block of blockAdds) {
66
+ lines.push(` + ${block.name}`);
67
+ }
68
+ lines.push("");
69
+ }
70
+ if (configInjects.length > 0) {
71
+ lines.push(`Config injections (${configInjects.length}):`);
72
+ for (const op of configInjects) {
73
+ lines.push(` ~ ${relative(rootDir, op.target)} (${op.description})`);
74
+ }
75
+ lines.push("");
76
+ }
77
+ if (installOps.length > 0 || gitOps.length > 0) {
78
+ lines.push("Post-scaffold:");
79
+ for (const op of installOps) {
80
+ lines.push(` $ ${op.command}`);
81
+ }
82
+ for (const op of gitOps) {
83
+ if (op.action === "init") {
84
+ lines.push(" $ git init");
85
+ } else if (op.action === "add-all") {
86
+ lines.push(" $ git add .");
87
+ } else {
88
+ lines.push(` $ git commit -m "${op.message ?? ""}"`);
89
+ }
90
+ }
91
+ lines.push("");
92
+ }
93
+ const summary = collector.countByType();
94
+ lines.push(`Total operations: ${Object.values(summary).reduce((a, b) => a + b, 0)}`);
95
+ await output(lines, { mode: "human" });
96
+ }
97
+
98
+ export { renderOperationPlan };
@@ -0,0 +1,61 @@
1
+ // @bun
2
+ import {
3
+ runAdd
4
+ } from "./outfitter-s6k8y2p4.js";
5
+ import {
6
+ ScaffoldError
7
+ } from "./outfitter-8y2dfx6n.js";
8
+
9
+ // apps/outfitter/src/engine/blocks.ts
10
+ import { Result } from "@outfitter/contracts";
11
+ async function addBlocks(targetDir, blocks, options) {
12
+ const mergedResult = {
13
+ created: [],
14
+ skipped: [],
15
+ overwritten: [],
16
+ dependencies: {},
17
+ devDependencies: {}
18
+ };
19
+ for (const blockName of blocks) {
20
+ const result = await runAdd({
21
+ block: blockName,
22
+ force: options.force,
23
+ dryRun: Boolean(options.collector),
24
+ cwd: targetDir
25
+ });
26
+ if (result.isErr()) {
27
+ return Result.err(new ScaffoldError(`Failed to add block '${blockName}': ${result.error.message}`));
28
+ }
29
+ if (options.collector) {
30
+ options.collector.add({
31
+ type: "block-add",
32
+ name: blockName,
33
+ files: [...result.value.created, ...result.value.overwritten]
34
+ });
35
+ for (const [name, version] of Object.entries(result.value.dependencies)) {
36
+ options.collector.add({
37
+ type: "dependency-add",
38
+ name,
39
+ version,
40
+ section: "dependencies"
41
+ });
42
+ }
43
+ for (const [name, version] of Object.entries(result.value.devDependencies)) {
44
+ options.collector.add({
45
+ type: "dependency-add",
46
+ name,
47
+ version,
48
+ section: "devDependencies"
49
+ });
50
+ }
51
+ }
52
+ mergedResult.created.push(...result.value.created);
53
+ mergedResult.skipped.push(...result.value.skipped);
54
+ mergedResult.overwritten.push(...result.value.overwritten);
55
+ Object.assign(mergedResult.dependencies, result.value.dependencies);
56
+ Object.assign(mergedResult.devDependencies, result.value.devDependencies);
57
+ }
58
+ return Result.ok(mergedResult);
59
+ }
60
+
61
+ export { addBlocks };