create-better-t-stack 3.9.0 → 3.11.0-pr749.0

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 (182) hide show
  1. package/README.md +2 -1
  2. package/bin/create-better-t-stack +98 -0
  3. package/package.json +69 -59
  4. package/src/api.ts +203 -0
  5. package/src/cli.ts +185 -0
  6. package/src/constants.ts +270 -0
  7. package/src/helpers/addons/addons-setup.ts +201 -0
  8. package/src/helpers/addons/examples-setup.ts +137 -0
  9. package/src/helpers/addons/fumadocs-setup.ts +99 -0
  10. package/src/helpers/addons/oxlint-setup.ts +36 -0
  11. package/src/helpers/addons/ruler-setup.ts +135 -0
  12. package/src/helpers/addons/starlight-setup.ts +45 -0
  13. package/src/helpers/addons/tauri-setup.ts +90 -0
  14. package/src/helpers/addons/tui-setup.ts +64 -0
  15. package/src/helpers/addons/ultracite-setup.ts +228 -0
  16. package/src/helpers/addons/vite-pwa-setup.ts +59 -0
  17. package/src/helpers/addons/wxt-setup.ts +86 -0
  18. package/src/helpers/core/add-addons.ts +85 -0
  19. package/src/helpers/core/add-deployment.ts +102 -0
  20. package/src/helpers/core/api-setup.ts +280 -0
  21. package/src/helpers/core/auth-setup.ts +203 -0
  22. package/src/helpers/core/backend-setup.ts +69 -0
  23. package/src/helpers/core/command-handlers.ts +354 -0
  24. package/src/helpers/core/convex-codegen.ts +14 -0
  25. package/src/helpers/core/create-project.ts +134 -0
  26. package/src/helpers/core/create-readme.ts +694 -0
  27. package/src/helpers/core/db-setup.ts +184 -0
  28. package/src/helpers/core/detect-project-config.ts +41 -0
  29. package/src/helpers/core/env-setup.ts +481 -0
  30. package/src/helpers/core/git.ts +23 -0
  31. package/src/helpers/core/install-dependencies.ts +29 -0
  32. package/src/helpers/core/payments-setup.ts +48 -0
  33. package/src/helpers/core/post-installation.ts +403 -0
  34. package/src/helpers/core/project-config.ts +250 -0
  35. package/src/helpers/core/runtime-setup.ts +76 -0
  36. package/src/helpers/core/template-manager.ts +917 -0
  37. package/src/helpers/core/workspace-setup.ts +184 -0
  38. package/src/helpers/database-providers/d1-setup.ts +28 -0
  39. package/src/helpers/database-providers/docker-compose-setup.ts +50 -0
  40. package/src/helpers/database-providers/mongodb-atlas-setup.ts +182 -0
  41. package/src/helpers/database-providers/neon-setup.ts +240 -0
  42. package/src/helpers/database-providers/planetscale-setup.ts +78 -0
  43. package/src/helpers/database-providers/prisma-postgres-setup.ts +193 -0
  44. package/src/helpers/database-providers/supabase-setup.ts +196 -0
  45. package/src/helpers/database-providers/turso-setup.ts +309 -0
  46. package/src/helpers/deployment/alchemy/alchemy-combined-setup.ts +80 -0
  47. package/src/helpers/deployment/alchemy/alchemy-next-setup.ts +52 -0
  48. package/src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts +105 -0
  49. package/src/helpers/deployment/alchemy/alchemy-react-router-setup.ts +33 -0
  50. package/src/helpers/deployment/alchemy/alchemy-solid-setup.ts +33 -0
  51. package/src/helpers/deployment/alchemy/alchemy-svelte-setup.ts +99 -0
  52. package/src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts +34 -0
  53. package/src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts +99 -0
  54. package/src/helpers/deployment/alchemy/env-dts-setup.ts +76 -0
  55. package/src/helpers/deployment/alchemy/index.ts +7 -0
  56. package/src/helpers/deployment/server-deploy-setup.ts +55 -0
  57. package/src/helpers/deployment/web-deploy-setup.ts +58 -0
  58. package/src/index.ts +51 -0
  59. package/src/prompts/addons.ts +200 -0
  60. package/src/prompts/api.ts +49 -0
  61. package/src/prompts/auth.ts +84 -0
  62. package/src/prompts/backend.ts +83 -0
  63. package/src/prompts/config-prompts.ts +138 -0
  64. package/src/prompts/database-setup.ts +112 -0
  65. package/src/prompts/database.ts +57 -0
  66. package/src/prompts/examples.ts +60 -0
  67. package/src/prompts/frontend.ts +118 -0
  68. package/src/prompts/git.ts +16 -0
  69. package/src/prompts/install.ts +16 -0
  70. package/src/prompts/orm.ts +53 -0
  71. package/src/prompts/package-manager.ts +32 -0
  72. package/src/prompts/payments.ts +50 -0
  73. package/src/prompts/project-name.ts +86 -0
  74. package/src/prompts/runtime.ts +47 -0
  75. package/src/prompts/server-deploy.ts +91 -0
  76. package/src/prompts/web-deploy.ts +107 -0
  77. package/src/tui/app.tsx +1062 -0
  78. package/src/types.ts +70 -0
  79. package/src/utils/add-package-deps.ts +57 -0
  80. package/src/utils/analytics.ts +39 -0
  81. package/src/utils/better-auth-plugin-setup.ts +71 -0
  82. package/src/utils/bts-config.ts +122 -0
  83. package/src/utils/command-exists.ts +16 -0
  84. package/src/utils/compatibility-rules.ts +337 -0
  85. package/src/utils/compatibility.ts +11 -0
  86. package/src/utils/config-processing.ts +130 -0
  87. package/src/utils/config-validation.ts +470 -0
  88. package/src/utils/display-config.ts +96 -0
  89. package/src/utils/docker-utils.ts +70 -0
  90. package/src/utils/errors.ts +30 -0
  91. package/src/utils/file-formatter.ts +11 -0
  92. package/src/utils/generate-reproducible-command.ts +53 -0
  93. package/src/utils/get-latest-cli-version.ts +27 -0
  94. package/src/utils/get-package-manager.ts +13 -0
  95. package/src/utils/open-url.ts +18 -0
  96. package/src/utils/package-runner.ts +23 -0
  97. package/src/utils/project-directory.ts +102 -0
  98. package/src/utils/project-name-validation.ts +43 -0
  99. package/src/utils/render-title.ts +48 -0
  100. package/src/utils/setup-catalogs.ts +192 -0
  101. package/src/utils/sponsors.ts +101 -0
  102. package/src/utils/telemetry.ts +19 -0
  103. package/src/utils/template-processor.ts +64 -0
  104. package/src/utils/templates.ts +94 -0
  105. package/src/utils/ts-morph.ts +26 -0
  106. package/src/validation.ts +117 -0
  107. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +5 -7
  108. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +17 -17
  109. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +4 -4
  110. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +2 -2
  111. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +10 -10
  112. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +13 -5
  113. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +14 -12
  114. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +13 -16
  115. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +11 -5
  116. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +4 -4
  117. package/templates/auth/better-auth/fullstack/tanstack-start/src/routes/api/auth/$.ts.hbs +1 -1
  118. package/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx.hbs +17 -15
  119. package/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx.hbs +16 -15
  120. package/templates/auth/better-auth/web/react/tanstack-router/src/components/{user-menu.tsx → user-menu.tsx.hbs} +16 -15
  121. package/templates/auth/better-auth/web/react/tanstack-start/src/components/{user-menu.tsx → user-menu.tsx.hbs} +16 -15
  122. package/templates/backend/convex/packages/backend/convex/README.md +4 -4
  123. package/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +17 -0
  124. package/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs +1 -1
  125. package/templates/examples/ai/convex/packages/backend/convex/agent.ts.hbs +9 -0
  126. package/templates/examples/ai/convex/packages/backend/convex/chat.ts.hbs +67 -0
  127. package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +301 -3
  128. package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +296 -10
  129. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +180 -1
  130. package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +172 -9
  131. package/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs +156 -6
  132. package/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs +156 -4
  133. package/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs +159 -6
  134. package/templates/frontend/react/next/package.json.hbs +8 -7
  135. package/templates/frontend/react/next/src/app/layout.tsx.hbs +28 -1
  136. package/templates/frontend/react/next/src/components/mode-toggle.tsx.hbs +4 -6
  137. package/templates/frontend/react/next/src/components/providers.tsx.hbs +14 -4
  138. package/templates/frontend/react/react-router/package.json.hbs +2 -1
  139. package/templates/frontend/react/{tanstack-router/src/components/mode-toggle.tsx → react-router/src/components/mode-toggle.tsx.hbs} +4 -6
  140. package/templates/frontend/react/tanstack-router/package.json.hbs +2 -1
  141. package/templates/frontend/react/{react-router/src/components/mode-toggle.tsx → tanstack-router/src/components/mode-toggle.tsx.hbs} +4 -6
  142. package/templates/frontend/react/tanstack-start/package.json.hbs +2 -1
  143. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +6 -0
  144. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +13 -14
  145. package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +5 -0
  146. package/templates/frontend/react/web-base/components.json +5 -2
  147. package/templates/frontend/react/web-base/src/components/ui/button.tsx.hbs +57 -0
  148. package/templates/frontend/react/web-base/src/components/ui/card.tsx.hbs +103 -0
  149. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx.hbs +26 -0
  150. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx.hbs +262 -0
  151. package/templates/frontend/react/web-base/src/components/ui/input.tsx.hbs +20 -0
  152. package/templates/frontend/react/web-base/src/components/ui/label.tsx.hbs +20 -0
  153. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +13 -0
  154. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx.hbs +44 -0
  155. package/templates/frontend/react/web-base/src/index.css.hbs +58 -64
  156. package/dist/cli.d.mts +0 -1
  157. package/dist/cli.mjs +0 -8
  158. package/dist/index.d.mts +0 -347
  159. package/dist/index.mjs +0 -4
  160. package/dist/src-DLvUK0Qf.mjs +0 -7069
  161. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +0 -7
  162. package/templates/examples/ai/web/react/base/src/components/response.tsx.hbs +0 -22
  163. package/templates/frontend/react/web-base/src/components/ui/button.tsx +0 -56
  164. package/templates/frontend/react/web-base/src/components/ui/card.tsx +0 -75
  165. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx +0 -27
  166. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx +0 -228
  167. package/templates/frontend/react/web-base/src/components/ui/input.tsx +0 -21
  168. package/templates/frontend/react/web-base/src/components/ui/label.tsx +0 -19
  169. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx +0 -13
  170. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx +0 -25
  171. /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  172. /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  173. /package/templates/auth/better-auth/web/react/tanstack-router/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  174. /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  175. /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  176. /package/templates/auth/better-auth/web/react/tanstack-start/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  177. /package/templates/auth/better-auth/web/solid/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  178. /package/templates/auth/better-auth/web/solid/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  179. /package/templates/auth/better-auth/web/solid/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  180. /package/templates/frontend/react/react-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
  181. /package/templates/frontend/react/tanstack-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
  182. /package/templates/frontend/react/web-base/src/lib/{utils.ts → utils.ts.hbs} +0 -0
