create-better-fullstack 2.0.3 → 2.1.1

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,280 @@
1
+ #!/usr/bin/env node
2
+ import { a as handleError, m as readBtsConfig } from "./errors-D9yiiGVq.mjs";
3
+ import { t as renderTitle } from "./render-title-zvyKC1ej.mjs";
4
+ import { t as runGeneratedChecks } from "./generated-checks-DUvVXWId.mjs";
5
+ import { intro, log, spinner } from "@clack/prompts";
6
+ import pc from "picocolors";
7
+ import fs from "fs-extra";
8
+ import path from "node:path";
9
+ import { $ } from "execa";
10
+
11
+ //#region src/commands/doctor.ts
12
+ const NON_TS_BACKEND_ECOSYSTEMS = new Set([
13
+ "go",
14
+ "rust",
15
+ "python",
16
+ "elixir",
17
+ "java",
18
+ "dotnet"
19
+ ]);
20
+ const IGNORED_DIRECTORIES = new Set([
21
+ "node_modules",
22
+ ".git",
23
+ "dist",
24
+ "build",
25
+ ".next",
26
+ ".expo",
27
+ ".turbo",
28
+ "target",
29
+ ".venv",
30
+ "deps",
31
+ "_build"
32
+ ]);
33
+ const JS_LOCKFILES = [
34
+ "bun.lock",
35
+ "bun.lockb",
36
+ "pnpm-lock.yaml",
37
+ "package-lock.json",
38
+ "yarn.lock"
39
+ ];
40
+ const NATIVE_LOCKFILES = {
41
+ rust: {
42
+ file: "Cargo.lock",
43
+ hint: "cargo build"
44
+ },
45
+ go: {
46
+ file: "go.sum",
47
+ hint: "go mod tidy"
48
+ },
49
+ python: {
50
+ file: "uv.lock",
51
+ hint: "uv sync"
52
+ },
53
+ elixir: {
54
+ file: "mix.lock",
55
+ hint: "mix deps.get"
56
+ }
57
+ };
58
+ function statusIcon(status) {
59
+ switch (status) {
60
+ case "pass": return pc.green("✓");
61
+ case "warn": return pc.yellow("!");
62
+ case "fail": return pc.red("✗");
63
+ }
64
+ }
65
+ function hasNativeChecks(config) {
66
+ if (NON_TS_BACKEND_ECOSYSTEMS.has(config.ecosystem)) return true;
67
+ return (config.stackParts ?? []).some((part) => part.source !== "provided" && part.role === "backend" && NON_TS_BACKEND_ECOSYSTEMS.has(part.ecosystem));
68
+ }
69
+ async function checkInstalledDependencies(projectDir, config) {
70
+ const checks = [];
71
+ if (await fs.pathExists(path.join(projectDir, "package.json"))) {
72
+ const lockfile = JS_LOCKFILES.find((name) => fs.existsSync(path.join(projectDir, name)));
73
+ checks.push(lockfile ? {
74
+ label: "Lockfile",
75
+ status: "pass",
76
+ detail: lockfile
77
+ } : {
78
+ label: "Lockfile",
79
+ status: "warn",
80
+ detail: "No JavaScript lockfile found at the project root"
81
+ });
82
+ const nodeModulesExists = await fs.pathExists(path.join(projectDir, "node_modules"));
83
+ checks.push(nodeModulesExists ? {
84
+ label: "node_modules",
85
+ status: "pass"
86
+ } : {
87
+ label: "node_modules",
88
+ status: "fail",
89
+ detail: `Dependencies are not installed. Run \`${config.packageManager ?? "npm"} install\`.`
90
+ });
91
+ }
92
+ const native = NATIVE_LOCKFILES[config.ecosystem];
93
+ if (native) {
94
+ const exists = await fs.pathExists(path.join(projectDir, native.file)) || await fs.pathExists(path.join(projectDir, "apps/server", native.file));
95
+ checks.push(exists ? {
96
+ label: native.file,
97
+ status: "pass"
98
+ } : {
99
+ label: native.file,
100
+ status: "warn",
101
+ detail: `Not found. Run \`${native.hint}\` to fetch dependencies.`
102
+ });
103
+ }
104
+ return checks;
105
+ }
106
+ async function findEnvExampleFiles(rootDir) {
107
+ const results = [];
108
+ async function walk(dir, depth) {
109
+ if (depth > 5) return;
110
+ let entries;
111
+ try {
112
+ entries = await fs.readdir(dir, { withFileTypes: true });
113
+ } catch {
114
+ return;
115
+ }
116
+ for (const entry of entries) if (entry.isDirectory()) {
117
+ if (IGNORED_DIRECTORIES.has(entry.name)) continue;
118
+ await walk(path.join(dir, entry.name), depth + 1);
119
+ } else if (entry.name === ".env.example") results.push(path.join(dir, entry.name));
120
+ }
121
+ await walk(rootDir, 0);
122
+ return results;
123
+ }
124
+ function parseEnvKeys(content) {
125
+ const map = /* @__PURE__ */ new Map();
126
+ for (const rawLine of content.split("\n")) {
127
+ const line = rawLine.trim();
128
+ if (!line || line.startsWith("#")) continue;
129
+ const eq = line.indexOf("=");
130
+ if (eq === -1) continue;
131
+ const key = line.slice(0, eq).trim();
132
+ if (key) map.set(key, line.slice(eq + 1).trim());
133
+ }
134
+ return map;
135
+ }
136
+ async function checkEnvFiles(projectDir) {
137
+ const checks = [];
138
+ const exampleFiles = await findEnvExampleFiles(projectDir);
139
+ for (const examplePath of exampleFiles) {
140
+ const envPath = examplePath.replace(/\.example$/, "");
141
+ const relExample = path.relative(projectDir, examplePath) || ".env.example";
142
+ const exampleKeys = parseEnvKeys(await fs.readFile(examplePath, "utf-8"));
143
+ if (exampleKeys.size === 0) continue;
144
+ if (!await fs.pathExists(envPath)) {
145
+ checks.push({
146
+ label: relExample,
147
+ status: "warn",
148
+ detail: `Missing ${path.relative(projectDir, envPath)} (copy from .env.example and fill in values)`
149
+ });
150
+ continue;
151
+ }
152
+ const envKeys = parseEnvKeys(await fs.readFile(envPath, "utf-8"));
153
+ const missing = [];
154
+ for (const key of exampleKeys.keys()) {
155
+ const value = envKeys.get(key);
156
+ if (value === void 0 || value === "") missing.push(key);
157
+ }
158
+ checks.push(missing.length > 0 ? {
159
+ label: path.relative(projectDir, envPath),
160
+ status: "warn",
161
+ detail: `Missing or empty: ${missing.join(", ")}`
162
+ } : {
163
+ label: path.relative(projectDir, envPath),
164
+ status: "pass"
165
+ });
166
+ }
167
+ return checks;
168
+ }
169
+ async function runBuildChecks(config, json) {
170
+ const checks = [];
171
+ const projectDir = config.projectDir;
172
+ const rootPackageJsonPath = path.join(projectDir, "package.json");
173
+ if (await fs.pathExists(rootPackageJsonPath)) {
174
+ if ((await fs.readJson(rootPackageJsonPath).catch(() => ({}))).scripts?.["check-types"]) {
175
+ const pm = config.packageManager ?? "npm";
176
+ const s = json ? null : spinner();
177
+ s?.start("Running type checks (check-types)...");
178
+ const result = await $({
179
+ cwd: projectDir,
180
+ reject: false,
181
+ stdout: json ? "ignore" : "inherit",
182
+ stderr: json ? "ignore" : "inherit"
183
+ })`${pm} run check-types`;
184
+ if (result.exitCode === 0) {
185
+ s?.stop("Type checks passed");
186
+ checks.push({
187
+ label: "check-types",
188
+ status: "pass"
189
+ });
190
+ } else {
191
+ s?.stop(pc.red("Type checks failed"));
192
+ checks.push({
193
+ label: "check-types",
194
+ status: "fail",
195
+ detail: `\`${pm} run check-types\` exited with code ${result.exitCode ?? `signal ${result.signal}`}`
196
+ });
197
+ }
198
+ }
199
+ }
200
+ if (hasNativeChecks(config)) if (json) checks.push({
201
+ label: "ecosystem build checks",
202
+ status: "warn",
203
+ detail: "Skipped in --json mode. Re-run without --json to execute native build checks."
204
+ });
205
+ else try {
206
+ await runGeneratedChecks(config);
207
+ checks.push({
208
+ label: "ecosystem build checks",
209
+ status: "pass"
210
+ });
211
+ } catch (error) {
212
+ checks.push({
213
+ label: "ecosystem build checks",
214
+ status: "fail",
215
+ detail: error instanceof Error ? error.message : String(error)
216
+ });
217
+ }
218
+ return checks;
219
+ }
220
+ async function doctorCommand(input) {
221
+ const projectDir = path.resolve(input.projectDir || process.cwd());
222
+ const json = input.json ?? false;
223
+ const btsConfig = await readBtsConfig(projectDir);
224
+ if (!btsConfig) {
225
+ if (json) {
226
+ console.log(JSON.stringify({
227
+ projectDir,
228
+ ok: false,
229
+ error: "No Better Fullstack project found (bts.jsonc missing or invalid)."
230
+ }, null, 2));
231
+ process.exit(1);
232
+ }
233
+ handleError(`No Better Fullstack project found in ${projectDir}. Make sure bts.jsonc exists.`);
234
+ }
235
+ const config = {
236
+ ...btsConfig,
237
+ projectDir
238
+ };
239
+ if (!json) {
240
+ renderTitle();
241
+ intro(pc.magenta(`Diagnosing ${pc.cyan(path.basename(projectDir))}`));
242
+ log.info(pc.dim(`Path: ${projectDir}`));
243
+ log.info(pc.dim(`Ecosystem: ${btsConfig.ecosystem}`));
244
+ if (btsConfig.graphSummary) log.info(pc.dim(`Stack: ${btsConfig.graphSummary}`));
245
+ }
246
+ const checks = [{
247
+ label: "bts.jsonc",
248
+ status: "pass",
249
+ detail: `version ${btsConfig.version}`
250
+ }];
251
+ checks.push(...await checkInstalledDependencies(projectDir, btsConfig));
252
+ checks.push(...await checkEnvFiles(projectDir));
253
+ if (!input.skipChecks) checks.push(...await runBuildChecks(config, json));
254
+ const counts = {
255
+ pass: 0,
256
+ warn: 0,
257
+ fail: 0
258
+ };
259
+ for (const check of checks) counts[check.status] += 1;
260
+ if (json) console.log(JSON.stringify({
261
+ projectDir,
262
+ ecosystem: btsConfig.ecosystem,
263
+ ok: counts.fail === 0,
264
+ summary: counts,
265
+ checks
266
+ }, null, 2));
267
+ else {
268
+ log.message("");
269
+ for (const check of checks) log.message(`${statusIcon(check.status)} ${check.label}${check.detail ? pc.dim(` — ${check.detail}`) : ""}`);
270
+ log.message("");
271
+ const summaryLine = `${pc.green(`${counts.pass} passed`)}, ${pc.yellow(`${counts.warn} warnings`)}, ${pc.red(`${counts.fail} failed`)}`;
272
+ if (counts.fail > 0) log.error(`Diagnosis complete: ${summaryLine}`);
273
+ else if (counts.warn > 0) log.warn(`Diagnosis complete: ${summaryLine}`);
274
+ else log.success(`Diagnosis complete: ${summaryLine}`);
275
+ }
276
+ if (counts.fail > 0) process.exit(1);
277
+ }
278
+
279
+ //#endregion
280
+ export { doctorCommand };
@@ -1,76 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as __reExport } from "./chunk-CCII7kTE.mjs";
3
+ import { cancel } from "@clack/prompts";
4
+ import pc from "picocolors";
3
5
  import fs from "fs-extra";
4
6
  import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { createCliDefaultProjectConfigBase } from "@better-fullstack/types";
7
7
  import * as JSONC from "jsonc-parser";
8
+ import { createCliDefaultProjectConfigBase } from "@better-fullstack/types";
9
+ import { fileURLToPath } from "node:url";
10
+ import { AsyncLocalStorage } from "node:async_hooks";
11
+ import consola from "consola";
8
12
 
9
- //#region src/utils/get-package-manager.ts
10
- const getUserPkgManager = () => {
11
- const userAgent = process.env.npm_config_user_agent;
12
- if (userAgent?.startsWith("pnpm")) return "pnpm";
13
- if (userAgent?.startsWith("bun")) return "bun";
14
- if (userAgent?.startsWith("yarn")) return "yarn";
15
- return "npm";
16
- };
17
-
18
- //#endregion
19
- //#region src/constants.ts
20
- const __filename = fileURLToPath(import.meta.url);
21
- const distPath = path.dirname(__filename);
22
- const PKG_ROOT = path.join(distPath, "../");
23
- const DEFAULT_CONFIG_BASE = createCliDefaultProjectConfigBase(getUserPkgManager());
24
- function getDefaultConfig() {
25
- return {
26
- ...DEFAULT_CONFIG_BASE,
27
- projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
28
- packageManager: getUserPkgManager(),
29
- frontend: [...DEFAULT_CONFIG_BASE.frontend],
30
- addons: [...DEFAULT_CONFIG_BASE.addons],
31
- examples: [...DEFAULT_CONFIG_BASE.examples],
32
- rustLibraries: [...DEFAULT_CONFIG_BASE.rustLibraries],
33
- pythonAi: [...DEFAULT_CONFIG_BASE.pythonAi],
34
- javaLibraries: [...DEFAULT_CONFIG_BASE.javaLibraries],
35
- javaTestingLibraries: [...DEFAULT_CONFIG_BASE.javaTestingLibraries],
36
- aiDocs: [...DEFAULT_CONFIG_BASE.aiDocs]
37
- };
38
- }
39
- const DEFAULT_CONFIG = getDefaultConfig();
40
- /**
41
- * Default UI library for each frontend framework
42
- * Falls back based on what's compatible
43
- */
44
- const DEFAULT_UI_LIBRARY_BY_FRONTEND = {
45
- "tanstack-router": "shadcn-ui",
46
- "react-router": "shadcn-ui",
47
- "react-vite": "shadcn-ui",
48
- "tanstack-start": "shadcn-ui",
49
- next: "shadcn-ui",
50
- vinext: "shadcn-ui",
51
- nuxt: "daisyui",
52
- svelte: "daisyui",
53
- solid: "daisyui",
54
- "solid-start": "daisyui",
55
- astro: "daisyui",
56
- qwik: "daisyui",
57
- angular: "daisyui",
58
- redwood: "daisyui",
59
- fresh: "daisyui",
60
- "native-bare": "none",
61
- "native-uniwind": "none",
62
- "native-unistyles": "none",
63
- none: "none"
64
- };
65
-
66
- //#endregion
67
- //#region src/utils/get-latest-cli-version.ts
68
- const getLatestCLIVersion = () => {
69
- const packageJsonPath = path.join(PKG_ROOT, "package.json");
70
- return fs.readJSONSync(packageJsonPath).version ?? "1.0.0";
71
- };
72
-
73
- //#endregion
74
13
  //#region src/types.ts
75
14
  var types_exports = {};
76
15
  import * as import__better_fullstack_types from "@better-fullstack/types";
@@ -228,16 +167,93 @@ function getGraphBackendDeployInstructions(config) {
228
167
  }
229
168
  }
230
169
 
170
+ //#endregion
171
+ //#region src/utils/get-package-manager.ts
172
+ const getUserPkgManager = () => {
173
+ const userAgent = process.env.npm_config_user_agent;
174
+ if (userAgent?.startsWith("pnpm")) return "pnpm";
175
+ if (userAgent?.startsWith("bun")) return "bun";
176
+ if (userAgent?.startsWith("yarn")) return "yarn";
177
+ return "npm";
178
+ };
179
+
180
+ //#endregion
181
+ //#region src/constants.ts
182
+ const __filename = fileURLToPath(import.meta.url);
183
+ const distPath = path.dirname(__filename);
184
+ const PKG_ROOT = path.join(distPath, "../");
185
+ const DEFAULT_CONFIG_BASE = createCliDefaultProjectConfigBase(getUserPkgManager());
186
+ function getDefaultConfig() {
187
+ return {
188
+ ...DEFAULT_CONFIG_BASE,
189
+ projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
190
+ packageManager: getUserPkgManager(),
191
+ frontend: [...DEFAULT_CONFIG_BASE.frontend],
192
+ addons: [...DEFAULT_CONFIG_BASE.addons],
193
+ examples: [...DEFAULT_CONFIG_BASE.examples],
194
+ rustLibraries: [...DEFAULT_CONFIG_BASE.rustLibraries],
195
+ pythonAi: [...DEFAULT_CONFIG_BASE.pythonAi],
196
+ javaLibraries: [...DEFAULT_CONFIG_BASE.javaLibraries],
197
+ javaTestingLibraries: [...DEFAULT_CONFIG_BASE.javaTestingLibraries],
198
+ aiDocs: [...DEFAULT_CONFIG_BASE.aiDocs]
199
+ };
200
+ }
201
+ const DEFAULT_CONFIG = getDefaultConfig();
202
+ /**
203
+ * Default UI library for each frontend framework
204
+ * Falls back based on what's compatible
205
+ */
206
+ const DEFAULT_UI_LIBRARY_BY_FRONTEND = {
207
+ "tanstack-router": "shadcn-ui",
208
+ "react-router": "shadcn-ui",
209
+ "react-vite": "shadcn-ui",
210
+ "tanstack-start": "shadcn-ui",
211
+ next: "shadcn-ui",
212
+ vinext: "shadcn-ui",
213
+ nuxt: "daisyui",
214
+ svelte: "daisyui",
215
+ solid: "daisyui",
216
+ "solid-start": "daisyui",
217
+ astro: "daisyui",
218
+ qwik: "daisyui",
219
+ angular: "daisyui",
220
+ redwood: "daisyui",
221
+ fresh: "daisyui",
222
+ "native-bare": "none",
223
+ "native-uniwind": "none",
224
+ "native-unistyles": "none",
225
+ none: "none"
226
+ };
227
+
228
+ //#endregion
229
+ //#region src/utils/get-latest-cli-version.ts
230
+ const getLatestCLIVersion = () => {
231
+ const packageJsonPath = path.join(PKG_ROOT, "package.json");
232
+ return fs.readJSONSync(packageJsonPath).version ?? "1.0.0";
233
+ };
234
+
231
235
  //#endregion
