create-better-t-stack 3.13.1-pr788.c579ceb → 3.13.2-dev.41e5758
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-DtAyzhjL.mjs → src-BDoImlsP.mjs} +110 -1017
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as create, c as docs, d as sponsors, i as builder, l as generateVirtualProject, n as TEMPLATE_COUNT, o as createBtsCli, r as VirtualFileSystem, s as createVirtual, t as EMBEDDED_TEMPLATES, u as router } from "./src-
|
|
2
|
+
import { a as create, c as docs, d as sponsors, i as builder, l as generateVirtualProject, n as TEMPLATE_COUNT, o as createBtsCli, r as VirtualFileSystem, s as createVirtual, t as EMBEDDED_TEMPLATES, u as router } from "./src-BDoImlsP.mjs";
|
|
3
3
|
|
|
4
4
|
export { EMBEDDED_TEMPLATES, TEMPLATE_COUNT, VirtualFileSystem, builder, create, createBtsCli, createVirtual, docs, generateVirtualProject, router, sponsors };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as __reExport } from "./chunk-DPg_XC7m.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
|
|
4
4
|
import { createRouterClient, os } from "@orpc/server";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
import { createCli } from "trpc-cli";
|
|
@@ -10,15 +10,13 @@ import fs from "fs-extra";
|
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, TEMPLATE_COUNT, VirtualFileSystem, dependencyVersionMap, generateVirtualProject, generateVirtualProject as generateVirtualProject$1 } from "@better-t-stack/template-generator";
|
|
13
|
-
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
14
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
+
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
15
15
|
import gradient from "gradient-string";
|
|
16
16
|
import { writeTreeToFilesystem } from "@better-t-stack/template-generator/fs-writer";
|
|
17
17
|
import { $, execa } from "execa";
|
|
18
|
-
import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
|
|
19
18
|
import * as JSONC from "jsonc-parser";
|
|
20
19
|
import { format } from "oxfmt";
|
|
21
|
-
import { glob } from "tinyglobby";
|
|
22
20
|
import os$1 from "node:os";
|
|
23
21
|
|
|
24
22
|
//#region src/utils/get-package-manager.ts
|
|
@@ -92,6 +90,59 @@ const ADDON_COMPATIBILITY = {
|
|
|
92
90
|
none: []
|
|
93
91
|
};
|
|
94
92
|
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/utils/context.ts
|
|
95
|
+
const cliStorage = new AsyncLocalStorage();
|
|
96
|
+
function defaultContext() {
|
|
97
|
+
return {
|
|
98
|
+
navigation: {
|
|
99
|
+
isFirstPrompt: false,
|
|
100
|
+
lastPromptShownUI: false
|
|
101
|
+
},
|
|
102
|
+
silent: false,
|
|
103
|
+
verbose: false
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function getContext() {
|
|
107
|
+
const ctx = cliStorage.getStore();
|
|
108
|
+
if (!ctx) return defaultContext();
|
|
109
|
+
return ctx;
|
|
110
|
+
}
|
|
111
|
+
function tryGetContext() {
|
|
112
|
+
return cliStorage.getStore();
|
|
113
|
+
}
|
|
114
|
+
function isSilent() {
|
|
115
|
+
return getContext().silent;
|
|
116
|
+
}
|
|
117
|
+
function isFirstPrompt() {
|
|
118
|
+
return getContext().navigation.isFirstPrompt;
|
|
119
|
+
}
|
|
120
|
+
function didLastPromptShowUI() {
|
|
121
|
+
return getContext().navigation.lastPromptShownUI;
|
|
122
|
+
}
|
|
123
|
+
function setIsFirstPrompt$1(value) {
|
|
124
|
+
const ctx = tryGetContext();
|
|
125
|
+
if (ctx) ctx.navigation.isFirstPrompt = value;
|
|
126
|
+
}
|
|
127
|
+
function setLastPromptShownUI(value) {
|
|
128
|
+
const ctx = tryGetContext();
|
|
129
|
+
if (ctx) ctx.navigation.lastPromptShownUI = value;
|
|
130
|
+
}
|
|
131
|
+
async function runWithContextAsync(options, fn) {
|
|
132
|
+
const ctx = {
|
|
133
|
+
navigation: {
|
|
134
|
+
isFirstPrompt: false,
|
|
135
|
+
lastPromptShownUI: false
|
|
136
|
+
},
|
|
137
|
+
silent: options.silent ?? false,
|
|
138
|
+
verbose: options.verbose ?? false,
|
|
139
|
+
projectDir: options.projectDir,
|
|
140
|
+
projectName: options.projectName,
|
|
141
|
+
packageManager: options.packageManager
|
|
142
|
+
};
|
|
143
|
+
return cliStorage.run(ctx, fn);
|
|
144
|
+
}
|
|
145
|
+
|
|
95
146
|
//#endregion
|
|
96
147
|
//#region src/utils/errors.ts
|
|
97
148
|
var UserCancelledError = class extends Error {
|
|
@@ -107,17 +158,20 @@ var CLIError = class extends Error {
|
|
|
107
158
|
}
|
|
108
159
|
};
|
|
109
160
|
function exitWithError(message) {
|
|
161
|
+
if (isSilent()) throw new CLIError(message);
|
|
110
162
|
consola.error(pc.red(message));
|
|
111
|
-
|
|
163
|
+
process.exit(1);
|
|
112
164
|
}
|
|
113
165
|
function exitCancelled(message = "Operation cancelled") {
|
|
166
|
+
if (isSilent()) throw new UserCancelledError(message);
|
|
114
167
|
cancel(pc.red(message));
|
|
115
|
-
|
|
168
|
+
process.exit(1);
|
|
116
169
|
}
|
|
117
170
|
function handleError(error, fallbackMessage) {
|
|
118
171
|
const message = error instanceof Error ? error.message : fallbackMessage || String(error);
|
|
172
|
+
if (isSilent()) throw error instanceof Error ? error : new Error(message);
|
|
119
173
|
consola.error(pc.red(message));
|
|
120
|
-
|
|
174
|
+
process.exit(1);
|
|
121
175
|
}
|
|
122
176
|
|
|
123
177
|
//#endregion
|
|
@@ -263,64 +317,8 @@ function validateExamplesCompatibility(examples, backend, database, frontend, ap
|
|
|
263
317
|
}
|
|
264
318
|
}
|
|
265
319
|
|
|
266
|
-
//#endregion
|
|
267
|
-
//#region src/utils/context.ts
|
|
268
|
-
const cliStorage = new AsyncLocalStorage();
|
|
269
|
-
function defaultContext() {
|
|
270
|
-
return {
|
|
271
|
-
navigation: {
|
|
272
|
-
isFirstPrompt: false,
|
|
273
|
-
lastPromptShownUI: false
|
|
274
|
-
},
|
|
275
|
-
silent: false,
|
|
276
|
-
verbose: false
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
function getContext() {
|
|
280
|
-
const ctx = cliStorage.getStore();
|
|
281
|
-
if (!ctx) return defaultContext();
|
|
282
|
-
return ctx;
|
|
283
|
-
}
|
|
284
|
-
function tryGetContext() {
|
|
285
|
-
return cliStorage.getStore();
|
|
286
|
-
}
|
|
287
|
-
function isSilent() {
|
|
288
|
-
return getContext().silent;
|
|
289
|
-
}
|
|
290
|
-
function isFirstPrompt() {
|
|
291
|
-
return getContext().navigation.isFirstPrompt;
|
|
292
|
-
}
|
|
293
|
-
function didLastPromptShowUI() {
|
|
294
|
-
return getContext().navigation.lastPromptShownUI;
|
|
295
|
-
}
|
|
296
|
-
function setIsFirstPrompt$1(value) {
|
|
297
|
-
const ctx = tryGetContext();
|
|
298
|
-
if (ctx) ctx.navigation.isFirstPrompt = value;
|
|
299
|
-
}
|
|
300
|
-
function setLastPromptShownUI(value) {
|
|
301
|
-
const ctx = tryGetContext();
|
|
302
|
-
if (ctx) ctx.navigation.lastPromptShownUI = value;
|
|
303
|
-
}
|
|
304
|
-
async function runWithContextAsync(options, fn) {
|
|
305
|
-
const ctx = {
|
|
306
|
-
navigation: {
|
|
307
|
-
isFirstPrompt: false,
|
|
308
|
-
lastPromptShownUI: false
|
|
309
|
-
},
|
|
310
|
-
silent: options.silent ?? false,
|
|
311
|
-
verbose: options.verbose ?? false,
|
|
312
|
-
projectDir: options.projectDir,
|
|
313
|
-
projectName: options.projectName,
|
|
314
|
-
packageManager: options.packageManager
|
|
315
|
-
};
|
|
316
|
-
return cliStorage.run(ctx, fn);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
320
|
//#endregion
|
|
320
321
|
//#region src/utils/navigation.ts
|
|
321
|
-
/**
|
|
322
|
-
* Navigation symbols and utilities for prompt navigation
|
|
323
|
-
*/
|
|
324
322
|
const GO_BACK_SYMBOL = Symbol("clack:goBack");
|
|
325
323
|
function isGoBack(value) {
|
|
326
324
|
return value === GO_BACK_SYMBOL;
|
|
@@ -654,11 +652,11 @@ async function getAddonsChoice(addons, frontends, auth) {
|
|
|
654
652
|
else if (ADDON_GROUPS.Linting.includes(addon)) groupedOptions.Linting.push(option);
|
|
655
653
|
else if (ADDON_GROUPS.Other.includes(addon)) groupedOptions.Other.push(option);
|
|
656
654
|
}
|
|
657
|
-
Object.keys(groupedOptions).forEach((group
|
|
658
|
-
if (groupedOptions[group
|
|
655
|
+
Object.keys(groupedOptions).forEach((group) => {
|
|
656
|
+
if (groupedOptions[group].length === 0) delete groupedOptions[group];
|
|
659
657
|
else {
|
|
660
|
-
const groupOrder = ADDON_GROUPS[group
|
|
661
|
-
groupedOptions[group
|
|
658
|
+
const groupOrder = ADDON_GROUPS[group] || [];
|
|
659
|
+
groupedOptions[group].sort((a, b) => {
|
|
662
660
|
return groupOrder.indexOf(a.value) - groupOrder.indexOf(b.value);
|
|
663
661
|
});
|
|
664
662
|
}
|
|
@@ -2029,63 +2027,6 @@ function validateConfigCompatibility(config, providedFlags, options) {
|
|
|
2029
2027
|
else validateConfigForProgrammaticUse(config);
|
|
2030
2028
|
}
|
|
2031
2029
|
|
|
2032
|
-
//#endregion
|
|
2033
|
-
//#region src/utils/ts-morph.ts
|
|
2034
|
-
const tsProject = new Project({
|
|
2035
|
-
useInMemoryFileSystem: false,
|
|
2036
|
-
skipAddingFilesFromTsConfig: true,
|
|
2037
|
-
manipulationSettings: {
|
|
2038
|
-
quoteKind: QuoteKind.Single,
|
|
2039
|
-
indentationText: IndentationText.TwoSpaces
|
|
2040
|
-
}
|
|
2041
|
-
});
|
|
2042
|
-
function ensureArrayProperty(obj, name) {
|
|
2043
|
-
return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
|
|
2044
|
-
name,
|
|
2045
|
-
initializer: "[]"
|
|
2046
|
-
}).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
//#endregion
|
|
2050
|
-
//#region src/utils/better-auth-plugin-setup.ts
|
|
2051
|
-
async function setupBetterAuthPlugins(projectDir, config) {
|
|
2052
|
-
const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
|
|
2053
|
-
const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);
|
|
2054
|
-
if (!authIndexFile) return;
|
|
2055
|
-
const pluginsToAdd = [];
|
|
2056
|
-
const importsToAdd = [];
|
|
2057
|
-
if (config.backend === "self" && config.frontend?.includes("tanstack-start")) {
|
|
2058
|
-
pluginsToAdd.push("tanstackStartCookies()");
|
|
2059
|
-
importsToAdd.push("import { tanstackStartCookies } from \"better-auth/tanstack-start\";");
|
|
2060
|
-
}
|
|
2061
|
-
if (config.backend === "self" && config.frontend?.includes("next")) {
|
|
2062
|
-
pluginsToAdd.push("nextCookies()");
|
|
2063
|
-
importsToAdd.push("import { nextCookies } from \"better-auth/next-js\";");
|
|
2064
|
-
}
|
|
2065
|
-
if (config.frontend?.includes("native-bare") || config.frontend?.includes("native-uniwind") || config.frontend?.includes("native-unistyles")) {
|
|
2066
|
-
pluginsToAdd.push("expo()");
|
|
2067
|
-
importsToAdd.push("import { expo } from \"@better-auth/expo\";");
|
|
2068
|
-
}
|
|
2069
|
-
if (pluginsToAdd.length === 0) return;
|
|
2070
|
-
importsToAdd.forEach((importStatement) => {
|
|
2071
|
-
if (!authIndexFile.getImportDeclaration((declaration) => declaration.getModuleSpecifierValue().includes(importStatement.split("\"")[1]))) authIndexFile.insertImportDeclaration(0, {
|
|
2072
|
-
moduleSpecifier: importStatement.split("\"")[1],
|
|
2073
|
-
namedImports: [importStatement.split("{")[1].split("}")[0].trim()]
|
|
2074
|
-
});
|
|
2075
|
-
});
|
|
2076
|
-
const betterAuthCall = authIndexFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((call) => call.getExpression().getText() === "betterAuth");
|
|
2077
|
-
if (betterAuthCall) {
|
|
2078
|
-
const configObject = betterAuthCall.getArguments()[0];
|
|
2079
|
-
if (configObject && configObject.getKind() === SyntaxKind.ObjectLiteralExpression) {
|
|
2080
|
-
const pluginsArray = ensureArrayProperty(configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression), "plugins");
|
|
2081
|
-
pluginsToAdd.forEach((plugin) => {
|
|
2082
|
-
pluginsArray.addElement(plugin);
|
|
2083
|
-
});
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
authIndexFile.save();
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
2030
|
//#endregion
|
|
2090
2031
|
//#region src/utils/bts-config.ts
|
|
2091
2032
|
const BTS_CONFIG_FILE = "bts.jsonc";
|
|
@@ -2148,7 +2089,7 @@ const formatOptions = {
|
|
|
2148
2089
|
experimentalSortPackageJson: true,
|
|
2149
2090
|
experimentalSortImports: { order: "asc" }
|
|
2150
2091
|
};
|
|
2151
|
-
async function
|
|
2092
|
+
async function formatCode(filePath, content) {
|
|
2152
2093
|
try {
|
|
2153
2094
|
const result = await format(path.basename(filePath), content, formatOptions);
|
|
2154
2095
|
if (result.errors && result.errors.length > 0) return null;
|
|
@@ -2157,46 +2098,25 @@ async function formatFile(filePath, content) {
|
|
|
2157
2098
|
return null;
|
|
2158
2099
|
}
|
|
2159
2100
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
async
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
}
|
|
2173
|
-
await
|
|
2174
|
-
try {
|
|
2175
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
2176
|
-
const formatted = await formatFile(filePath, content);
|
|
2177
|
-
if (formatted && formatted !== content) await fs.writeFile(filePath, formatted, "utf-8");
|
|
2178
|
-
} catch {}
|
|
2179
|
-
}));
|
|
2101
|
+
async function formatProject(projectDir) {
|
|
2102
|
+
async function formatDirectory(dir) {
|
|
2103
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2104
|
+
await Promise.all(entries.map(async (entry) => {
|
|
2105
|
+
const fullPath = path.join(dir, entry.name);
|
|
2106
|
+
if (entry.isDirectory()) await formatDirectory(fullPath);
|
|
2107
|
+
else if (entry.isFile()) try {
|
|
2108
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
2109
|
+
const formatted = await formatCode(fullPath, content);
|
|
2110
|
+
if (formatted && formatted !== content) await fs.writeFile(fullPath, formatted, "utf-8");
|
|
2111
|
+
} catch {}
|
|
2112
|
+
}));
|
|
2113
|
+
}
|
|
2114
|
+
await formatDirectory(projectDir);
|
|
2180
2115
|
}
|
|
2181
2116
|
|
|
2182
2117
|
//#endregion
|
|
2183
2118
|
//#region src/utils/package-runner.ts
|
|
2184
2119
|
/**
|
|
2185
|
-
* Returns the appropriate command for running a package without installing it globally,
|
|
2186
|
-
* based on the selected package manager.
|
|
2187
|
-
*
|
|
2188
|
-
* @param packageManager - The selected package manager (e.g., 'npm', 'yarn', 'pnpm', 'bun').
|
|
2189
|
-
* @param commandWithArgs - The command to run, including arguments (e.g., "prisma generate --schema=./prisma/schema.prisma").
|
|
2190
|
-
* @returns The full command string (e.g., "npx prisma generate --schema=./prisma/schema.prisma").
|
|
2191
|
-
*/
|
|
2192
|
-
function getPackageExecutionCommand(packageManager, commandWithArgs) {
|
|
2193
|
-
switch (packageManager) {
|
|
2194
|
-
case "pnpm": return `pnpm dlx ${commandWithArgs}`;
|
|
2195
|
-
case "bun": return `bunx ${commandWithArgs}`;
|
|
2196
|
-
default: return `npx ${commandWithArgs}`;
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
/**
|
|
2200
2120
|
* Returns the command and arguments as an array for use with execa's $ template syntax.
|
|
2201
2121
|
* This avoids the need for shell: true and provides better escaping.
|
|
2202
2122
|
*
|
|
@@ -2303,117 +2223,6 @@ async function setupFumadocs(config) {
|
|
|
2303
2223
|
}
|
|
2304
2224
|
}
|
|
2305
2225
|
|
|
2306
|
-
//#endregion
|
|
2307
|
-
//#region src/helpers/addons/oxlint-setup.ts
|
|
2308
|
-
/**
|
|
2309
|
-
* Oxlint setup - CLI-only operations
|
|
2310
|
-
* NOTE: Dependencies are handled by template-generator's addons-deps.ts processor
|
|
2311
|
-
* This file only handles external CLI initialization (oxlint --init, oxfmt --init)
|
|
2312
|
-
*/
|
|
2313
|
-
async function setupOxlint(projectDir, packageManager) {
|
|
2314
|
-
const s = spinner();
|
|
2315
|
-
s.start("Initializing oxlint and oxfmt...");
|
|
2316
|
-
const oxlintArgs = getPackageExecutionArgs(packageManager, "oxlint@latest --init");
|
|
2317
|
-
await $({
|
|
2318
|
-
cwd: projectDir,
|
|
2319
|
-
env: { CI: "true" }
|
|
2320
|
-
})`${oxlintArgs}`;
|
|
2321
|
-
const oxfmtArgs = getPackageExecutionArgs(packageManager, "oxfmt@latest --init");
|
|
2322
|
-
await $({
|
|
2323
|
-
cwd: projectDir,
|
|
2324
|
-
env: { CI: "true" }
|
|
2325
|
-
})`${oxfmtArgs}`;
|
|
2326
|
-
s.stop("oxlint and oxfmt initialized successfully!");
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
//#endregion
|
|
2330
|
-
//#region src/helpers/addons/ruler-setup.ts
|
|
2331
|
-
async function setupRuler(config) {
|
|
2332
|
-
const { packageManager, projectDir } = config;
|
|
2333
|
-
try {
|
|
2334
|
-
log.info("Setting up Ruler...");
|
|
2335
|
-
const rulerDir = path.join(projectDir, ".ruler");
|
|
2336
|
-
if (!await fs.pathExists(rulerDir)) {
|
|
2337
|
-
log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
|
|
2338
|
-
return;
|
|
2339
|
-
}
|
|
2340
|
-
const selectedEditors = await autocompleteMultiselect({
|
|
2341
|
-
message: "Select AI assistants for Ruler",
|
|
2342
|
-
options: Object.entries({
|
|
2343
|
-
amp: { label: "AMP" },
|
|
2344
|
-
copilot: { label: "GitHub Copilot" },
|
|
2345
|
-
claude: { label: "Claude Code" },
|
|
2346
|
-
codex: { label: "OpenAI Codex CLI" },
|
|
2347
|
-
cursor: { label: "Cursor" },
|
|
2348
|
-
windsurf: { label: "Windsurf" },
|
|
2349
|
-
cline: { label: "Cline" },
|
|
2350
|
-
aider: { label: "Aider" },
|
|
2351
|
-
firebase: { label: "Firebase Studio" },
|
|
2352
|
-
"gemini-cli": { label: "Gemini CLI" },
|
|
2353
|
-
junie: { label: "Junie" },
|
|
2354
|
-
kilocode: { label: "Kilo Code" },
|
|
2355
|
-
opencode: { label: "OpenCode" },
|
|
2356
|
-
crush: { label: "Crush" },
|
|
2357
|
-
zed: { label: "Zed" },
|
|
2358
|
-
qwen: { label: "Qwen" },
|
|
2359
|
-
amazonqcli: { label: "Amazon Q CLI" },
|
|
2360
|
-
augmentcode: { label: "AugmentCode" },
|
|
2361
|
-
firebender: { label: "Firebender" },
|
|
2362
|
-
goose: { label: "Goose" },
|
|
2363
|
-
jules: { label: "Jules" },
|
|
2364
|
-
kiro: { label: "Kiro" },
|
|
2365
|
-
openhands: { label: "Open Hands" },
|
|
2366
|
-
roo: { label: "RooCode" },
|
|
2367
|
-
trae: { label: "Trae AI" },
|
|
2368
|
-
warp: { label: "Warp" }
|
|
2369
|
-
}).map(([key, v]) => ({
|
|
2370
|
-
value: key,
|
|
2371
|
-
label: v.label
|
|
2372
|
-
})),
|
|
2373
|
-
required: false
|
|
2374
|
-
});
|
|
2375
|
-
if (isCancel(selectedEditors)) return exitCancelled("Operation cancelled");
|
|
2376
|
-
if (selectedEditors.length === 0) {
|
|
2377
|
-
log.info("No AI assistants selected. To apply rules later, run:");
|
|
2378
|
-
log.info(pc.cyan(`${getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only")}`));
|
|
2379
|
-
return;
|
|
2380
|
-
}
|
|
2381
|
-
const configFile = path.join(rulerDir, "ruler.toml");
|
|
2382
|
-
let updatedConfig = await fs.readFile(configFile, "utf-8");
|
|
2383
|
-
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
2384
|
-
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
2385
|
-
await fs.writeFile(configFile, updatedConfig);
|
|
2386
|
-
await addRulerScriptToPackageJson(projectDir, packageManager);
|
|
2387
|
-
const s = spinner();
|
|
2388
|
-
s.start("Applying rules with Ruler...");
|
|
2389
|
-
try {
|
|
2390
|
-
const rulerApplyArgs = getPackageExecutionArgs(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`);
|
|
2391
|
-
await $({
|
|
2392
|
-
cwd: projectDir,
|
|
2393
|
-
env: { CI: "true" }
|
|
2394
|
-
})`${rulerApplyArgs}`;
|
|
2395
|
-
s.stop("Applied rules with Ruler");
|
|
2396
|
-
} catch {
|
|
2397
|
-
s.stop(pc.red("Failed to apply rules"));
|
|
2398
|
-
}
|
|
2399
|
-
} catch (error) {
|
|
2400
|
-
log.error(pc.red("Failed to set up Ruler"));
|
|
2401
|
-
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
async function addRulerScriptToPackageJson(projectDir, packageManager) {
|
|
2405
|
-
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
2406
|
-
if (!await fs.pathExists(rootPackageJsonPath)) {
|
|
2407
|
-
log.warn("Root package.json not found, skipping ruler:apply script addition");
|
|
2408
|
-
return;
|
|
2409
|
-
}
|
|
2410
|
-
const packageJson = await fs.readJson(rootPackageJsonPath);
|
|
2411
|
-
if (!packageJson.scripts) packageJson.scripts = {};
|
|
2412
|
-
const rulerApplyCommand = getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only");
|
|
2413
|
-
packageJson.scripts["ruler:apply"] = rulerApplyCommand;
|
|
2414
|
-
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
2226
|
//#endregion
|
|
2418
2227
|
//#region src/helpers/addons/starlight-setup.ts
|
|
2419
2228
|
async function setupStarlight(config) {
|
|
@@ -2527,117 +2336,6 @@ async function setupTui(config) {
|
|
|
2527
2336
|
}
|
|
2528
2337
|
}
|
|
2529
2338
|
|
|
2530
|
-
//#endregion
|
|
2531
|
-
//#region src/helpers/addons/ultracite-setup.ts
|
|
2532
|
-
/**
|
|
2533
|
-
* Ultracite setup - CLI-only operations
|
|
2534
|
-
* NOTE: Dependencies (husky, lint-staged) are handled by template-generator's addons-deps.ts
|
|
2535
|
-
* This file handles interactive prompts and external CLI initialization
|
|
2536
|
-
*/
|
|
2537
|
-
const EDITORS = {
|
|
2538
|
-
vscode: { label: "VSCode / Cursor / Windsurf" },
|
|
2539
|
-
zed: { label: "Zed" }
|
|
2540
|
-
};
|
|
2541
|
-
const AGENTS = {
|
|
2542
|
-
"vscode-copilot": { label: "VS Code Copilot" },
|
|
2543
|
-
cursor: { label: "Cursor" },
|
|
2544
|
-
windsurf: { label: "Windsurf" },
|
|
2545
|
-
zed: { label: "Zed" },
|
|
2546
|
-
claude: { label: "Claude" },
|
|
2547
|
-
codex: { label: "Codex" },
|
|
2548
|
-
kiro: { label: "Kiro" },
|
|
2549
|
-
cline: { label: "Cline" },
|
|
2550
|
-
amp: { label: "Amp" },
|
|
2551
|
-
aider: { label: "Aider" },
|
|
2552
|
-
"firebase-studio": { label: "Firebase Studio" },
|
|
2553
|
-
"open-hands": { label: "Open Hands" },
|
|
2554
|
-
"gemini-cli": { label: "Gemini CLI" },
|
|
2555
|
-
junie: { label: "Junie" },
|
|
2556
|
-
augmentcode: { label: "AugmentCode" },
|
|
2557
|
-
"kilo-code": { label: "Kilo Code" },
|
|
2558
|
-
goose: { label: "Goose" },
|
|
2559
|
-
"roo-code": { label: "Roo Code" }
|
|
2560
|
-
};
|
|
2561
|
-
const HOOKS = {
|
|
2562
|
-
cursor: { label: "Cursor" },
|
|
2563
|
-
claude: { label: "Claude" }
|
|
2564
|
-
};
|
|
2565
|
-
function getFrameworksFromFrontend(frontend) {
|
|
2566
|
-
const frameworkMap = {
|
|
2567
|
-
"tanstack-router": "react",
|
|
2568
|
-
"react-router": "react",
|
|
2569
|
-
"tanstack-start": "react",
|
|
2570
|
-
next: "next",
|
|
2571
|
-
nuxt: "vue",
|
|
2572
|
-
"native-bare": "react",
|
|
2573
|
-
"native-uniwind": "react",
|
|
2574
|
-
"native-unistyles": "react",
|
|
2575
|
-
svelte: "svelte",
|
|
2576
|
-
solid: "solid"
|
|
2577
|
-
};
|
|
2578
|
-
const frameworks = /* @__PURE__ */ new Set();
|
|
2579
|
-
for (const f of frontend) if (f !== "none" && frameworkMap[f]) frameworks.add(frameworkMap[f]);
|
|
2580
|
-
return Array.from(frameworks);
|
|
2581
|
-
}
|
|
2582
|
-
async function setupUltracite(config, hasHusky) {
|
|
2583
|
-
const { packageManager, projectDir, frontend } = config;
|
|
2584
|
-
try {
|
|
2585
|
-
log.info("Setting up Ultracite...");
|
|
2586
|
-
const result = await group({
|
|
2587
|
-
editors: () => multiselect({
|
|
2588
|
-
message: "Choose editors",
|
|
2589
|
-
options: Object.entries(EDITORS).map(([key, editor]) => ({
|
|
2590
|
-
value: key,
|
|
2591
|
-
label: editor.label
|
|
2592
|
-
})),
|
|
2593
|
-
required: true
|
|
2594
|
-
}),
|
|
2595
|
-
agents: () => autocompleteMultiselect({
|
|
2596
|
-
message: "Choose agents",
|
|
2597
|
-
options: Object.entries(AGENTS).map(([key, agent]) => ({
|
|
2598
|
-
value: key,
|
|
2599
|
-
label: agent.label
|
|
2600
|
-
})),
|
|
2601
|
-
required: true
|
|
2602
|
-
}),
|
|
2603
|
-
hooks: () => autocompleteMultiselect({
|
|
2604
|
-
message: "Choose hooks",
|
|
2605
|
-
options: Object.entries(HOOKS).map(([key, hook]) => ({
|
|
2606
|
-
value: key,
|
|
2607
|
-
label: hook.label
|
|
2608
|
-
}))
|
|
2609
|
-
})
|
|
2610
|
-
}, { onCancel: () => {
|
|
2611
|
-
exitCancelled("Operation cancelled");
|
|
2612
|
-
} });
|
|
2613
|
-
const editors = result.editors;
|
|
2614
|
-
const agents = result.agents;
|
|
2615
|
-
const hooks = result.hooks;
|
|
2616
|
-
const frameworks = getFrameworksFromFrontend(frontend);
|
|
2617
|
-
const ultraciteArgs = [
|
|
2618
|
-
"init",
|
|
2619
|
-
"--pm",
|
|
2620
|
-
packageManager
|
|
2621
|
-
];
|
|
2622
|
-
if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
|
|
2623
|
-
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
2624
|
-
if (agents.length > 0) ultraciteArgs.push("--agents", ...agents);
|
|
2625
|
-
if (hooks.length > 0) ultraciteArgs.push("--hooks", ...hooks);
|
|
2626
|
-
if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
|
|
2627
|
-
const args = getPackageExecutionArgs(packageManager, `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`);
|
|
2628
|
-
const s = spinner();
|
|
2629
|
-
s.start("Running Ultracite init command...");
|
|
2630
|
-
await $({
|
|
2631
|
-
cwd: projectDir,
|
|
2632
|
-
env: { CI: "true" }
|
|
2633
|
-
})`${args}`;
|
|
2634
|
-
s.stop("Ultracite setup successfully!");
|
|
2635
|
-
} catch (error) {
|
|
2636
|
-
log.error(pc.red("Failed to set up Ultracite"));
|
|
2637
|
-
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
2339
|
//#endregion
|
|
2642
2340
|
//#region src/helpers/addons/wxt-setup.ts
|
|
2643
2341
|
const TEMPLATES = {
|
|
@@ -2702,37 +2400,20 @@ async function setupWxt(config) {
|
|
|
2702
2400
|
|
|
2703
2401
|
//#endregion
|
|
2704
2402
|
//#region src/helpers/addons/addons-setup.ts
|
|
2705
|
-
async function setupAddons(config
|
|
2706
|
-
const { addons, frontend
|
|
2403
|
+
async function setupAddons(config) {
|
|
2404
|
+
const { addons, frontend } = config;
|
|
2707
2405
|
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
|
|
2708
2406
|
const hasNuxtFrontend = frontend.includes("nuxt");
|
|
2709
2407
|
const hasSvelteFrontend = frontend.includes("svelte");
|
|
2710
2408
|
const hasSolidFrontend = frontend.includes("solid");
|
|
2711
2409
|
const hasNextFrontend = frontend.includes("next");
|
|
2712
|
-
if (addons.includes("turborepo") && isAddCommand) log.info(`${pc.yellow("Update your package.json scripts:")}
|
|
2713
|
-
|
|
2714
|
-
${pc.dim("Replace:")} ${pc.yellow("\"pnpm -r dev\"")} ${pc.dim("→")} ${pc.green("\"turbo dev\"")}
|
|
2715
|
-
${pc.dim("Replace:")} ${pc.yellow("\"pnpm --filter web dev\"")} ${pc.dim("→")} ${pc.green("\"turbo -F web dev\"")}
|
|
2716
|
-
|
|
2717
|
-
${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
|
2718
|
-
`);
|
|
2719
2410
|
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
|
|
2720
|
-
const hasUltracite = addons.includes("ultracite");
|
|
2721
|
-
const hasHusky = addons.includes("husky");
|
|
2722
|
-
const hasOxlint = addons.includes("oxlint");
|
|
2723
|
-
if (hasUltracite) await setupUltracite(config, hasHusky);
|
|
2724
|
-
if (hasOxlint) await setupOxlint(projectDir, packageManager);
|
|
2725
2411
|
if (addons.includes("starlight")) await setupStarlight(config);
|
|
2726
|
-
if (addons.includes("ruler")) await setupRuler(config);
|
|
2727
2412
|
if (addons.includes("fumadocs")) await setupFumadocs(config);
|
|
2728
2413
|
if (addons.includes("opentui")) await setupTui(config);
|
|
2729
2414
|
if (addons.includes("wxt")) await setupWxt(config);
|
|
2730
2415
|
}
|
|
2731
2416
|
|
|
2732
|
-
//#endregion
|
|
2733
|
-
//#region src/helpers/addons/examples-setup.ts
|
|
2734
|
-
async function setupExamples(_config) {}
|
|
2735
|
-
|
|
2736
2417
|
//#endregion
|
|
2737
2418
|
//#region src/utils/add-package-deps.ts
|
|
2738
2419
|
const addPackageDependency = async (opts) => {
|
|
@@ -2757,305 +2438,33 @@ const addPackageDependency = async (opts) => {
|
|
|
2757
2438
|
};
|
|
2758
2439
|
|
|
2759
2440
|
//#endregion
|
|
2760
|
-
//#region src/
|
|
2761
|
-
function
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
};
|
|
2781
|
-
let key = "VITE_SERVER_URL";
|
|
2782
|
-
if (hasNextJs) key = "NEXT_PUBLIC_SERVER_URL";
|
|
2783
|
-
else if (hasNuxt) key = "NUXT_PUBLIC_SERVER_URL";
|
|
2784
|
-
else if (hasSvelte) key = "PUBLIC_SERVER_URL";
|
|
2785
|
-
else if (hasTanstackStart) key = "VITE_SERVER_URL";
|
|
2786
|
-
return {
|
|
2787
|
-
key,
|
|
2788
|
-
value: "http://localhost:3000",
|
|
2789
|
-
write: true
|
|
2790
|
-
};
|
|
2791
|
-
}
|
|
2792
|
-
function getConvexVar(frontend) {
|
|
2793
|
-
const hasNextJs = frontend.includes("next");
|
|
2794
|
-
const hasNuxt = frontend.includes("nuxt");
|
|
2795
|
-
const hasSvelte = frontend.includes("svelte");
|
|
2796
|
-
const hasTanstackStart = frontend.includes("tanstack-start");
|
|
2797
|
-
if (hasNextJs) return "NEXT_PUBLIC_CONVEX_URL";
|
|
2798
|
-
if (hasNuxt) return "NUXT_PUBLIC_CONVEX_URL";
|
|
2799
|
-
if (hasSvelte) return "PUBLIC_CONVEX_URL";
|
|
2800
|
-
if (hasTanstackStart) return "VITE_CONVEX_URL";
|
|
2801
|
-
return "VITE_CONVEX_URL";
|
|
2802
|
-
}
|
|
2803
|
-
async function addEnvVariablesToFile(filePath, variables) {
|
|
2804
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
2805
|
-
let envContent = "";
|
|
2806
|
-
if (await fs.pathExists(filePath)) envContent = await fs.readFile(filePath, "utf8");
|
|
2807
|
-
let modified = false;
|
|
2808
|
-
let contentToAdd = "";
|
|
2809
|
-
const exampleVariables = [];
|
|
2810
|
-
for (const { key, value, condition, comment } of variables) if (condition) {
|
|
2811
|
-
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
2812
|
-
const valueToWrite = value ?? "";
|
|
2813
|
-
exampleVariables.push(`${key}=`);
|
|
2814
|
-
if (regex.test(envContent)) {
|
|
2815
|
-
const existingMatch = envContent.match(regex);
|
|
2816
|
-
if (existingMatch && existingMatch[0] !== `${key}=${valueToWrite}`) {
|
|
2817
|
-
envContent = envContent.replace(regex, `${key}=${valueToWrite}`);
|
|
2818
|
-
modified = true;
|
|
2819
|
-
}
|
|
2820
|
-
} else {
|
|
2821
|
-
if (comment) contentToAdd += `# ${comment}\n`;
|
|
2822
|
-
contentToAdd += `${key}=${valueToWrite}\n`;
|
|
2823
|
-
modified = true;
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
2826
|
-
if (contentToAdd) {
|
|
2827
|
-
if (envContent.length > 0 && !envContent.endsWith("\n")) envContent += "\n";
|
|
2828
|
-
envContent += contentToAdd;
|
|
2829
|
-
}
|
|
2830
|
-
if (modified) await fs.writeFile(filePath, envContent.trimEnd());
|
|
2831
|
-
const exampleFilePath = filePath.replace(/\.env$/, ".env.example");
|
|
2832
|
-
let exampleEnvContent = "";
|
|
2833
|
-
if (await fs.pathExists(exampleFilePath)) exampleEnvContent = await fs.readFile(exampleFilePath, "utf8");
|
|
2834
|
-
let exampleModified = false;
|
|
2835
|
-
let exampleContentToAdd = "";
|
|
2836
|
-
for (const exampleVar of exampleVariables) {
|
|
2837
|
-
const key = exampleVar.split("=")[0];
|
|
2838
|
-
if (!new RegExp(`^${key}=.*$`, "m").test(exampleEnvContent)) {
|
|
2839
|
-
exampleContentToAdd += `${exampleVar}\n`;
|
|
2840
|
-
exampleModified = true;
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
|
-
if (exampleContentToAdd) {
|
|
2844
|
-
if (exampleEnvContent.length > 0 && !exampleEnvContent.endsWith("\n")) exampleEnvContent += "\n";
|
|
2845
|
-
exampleEnvContent += exampleContentToAdd;
|
|
2846
|
-
}
|
|
2847
|
-
if (exampleModified || !await fs.pathExists(exampleFilePath)) await fs.writeFile(exampleFilePath, exampleEnvContent.trimEnd());
|
|
2848
|
-
}
|
|
2849
|
-
async function setupEnvironmentVariables(config) {
|
|
2850
|
-
const { backend, frontend, database, auth, examples, dbSetup, projectDir, webDeploy, serverDeploy } = config;
|
|
2851
|
-
const hasReactRouter = frontend.includes("react-router");
|
|
2852
|
-
const hasTanStackRouter = frontend.includes("tanstack-router");
|
|
2853
|
-
const hasTanStackStart = frontend.includes("tanstack-start");
|
|
2854
|
-
const hasNextJs = frontend.includes("next");
|
|
2855
|
-
const hasNuxt = frontend.includes("nuxt");
|
|
2856
|
-
const hasSvelte = frontend.includes("svelte");
|
|
2857
|
-
const hasSolid = frontend.includes("solid");
|
|
2858
|
-
const hasWebFrontend$1 = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte;
|
|
2859
|
-
if (hasWebFrontend$1) {
|
|
2860
|
-
const clientDir = path.join(projectDir, "apps/web");
|
|
2861
|
-
if (await fs.pathExists(clientDir)) {
|
|
2862
|
-
const baseVar = getClientServerVar(frontend, backend);
|
|
2863
|
-
const clientVars = [{
|
|
2864
|
-
key: backend === "convex" ? getConvexVar(frontend) : baseVar.key,
|
|
2865
|
-
value: backend === "convex" ? "https://<YOUR_CONVEX_URL>" : baseVar.value,
|
|
2866
|
-
condition: backend === "convex" ? true : baseVar.write
|
|
2867
|
-
}];
|
|
2868
|
-
if (backend === "convex" && auth === "clerk") {
|
|
2869
|
-
if (hasNextJs) clientVars.push({
|
|
2870
|
-
key: "NEXT_PUBLIC_CLERK_FRONTEND_API_URL",
|
|
2871
|
-
value: "",
|
|
2872
|
-
condition: true
|
|
2873
|
-
}, {
|
|
2874
|
-
key: "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY",
|
|
2875
|
-
value: "",
|
|
2876
|
-
condition: true
|
|
2877
|
-
}, {
|
|
2878
|
-
key: "CLERK_SECRET_KEY",
|
|
2879
|
-
value: "",
|
|
2880
|
-
condition: true
|
|
2881
|
-
});
|
|
2882
|
-
else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {
|
|
2883
|
-
clientVars.push({
|
|
2884
|
-
key: "VITE_CLERK_PUBLISHABLE_KEY",
|
|
2885
|
-
value: "",
|
|
2886
|
-
condition: true
|
|
2887
|
-
});
|
|
2888
|
-
if (hasTanStackStart) clientVars.push({
|
|
2889
|
-
key: "CLERK_SECRET_KEY",
|
|
2890
|
-
value: "",
|
|
2891
|
-
condition: true
|
|
2892
|
-
});
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
if (backend === "convex" && auth === "better-auth") {
|
|
2896
|
-
if (hasNextJs) clientVars.push({
|
|
2897
|
-
key: "NEXT_PUBLIC_CONVEX_SITE_URL",
|
|
2898
|
-
value: "https://<YOUR_CONVEX_URL>",
|
|
2899
|
-
condition: true
|
|
2900
|
-
});
|
|
2901
|
-
else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) clientVars.push({
|
|
2902
|
-
key: "VITE_CONVEX_SITE_URL",
|
|
2903
|
-
value: "https://<YOUR_CONVEX_URL>",
|
|
2904
|
-
condition: true
|
|
2905
|
-
});
|
|
2906
|
-
}
|
|
2907
|
-
await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2910
|
-
if (frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles")) {
|
|
2911
|
-
const nativeDir = path.join(projectDir, "apps/native");
|
|
2912
|
-
if (await fs.pathExists(nativeDir)) {
|
|
2913
|
-
let envVarName = "EXPO_PUBLIC_SERVER_URL";
|
|
2914
|
-
let serverUrl = "http://localhost:3000";
|
|
2915
|
-
if (backend === "self") serverUrl = "http://localhost:3001";
|
|
2916
|
-
if (backend === "convex") {
|
|
2917
|
-
envVarName = "EXPO_PUBLIC_CONVEX_URL";
|
|
2918
|
-
serverUrl = "https://<YOUR_CONVEX_URL>";
|
|
2919
|
-
}
|
|
2920
|
-
const nativeVars = [{
|
|
2921
|
-
key: envVarName,
|
|
2922
|
-
value: serverUrl,
|
|
2923
|
-
condition: true
|
|
2924
|
-
}];
|
|
2925
|
-
if (backend === "convex" && auth === "clerk") nativeVars.push({
|
|
2926
|
-
key: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY",
|
|
2927
|
-
value: "",
|
|
2928
|
-
condition: true
|
|
2929
|
-
});
|
|
2930
|
-
if (backend === "convex" && auth === "better-auth") nativeVars.push({
|
|
2931
|
-
key: "EXPO_PUBLIC_CONVEX_SITE_URL",
|
|
2932
|
-
value: "https://<YOUR_CONVEX_URL>",
|
|
2933
|
-
condition: true
|
|
2934
|
-
});
|
|
2935
|
-
await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
if (backend === "convex") {
|
|
2939
|
-
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
2940
|
-
if (await fs.pathExists(convexBackendDir)) {
|
|
2941
|
-
const envLocalPath = path.join(convexBackendDir, ".env.local");
|
|
2942
|
-
let commentBlocks = "";
|
|
2943
|
-
if (examples?.includes("ai")) commentBlocks += `# Set Google AI API key for AI agent
|
|
2944
|
-
# npx convex env set GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
|
|
2945
|
-
|
|
2946
|
-
`;
|
|
2947
|
-
if (auth === "better-auth") commentBlocks += `# Set Convex environment variables
|
|
2948
|
-
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
2949
|
-
${hasWebFrontend$1 ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}`;
|
|
2950
|
-
if (commentBlocks) {
|
|
2951
|
-
let existingContent = "";
|
|
2952
|
-
if (await fs.pathExists(envLocalPath)) existingContent = await fs.readFile(envLocalPath, "utf8");
|
|
2953
|
-
await fs.writeFile(envLocalPath, commentBlocks + existingContent);
|
|
2954
|
-
}
|
|
2955
|
-
const convexBackendVars = [];
|
|
2956
|
-
if (examples?.includes("ai")) convexBackendVars.push({
|
|
2957
|
-
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
2958
|
-
value: "",
|
|
2959
|
-
condition: true,
|
|
2960
|
-
comment: "Google AI API key for AI agent"
|
|
2961
|
-
});
|
|
2962
|
-
if (auth === "better-auth") {
|
|
2963
|
-
const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
2964
|
-
const hasWeb = hasWebFrontend$1;
|
|
2965
|
-
if (hasNative) convexBackendVars.push({
|
|
2966
|
-
key: "EXPO_PUBLIC_CONVEX_SITE_URL",
|
|
2967
|
-
value: "",
|
|
2968
|
-
condition: true,
|
|
2969
|
-
comment: "Same as CONVEX_URL but ends in .site"
|
|
2970
|
-
});
|
|
2971
|
-
if (hasWeb) convexBackendVars.push({
|
|
2972
|
-
key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
|
|
2973
|
-
value: "",
|
|
2974
|
-
condition: true,
|
|
2975
|
-
comment: "Same as CONVEX_URL but ends in .site"
|
|
2976
|
-
}, {
|
|
2977
|
-
key: "SITE_URL",
|
|
2978
|
-
value: "http://localhost:3001",
|
|
2979
|
-
condition: true,
|
|
2980
|
-
comment: "Web app URL for authentication"
|
|
2981
|
-
});
|
|
2982
|
-
}
|
|
2983
|
-
if (convexBackendVars.length > 0) await addEnvVariablesToFile(envLocalPath, convexBackendVars);
|
|
2984
|
-
}
|
|
2985
|
-
return;
|
|
2986
|
-
}
|
|
2987
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
2988
|
-
let corsOrigin = "http://localhost:3001";
|
|
2989
|
-
if (backend === "self") corsOrigin = "http://localhost:3001";
|
|
2990
|
-
else if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
|
|
2991
|
-
let databaseUrl = null;
|
|
2992
|
-
if (database !== "none" && dbSetup === "none") switch (database) {
|
|
2993
|
-
case "postgres":
|
|
2994
|
-
databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
|
|
2441
|
+
//#region src/utils/env-utils.ts
|
|
2442
|
+
async function addEnvVariablesToFile(envPath, variables) {
|
|
2443
|
+
let content = "";
|
|
2444
|
+
if (fs.existsSync(envPath)) content = await fs.readFile(envPath, "utf-8");
|
|
2445
|
+
else await fs.ensureFile(envPath);
|
|
2446
|
+
const existingLines = content.split("\n");
|
|
2447
|
+
const newLines = [];
|
|
2448
|
+
const keysToAdd = /* @__PURE__ */ new Map();
|
|
2449
|
+
for (const variable of variables) {
|
|
2450
|
+
if (variable.condition === false || !variable.key) continue;
|
|
2451
|
+
keysToAdd.set(variable.key, variable.value);
|
|
2452
|
+
}
|
|
2453
|
+
let foundKeys = /* @__PURE__ */ new Set();
|
|
2454
|
+
for (const line of existingLines) {
|
|
2455
|
+
const trimmedLine = line.trim();
|
|
2456
|
+
let lineProcessed = false;
|
|
2457
|
+
for (const [key, value] of keysToAdd) if (trimmedLine.startsWith(`${key}=`)) {
|
|
2458
|
+
newLines.push(`${key}=${value}`);
|
|
2459
|
+
foundKeys.add(key);
|
|
2460
|
+
lineProcessed = true;
|
|
2995
2461
|
break;
|
|
2996
|
-
case "mysql":
|
|
2997
|
-
databaseUrl = "mysql://root:password@localhost:3306/mydb";
|
|
2998
|
-
break;
|
|
2999
|
-
case "mongodb":
|
|
3000
|
-
databaseUrl = "mongodb://localhost:27017/mydatabase";
|
|
3001
|
-
break;
|
|
3002
|
-
case "sqlite":
|
|
3003
|
-
if (config.runtime === "workers" || webDeploy === "cloudflare" || serverDeploy === "cloudflare") databaseUrl = "http://127.0.0.1:8080";
|
|
3004
|
-
else {
|
|
3005
|
-
const dbAppDir = backend === "self" ? "apps/web" : "apps/server";
|
|
3006
|
-
databaseUrl = `file:${path.join(config.projectDir, dbAppDir, "local.db")}`;
|
|
3007
|
-
}
|
|
3008
|
-
break;
|
|
3009
|
-
}
|
|
3010
|
-
const serverVars = [
|
|
3011
|
-
{
|
|
3012
|
-
key: "BETTER_AUTH_SECRET",
|
|
3013
|
-
value: generateAuthSecret(),
|
|
3014
|
-
condition: !!auth
|
|
3015
|
-
},
|
|
3016
|
-
{
|
|
3017
|
-
key: "BETTER_AUTH_URL",
|
|
3018
|
-
value: backend === "self" ? "http://localhost:3001" : "http://localhost:3000",
|
|
3019
|
-
condition: !!auth
|
|
3020
|
-
},
|
|
3021
|
-
{
|
|
3022
|
-
key: "POLAR_ACCESS_TOKEN",
|
|
3023
|
-
value: "",
|
|
3024
|
-
condition: config.payments === "polar"
|
|
3025
|
-
},
|
|
3026
|
-
{
|
|
3027
|
-
key: "POLAR_SUCCESS_URL",
|
|
3028
|
-
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
|
|
3029
|
-
condition: config.payments === "polar"
|
|
3030
|
-
},
|
|
3031
|
-
{
|
|
3032
|
-
key: "CORS_ORIGIN",
|
|
3033
|
-
value: corsOrigin,
|
|
3034
|
-
condition: true
|
|
3035
|
-
},
|
|
3036
|
-
{
|
|
3037
|
-
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
3038
|
-
value: "",
|
|
3039
|
-
condition: examples?.includes("ai") || false
|
|
3040
|
-
},
|
|
3041
|
-
{
|
|
3042
|
-
key: "DATABASE_URL",
|
|
3043
|
-
value: databaseUrl,
|
|
3044
|
-
condition: database !== "none" && dbSetup === "none"
|
|
3045
2462
|
}
|
|
3046
|
-
|
|
3047
|
-
if (backend === "self") {
|
|
3048
|
-
const webDir = path.join(projectDir, "apps/web");
|
|
3049
|
-
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverVars);
|
|
3050
|
-
} else if (await fs.pathExists(serverDir)) await addEnvVariablesToFile(path.join(serverDir, ".env"), serverVars);
|
|
3051
|
-
if (webDeploy === "cloudflare" && serverDeploy === "cloudflare" || webDeploy === "cloudflare" || serverDeploy === "cloudflare") {
|
|
3052
|
-
const infraDir = path.join(projectDir, "packages/infra");
|
|
3053
|
-
if (await fs.pathExists(infraDir)) await addEnvVariablesToFile(path.join(infraDir, ".env"), [{
|
|
3054
|
-
key: "ALCHEMY_PASSWORD",
|
|
3055
|
-
value: "please-change-this",
|
|
3056
|
-
condition: true
|
|
3057
|
-
}]);
|
|
2463
|
+
if (!lineProcessed) newLines.push(line);
|
|
3058
2464
|
}
|
|
2465
|
+
for (const [key, value] of keysToAdd) if (!foundKeys.has(key)) newLines.push(`${key}=${value}`);
|
|
2466
|
+
if (newLines.length > 0 && newLines[newLines.length - 1] === "") newLines.pop();
|
|
2467
|
+
if (foundKeys.size > 0 || keysToAdd.size > foundKeys.size) await fs.writeFile(envPath, newLines.join("\n") + "\n");
|
|
3059
2468
|
}
|
|
3060
2469
|
|
|
3061
2470
|
//#endregion
|
|
@@ -3831,9 +3240,9 @@ async function selectTursoGroup() {
|
|
|
3831
3240
|
}
|
|
3832
3241
|
const selectedGroup = await select({
|
|
3833
3242
|
message: "Select a Turso database group:",
|
|
3834
|
-
options: groups.map((group
|
|
3835
|
-
value: group
|
|
3836
|
-
label: `${group
|
|
3243
|
+
options: groups.map((group) => ({
|
|
3244
|
+
value: group.name,
|
|
3245
|
+
label: `${group.name} (${group.locations})`
|
|
3837
3246
|
}))
|
|
3838
3247
|
});
|
|
3839
3248
|
if (isCancel(selectedGroup)) return exitCancelled("Operation cancelled");
|
|
@@ -4007,314 +3416,6 @@ async function setupDatabase(config, cliInput) {
|
|
|
4007
3416
|
}
|
|
4008
3417
|
}
|
|
4009
3418
|
|
|
4010
|
-
//#endregion
|
|
4011
|
-
//#region src/helpers/deployment/alchemy/alchemy-next-setup.ts
|
|
4012
|
-
/**
|
|
4013
|
-
* Alchemy Next.js setup - CLI-only operations
|
|
4014
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4015
|
-
* This only modifies config files for "add deploy" command
|
|
4016
|
-
*/
|
|
4017
|
-
async function setupNextAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4018
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4019
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4020
|
-
const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
|
|
4021
|
-
await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
|
4022
|
-
|
|
4023
|
-
export default defineCloudflareConfig({});
|
|
4024
|
-
`);
|
|
4025
|
-
const gitignorePath = path.join(webAppDir, ".gitignore");
|
|
4026
|
-
if (await fs.pathExists(gitignorePath)) {
|
|
4027
|
-
if (!(await fs.readFile(gitignorePath, "utf-8")).includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
|
4028
|
-
} else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
|
|
4029
|
-
}
|
|
4030
|
-
|
|
4031
|
-
//#endregion
|
|
4032
|
-
//#region src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts
|
|
4033
|
-
/**
|
|
4034
|
-
* Alchemy Nuxt setup - CLI-only operations
|
|
4035
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4036
|
-
* This only modifies config files for "add deploy" command
|
|
4037
|
-
*/
|
|
4038
|
-
async function setupNuxtAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4039
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4040
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4041
|
-
const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
|
|
4042
|
-
if (!await fs.pathExists(nuxtConfigPath)) return;
|
|
4043
|
-
try {
|
|
4044
|
-
const project = new Project({ manipulationSettings: {
|
|
4045
|
-
indentationText: IndentationText.TwoSpaces,
|
|
4046
|
-
quoteKind: QuoteKind.Double
|
|
4047
|
-
} });
|
|
4048
|
-
project.addSourceFileAtPath(nuxtConfigPath);
|
|
4049
|
-
const exportAssignment = project.getSourceFileOrThrow(nuxtConfigPath).getExportAssignment((d) => !d.isExportEquals());
|
|
4050
|
-
if (!exportAssignment) return;
|
|
4051
|
-
const defineConfigCall = exportAssignment.getExpression();
|
|
4052
|
-
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
|
|
4053
|
-
let configObject = defineConfigCall.getArguments()[0];
|
|
4054
|
-
if (!configObject) configObject = defineConfigCall.addArgument("{}");
|
|
4055
|
-
if (Node.isObjectLiteralExpression(configObject)) {
|
|
4056
|
-
if (!configObject.getProperty("nitro")) configObject.addPropertyAssignment({
|
|
4057
|
-
name: "nitro",
|
|
4058
|
-
initializer: `{
|
|
4059
|
-
preset: "cloudflare_module",
|
|
4060
|
-
cloudflare: {
|
|
4061
|
-
deployConfig: true,
|
|
4062
|
-
nodeCompat: true
|
|
4063
|
-
}
|
|
4064
|
-
}`
|
|
4065
|
-
});
|
|
4066
|
-
const modulesProperty = configObject.getProperty("modules");
|
|
4067
|
-
if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
|
|
4068
|
-
const initializer = modulesProperty.getInitializer();
|
|
4069
|
-
if (Node.isArrayLiteralExpression(initializer)) {
|
|
4070
|
-
if (!initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'")) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
4071
|
-
}
|
|
4072
|
-
} else if (!modulesProperty) configObject.addPropertyAssignment({
|
|
4073
|
-
name: "modules",
|
|
4074
|
-
initializer: "[\"nitro-cloudflare-dev\"]"
|
|
4075
|
-
});
|
|
4076
|
-
}
|
|
4077
|
-
await project.save();
|
|
4078
|
-
} catch (error) {
|
|
4079
|
-
console.warn("Failed to update nuxt.config.ts:", error);
|
|
4080
|
-
}
|
|
4081
|
-
}
|
|
4082
|
-
|
|
4083
|
-
//#endregion
|
|
4084
|
-
//#region src/helpers/deployment/alchemy/alchemy-svelte-setup.ts
|
|
4085
|
-
/**
|
|
4086
|
-
* Alchemy Svelte setup - CLI-only operations
|
|
4087
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4088
|
-
* This only modifies config files for "add deploy" command
|
|
4089
|
-
*/
|
|
4090
|
-
async function setupSvelteAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4091
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4092
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4093
|
-
const svelteConfigPath = path.join(webAppDir, "svelte.config.js");
|
|
4094
|
-
if (!await fs.pathExists(svelteConfigPath)) return;
|
|
4095
|
-
try {
|
|
4096
|
-
const project = new Project({ manipulationSettings: {
|
|
4097
|
-
indentationText: IndentationText.TwoSpaces,
|
|
4098
|
-
quoteKind: QuoteKind.Single
|
|
4099
|
-
} });
|
|
4100
|
-
project.addSourceFileAtPath(svelteConfigPath);
|
|
4101
|
-
const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
|
|
4102
|
-
const adapterImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
4103
|
-
if (adapterImport) {
|
|
4104
|
-
adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
|
|
4105
|
-
adapterImport.removeDefaultImport();
|
|
4106
|
-
adapterImport.setDefaultImport("alchemy");
|
|
4107
|
-
} else sourceFile.insertImportDeclaration(0, {
|
|
4108
|
-
moduleSpecifier: "alchemy/cloudflare/sveltekit",
|
|
4109
|
-
defaultImport: "alchemy"
|
|
4110
|
-
});
|
|
4111
|
-
const configVariable = sourceFile.getVariableDeclaration("config");
|
|
4112
|
-
if (configVariable) {
|
|
4113
|
-
const initializer = configVariable.getInitializer();
|
|
4114
|
-
if (Node.isObjectLiteralExpression(initializer)) updateAdapterInConfig(initializer);
|
|
4115
|
-
}
|
|
4116
|
-
await project.save();
|
|
4117
|
-
} catch (error) {
|
|
4118
|
-
console.warn("Failed to update svelte.config.js:", error);
|
|
4119
|
-
}
|
|
4120
|
-
}
|
|
4121
|
-
function updateAdapterInConfig(configObject) {
|
|
4122
|
-
if (!Node.isObjectLiteralExpression(configObject)) return;
|
|
4123
|
-
const kitProperty = configObject.getProperty("kit");
|
|
4124
|
-
if (kitProperty && Node.isPropertyAssignment(kitProperty)) {
|
|
4125
|
-
const kitInitializer = kitProperty.getInitializer();
|
|
4126
|
-
if (Node.isObjectLiteralExpression(kitInitializer)) {
|
|
4127
|
-
const adapterProperty = kitInitializer.getProperty("adapter");
|
|
4128
|
-
if (adapterProperty && Node.isPropertyAssignment(adapterProperty)) {
|
|
4129
|
-
const initializer = adapterProperty.getInitializer();
|
|
4130
|
-
if (Node.isCallExpression(initializer)) {
|
|
4131
|
-
const expression = initializer.getExpression();
|
|
4132
|
-
if (Node.isIdentifier(expression) && expression.getText() === "adapter") expression.replaceWithText("alchemy");
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
}
|
|
4137
|
-
}
|
|
4138
|
-
|
|
4139
|
-
//#endregion
|
|
4140
|
-
//#region src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts
|
|
4141
|
-
/**
|
|
4142
|
-
* Alchemy TanStack Start setup - CLI-only operations
|
|
4143
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4144
|
-
* This only modifies vite.config.ts for "add deploy" command
|
|
4145
|
-
*/
|
|
4146
|
-
async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4147
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4148
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4149
|
-
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
4150
|
-
if (await fs.pathExists(viteConfigPath)) try {
|
|
4151
|
-
const project = new Project({ manipulationSettings: {
|
|
4152
|
-
indentationText: IndentationText.TwoSpaces,
|
|
4153
|
-
quoteKind: QuoteKind.Double
|
|
4154
|
-
} });
|
|
4155
|
-
project.addSourceFileAtPath(viteConfigPath);
|
|
4156
|
-
const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
|
|
4157
|
-
const alchemyImport = sourceFile.getImportDeclaration("alchemy/cloudflare/tanstack-start");
|
|
4158
|
-
if (!alchemyImport) sourceFile.addImportDeclaration({
|
|
4159
|
-
moduleSpecifier: "alchemy/cloudflare/tanstack-start",
|
|
4160
|
-
defaultImport: "alchemy"
|
|
4161
|
-
});
|
|
4162
|
-
else alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
|
|
4163
|
-
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
4164
|
-
if (!exportAssignment) return;
|
|
4165
|
-
const defineConfigCall = exportAssignment.getExpression();
|
|
4166
|
-
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineConfig") return;
|
|
4167
|
-
let configObject = defineConfigCall.getArguments()[0];
|
|
4168
|
-
if (!configObject) configObject = defineConfigCall.addArgument("{}");
|
|
4169
|
-
if (Node.isObjectLiteralExpression(configObject)) {
|
|
4170
|
-
const pluginsProperty = configObject.getProperty("plugins");
|
|
4171
|
-
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
|
4172
|
-
const initializer = pluginsProperty.getInitializer();
|
|
4173
|
-
if (Node.isArrayLiteralExpression(initializer)) {
|
|
4174
|
-
if (!initializer.getElements().some((el) => el.getText().includes("alchemy("))) initializer.addElement("alchemy()");
|
|
4175
|
-
}
|
|
4176
|
-
} else configObject.addPropertyAssignment({
|
|
4177
|
-
name: "plugins",
|
|
4178
|
-
initializer: "[alchemy()]"
|
|
4179
|
-
});
|
|
4180
|
-
}
|
|
4181
|
-
await project.save();
|
|
4182
|
-
} catch (error) {
|
|
4183
|
-
console.warn("Failed to update vite.config.ts:", error);
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
|
|
4187
|
-
//#endregion
|
|
4188
|
-
//#region src/helpers/deployment/alchemy/alchemy-vite-setup.ts
|
|
4189
|
-
/**
|
|
4190
|
-
* Alchemy setup for Vite-based frontends (React Router, Solid, TanStack Router)
|
|
4191
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4192
|
-
* These frontends don't need config modifications - templates already have correct setup
|
|
4193
|
-
*/
|
|
4194
|
-
async function setupReactRouterAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4195
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4196
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4197
|
-
}
|
|
4198
|
-
async function setupSolidAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4199
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4200
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4201
|
-
}
|
|
4202
|
-
async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4203
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4204
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4205
|
-
}
|
|
4206
|
-
|
|
4207
|
-
//#endregion
|
|
4208
|
-
//#region src/helpers/deployment/alchemy/alchemy-combined-setup.ts
|
|
4209
|
-
function getInfraFilter(packageManager, hasTurborepo, infraWorkspace) {
|
|
4210
|
-
if (hasTurborepo) return (script) => `turbo -F ${infraWorkspace} ${script}`;
|
|
4211
|
-
switch (packageManager) {
|
|
4212
|
-
case "pnpm": return (script) => `pnpm --filter ${infraWorkspace} ${script}`;
|
|
4213
|
-
case "npm": return (script) => `npm run ${script} --workspace ${infraWorkspace}`;
|
|
4214
|
-
case "bun": return (script) => `bun run --filter ${infraWorkspace} ${script}`;
|
|
4215
|
-
}
|
|
4216
|
-
}
|
|
4217
|
-
async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
|
|
4218
|
-
await setupInfraScripts(projectDir, packageManager, config);
|
|
4219
|
-
const frontend = config.frontend;
|
|
4220
|
-
const isNext = frontend.includes("next");
|
|
4221
|
-
const isNuxt = frontend.includes("nuxt");
|
|
4222
|
-
const isSvelte = frontend.includes("svelte");
|
|
4223
|
-
const isTanstackRouter = frontend.includes("tanstack-router");
|
|
4224
|
-
const isTanstackStart = frontend.includes("tanstack-start");
|
|
4225
|
-
const isReactRouter = frontend.includes("react-router");
|
|
4226
|
-
const isSolid = frontend.includes("solid");
|
|
4227
|
-
if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4228
|
-
else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4229
|
-
else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4230
|
-
else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4231
|
-
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4232
|
-
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4233
|
-
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4234
|
-
}
|
|
4235
|
-
async function setupInfraScripts(projectDir, packageManager, config) {
|
|
4236
|
-
const projectName = config.projectName;
|
|
4237
|
-
const hasTurborepo = config.addons.includes("turborepo");
|
|
4238
|
-
const infraWorkspace = `@${projectName}/infra`;
|
|
4239
|
-
const rootPkgPath = path.join(projectDir, "package.json");
|
|
4240
|
-
if (await fs.pathExists(rootPkgPath)) {
|
|
4241
|
-
const pkg = await fs.readJson(rootPkgPath);
|
|
4242
|
-
const filter = getInfraFilter(packageManager, hasTurborepo, infraWorkspace);
|
|
4243
|
-
pkg.scripts = {
|
|
4244
|
-
...pkg.scripts,
|
|
4245
|
-
deploy: filter("deploy"),
|
|
4246
|
-
destroy: filter("destroy")
|
|
4247
|
-
};
|
|
4248
|
-
await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
|
|
4249
|
-
}
|
|
4250
|
-
if (config.serverDeploy === "cloudflare") {
|
|
4251
|
-
const serverPkgPath = path.join(projectDir, "apps/server/package.json");
|
|
4252
|
-
if (await fs.pathExists(serverPkgPath)) {
|
|
4253
|
-
const serverPkg = await fs.readJson(serverPkgPath);
|
|
4254
|
-
if (serverPkg.scripts?.dev) {
|
|
4255
|
-
serverPkg.scripts["dev:bare"] = serverPkg.scripts.dev;
|
|
4256
|
-
delete serverPkg.scripts.dev;
|
|
4257
|
-
await fs.writeJson(serverPkgPath, serverPkg, { spaces: 2 });
|
|
4258
|
-
}
|
|
4259
|
-
}
|
|
4260
|
-
}
|
|
4261
|
-
if (config.webDeploy === "cloudflare") {
|
|
4262
|
-
const webPkgPath = path.join(projectDir, "apps/web/package.json");
|
|
4263
|
-
if (await fs.pathExists(webPkgPath)) {
|
|
4264
|
-
const webPkg = await fs.readJson(webPkgPath);
|
|
4265
|
-
if (webPkg.scripts?.dev) {
|
|
4266
|
-
webPkg.scripts["dev:bare"] = webPkg.scripts.dev;
|
|
4267
|
-
delete webPkg.scripts.dev;
|
|
4268
|
-
await fs.writeJson(webPkgPath, webPkg, { spaces: 2 });
|
|
4269
|
-
}
|
|
4270
|
-
}
|
|
4271
|
-
}
|
|
4272
|
-
}
|
|
4273
|
-
|
|
4274
|
-
//#endregion
|
|
4275
|
-
//#region src/helpers/deployment/server-deploy-setup.ts
|
|
4276
|
-
/**
|
|
4277
|
-
* Server deploy setup - CLI-only operations
|
|
4278
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts processor
|
|
4279
|
-
* This file only handles external CLI calls for "add deploy" command
|
|
4280
|
-
*/
|
|
4281
|
-
async function setupServerDeploy(config) {
|
|
4282
|
-
const { serverDeploy, webDeploy, projectDir, packageManager } = config;
|
|
4283
|
-
if (serverDeploy === "none") return;
|
|
4284
|
-
if (serverDeploy === "cloudflare" && webDeploy === "cloudflare") return;
|
|
4285
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
4286
|
-
if (!await fs.pathExists(serverDir)) return;
|
|
4287
|
-
if (serverDeploy === "cloudflare") await setupInfraScripts(projectDir, packageManager, config);
|
|
4288
|
-
}
|
|
4289
|
-
|
|
4290
|
-
//#endregion
|
|
4291
|
-
//#region src/helpers/deployment/web-deploy-setup.ts
|
|
4292
|
-
async function setupWebDeploy(config) {
|
|
4293
|
-
const { webDeploy, serverDeploy, frontend, projectDir } = config;
|
|
4294
|
-
const { packageManager } = config;
|
|
4295
|
-
if (webDeploy === "none") return;
|
|
4296
|
-
if (webDeploy !== "cloudflare") return;
|
|
4297
|
-
if (webDeploy === "cloudflare" && serverDeploy === "cloudflare") {
|
|
4298
|
-
await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
|
|
4299
|
-
return;
|
|
4300
|
-
}
|
|
4301
|
-
await setupInfraScripts(projectDir, packageManager, config);
|
|
4302
|
-
const isNext = frontend.includes("next");
|
|
4303
|
-
const isNuxt = frontend.includes("nuxt");
|
|
4304
|
-
const isSvelte = frontend.includes("svelte");
|
|
4305
|
-
const isTanstackRouter = frontend.includes("tanstack-router");
|
|
4306
|
-
const isTanstackStart = frontend.includes("tanstack-start");
|
|
4307
|
-
const isReactRouter = frontend.includes("react-router");
|
|
4308
|
-
const isSolid = frontend.includes("solid");
|
|
4309
|
-
if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager);
|
|
4310
|
-
else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
|
4311
|
-
else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
|
4312
|
-
else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
|
4313
|
-
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
|
4314
|
-
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
|
4315
|
-
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
|
|
4316
|
-
}
|
|
4317
|
-
|
|
4318
3419
|
//#endregion
|
|
4319
3420
|
//#region src/helpers/core/git.ts
|
|
4320
3421
|
async function initializeGit(projectDir, useGit) {
|
|
@@ -4582,19 +3683,11 @@ async function createProject(options, cliInput = {}) {
|
|
|
4582
3683
|
});
|
|
4583
3684
|
if (!result.success || !result.tree) throw new Error(result.error || "Failed to generate project templates");
|
|
4584
3685
|
await writeTreeToFilesystem(result.tree, projectDir);
|
|
4585
|
-
await formatProjectFiles(projectDir);
|
|
4586
3686
|
await setPackageManagerVersion(projectDir, options.packageManager);
|
|
4587
3687
|
if (!isConvex && options.database !== "none") await setupDatabase(options, cliInput);
|
|
4588
|
-
if (options.examples.length > 0 && options.examples[0] !== "none") await /* @__PURE__ */ setupExamples(options);
|
|
4589
3688
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
4590
|
-
if (options.auth === "better-auth" && !isConvex) {
|
|
4591
|
-
const authPackageDir = `${projectDir}/packages/auth`;
|
|
4592
|
-
if (await fs.pathExists(authPackageDir)) await setupBetterAuthPlugins(projectDir, options);
|
|
4593
|
-
}
|
|
4594
|
-
await setupEnvironmentVariables(options);
|
|
4595
|
-
await setupWebDeploy(options);
|
|
4596
|
-
await setupServerDeploy(options);
|
|
4597
3689
|
await writeBtsConfig(options);
|
|
3690
|
+
await formatProject(projectDir);
|
|
4598
3691
|
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
|
4599
3692
|
if (options.install) await installDependencies({
|
|
4600
3693
|
projectDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "3.13.
|
|
3
|
+
"version": "3.13.2-dev.41e5758",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"better-auth",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"prepublishOnly": "npm run build"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@better-t-stack/template-generator": "
|
|
74
|
-
"@better-t-stack/types": "3.13.
|
|
73
|
+
"@better-t-stack/template-generator": "3.13.2-dev.41e5758",
|
|
74
|
+
"@better-t-stack/types": "3.13.2-dev.41e5758",
|
|
75
75
|
"@clack/core": "^0.5.0",
|
|
76
76
|
"@clack/prompts": "^1.0.0-alpha.8",
|
|
77
77
|
"@orpc/server": "^1.13.0",
|