create-better-fullstack 2.1.4 → 2.1.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.
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ import fs from "fs-extra";
3
+ import path from "node:path";
4
+ import { createHash } from "node:crypto";
5
+
6
+ //#region src/utils/scaffold-manifest.ts
7
+ /**
8
+ * Scaffold baseline manifest ("bts.lock.json").
9
+ *
10
+ * Recorded once at create time (after formatting, before install) as a map of
11
+ * every on-disk file path -> sha256 of its bytes. `bfs update` later re-renders
12
+ * the project with the current templates and uses this baseline to tell apart
13
+ * three cases per file: the template moved but the file was never touched (safe
14
+ * to patch), the user edited the file (keep as-is), or both changed (conflict).
15
+ * Without a recorded baseline that distinction is impossible.
16
+ */
17
+ const SCAFFOLD_MANIFEST_FILE = "bts.lock.json";
18
+ const MANIFEST_VERSION = "1";
19
+ /** Directories never worth hashing (dependencies / VCS metadata). */
20
+ const EXCLUDED_DIR_NAMES = new Set(["node_modules", ".git"]);
21
+ /**
22
+ * Files whose on-disk bytes are NOT a pure-template render — the manifest
23
+ * itself, the config file (regenerated on update), and package-manager /
24
+ * toolchain lockfiles that install mutates. Excluding them keeps the baseline
25
+ * focused on template-comparable content so `bfs update` never mistakes an
26
+ * install artifact for template drift.
27
+ */
28
+ const EXCLUDED_FILE_NAMES = new Set([
29
+ SCAFFOLD_MANIFEST_FILE,
30
+ "bts.jsonc",
31
+ "bun.lock",
32
+ "bun.lockb",
33
+ "package-lock.json",
34
+ "pnpm-lock.yaml",
35
+ "yarn.lock",
36
+ "Cargo.lock",
37
+ "uv.lock",
38
+ "poetry.lock",
39
+ "go.sum",
40
+ "mix.lock"
41
+ ]);
42
+ function hashContent(content) {
43
+ return createHash("sha256").update(content).digest("hex");
44
+ }
45
+ function toPosixRelative(rootDir, fullPath) {
46
+ return path.relative(rootDir, fullPath).split(path.sep).join("/");
47
+ }
48
+ async function walkFiles(rootDir) {
49
+ const results = [];
50
+ async function walk(dir) {
51
+ let entries;
52
+ try {
53
+ entries = await fs.readdir(dir, { withFileTypes: true });
54
+ } catch {
55
+ return;
56
+ }
57
+ for (const entry of entries) {
58
+ const fullPath = path.join(dir, entry.name);
59
+ if (entry.isDirectory()) {
60
+ if (EXCLUDED_DIR_NAMES.has(entry.name)) continue;
61
+ await walk(fullPath);
62
+ } else if (entry.isFile()) {
63
+ if (EXCLUDED_FILE_NAMES.has(entry.name)) continue;
64
+ results.push(fullPath);
65
+ }
66
+ }
67
+ }
68
+ await walk(rootDir);
69
+ return results;
70
+ }
71
+ /** Walk the project on disk and return a deterministic path -> sha256 map. */
72
+ async function computeScaffoldHashes(projectDir) {
73
+ const files = await walkFiles(projectDir);
74
+ const entries = await Promise.all(files.map(async (fullPath) => {
75
+ const bytes = await fs.readFile(fullPath);
76
+ return [toPosixRelative(projectDir, fullPath), hashContent(bytes)];
77
+ }));
78
+ return Object.fromEntries(entries.sort(([a], [b]) => a.localeCompare(b)));
79
+ }
80
+ async function writeScaffoldManifest(projectDir, manifest) {
81
+ const sorted = {
82
+ version: manifest.version,
83
+ createdAt: manifest.createdAt,
84
+ hashes: Object.fromEntries(Object.entries(manifest.hashes).sort(([a], [b]) => a.localeCompare(b)))
85
+ };
86
+ const manifestPath = path.join(projectDir, SCAFFOLD_MANIFEST_FILE);
87
+ await fs.writeFile(manifestPath, `${JSON.stringify(sorted, null, 2)}\n`, "utf-8");
88
+ }
89
+ /**
90
+ * Record the scaffold baseline manifest for a freshly created project.
91
+ *
92
+ * Best-effort by design: any failure returns null instead of throwing, so a
93
+ * problem here can only disable `bfs update`'s auto-patching — it must never
94
+ * break the create path.
95
+ */
96
+ async function recordScaffoldManifest(projectDir, metadata = {}) {
97
+ try {
98
+ const manifest = {
99
+ version: MANIFEST_VERSION,
100
+ createdAt: metadata.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
101
+ hashes: await computeScaffoldHashes(projectDir)
102
+ };
103
+ await writeScaffoldManifest(projectDir, manifest);
104
+ return manifest;
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+ async function readScaffoldManifest(projectDir) {
110
+ try {
111
+ const manifestPath = path.join(projectDir, SCAFFOLD_MANIFEST_FILE);
112
+ if (!await fs.pathExists(manifestPath)) return null;
113
+ const raw = await fs.readFile(manifestPath, "utf-8");
114
+ const parsed = JSON.parse(raw);
115
+ if (!parsed || typeof parsed !== "object" || typeof parsed.hashes !== "object") return null;
116
+ return parsed;
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
121
+
122
+ //#endregion
123
+ export { writeScaffoldManifest as i, readScaffoldManifest as n, recordScaffoldManifest as r, hashContent as t };
@@ -0,0 +1,401 @@
1
+ #!/usr/bin/env node
2
+ import { r as readBtsConfig } from "./bts-config-BceXPcpI.mjs";
3
+ import { t as renderTitle } from "./render-title-zvyKC1ej.mjs";
4
+ import { a as handleError } from "./errors-ns_o2OKg.mjs";
5
+ import "./file-formatter-XU6ti05V.mjs";
6
+ import { i as writeScaffoldManifest, n as readScaffoldManifest, r as recordScaffoldManifest, t as hashContent } from "./scaffold-manifest-GV1fbhpD.mjs";
7
+ import { c as formatGeneratedTree, d as treeToFileMap, l as generateTree, s as configFromBtsConfig } from "./mcp-entry.mjs";
8
+ import { intro, log, outro } from "@clack/prompts";
9
+ import pc from "picocolors";
10
+ import fs from "fs-extra";
11
+ import path from "node:path";
12
+ import { tmpdir } from "node:os";
13
+ import { writeSelectedFiles } from "@better-fullstack/template-generator/fs-writer";
14
+
15
+ //#region src/helpers/core/scaffold-upgrade.ts
16
+ const BINARY_FILE_MARKER = "[Binary file]";
17
+ /**
18
+ * Files whose on-disk bytes are mutated by create-time post-processing
19
+ * (package-manager version, dependency version channel, db-setup, addons) or by
20
+ * dependency install, so their scaffold baseline is not a pure-template render.
21
+ * Never auto-patched — always routed to manual review. A structured merge
22
+ * (reusing stack-update's mergePackageJson / mergeEnvExample) is a deferred
23
+ * follow-up; the MVP is conservative to avoid clobbering post-processed deps.
24
+ */
25
+ function isStructuredMergeFile(relPath) {
26
+ const name = path.basename(relPath);
27
+ return name === "package.json" || name === ".env" || name.endsWith(".env.example") || name === "bun.lock" || name === "bun.lockb" || name === "package-lock.json" || name === "pnpm-lock.yaml" || name === "yarn.lock";
28
+ }
29
+ /**
30
+ * Generated docs (README) are re-derived from project mode / stack summary at
31
+ * render time, so their bytes legitimately differ between the create-time
32
+ * render and a later re-render even when untouched. Mirror stack-update's
33
+ * isSkippableGeneratedDoc: never auto-patch them, and never flag them as drift.
34
+ */
35
+ function isSkippableDoc(relPath) {
36
+ return path.basename(relPath).toLowerCase() === "readme.md";
37
+ }
38
+ async function inferProjectName(projectDir) {
39
+ const packageJson = await fs.readJson(path.join(projectDir, "package.json")).catch(() => null);
40
+ if (packageJson && typeof packageJson.name === "string" && packageJson.name.trim()) return packageJson.name.trim();
41
+ return path.basename(projectDir);
42
+ }
43
+ /**
44
+ * Render the project with the current bundled templates and return a
45
+ * deterministic path -> sha256 map of the formatted output. Text files hash
46
+ * their formatted content directly; binary files are materialized to a temp
47
+ * dir (mirroring how create writes them) and hashed from bytes.
48
+ */
49
+ async function computeRenderHashes(tree) {
50
+ const fileMap = treeToFileMap(tree);
51
+ const hashes = /* @__PURE__ */ new Map();
52
+ const binaryPaths = [];
53
+ for (const [filePath, file] of fileMap) if (file.content === BINARY_FILE_MARKER) binaryPaths.push(filePath);
54
+ else hashes.set(filePath, hashContent(Buffer.from(file.content, "utf-8")));
55
+ if (binaryPaths.length > 0) {
56
+ const binarySet = new Set(binaryPaths);
57
+ const tempDir = await fs.mkdtemp(path.join(tmpdir(), "bfs-update-binary-"));
58
+ try {
59
+ const written = await writeSelectedFiles(tree, tempDir, (candidate) => binarySet.has(candidate));
60
+ for (const filePath of written) {
61
+ const bytes = await fs.readFile(path.join(tempDir, filePath));
62
+ hashes.set(filePath, hashContent(bytes));
63
+ }
64
+ } finally {
65
+ await fs.rm(tempDir, {
66
+ recursive: true,
67
+ force: true
68
+ });
69
+ }
70
+ }
71
+ return hashes;
72
+ }
73
+ async function renderCurrentProject(projectDir) {
74
+ const btsConfig = await readBtsConfig(projectDir);
75
+ if (!btsConfig) return { error: `No bts.jsonc found in ${projectDir}. Is this a Better Fullstack project?` };
76
+ const currentConfig = configFromBtsConfig(btsConfig, projectDir, await inferProjectName(projectDir));
77
+ try {
78
+ const tree = await generateTree(currentConfig);
79
+ await formatGeneratedTree(tree);
80
+ return {
81
+ tree,
82
+ renderHashes: await computeRenderHashes(tree)
83
+ };
84
+ } catch (error) {
85
+ return { error: `Failed to render current templates: ${error instanceof Error ? error.message : String(error)}` };
86
+ }
87
+ }
88
+ function summarize(projectDir, files, manifest) {
89
+ const byCategory = (category) => files.filter((file) => file.category === category).map((file) => file.path);
90
+ const drift = byCategory("drift");
91
+ const newFiles = byCategory("new-file");
92
+ return {
93
+ success: true,
94
+ projectDir,
95
+ hasBaseline: manifest !== null,
96
+ baselineCreatedAt: manifest?.createdAt,
97
+ files,
98
+ unchanged: byCategory("unchanged"),
99
+ drift,
100
+ userEdited: byCategory("user-edited"),
101
+ conflicts: byCategory("conflict"),
102
+ manual: files.filter((file) => file.category === "manual"),
103
+ newFiles,
104
+ removed: byCategory("removed"),
105
+ actionable: [...drift, ...newFiles].sort()
106
+ };
107
+ }
108
+ /**
109
+ * Classify every current-template file against the on-disk project and the
110
+ * recorded scaffold baseline. Pure read-only planning — writes nothing.
111
+ */
112
+ async function planScaffoldUpgrade(projectDirInput) {
113
+ const projectDir = path.resolve(projectDirInput);
114
+ const rendered = await renderCurrentProject(projectDir);
115
+ if ("error" in rendered) return {
116
+ success: false,
117
+ projectDir,
118
+ error: rendered.error
119
+ };
120
+ const { renderHashes } = rendered;
121
+ const manifest = await readScaffoldManifest(projectDir);
122
+ const baseline = manifest?.hashes ?? {};
123
+ const hasBaseline = manifest !== null;
124
+ const files = [];
125
+ const renderPaths = [...renderHashes.keys()].sort();
126
+ for (const filePath of renderPaths) {
127
+ const renderHash = renderHashes.get(filePath);
128
+ const fullPath = path.join(projectDir, filePath);
129
+ if (!await fs.pathExists(fullPath)) {
130
+ if (baseline[filePath] !== void 0) {
131
+ files.push({
132
+ path: filePath,
133
+ category: "user-edited",
134
+ reason: "deleted locally"
135
+ });
136
+ continue;
137
+ }
138
+ files.push({
139
+ path: filePath,
140
+ category: "new-file"
141
+ });
142
+ continue;
143
+ }
144
+ const diskBytes = await fs.readFile(fullPath).catch(() => void 0);
145
+ if (!diskBytes) {
146
+ files.push({
147
+ path: filePath,
148
+ category: "manual",
149
+ reason: "unreadable on disk"
150
+ });
151
+ continue;
152
+ }
153
+ const diskHash = hashContent(diskBytes);
154
+ if (diskHash === renderHash) {
155
+ files.push({
156
+ path: filePath,
157
+ category: "unchanged"
158
+ });
159
+ continue;
160
+ }
161
+ if (isSkippableDoc(filePath)) continue;
162
+ if (isStructuredMergeFile(filePath)) {
163
+ files.push({
164
+ path: filePath,
165
+ category: "manual",
166
+ reason: "post-processed file — merge dependencies/env by hand"
167
+ });
168
+ continue;
169
+ }
170
+ const baselineHash = baseline[filePath];
171
+ if (baselineHash === void 0) {
172
+ files.push({
173
+ path: filePath,
174
+ category: "manual",
175
+ reason: hasBaseline ? "no baseline recorded for this file" : "no scaffold baseline — run `update --record-baseline` first"
176
+ });
177
+ continue;
178
+ }
179
+ if (diskHash === baselineHash) {
180
+ files.push({
181
+ path: filePath,
182
+ category: "drift"
183
+ });
184
+ continue;
185
+ }
186
+ if (renderHash === baselineHash) {
187
+ files.push({
188
+ path: filePath,
189
+ category: "user-edited"
190
+ });
191
+ continue;
192
+ }
193
+ files.push({
194
+ path: filePath,
195
+ category: "conflict",
196
+ reason: "both the template and your local copy changed"
197
+ });
198
+ }
199
+ const renderPathSet = new Set(renderPaths);
200
+ const removed = [];
201
+ for (const baselinePath of Object.keys(baseline)) {
202
+ if (renderPathSet.has(baselinePath) || isStructuredMergeFile(baselinePath)) continue;
203
+ if (await fs.pathExists(path.join(projectDir, baselinePath))) removed.push(baselinePath);
204
+ }
205
+ for (const removedPath of removed.sort()) files.push({
206
+ path: removedPath,
207
+ category: "removed",
208
+ reason: "no longer produced by the current templates (not auto-deleted)"
209
+ });
210
+ return summarize(projectDir, files, manifest);
211
+ }
212
+ /**
213
+ * Apply the safe part of the plan: overwrite template-drift files and write
214
+ * brand-new template files, then refresh the baseline for every file that now
215
+ * matches the current render. Conflicts, local edits, and post-processed files
216
+ * are left untouched (and reported by the caller for manual review).
217
+ */
218
+ async function applyScaffoldUpgrade(projectDirInput) {
219
+ const plan = await planScaffoldUpgrade(projectDirInput);
220
+ if (!plan.success) return plan;
221
+ const { projectDir } = plan;
222
+ const rendered = await renderCurrentProject(projectDir);
223
+ if ("error" in rendered) return {
224
+ success: false,
225
+ projectDir,
226
+ error: rendered.error
227
+ };
228
+ const { tree, renderHashes } = rendered;
229
+ const toWrite = new Set([...plan.drift, ...plan.newFiles]);
230
+ if (toWrite.size > 0) await writeSelectedFiles(tree, projectDir, (candidate) => toWrite.has(candidate));
231
+ const manifest = await readScaffoldManifest(projectDir);
232
+ if (manifest) {
233
+ for (const filePath of new Set([...plan.unchanged, ...toWrite])) {
234
+ const renderHash = renderHashes.get(filePath);
235
+ if (renderHash) manifest.hashes[filePath] = renderHash;
236
+ }
237
+ await writeScaffoldManifest(projectDir, manifest);
238
+ }
239
+ return {
240
+ ...plan,
241
+ applied: {
242
+ patched: [...plan.drift],
243
+ added: [...plan.newFiles]
244
+ }
245
+ };
246
+ }
247
+
248
+ //#endregion
249
+ //#region src/commands/update.ts
250
+ function formatCount(count, noun) {
251
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
252
+ }
253
+ function failUpdate(projectDir, error, json) {
254
+ if (json) {
255
+ console.log(JSON.stringify({
256
+ projectDir,
257
+ ok: false,
258
+ error
259
+ }, null, 2));
260
+ process.exit(1);
261
+ }
262
+ handleError(error);
263
+ }
264
+ function reportGroup(title, marker, entries) {
265
+ if (entries.length === 0) return;
266
+ log.message(`${title} (${entries.length}):`);
267
+ for (const entry of entries) log.message(pc.dim(` ${marker} ${entry}`));
268
+ }
269
+ function reportManual(entries) {
270
+ if (entries.length === 0) return;
271
+ log.message(`Needs manual review (${entries.length}):`);
272
+ for (const entry of entries) log.message(pc.dim(` ! ${entry.path}${entry.reason ? ` — ${entry.reason}` : ""}`));
273
+ }
274
+ function reportRemoved(plan) {
275
+ const removed = plan.files.filter((file) => file.category === "removed");
276
+ if (removed.length === 0) return;
277
+ log.message(`Removed by templates (${removed.length}):`);
278
+ for (const entry of removed) log.message(pc.dim(` - ${entry.path}${entry.reason ? ` — ${entry.reason}` : ""}`));
279
+ }
280
+ function renderPlan(plan) {
281
+ log.info(pc.dim(`Path: ${plan.projectDir}`));
282
+ log.info(pc.dim(plan.hasBaseline ? `Baseline: bts.lock.json${plan.baselineCreatedAt ? ` (recorded ${plan.baselineCreatedAt})` : ""}` : "Baseline: none — run `update --record-baseline` to enable safe auto-patching"));
283
+ log.message("");
284
+ reportGroup("Template drift (safe to patch)", "~", plan.drift);
285
+ reportGroup("New files from templates", "+", plan.newFiles);
286
+ reportGroup("Locally edited (kept as-is)", "*", plan.userEdited);
287
+ reportGroup("Conflicts (template + local both changed)", "!", plan.conflicts);
288
+ reportManual(plan.manual);
289
+ reportRemoved(plan);
290
+ log.message("");
291
+ log.message(pc.dim(`${plan.unchanged.length} up to date · ${plan.drift.length} drift · ${plan.newFiles.length} new · ${plan.userEdited.length} local · ${plan.conflicts.length} conflict · ${plan.manual.length} manual`));
292
+ }
293
+ function toJsonPlan(plan) {
294
+ return {
295
+ projectDir: plan.projectDir,
296
+ hasBaseline: plan.hasBaseline,
297
+ baselineCreatedAt: plan.baselineCreatedAt,
298
+ summary: {
299
+ unchanged: plan.unchanged.length,
300
+ drift: plan.drift.length,
301
+ newFiles: plan.newFiles.length,
302
+ userEdited: plan.userEdited.length,
303
+ conflicts: plan.conflicts.length,
304
+ manual: plan.manual.length,
305
+ removed: plan.removed.length
306
+ },
307
+ drift: plan.drift,
308
+ newFiles: plan.newFiles,
309
+ userEdited: plan.userEdited,
310
+ conflicts: plan.conflicts,
311
+ manual: plan.manual,
312
+ removed: plan.removed,
313
+ actionable: plan.actionable
314
+ };
315
+ }
316
+ async function updateCommand(input) {
317
+ const projectDir = path.resolve(input.projectDir || process.cwd());
318
+ const json = input.json ?? false;
319
+ const apply = input.apply ?? false;
320
+ const check = input.check ?? false;
321
+ const dryRun = input.dryRun ?? false;
322
+ const recordBaseline = input.recordBaseline ?? false;
323
+ if (dryRun && apply) return failUpdate(projectDir, "`--dry-run` cannot be combined with `--apply`.", json);
324
+ if (dryRun && recordBaseline) return failUpdate(projectDir, "`--dry-run` cannot be combined with `--record-baseline`.", json);
325
+ if (check && apply) return failUpdate(projectDir, "`--check` cannot be combined with `--apply`.", json);
326
+ if (check && recordBaseline) return failUpdate(projectDir, "`--check` cannot be combined with `--record-baseline`.", json);
327
+ if (apply && recordBaseline) return failUpdate(projectDir, "`--apply` cannot be combined with `--record-baseline`.", json);
328
+ if (!await readBtsConfig(projectDir)) {
329
+ const message = `No Better Fullstack project found in ${projectDir}. Make sure bts.jsonc exists.`;
330
+ if (json) {
331
+ console.log(JSON.stringify({
332
+ projectDir,
333
+ ok: false,
334
+ error: message
335
+ }, null, 2));
336
+ process.exit(1);
337
+ }
338
+ handleError(message);
339
+ }
340
+ if (recordBaseline) {
341
+ const manifest = await recordScaffoldManifest(projectDir);
342
+ if (json) {
343
+ console.log(JSON.stringify({
344
+ projectDir,
345
+ ok: manifest !== null,
346
+ recordedBaseline: manifest !== null,
347
+ files: manifest ? Object.keys(manifest.hashes).length : 0
348
+ }, null, 2));
349
+ if (!manifest) process.exit(1);
350
+ return;
351
+ }
352
+ renderTitle();
353
+ intro(pc.magenta("Record scaffold baseline"));
354
+ if (!manifest) handleError(`Failed to record a baseline for ${projectDir}.`);
355
+ log.success(pc.green(`Recorded bts.lock.json with ${formatCount(Object.keys(manifest.hashes).length, "file")}.`));
356
+ outro(pc.magenta("Baseline recorded. Future `bfs update` runs can now auto-patch template drift."));
357
+ return;
358
+ }
359
+ let plan;
360
+ let applied;
361
+ if (apply) {
362
+ const result = await applyScaffoldUpgrade(projectDir);
363
+ if (!result.success) return failUpdate(projectDir, result.error, json);
364
+ plan = result;
365
+ applied = result.applied;
366
+ } else {
367
+ const result = await planScaffoldUpgrade(projectDir);
368
+ if (!result.success) return failUpdate(projectDir, result.error, json);
369
+ plan = result;
370
+ applied = void 0;
371
+ }
372
+ if (json) {
373
+ console.log(JSON.stringify({
374
+ ...toJsonPlan(plan),
375
+ ok: true,
376
+ mode: apply ? "apply" : check ? "check" : "dry-run",
377
+ applied
378
+ }, null, 2));
379
+ if (check && plan.actionable.length > 0) process.exit(1);
380
+ return;
381
+ }
382
+ renderTitle();
383
+ intro(pc.magenta(apply ? `Updating ${pc.cyan(path.basename(projectDir))} to current templates` : `Update plan for ${pc.cyan(path.basename(projectDir))}`));
384
+ renderPlan(plan);
385
+ log.message("");
386
+ if (applied) {
387
+ if (applied.patched.length + applied.added.length === 0) log.success(pc.green("Already up to date. No template-drift patches to apply."));
388
+ else log.success(pc.green(`Applied ${formatCount(applied.patched.length, "patch")} and added ${formatCount(applied.added.length, "file")}.`));
389
+ const leftover = plan.conflicts.length + plan.manual.length;
390
+ if (leftover > 0) log.warn(pc.yellow(`${formatCount(leftover, "file")} still need manual review (conflicts + post-processed files).`));
391
+ outro(pc.magenta("Update complete."));
392
+ return;
393
+ }
394
+ if (plan.actionable.length === 0) log.success(pc.green("Up to date with the current templates."));
395
+ else log.info(pc.cyan(`Run \`bfs update --apply\` to patch ${formatCount(plan.drift.length, "drift file")} and add ${formatCount(plan.newFiles.length, "new file")}.`));
396
+ outro(pc.magenta(apply ? "Update complete." : "Dry run — no files were written."));
397
+ if (check && plan.actionable.length > 0) process.exit(1);
398
+ }
399
+
400
+ //#endregion
401
+ export { updateCommand };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-fullstack",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "Scaffold production-ready fullstack apps in seconds. Pick your stack from 425 options — the CLI wires everything together.",
5
5
  "keywords": [
6
6
  "algolia",
@@ -128,8 +128,8 @@
128
128
  "prepublishOnly": "npm run build"
129
129
  },
130
130
  "dependencies": {
131
- "@better-fullstack/template-generator": "^2.1.4",
132
- "@better-fullstack/types": "^2.1.4",
131
+ "@better-fullstack/template-generator": "^2.1.6",
132
+ "@better-fullstack/types": "^2.1.6",
133
133
  "@clack/core": "^0.5.0",
134
134
  "@clack/prompts": "^1.6.0",
135
135
  "@orpc/server": "^1.14.6",
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./errors-Cyol8zbN.mjs";
3
- import { t as setupAddons } from "./addons-setup-CyrP1IV-.mjs";
4
-
5
- export { setupAddons };
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./errors-Cyol8zbN.mjs";
3
- import "./file-formatter-B3dsev2l.mjs";
4
- import { i as startMcpServer, n as MCP_STACK_UPDATE_SCHEMA, r as getMcpGraphPreview, t as MCP_PLAN_CREATE_SCHEMA } from "./mcp-entry.mjs";
5
-
6
- export { startMcpServer };
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./errors-Cyol8zbN.mjs";
3
- import { a as createBtsCli, c as history, d as telemetry, i as create, l as router, n as builder, o as docs, r as check, s as doctor, t as add, u as sponsors } from "./run-BYse4yJy.mjs";
4
- import "./render-title-zvyKC1ej.mjs";
5
- import "./addons-setup-CyrP1IV-.mjs";
6
- import "./file-formatter-B3dsev2l.mjs";
7
- import "./install-dependencies-CgNh-aOy.mjs";
8
- import "./generated-checks-C8hn9w2i.mjs";
9
-
10
- export { createBtsCli };