232
236
  //#region src/utils/bts-config.ts
233
237
  const BTS_CONFIG_FILE = "bts.jsonc";
238
+ const MOBILE_CONFIG_FIELDS = [
239
+ "mobileNavigation",
240
+ "mobileUI",
241
+ "mobileStorage",
242
+ "mobileTesting",
243
+ "mobilePush",
244
+ "mobileOTA",
245
+ "mobileDeepLinking"
246
+ ];
234
247
  function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
235
248
  if (!stackParts) return projectConfig;
236
249
  const legacyConfig = (0, types_exports.stackPartsToLegacyProjectConfigPartial)(stackParts);
237
250
  const selectedEcosystems = new Set(stackParts.filter((part) => part.source !== "provided").map((part) => part.ecosystem));
251
+ selectedEcosystems.add(projectConfig.ecosystem);
252
+ const hasSelectedEcosystemStackParts = stackParts.some((part) => part.source !== "provided" && part.ecosystem !== "universal");
238
253
  const normalized = {
239
254
  ...projectConfig,
240
- ...legacyConfig
255
+ ...legacyConfig,
256
+ ecosystem: hasSelectedEcosystemStackParts ? legacyConfig.ecosystem ?? projectConfig.ecosystem : projectConfig.ecosystem
241
257
  };
242
258
  if (!selectedEcosystems.has("rust")) {
243
259
  normalized.rustWebFramework = "none";
@@ -285,6 +301,7 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
285
301
  normalized.goConfig = "none";
286
302
  normalized.goObservability = "none";
287
303
  }
304
+ if (selectedEcosystems.has("go") && projectConfig.auth === "go-better-auth" && legacyConfig.auth === "none") normalized.auth = projectConfig.auth;
288
305
  if (!selectedEcosystems.has("java")) {
289
306
  normalized.javaWebFramework = "none";
290
307
  normalized.javaBuildTool = "none";
@@ -326,7 +343,15 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
326
343
  normalized.elixirDeploy = "none";
327
344
  normalized.elixirLibraries = [];
328
345
  }
329
- if (!selectedEcosystems.has("react-native")) {
346
+ const projectHasNativeFrontend = projectConfig.frontend.some((frontend) => frontend.startsWith("native-"));
347
+ const normalizedHasNativeFrontend = normalized.frontend.some((frontend) => frontend.startsWith("native-"));
348
+ const hasNativeFrontend = projectHasNativeFrontend || normalizedHasNativeFrontend;
349
+ if (hasNativeFrontend) for (const field of MOBILE_CONFIG_FIELDS) {
350
+ const projectValue = projectConfig[field];
351
+ const legacyValue = legacyConfig[field];
352
+ if (projectValue !== void 0 && projectValue !== "none" && legacyValue === "none") normalized[field] = projectValue;
353
+ }
354
+ if (!selectedEcosystems.has("react-native") && !hasNativeFrontend) {
330
355
  normalized.mobileNavigation = "none";
331
356
  normalized.mobileUI = "none";
332
357
  normalized.mobileStorage = "none";
@@ -425,8 +450,16 @@ function buildBtsConfigForPersistence(projectConfig, metadata = {}) {
425
450
  api: persistedConfig.api,
426
451
  webDeploy: persistedConfig.webDeploy,
427
452
  serverDeploy: persistedConfig.serverDeploy,
453
+ astroIntegration: persistedConfig.astroIntegration,
428
454
  cssFramework: persistedConfig.cssFramework,
429
455
  uiLibrary: persistedConfig.uiLibrary,
456
+ shadcnBase: persistedConfig.shadcnBase,
457
+ shadcnStyle: persistedConfig.shadcnStyle,
458
+ shadcnIconLibrary: persistedConfig.shadcnIconLibrary,
459
+ shadcnColorTheme: persistedConfig.shadcnColorTheme,
460
+ shadcnBaseColor: persistedConfig.shadcnBaseColor,
461
+ shadcnFont: persistedConfig.shadcnFont,
462
+ shadcnRadius: persistedConfig.shadcnRadius,
430
463
  realtime: persistedConfig.realtime,
431
464
  jobQueue: persistedConfig.jobQueue,
432
465
  animation: persistedConfig.animation,
@@ -446,6 +479,7 @@ function buildBtsConfigForPersistence(projectConfig, metadata = {}) {
446
479
  rateLimit: persistedConfig.rateLimit,
447
480
  i18n: persistedConfig.i18n,
448
481
  search: persistedConfig.search,
482
+ vectorDb: persistedConfig.vectorDb,
449
483
  fileStorage: persistedConfig.fileStorage,
450
484
  rustWebFramework: persistedConfig.rustWebFramework,
451
485
  rustFrontend: persistedConfig.rustFrontend,
@@ -537,8 +571,8 @@ function previewBtsConfigUpdate(currentConfig, updates) {
537
571
  createdAt: currentConfig.createdAt
538
572
  });
539
573
  }
540
- async function writeBtsConfig(projectConfig) {
541
- const btsConfig = buildBtsConfigForPersistence(projectConfig);
574
+ async function writeBtsConfig(projectConfig, metadata = {}) {
575
+ const btsConfig = buildBtsConfigForPersistence(projectConfig, metadata);
542
576
  const baseContent = {
543
577
  $schema: "https://better-fullstack-web.vercel.app/schema.json",
544
578
  version: btsConfig.version,
@@ -571,8 +605,16 @@ async function writeBtsConfig(projectConfig) {
571
605
  api: btsConfig.api,
572
606
  webDeploy: btsConfig.webDeploy,
573
607
  serverDeploy: btsConfig.serverDeploy,
608
+ astroIntegration: btsConfig.astroIntegration,
574
609
  cssFramework: btsConfig.cssFramework,
575
610
  uiLibrary: btsConfig.uiLibrary,
611
+ shadcnBase: btsConfig.shadcnBase,
612
+ shadcnStyle: btsConfig.shadcnStyle,
613
+ shadcnIconLibrary: btsConfig.shadcnIconLibrary,
614
+ shadcnColorTheme: btsConfig.shadcnColorTheme,
615
+ shadcnBaseColor: btsConfig.shadcnBaseColor,
616
+ shadcnFont: btsConfig.shadcnFont,
617
+ shadcnRadius: btsConfig.shadcnRadius,
576
618
  realtime: btsConfig.realtime,
577
619
  jobQueue: btsConfig.jobQueue,
578
620
  animation: btsConfig.animation,
@@ -592,6 +634,7 @@ async function writeBtsConfig(projectConfig) {
592
634
  rateLimit: btsConfig.rateLimit,
593
635
  i18n: btsConfig.i18n,
594
636
  search: btsConfig.search,
637
+ vectorDb: btsConfig.vectorDb,
595
638
  fileStorage: btsConfig.fileStorage,
596
639
  rustWebFramework: btsConfig.rustWebFramework,
597
640
  rustFrontend: btsConfig.rustFrontend,
@@ -708,6 +751,27 @@ async function readBtsConfig(projectDir) {
708
751
  return null;
709
752
  }
710
753
  }
754
+ async function readBtsConfigFromFile(filePath) {
755
+ try {
756
+ const resolved = path.resolve(process.cwd(), filePath);
757
+ if (!await fs.pathExists(resolved)) return null;
758
+ const configPath = (await fs.stat(resolved)).isDirectory() ? path.join(resolved, BTS_CONFIG_FILE) : resolved;
759
+ if (!await fs.pathExists(configPath)) return null;
760
+ const configContent = await fs.readFile(configPath, "utf-8");
761
+ const errors = [];
762
+ const config = JSONC.parse(configContent, errors, {
763
+ allowTrailingComma: true,
764
+ disallowComments: false
765
+ });
766
+ if (errors.length > 0 || config === void 0 || typeof config !== "object") return null;
767
+ return buildBtsConfigForPersistence(config, {
768
+ version: config.version,
769
+ createdAt: config.createdAt
770
+ });
771
+ } catch {
772
+ return null;
773
+ }
774
+ }
711
775
  async function updateBtsConfig(projectDir, updates) {
712
776
  try {
713
777
  const configPath = path.join(projectDir, BTS_CONFIG_FILE);
@@ -734,4 +798,83 @@ async function updateBtsConfig(projectDir, updates) {
734
798
  }
735
799
 
736
800
  //#endregion
737
- export { getUserPkgManager as _, getEffectiveStack as a, getGraphPart as c, hasGraphPart as d, types_exports as f, getDefaultConfig as g, DEFAULT_UI_LIBRARY_BY_FRONTEND as h, writeBtsConfig as i, getGraphSummary as l, DEFAULT_CONFIG as m, readBtsConfig as n, getGraphBackendDeployInstructions as o, getLatestCLIVersion as p, updateBtsConfig as r, getGraphBackendUrl as s, previewBtsConfigUpdate as t, getPrimaryGraphPart as u };
801
+ //#region src/utils/context.ts
802
+ const cliStorage = new AsyncLocalStorage();
803
+ function defaultContext() {
804
+ return {
805
+ navigation: {
806
+ isFirstPrompt: false,
807
+ lastPromptShownUI: false
808
+ },
809
+ silent: false
810
+ };
811
+ }
812
+ function getContext() {
813
+ const ctx = cliStorage.getStore();
814
+ if (!ctx) return defaultContext();
815
+ return ctx;
816
+ }
817
+ function tryGetContext() {
818
+ return cliStorage.getStore();
819
+ }
820
+ function isSilent() {
821
+ return getContext().silent;
822
+ }
823
+ function isFirstPrompt() {
824
+ return getContext().navigation.isFirstPrompt;
825
+ }
826
+ function didLastPromptShowUI() {
827
+ return getContext().navigation.lastPromptShownUI;
828
+ }
829
+ function setIsFirstPrompt(value) {
830
+ const ctx = tryGetContext();
831
+ if (ctx) ctx.navigation.isFirstPrompt = value;
832
+ }
833
+ function setLastPromptShownUI(value) {
834
+ const ctx = tryGetContext();
835
+ if (ctx) ctx.navigation.lastPromptShownUI = value;
836
+ }
837
+ async function runWithContextAsync(options, fn) {
838
+ const ctx = {
839
+ navigation: {
840
+ isFirstPrompt: false,
841
+ lastPromptShownUI: false
842
+ },
843
+ silent: options.silent ?? false
844
+ };
845
+ return cliStorage.run(ctx, fn);
846
+ }
847
+
848
+ //#endregion
849
+ //#region src/utils/errors.ts
850
+ var UserCancelledError = class extends Error {
851
+ constructor(message = "Operation cancelled") {
852
+ super(message);
853
+ this.name = "UserCancelledError";
854
+ }
855
+ };
856
+ var CLIError = class extends Error {
857
+ constructor(message) {
858
+ super(message);
859
+ this.name = "CLIError";
860
+ }
861
+ };
862
+ function exitWithError(message) {
863
+ if (isSilent()) throw new CLIError(message);
864
+ consola.error(pc.red(message));
865
+ process.exit(1);
866
+ }
867
+ function exitCancelled(message = "Operation cancelled") {
868
+ if (isSilent()) throw new UserCancelledError(message);
869
+ cancel(pc.red(message));
870
+ process.exit(1);
871
+ }
872
+ function handleError(error, fallbackMessage) {
873
+ const message = error instanceof Error ? error.message : fallbackMessage || String(error);
874
+ if (isSilent()) throw error instanceof Error ? error : new Error(message);
875
+ consola.error(pc.red(message));
876
+ process.exit(1);
877
+ }
878
+
879
+ //#endregion
880
+ export { types_exports as A, getEffectiveStack as C, getGraphSummary as D, getGraphPart as E, getPrimaryGraphPart as O, getUserPkgManager as S, getGraphBackendUrl as T, writeBtsConfig as _, handleError as a, DEFAULT_UI_LIBRARY_BY_FRONTEND as b, isSilent as c, setLastPromptShownUI as d, buildBtsConfigForPersistence as f, updateBtsConfig as g, readBtsConfigFromFile as h, exitWithError as i, hasGraphPart as k, runWithContextAsync as l, readBtsConfig as m, UserCancelledError as n, didLastPromptShowUI as o, previewBtsConfigUpdate as p, exitCancelled as r, isFirstPrompt as s, CLIError as t, setIsFirstPrompt as u, getLatestCLIVersion as v, getGraphBackendDeployInstructions as w, getDefaultConfig as x, DEFAULT_CONFIG as y };