package/src/types.ts ADDED
@@ -0,0 +1,70 @@
1
+ // Re-export everything from the shared types package
2
+ export {
3
+ // Schemas
4
+ DatabaseSchema,
5
+ ORMSchema,
6
+ BackendSchema,
7
+ RuntimeSchema,
8
+ FrontendSchema,
9
+ AddonsSchema,
10
+ ExamplesSchema,
11
+ PackageManagerSchema,
12
+ DatabaseSetupSchema,
13
+ APISchema,
14
+ AuthSchema,
15
+ PaymentsSchema,
16
+ WebDeploySchema,
17
+ ServerDeploySchema,
18
+ DirectoryConflictSchema,
19
+ TemplateSchema,
20
+ ProjectNameSchema,
21
+ CreateInputSchema,
22
+ AddInputSchema,
23
+ CLIInputSchema,
24
+ ProjectConfigSchema,
25
+ BetterTStackConfigSchema,
26
+ InitResultSchema,
27
+ // Enum value arrays
28
+ DATABASE_VALUES,
29
+ ORM_VALUES,
30
+ BACKEND_VALUES,
31
+ RUNTIME_VALUES,
32
+ FRONTEND_VALUES,
33
+ ADDONS_VALUES,
34
+ EXAMPLES_VALUES,
35
+ PACKAGE_MANAGER_VALUES,
36
+ DATABASE_SETUP_VALUES,
37
+ API_VALUES,
38
+ AUTH_VALUES,
39
+ PAYMENTS_VALUES,
40
+ WEB_DEPLOY_VALUES,
41
+ SERVER_DEPLOY_VALUES,
42
+ DIRECTORY_CONFLICT_VALUES,
43
+ TEMPLATE_VALUES,
44
+ } from "@better-t-stack/types";
45
+
46
+ export type {
47
+ Database,
48
+ ORM,
49
+ Backend,
50
+ Runtime,
51
+ Frontend,
52
+ Addons,
53
+ Examples,
54
+ PackageManager,
55
+ DatabaseSetup,
56
+ API,
57
+ Auth,
58
+ Payments,
59
+ WebDeploy,
60
+ ServerDeploy,
61
+ DirectoryConflict,
62
+ Template,
63
+ ProjectName,
64
+ CreateInput,
65
+ AddInput,
66
+ CLIInput,
67
+ ProjectConfig,
68
+ BetterTStackConfig,
69
+ InitResult,
70
+ } from "@better-t-stack/types";
@@ -0,0 +1,57 @@
1
+ import path from "node:path";
2
+ import fs from "fs-extra";
3
+
4
+ import { type AvailableDependencies, dependencyVersionMap } from "../constants";
5
+
6
+ export const addPackageDependency = async (opts: {
7
+ dependencies?: AvailableDependencies[];
8
+ devDependencies?: AvailableDependencies[];
9
+ customDependencies?: Record<string, string>;
10
+ customDevDependencies?: Record<string, string>;
11
+ projectDir: string;
12
+ }) => {
13
+ const {
14
+ dependencies = [],
15
+ devDependencies = [],
16
+ customDependencies = {},
17
+ customDevDependencies = {},
18
+ projectDir,
19
+ } = opts;
20
+
21
+ const pkgJsonPath = path.join(projectDir, "package.json");
22
+
23
+ const pkgJson = await fs.readJson(pkgJsonPath);
24
+
25
+ if (!pkgJson.dependencies) pkgJson.dependencies = {};
26
+ if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
27
+
28
+ for (const pkgName of dependencies) {
29
+ const version = dependencyVersionMap[pkgName];
30
+ if (version) {
31
+ pkgJson.dependencies[pkgName] = version;
32
+ } else {
33
+ console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
34
+ }
35
+ }
36
+
37
+ for (const pkgName of devDependencies) {
38
+ const version = dependencyVersionMap[pkgName];
39
+ if (version) {
40
+ pkgJson.devDependencies[pkgName] = version;
41
+ } else {
42
+ console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
43
+ }
44
+ }
45
+
46
+ for (const [pkgName, version] of Object.entries(customDependencies)) {
47
+ pkgJson.dependencies[pkgName] = version;
48
+ }
49
+
50
+ for (const [pkgName, version] of Object.entries(customDevDependencies)) {
51
+ pkgJson.devDependencies[pkgName] = version;
52
+ }
53
+
54
+ await fs.writeJson(pkgJsonPath, pkgJson, {
55
+ spaces: 2,
56
+ });
57
+ };
@@ -0,0 +1,39 @@
1
+ import type { ProjectConfig } from "../types";
2
+ import { getLatestCLIVersion } from "./get-latest-cli-version";
3
+ import { isTelemetryEnabled } from "./telemetry";
4
+
5
+ const CONVEX_INGEST_URL = process.env.CONVEX_INGEST_URL;
6
+
7
+ async function sendConvexEvent(payload: Record<string, unknown>) {
8
+ if (!CONVEX_INGEST_URL) return;
9
+
10
+ try {
11
+ await fetch(CONVEX_INGEST_URL, {
12
+ method: "POST",
13
+ headers: {
14
+ "Content-Type": "application/json",
15
+ },
16
+ body: JSON.stringify(payload),
17
+ });
18
+ } catch {}
19
+ }
20
+
21
+ export async function trackProjectCreation(config: ProjectConfig, disableAnalytics = false) {
22
+ if (!isTelemetryEnabled() || disableAnalytics) return;
23
+
24
+ const {
25
+ projectName: _projectName,
26
+ projectDir: _projectDir,
27
+ relativePath: _relativePath,
28
+ ...safeConfig
29
+ } = config;
30
+
31
+ try {
32
+ await sendConvexEvent({
33
+ ...safeConfig,
34
+ cli_version: getLatestCLIVersion(),
35
+ node_version: typeof process !== "undefined" ? process.version : "",
36
+ platform: typeof process !== "undefined" ? process.platform : "",
37
+ });
38
+ } catch {}
39
+ }
@@ -0,0 +1,71 @@
1
+ import { SyntaxKind } from "ts-morph";
2
+ import type { ProjectConfig } from "../types";
3
+ import { ensureArrayProperty, tsProject } from "./ts-morph";
4
+
5
+ export async function setupBetterAuthPlugins(projectDir: string, config: ProjectConfig) {
6
+ const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
7
+ const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);
8
+
9
+ if (!authIndexFile) {
10
+ return;
11
+ }
12
+
13
+ const pluginsToAdd: string[] = [];
14
+ const importsToAdd: string[] = [];
15
+
16
+ if (config.backend === "self" && config.frontend?.includes("tanstack-start")) {
17
+ pluginsToAdd.push("tanstackStartCookies()");
18
+ importsToAdd.push('import { tanstackStartCookies } from "better-auth/tanstack-start";');
19
+ }
20
+
21
+ if (config.backend === "self" && config.frontend?.includes("next")) {
22
+ pluginsToAdd.push("nextCookies()");
23
+ importsToAdd.push('import { nextCookies } from "better-auth/next-js";');
24
+ }
25
+
26
+ if (
27
+ config.frontend?.includes("native-bare") ||
28
+ config.frontend?.includes("native-uniwind") ||
29
+ config.frontend?.includes("native-unistyles")
30
+ ) {
31
+ pluginsToAdd.push("expo()");
32
+ importsToAdd.push('import { expo } from "@better-auth/expo";');
33
+ }
34
+
35
+ if (pluginsToAdd.length === 0) {
36
+ return;
37
+ }
38
+
39
+ importsToAdd.forEach((importStatement) => {
40
+ const existingImport = authIndexFile.getImportDeclaration((declaration) =>
41
+ declaration.getModuleSpecifierValue().includes(importStatement.split('"')[1]),
42
+ );
43
+
44
+ if (!existingImport) {
45
+ authIndexFile.insertImportDeclaration(0, {
46
+ moduleSpecifier: importStatement.split('"')[1],
47
+ namedImports: [importStatement.split("{")[1].split("}")[0].trim()],
48
+ });
49
+ }
50
+ });
51
+
52
+ const betterAuthCall = authIndexFile
53
+ .getDescendantsOfKind(SyntaxKind.CallExpression)
54
+ .find((call) => call.getExpression().getText() === "betterAuth");
55
+
56
+ if (betterAuthCall) {
57
+ const configObject = betterAuthCall.getArguments()[0];
58
+
59
+ if (configObject && configObject.getKind() === SyntaxKind.ObjectLiteralExpression) {
60
+ const objLiteral = configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
61
+
62
+ const pluginsArray = ensureArrayProperty(objLiteral, "plugins");
63
+
64
+ pluginsToAdd.forEach((plugin) => {
65
+ pluginsArray.addElement(plugin);
66
+ });
67
+ }
68
+ }
69
+
70
+ authIndexFile.save();
71
+ }
@@ -0,0 +1,122 @@
1
+ import path from "node:path";
2
+ import fs from "fs-extra";
3
+ import * as JSONC from "jsonc-parser";
4
+ import type { BetterTStackConfig, ProjectConfig } from "../types";
5
+ import { getLatestCLIVersion } from "./get-latest-cli-version";
6
+
7
+ const BTS_CONFIG_FILE = "bts.jsonc";
8
+
9
+ export async function writeBtsConfig(projectConfig: ProjectConfig) {
10
+ const btsConfig: BetterTStackConfig = {
11
+ version: getLatestCLIVersion(),
12
+ createdAt: new Date().toISOString(),
13
+ database: projectConfig.database,
14
+ orm: projectConfig.orm,
15
+ backend: projectConfig.backend,
16
+ runtime: projectConfig.runtime,
17
+ frontend: projectConfig.frontend,
18
+ addons: projectConfig.addons,
19
+ examples: projectConfig.examples,
20
+ auth: projectConfig.auth,
21
+ payments: projectConfig.payments,
22
+ packageManager: projectConfig.packageManager,
23
+ dbSetup: projectConfig.dbSetup,
24
+ api: projectConfig.api,
25
+ webDeploy: projectConfig.webDeploy,
26
+ serverDeploy: projectConfig.serverDeploy,
27
+ };
28
+
29
+ const baseContent = {
30
+ $schema: "https://r2.better-t-stack.dev/schema.json",
31
+ version: btsConfig.version,
32
+ createdAt: btsConfig.createdAt,
33
+ database: btsConfig.database,
34
+ orm: btsConfig.orm,
35
+ backend: btsConfig.backend,
36
+ runtime: btsConfig.runtime,
37
+ frontend: btsConfig.frontend,
38
+ addons: btsConfig.addons,
39
+ examples: btsConfig.examples,
40
+ auth: btsConfig.auth,
41
+ payments: btsConfig.payments,
42
+ packageManager: btsConfig.packageManager,
43
+ dbSetup: btsConfig.dbSetup,
44
+ api: btsConfig.api,
45
+ webDeploy: btsConfig.webDeploy,
46
+ serverDeploy: btsConfig.serverDeploy,
47
+ };
48
+
49
+ let configContent = JSON.stringify(baseContent);
50
+
51
+ const formatResult = JSONC.format(configContent, undefined, {
52
+ tabSize: 2,
53
+ insertSpaces: true,
54
+ eol: "\n",
55
+ });
56
+
57
+ configContent = JSONC.applyEdits(configContent, formatResult);
58
+
59
+ const finalContent = `// Better-T-Stack configuration file
60
+ // safe to delete
61
+
62
+ ${configContent}`;
63
+ const configPath = path.join(projectConfig.projectDir, BTS_CONFIG_FILE);
64
+ await fs.writeFile(configPath, finalContent, "utf-8");
65
+ }
66
+
67
+ export async function readBtsConfig(projectDir: string) {
68
+ try {
69
+ const configPath = path.join(projectDir, BTS_CONFIG_FILE);
70
+
71
+ if (!(await fs.pathExists(configPath))) {
72
+ return null;
73
+ }
74
+
75
+ const configContent = await fs.readFile(configPath, "utf-8");
76
+
77
+ const errors: JSONC.ParseError[] = [];
78
+ const config = JSONC.parse(configContent, errors, {
79
+ allowTrailingComma: true,
80
+ disallowComments: false,
81
+ }) as BetterTStackConfig;
82
+
83
+ if (errors.length > 0) {
84
+ console.warn("Warning: Found errors parsing bts.jsonc:", errors);
85
+ return null;
86
+ }
87
+
88
+ return config;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ export async function updateBtsConfig(
95
+ projectDir: string,
96
+ updates: Partial<Pick<BetterTStackConfig, "addons" | "webDeploy" | "serverDeploy">>,
97
+ ) {
98
+ try {
99
+ const configPath = path.join(projectDir, BTS_CONFIG_FILE);
100
+
101
+ if (!(await fs.pathExists(configPath))) {
102
+ return;
103
+ }
104
+
105
+ const configContent = await fs.readFile(configPath, "utf-8");
106
+
107
+ let modifiedContent = configContent;
108
+
109
+ for (const [key, value] of Object.entries(updates)) {
110
+ const editResult = JSONC.modify(modifiedContent, [key], value, {
111
+ formattingOptions: {
112
+ tabSize: 2,
113
+ insertSpaces: true,
114
+ eol: "\n",
115
+ },
116
+ });
117
+ modifiedContent = JSONC.applyEdits(modifiedContent, editResult);
118
+ }
119
+
120
+ await fs.writeFile(configPath, modifiedContent, "utf-8");
121
+ } catch {}
122
+ }
@@ -0,0 +1,16 @@
1
+ import { $ } from "bun";
2
+
3
+ export async function commandExists(command: string) {
4
+ try {
5
+ const isWindows = process.platform === "win32";
6
+ if (isWindows) {
7
+ const result = await $`where ${command}`.nothrow().quiet();
8
+ return result.exitCode === 0;
9
+ }
10
+
11
+ const result = await $`which ${command}`.nothrow().quiet();
12
+ return result.exitCode === 0;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }