create-better-t-stack 3.13.2-dev.e23feef → 3.13.2-dev.e295faf
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-BE-bsG6N.mjs → src-BG4Kj3E_.mjs} +175 -900
- 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-BG4Kj3E_.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, group, intro, isCancel, log, multiselect, 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,12 +10,11 @@ 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
20
|
import os$1 from "node:os";
|
|
@@ -91,6 +90,59 @@ const ADDON_COMPATIBILITY = {
|
|
|
91
90
|
none: []
|
|
92
91
|
};
|
|
93
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
|
+
|
|
94
146
|
//#endregion
|
|
95
147
|
//#region src/utils/errors.ts
|
|
96
148
|
var UserCancelledError = class extends Error {
|
|
@@ -106,17 +158,20 @@ var CLIError = class extends Error {
|
|
|
106
158
|
}
|
|
107
159
|
};
|
|
108
160
|
function exitWithError(message) {
|
|
161
|
+
if (isSilent()) throw new CLIError(message);
|
|
109
162
|
consola.error(pc.red(message));
|
|
110
|
-
|
|
163
|
+
process.exit(1);
|
|
111
164
|
}
|
|
112
165
|
function exitCancelled(message = "Operation cancelled") {
|
|
166
|
+
if (isSilent()) throw new UserCancelledError(message);
|
|
113
167
|
cancel(pc.red(message));
|
|
114
|
-
|
|
168
|
+
process.exit(1);
|
|
115
169
|
}
|
|
116
170
|
function handleError(error, fallbackMessage) {
|
|
117
171
|
const message = error instanceof Error ? error.message : fallbackMessage || String(error);
|
|
172
|
+
if (isSilent()) throw error instanceof Error ? error : new Error(message);
|
|
118
173
|
consola.error(pc.red(message));
|
|
119
|
-
|
|
174
|
+
process.exit(1);
|
|
120
175
|
}
|
|
121
176
|
|
|
122
177
|
//#endregion
|
|
@@ -262,64 +317,8 @@ function validateExamplesCompatibility(examples, backend, database, frontend, ap
|
|
|
262
317
|
}
|
|
263
318
|
}
|
|
264
319
|
|
|
265
|
-
//#endregion
|
|
266
|
-
//#region src/utils/context.ts
|
|
267
|
-
const cliStorage = new AsyncLocalStorage();
|
|
268
|
-
function defaultContext() {
|
|
269
|
-
return {
|
|
270
|
-
navigation: {
|
|
271
|
-
isFirstPrompt: false,
|
|
272
|
-
lastPromptShownUI: false
|
|
273
|
-
},
|
|
274
|
-
silent: false,
|
|
275
|
-
verbose: false
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
function getContext() {
|
|
279
|
-
const ctx = cliStorage.getStore();
|
|
280
|
-
if (!ctx) return defaultContext();
|
|
281
|
-
return ctx;
|
|
282
|
-
}
|
|
283
|
-
function tryGetContext() {
|
|
284
|
-
return cliStorage.getStore();
|
|
285
|
-
}
|
|
286
|
-
function isSilent() {
|
|
287
|
-
return getContext().silent;
|
|
288
|
-
}
|
|
289
|
-
function isFirstPrompt() {
|
|
290
|
-
return getContext().navigation.isFirstPrompt;
|
|
291
|
-
}
|
|
292
|
-
function didLastPromptShowUI() {
|
|
293
|
-
return getContext().navigation.lastPromptShownUI;
|
|
294
|
-
}
|
|
295
|
-
function setIsFirstPrompt$1(value) {
|
|
296
|
-
const ctx = tryGetContext();
|
|
297
|
-
if (ctx) ctx.navigation.isFirstPrompt = value;
|
|
298
|
-
}
|
|
299
|
-
function setLastPromptShownUI(value) {
|
|
300
|
-
const ctx = tryGetContext();
|
|
301
|
-
if (ctx) ctx.navigation.lastPromptShownUI = value;
|
|
302
|
-
}
|
|
303
|
-
async function runWithContextAsync(options, fn) {
|
|
304
|
-
const ctx = {
|
|
305
|
-
navigation: {
|
|
306
|
-
isFirstPrompt: false,
|
|
307
|
-
lastPromptShownUI: false
|
|
308
|
-
},
|
|
309
|
-
silent: options.silent ?? false,
|
|
310
|
-
verbose: options.verbose ?? false,
|
|
311
|
-
projectDir: options.projectDir,
|
|
312
|
-
projectName: options.projectName,
|
|
313
|
-
packageManager: options.packageManager
|
|
314
|
-
};
|
|
315
|
-
return cliStorage.run(ctx, fn);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
320
|
//#endregion
|
|
319
321
|
//#region src/utils/navigation.ts
|
|
320
|
-
/**
|
|
321
|
-
* Navigation symbols and utilities for prompt navigation
|
|
322
|
-
*/
|
|
323
322
|
const GO_BACK_SYMBOL = Symbol("clack:goBack");
|
|
324
323
|
function isGoBack(value) {
|
|
325
324
|
return value === GO_BACK_SYMBOL;
|
|
@@ -2028,63 +2027,6 @@ function validateConfigCompatibility(config, providedFlags, options) {
|
|
|
2028
2027
|
else validateConfigForProgrammaticUse(config);
|
|
2029
2028
|
}
|
|
2030
2029
|
|
|
2031
|
-
//#endregion
|
|
2032
|
-
//#region src/utils/ts-morph.ts
|
|
2033
|
-
const tsProject = new Project({
|
|
2034
|
-
useInMemoryFileSystem: false,
|
|
2035
|
-
skipAddingFilesFromTsConfig: true,
|
|
2036
|
-
manipulationSettings: {
|
|
2037
|
-
quoteKind: QuoteKind.Single,
|
|
2038
|
-
indentationText: IndentationText.TwoSpaces
|
|
2039
|
-
}
|
|
2040
|
-
});
|
|
2041
|
-
function ensureArrayProperty(obj, name) {
|
|
2042
|
-
return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
|
|
2043
|
-
name,
|
|
2044
|
-
initializer: "[]"
|
|
2045
|
-
}).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
//#endregion
|
|
2049
|
-
//#region src/utils/better-auth-plugin-setup.ts
|
|
2050
|
-
async function setupBetterAuthPlugins(projectDir, config) {
|
|
2051
|
-
const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
|
|
2052
|
-
const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);
|
|
2053
|
-
if (!authIndexFile) return;
|
|
2054
|
-
const pluginsToAdd = [];
|
|
2055
|
-
const importsToAdd = [];
|
|
2056
|
-
if (config.backend === "self" && config.frontend?.includes("tanstack-start")) {
|
|
2057
|
-
pluginsToAdd.push("tanstackStartCookies()");
|
|
2058
|
-
importsToAdd.push("import { tanstackStartCookies } from \"better-auth/tanstack-start\";");
|
|
2059
|
-
}
|
|
2060
|
-
if (config.backend === "self" && config.frontend?.includes("next")) {
|
|
2061
|
-
pluginsToAdd.push("nextCookies()");
|
|
2062
|
-
importsToAdd.push("import { nextCookies } from \"better-auth/next-js\";");
|
|
2063
|
-
}
|
|
2064
|
-
if (config.frontend?.includes("native-bare") || config.frontend?.includes("native-uniwind") || config.frontend?.includes("native-unistyles")) {
|
|
2065
|
-
pluginsToAdd.push("expo()");
|
|
2066
|
-
importsToAdd.push("import { expo } from \"@better-auth/expo\";");
|
|
2067
|
-
}
|
|
2068
|
-
if (pluginsToAdd.length === 0) return;
|
|
2069
|
-
importsToAdd.forEach((importStatement) => {
|
|
2070
|
-
if (!authIndexFile.getImportDeclaration((declaration) => declaration.getModuleSpecifierValue().includes(importStatement.split("\"")[1]))) authIndexFile.insertImportDeclaration(0, {
|
|
2071
|
-
moduleSpecifier: importStatement.split("\"")[1],
|
|
2072
|
-
namedImports: [importStatement.split("{")[1].split("}")[0].trim()]
|
|
2073
|
-
});
|
|
2074
|
-
});
|
|
2075
|
-
const betterAuthCall = authIndexFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((call) => call.getExpression().getText() === "betterAuth");
|
|
2076
|
-
if (betterAuthCall) {
|
|
2077
|
-
const configObject = betterAuthCall.getArguments()[0];
|
|
2078
|
-
if (configObject && configObject.getKind() === SyntaxKind.ObjectLiteralExpression) {
|
|
2079
|
-
const pluginsArray = ensureArrayProperty(configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression), "plugins");
|
|
2080
|
-
pluginsToAdd.forEach((plugin) => {
|
|
2081
|
-
pluginsArray.addElement(plugin);
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
authIndexFile.save();
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
2030
|
//#endregion
|
|
2089
2031
|
//#region src/utils/bts-config.ts
|
|
2090
2032
|
const BTS_CONFIG_FILE = "bts.jsonc";
|
|
@@ -2173,22 +2115,30 @@ async function formatProject(projectDir) {
|
|
|
2173
2115
|
}
|
|
2174
2116
|
|
|
2175
2117
|
//#endregion
|
|
2176
|
-
//#region src/utils/package-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
case "pnpm": return `pnpm dlx ${commandWithArgs}`;
|
|
2188
|
-
case "bun": return `bunx ${commandWithArgs}`;
|
|
2189
|
-
default: return `npx ${commandWithArgs}`;
|
|
2118
|
+
//#region src/utils/add-package-deps.ts
|
|
2119
|
+
const addPackageDependency = async (opts) => {
|
|
2120
|
+
const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
|
|
2121
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
2122
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
2123
|
+
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
2124
|
+
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
2125
|
+
for (const pkgName of dependencies) {
|
|
2126
|
+
const version = dependencyVersionMap[pkgName];
|
|
2127
|
+
if (version) pkgJson.dependencies[pkgName] = version;
|
|
2128
|
+
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
2190
2129
|
}
|
|
2191
|
-
|
|
2130
|
+
for (const pkgName of devDependencies) {
|
|
2131
|
+
const version = dependencyVersionMap[pkgName];
|
|
2132
|
+
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
2133
|
+
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
2134
|
+
}
|
|
2135
|
+
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
2136
|
+
for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
|
|
2137
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2138
|
+
};
|
|
2139
|
+
|
|
2140
|
+
//#endregion
|
|
2141
|
+
//#region src/utils/package-runner.ts
|
|
2192
2142
|
/**
|
|
2193
2143
|
* Returns the command and arguments as an array for use with execa's $ template syntax.
|
|
2194
2144
|
* This avoids the need for shell: true and provides better escaping.
|
|
@@ -2298,115 +2248,35 @@ async function setupFumadocs(config) {
|
|
|
2298
2248
|
|
|
2299
2249
|
//#endregion
|
|
2300
2250
|
//#region src/helpers/addons/oxlint-setup.ts
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2251
|
+
async function setupOxlint(projectDir, packageManager) {
|
|
2252
|
+
await addPackageDependency({
|
|
2253
|
+
devDependencies: ["oxlint", "oxfmt"],
|
|
2254
|
+
projectDir
|
|
2255
|
+
});
|
|
2256
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2257
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2258
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2259
|
+
packageJson.scripts = {
|
|
2260
|
+
...packageJson.scripts,
|
|
2261
|
+
check: "oxlint && oxfmt --write"
|
|
2262
|
+
};
|
|
2263
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2264
|
+
}
|
|
2307
2265
|
const s = spinner();
|
|
2266
|
+
const oxlintArgs = getPackageExecutionArgs(packageManager, "oxlint@latest --init");
|
|
2308
2267
|
s.start("Initializing oxlint and oxfmt...");
|
|
2309
|
-
const oxlintArgs = getPackageExecutionArgs(config.packageManager, "oxlint@latest --init");
|
|
2310
2268
|
await $({
|
|
2311
|
-
cwd:
|
|
2269
|
+
cwd: projectDir,
|
|
2312
2270
|
env: { CI: "true" }
|
|
2313
2271
|
})`${oxlintArgs}`;
|
|
2314
|
-
const oxfmtArgs = getPackageExecutionArgs(
|
|
2272
|
+
const oxfmtArgs = getPackageExecutionArgs(packageManager, "oxfmt@latest --init");
|
|
2315
2273
|
await $({
|
|
2316
|
-
cwd:
|
|
2274
|
+
cwd: projectDir,
|
|
2317
2275
|
env: { CI: "true" }
|
|
2318
2276
|
})`${oxfmtArgs}`;
|
|
2319
2277
|
s.stop("oxlint and oxfmt initialized successfully!");
|
|
2320
2278
|
}
|
|
2321
2279
|
|
|
2322
|
-
//#endregion
|
|
2323
|
-
//#region src/helpers/addons/ruler-setup.ts
|
|
2324
|
-
async function setupRuler(config) {
|
|
2325
|
-
const { packageManager, projectDir } = config;
|
|
2326
|
-
try {
|
|
2327
|
-
log.info("Setting up Ruler...");
|
|
2328
|
-
const rulerDir = path.join(projectDir, ".ruler");
|
|
2329
|
-
if (!await fs.pathExists(rulerDir)) {
|
|
2330
|
-
log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
|
|
2331
|
-
return;
|
|
2332
|
-
}
|
|
2333
|
-
const selectedEditors = await autocompleteMultiselect({
|
|
2334
|
-
message: "Select AI assistants for Ruler",
|
|
2335
|
-
options: Object.entries({
|
|
2336
|
-
amp: { label: "AMP" },
|
|
2337
|
-
copilot: { label: "GitHub Copilot" },
|
|
2338
|
-
claude: { label: "Claude Code" },
|
|
2339
|
-
codex: { label: "OpenAI Codex CLI" },
|
|
2340
|
-
cursor: { label: "Cursor" },
|
|
2341
|
-
windsurf: { label: "Windsurf" },
|
|
2342
|
-
cline: { label: "Cline" },
|
|
2343
|
-
aider: { label: "Aider" },
|
|
2344
|
-
firebase: { label: "Firebase Studio" },
|
|
2345
|
-
"gemini-cli": { label: "Gemini CLI" },
|
|
2346
|
-
junie: { label: "Junie" },
|
|
2347
|
-
kilocode: { label: "Kilo Code" },
|
|
2348
|
-
opencode: { label: "OpenCode" },
|
|
2349
|
-
crush: { label: "Crush" },
|
|
2350
|
-
zed: { label: "Zed" },
|
|
2351
|
-
qwen: { label: "Qwen" },
|
|
2352
|
-
amazonqcli: { label: "Amazon Q CLI" },
|
|
2353
|
-
augmentcode: { label: "AugmentCode" },
|
|
2354
|
-
firebender: { label: "Firebender" },
|
|
2355
|
-
goose: { label: "Goose" },
|
|
2356
|
-
jules: { label: "Jules" },
|
|
2357
|
-
kiro: { label: "Kiro" },
|
|
2358
|
-
openhands: { label: "Open Hands" },
|
|
2359
|
-
roo: { label: "RooCode" },
|
|
2360
|
-
trae: { label: "Trae AI" },
|
|
2361
|
-
warp: { label: "Warp" }
|
|
2362
|
-
}).map(([key, v]) => ({
|
|
2363
|
-
value: key,
|
|
2364
|
-
label: v.label
|
|
2365
|
-
})),
|
|
2366
|
-
required: false
|
|
2367
|
-
});
|
|
2368
|
-
if (isCancel(selectedEditors)) return exitCancelled("Operation cancelled");
|
|
2369
|
-
if (selectedEditors.length === 0) {
|
|
2370
|
-
log.info("No AI assistants selected. To apply rules later, run:");
|
|
2371
|
-
log.info(pc.cyan(`${getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only")}`));
|
|
2372
|
-
return;
|
|
2373
|
-
}
|
|
2374
|
-
const configFile = path.join(rulerDir, "ruler.toml");
|
|
2375
|
-
let updatedConfig = await fs.readFile(configFile, "utf-8");
|
|
2376
|
-
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
2377
|
-
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
2378
|
-
await fs.writeFile(configFile, updatedConfig);
|
|
2379
|
-
await addRulerScriptToPackageJson(projectDir, packageManager);
|
|
2380
|
-
const s = spinner();
|
|
2381
|
-
s.start("Applying rules with Ruler...");
|
|
2382
|
-
try {
|
|
2383
|
-
const rulerApplyArgs = getPackageExecutionArgs(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`);
|
|
2384
|
-
await $({
|
|
2385
|
-
cwd: projectDir,
|
|
2386
|
-
env: { CI: "true" }
|
|
2387
|
-
})`${rulerApplyArgs}`;
|
|
2388
|
-
s.stop("Applied rules with Ruler");
|
|
2389
|
-
} catch {
|
|
2390
|
-
s.stop(pc.red("Failed to apply rules"));
|
|
2391
|
-
}
|
|
2392
|
-
} catch (error) {
|
|
2393
|
-
log.error(pc.red("Failed to set up Ruler"));
|
|
2394
|
-
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
async function addRulerScriptToPackageJson(projectDir, packageManager) {
|
|
2398
|
-
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
2399
|
-
if (!await fs.pathExists(rootPackageJsonPath)) {
|
|
2400
|
-
log.warn("Root package.json not found, skipping ruler:apply script addition");
|
|
2401
|
-
return;
|
|
2402
|
-
}
|
|
2403
|
-
const packageJson = await fs.readJson(rootPackageJsonPath);
|
|
2404
|
-
if (!packageJson.scripts) packageJson.scripts = {};
|
|
2405
|
-
const rulerApplyCommand = getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only");
|
|
2406
|
-
packageJson.scripts["ruler:apply"] = rulerApplyCommand;
|
|
2407
|
-
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
2280
|
//#endregion
|
|
2411
2281
|
//#region src/helpers/addons/starlight-setup.ts
|
|
2412
2282
|
async function setupStarlight(config) {
|
|
@@ -2522,11 +2392,6 @@ async function setupTui(config) {
|
|
|
2522
2392
|
|
|
2523
2393
|
//#endregion
|
|
2524
2394
|
//#region src/helpers/addons/ultracite-setup.ts
|
|
2525
|
-
/**
|
|
2526
|
-
* Ultracite setup - CLI-only operations
|
|
2527
|
-
* NOTE: Dependencies (husky, lint-staged) are handled by template-generator's addons-deps.ts
|
|
2528
|
-
* This file handles interactive prompts and external CLI initialization
|
|
2529
|
-
*/
|
|
2530
2395
|
const EDITORS = {
|
|
2531
2396
|
vscode: { label: "VS Code" },
|
|
2532
2397
|
cursor: { label: "Cursor" },
|
|
@@ -2565,11 +2430,6 @@ const HOOKS = {
|
|
|
2565
2430
|
cursor: { label: "Cursor" },
|
|
2566
2431
|
windsurf: { label: "Windsurf" }
|
|
2567
2432
|
};
|
|
2568
|
-
const LINTERS = {
|
|
2569
|
-
biome: { label: "Biome (recommended)" },
|
|
2570
|
-
oxlint: { label: "OxLint" },
|
|
2571
|
-
eslint: { label: "ESLint" }
|
|
2572
|
-
};
|
|
2573
2433
|
function getFrameworksFromFrontend(frontend) {
|
|
2574
2434
|
const frameworkMap = {
|
|
2575
2435
|
"tanstack-router": "react",
|
|
@@ -2592,14 +2452,6 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2592
2452
|
try {
|
|
2593
2453
|
log.info("Setting up Ultracite...");
|
|
2594
2454
|
const result = await group({
|
|
2595
|
-
linter: () => select({
|
|
2596
|
-
message: "Choose linter/formatter",
|
|
2597
|
-
options: Object.entries(LINTERS).map(([key, linter$1]) => ({
|
|
2598
|
-
value: key,
|
|
2599
|
-
label: linter$1.label
|
|
2600
|
-
})),
|
|
2601
|
-
initialValue: "biome"
|
|
2602
|
-
}),
|
|
2603
2455
|
editors: () => multiselect({
|
|
2604
2456
|
message: "Choose editors",
|
|
2605
2457
|
options: Object.entries(EDITORS).map(([key, editor]) => ({
|
|
@@ -2608,7 +2460,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2608
2460
|
})),
|
|
2609
2461
|
required: true
|
|
2610
2462
|
}),
|
|
2611
|
-
agents: () =>
|
|
2463
|
+
agents: () => multiselect({
|
|
2612
2464
|
message: "Choose agents",
|
|
2613
2465
|
options: Object.entries(AGENTS).map(([key, agent]) => ({
|
|
2614
2466
|
value: key,
|
|
@@ -2616,8 +2468,8 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2616
2468
|
})),
|
|
2617
2469
|
required: true
|
|
2618
2470
|
}),
|
|
2619
|
-
hooks: () =>
|
|
2620
|
-
message: "Choose hooks
|
|
2471
|
+
hooks: () => multiselect({
|
|
2472
|
+
message: "Choose hooks",
|
|
2621
2473
|
options: Object.entries(HOOKS).map(([key, hook]) => ({
|
|
2622
2474
|
value: key,
|
|
2623
2475
|
label: hook.label
|
|
@@ -2626,7 +2478,6 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2626
2478
|
}, { onCancel: () => {
|
|
2627
2479
|
exitCancelled("Operation cancelled");
|
|
2628
2480
|
} });
|
|
2629
|
-
const linter = result.linter;
|
|
2630
2481
|
const editors = result.editors;
|
|
2631
2482
|
const agents = result.agents;
|
|
2632
2483
|
const hooks = result.hooks;
|
|
@@ -2634,9 +2485,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2634
2485
|
const ultraciteArgs = [
|
|
2635
2486
|
"init",
|
|
2636
2487
|
"--pm",
|
|
2637
|
-
packageManager
|
|
2638
|
-
"--linter",
|
|
2639
|
-
linter
|
|
2488
|
+
packageManager
|
|
2640
2489
|
];
|
|
2641
2490
|
if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
|
|
2642
2491
|
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
@@ -2721,355 +2570,96 @@ async function setupWxt(config) {
|
|
|
2721
2570
|
|
|
2722
2571
|
//#endregion
|
|
2723
2572
|
//#region src/helpers/addons/addons-setup.ts
|
|
2724
|
-
async function setupAddons(config
|
|
2725
|
-
const { addons, frontend, projectDir
|
|
2573
|
+
async function setupAddons(config) {
|
|
2574
|
+
const { addons, frontend, projectDir } = config;
|
|
2726
2575
|
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
|
|
2727
2576
|
const hasNuxtFrontend = frontend.includes("nuxt");
|
|
2728
2577
|
const hasSvelteFrontend = frontend.includes("svelte");
|
|
2729
2578
|
const hasSolidFrontend = frontend.includes("solid");
|
|
2730
2579
|
const hasNextFrontend = frontend.includes("next");
|
|
2731
|
-
if (addons.includes("turborepo") && isAddCommand) log.info(`${pc.yellow("Update your package.json scripts:")}
|
|
2732
|
-
|
|
2733
|
-
${pc.dim("Replace:")} ${pc.yellow("\"pnpm -r dev\"")} ${pc.dim("→")} ${pc.green("\"turbo dev\"")}
|
|
2734
|
-
${pc.dim("Replace:")} ${pc.yellow("\"pnpm --filter web dev\"")} ${pc.dim("→")} ${pc.green("\"turbo -F web dev\"")}
|
|
2735
|
-
|
|
2736
|
-
${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
|
2737
|
-
`);
|
|
2738
2580
|
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
|
|
2739
2581
|
const hasUltracite = addons.includes("ultracite");
|
|
2582
|
+
const hasBiome = addons.includes("biome");
|
|
2740
2583
|
const hasHusky = addons.includes("husky");
|
|
2741
|
-
|
|
2584
|
+
const hasOxlint = addons.includes("oxlint");
|
|
2585
|
+
if (hasUltracite) await setupUltracite(config, hasHusky);
|
|
2586
|
+
else {
|
|
2587
|
+
if (hasBiome) await setupBiome(projectDir);
|
|
2588
|
+
if (hasHusky) {
|
|
2589
|
+
let linter;
|
|
2590
|
+
if (hasOxlint) linter = "oxlint";
|
|
2591
|
+
else if (hasBiome) linter = "biome";
|
|
2592
|
+
await setupHusky(projectDir, linter);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
if (hasOxlint) await setupOxlint(projectDir, config.packageManager);
|
|
2742
2596
|
if (addons.includes("starlight")) await setupStarlight(config);
|
|
2743
|
-
if (addons.includes("ruler")) await setupRuler(config);
|
|
2744
2597
|
if (addons.includes("fumadocs")) await setupFumadocs(config);
|
|
2745
2598
|
if (addons.includes("opentui")) await setupTui(config);
|
|
2746
2599
|
if (addons.includes("wxt")) await setupWxt(config);
|
|
2747
|
-
if (hasUltracite) await setupUltracite(config, hasHusky);
|
|
2748
2600
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2601
|
+
async function setupBiome(projectDir) {
|
|
2602
|
+
await addPackageDependency({
|
|
2603
|
+
devDependencies: ["@biomejs/biome"],
|
|
2604
|
+
projectDir
|
|
2605
|
+
});
|
|
2606
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2607
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2608
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2609
|
+
packageJson.scripts = {
|
|
2610
|
+
...packageJson.scripts,
|
|
2611
|
+
check: "biome check --write ."
|
|
2612
|
+
};
|
|
2613
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2762
2614
|
}
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2615
|
+
}
|
|
2616
|
+
async function setupHusky(projectDir, linter) {
|
|
2617
|
+
await addPackageDependency({
|
|
2618
|
+
devDependencies: ["husky", "lint-staged"],
|
|
2619
|
+
projectDir
|
|
2620
|
+
});
|
|
2621
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2622
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2623
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2624
|
+
packageJson.scripts = {
|
|
2625
|
+
...packageJson.scripts,
|
|
2626
|
+
prepare: "husky"
|
|
2627
|
+
};
|
|
2628
|
+
if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
|
|
2629
|
+
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
2630
|
+
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
2631
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2767
2632
|
}
|
|
2768
|
-
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
2769
|
-
for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
|
|
2770
|
-
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2771
|
-
};
|
|
2772
|
-
|
|
2773
|
-
//#endregion
|
|
2774
|
-
//#region src/helpers/core/auth-setup.ts
|
|
2775
|
-
function generateAuthSecret(length = 32) {
|
|
2776
|
-
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
2777
|
-
let result = "";
|
|
2778
|
-
const charactersLength = 62;
|
|
2779
|
-
for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
2780
|
-
return result;
|
|
2781
2633
|
}
|
|
2782
2634
|
|
|
2783
2635
|
//#endregion
|
|
2784
|
-
//#region src/
|
|
2785
|
-
function
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
const
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
};
|
|
2805
|
-
}
|
|
2806
|
-
function getConvexVar(frontend) {
|
|
2807
|
-
const hasNextJs = frontend.includes("next");
|
|
2808
|
-
const hasNuxt = frontend.includes("nuxt");
|
|
2809
|
-
const hasSvelte = frontend.includes("svelte");
|
|
2810
|
-
const hasTanstackStart = frontend.includes("tanstack-start");
|
|
2811
|
-
if (hasNextJs) return "NEXT_PUBLIC_CONVEX_URL";
|
|
2812
|
-
if (hasNuxt) return "NUXT_PUBLIC_CONVEX_URL";
|
|
2813
|
-
if (hasSvelte) return "PUBLIC_CONVEX_URL";
|
|
2814
|
-
if (hasTanstackStart) return "VITE_CONVEX_URL";
|
|
2815
|
-
return "VITE_CONVEX_URL";
|
|
2816
|
-
}
|
|
2817
|
-
async function addEnvVariablesToFile(filePath, variables) {
|
|
2818
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
2819
|
-
let envContent = "";
|
|
2820
|
-
if (await fs.pathExists(filePath)) envContent = await fs.readFile(filePath, "utf8");
|
|
2821
|
-
let modified = false;
|
|
2822
|
-
let contentToAdd = "";
|
|
2823
|
-
const exampleVariables = [];
|
|
2824
|
-
for (const { key, value, condition, comment } of variables) if (condition) {
|
|
2825
|
-
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
2826
|
-
const valueToWrite = value ?? "";
|
|
2827
|
-
exampleVariables.push(`${key}=`);
|
|
2828
|
-
if (regex.test(envContent)) {
|
|
2829
|
-
const existingMatch = envContent.match(regex);
|
|
2830
|
-
if (existingMatch && existingMatch[0] !== `${key}=${valueToWrite}`) {
|
|
2831
|
-
envContent = envContent.replace(regex, `${key}=${valueToWrite}`);
|
|
2832
|
-
modified = true;
|
|
2833
|
-
}
|
|
2834
|
-
} else {
|
|
2835
|
-
if (comment) contentToAdd += `# ${comment}\n`;
|
|
2836
|
-
contentToAdd += `${key}=${valueToWrite}\n`;
|
|
2837
|
-
modified = true;
|
|
2838
|
-
}
|
|
2839
|
-
}
|
|
2840
|
-
if (contentToAdd) {
|
|
2841
|
-
if (envContent.length > 0 && !envContent.endsWith("\n")) envContent += "\n";
|
|
2842
|
-
envContent += contentToAdd;
|
|
2843
|
-
}
|
|
2844
|
-
if (modified) await fs.writeFile(filePath, envContent.trimEnd());
|
|
2845
|
-
const exampleFilePath = filePath.replace(/\.env$/, ".env.example");
|
|
2846
|
-
let exampleEnvContent = "";
|
|
2847
|
-
if (await fs.pathExists(exampleFilePath)) exampleEnvContent = await fs.readFile(exampleFilePath, "utf8");
|
|
2848
|
-
let exampleModified = false;
|
|
2849
|
-
let exampleContentToAdd = "";
|
|
2850
|
-
for (const exampleVar of exampleVariables) {
|
|
2851
|
-
const key = exampleVar.split("=")[0];
|
|
2852
|
-
if (!new RegExp(`^${key}=.*$`, "m").test(exampleEnvContent)) {
|
|
2853
|
-
exampleContentToAdd += `${exampleVar}\n`;
|
|
2854
|
-
exampleModified = true;
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
if (exampleContentToAdd) {
|
|
2858
|
-
if (exampleEnvContent.length > 0 && !exampleEnvContent.endsWith("\n")) exampleEnvContent += "\n";
|
|
2859
|
-
exampleEnvContent += exampleContentToAdd;
|
|
2860
|
-
}
|
|
2861
|
-
if (exampleModified || !await fs.pathExists(exampleFilePath)) await fs.writeFile(exampleFilePath, exampleEnvContent.trimEnd());
|
|
2862
|
-
}
|
|
2863
|
-
async function setupEnvironmentVariables(config) {
|
|
2864
|
-
const { backend, frontend, database, auth, examples, dbSetup, projectDir, webDeploy, serverDeploy } = config;
|
|
2865
|
-
const hasReactRouter = frontend.includes("react-router");
|
|
2866
|
-
const hasTanStackRouter = frontend.includes("tanstack-router");
|
|
2867
|
-
const hasTanStackStart = frontend.includes("tanstack-start");
|
|
2868
|
-
const hasNextJs = frontend.includes("next");
|
|
2869
|
-
const hasNuxt = frontend.includes("nuxt");
|
|
2870
|
-
const hasSvelte = frontend.includes("svelte");
|
|
2871
|
-
const hasSolid = frontend.includes("solid");
|
|
2872
|
-
const hasWebFrontend$1 = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte;
|
|
2873
|
-
if (hasWebFrontend$1) {
|
|
2874
|
-
const clientDir = path.join(projectDir, "apps/web");
|
|
2875
|
-
if (await fs.pathExists(clientDir)) {
|
|
2876
|
-
const baseVar = getClientServerVar(frontend, backend);
|
|
2877
|
-
const clientVars = [{
|
|
2878
|
-
key: backend === "convex" ? getConvexVar(frontend) : baseVar.key,
|
|
2879
|
-
value: backend === "convex" ? "https://<YOUR_CONVEX_URL>" : baseVar.value,
|
|
2880
|
-
condition: backend === "convex" ? true : baseVar.write
|
|
2881
|
-
}];
|
|
2882
|
-
if (backend === "convex" && auth === "clerk") {
|
|
2883
|
-
if (hasNextJs) clientVars.push({
|
|
2884
|
-
key: "NEXT_PUBLIC_CLERK_FRONTEND_API_URL",
|
|
2885
|
-
value: "",
|
|
2886
|
-
condition: true
|
|
2887
|
-
}, {
|
|
2888
|
-
key: "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY",
|
|
2889
|
-
value: "",
|
|
2890
|
-
condition: true
|
|
2891
|
-
}, {
|
|
2892
|
-
key: "CLERK_SECRET_KEY",
|
|
2893
|
-
value: "",
|
|
2894
|
-
condition: true
|
|
2895
|
-
});
|
|
2896
|
-
else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {
|
|
2897
|
-
clientVars.push({
|
|
2898
|
-
key: "VITE_CLERK_PUBLISHABLE_KEY",
|
|
2899
|
-
value: "",
|
|
2900
|
-
condition: true
|
|
2901
|
-
});
|
|
2902
|
-
if (hasTanStackStart) clientVars.push({
|
|
2903
|
-
key: "CLERK_SECRET_KEY",
|
|
2904
|
-
value: "",
|
|
2905
|
-
condition: true
|
|
2906
|
-
});
|
|
2907
|
-
}
|
|
2908
|
-
}
|
|
2909
|
-
if (backend === "convex" && auth === "better-auth") {
|
|
2910
|
-
if (hasNextJs) clientVars.push({
|
|
2911
|
-
key: "NEXT_PUBLIC_CONVEX_SITE_URL",
|
|
2912
|
-
value: "https://<YOUR_CONVEX_URL>",
|
|
2913
|
-
condition: true
|
|
2914
|
-
});
|
|
2915
|
-
else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) clientVars.push({
|
|
2916
|
-
key: "VITE_CONVEX_SITE_URL",
|
|
2917
|
-
value: "https://<YOUR_CONVEX_URL>",
|
|
2918
|
-
condition: true
|
|
2919
|
-
});
|
|
2920
|
-
}
|
|
2921
|
-
await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
if (frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles")) {
|
|
2925
|
-
const nativeDir = path.join(projectDir, "apps/native");
|
|
2926
|
-
if (await fs.pathExists(nativeDir)) {
|
|
2927
|
-
let envVarName = "EXPO_PUBLIC_SERVER_URL";
|
|
2928
|
-
let serverUrl = "http://localhost:3000";
|
|
2929
|
-
if (backend === "self") serverUrl = "http://localhost:3001";
|
|
2930
|
-
if (backend === "convex") {
|
|
2931
|
-
envVarName = "EXPO_PUBLIC_CONVEX_URL";
|
|
2932
|
-
serverUrl = "https://<YOUR_CONVEX_URL>";
|
|
2933
|
-
}
|
|
2934
|
-
const nativeVars = [{
|
|
2935
|
-
key: envVarName,
|
|
2936
|
-
value: serverUrl,
|
|
2937
|
-
condition: true
|
|
2938
|
-
}];
|
|
2939
|
-
if (backend === "convex" && auth === "clerk") nativeVars.push({
|
|
2940
|
-
key: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY",
|
|
2941
|
-
value: "",
|
|
2942
|
-
condition: true
|
|
2943
|
-
});
|
|
2944
|
-
if (backend === "convex" && auth === "better-auth") nativeVars.push({
|
|
2945
|
-
key: "EXPO_PUBLIC_CONVEX_SITE_URL",
|
|
2946
|
-
value: "https://<YOUR_CONVEX_URL>",
|
|
2947
|
-
condition: true
|
|
2948
|
-
});
|
|
2949
|
-
await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
if (backend === "convex") {
|
|
2953
|
-
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
2954
|
-
if (await fs.pathExists(convexBackendDir)) {
|
|
2955
|
-
const envLocalPath = path.join(convexBackendDir, ".env.local");
|
|
2956
|
-
let commentBlocks = "";
|
|
2957
|
-
if (examples?.includes("ai")) commentBlocks += `# Set Google AI API key for AI agent
|
|
2958
|
-
# npx convex env set GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
|
|
2959
|
-
|
|
2960
|
-
`;
|
|
2961
|
-
if (auth === "better-auth") commentBlocks += `# Set Convex environment variables
|
|
2962
|
-
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
2963
|
-
${hasWebFrontend$1 ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}`;
|
|
2964
|
-
if (commentBlocks) {
|
|
2965
|
-
let existingContent = "";
|
|
2966
|
-
if (await fs.pathExists(envLocalPath)) existingContent = await fs.readFile(envLocalPath, "utf8");
|
|
2967
|
-
await fs.writeFile(envLocalPath, commentBlocks + existingContent);
|
|
2968
|
-
}
|
|
2969
|
-
const convexBackendVars = [];
|
|
2970
|
-
if (examples?.includes("ai")) convexBackendVars.push({
|
|
2971
|
-
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
2972
|
-
value: "",
|
|
2973
|
-
condition: true,
|
|
2974
|
-
comment: "Google AI API key for AI agent"
|
|
2975
|
-
});
|
|
2976
|
-
if (auth === "better-auth") {
|
|
2977
|
-
const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
2978
|
-
const hasWeb = hasWebFrontend$1;
|
|
2979
|
-
if (hasNative) convexBackendVars.push({
|
|
2980
|
-
key: "EXPO_PUBLIC_CONVEX_SITE_URL",
|
|
2981
|
-
value: "",
|
|
2982
|
-
condition: true,
|
|
2983
|
-
comment: "Same as CONVEX_URL but ends in .site"
|
|
2984
|
-
});
|
|
2985
|
-
if (hasWeb) convexBackendVars.push({
|
|
2986
|
-
key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
|
|
2987
|
-
value: "",
|
|
2988
|
-
condition: true,
|
|
2989
|
-
comment: "Same as CONVEX_URL but ends in .site"
|
|
2990
|
-
}, {
|
|
2991
|
-
key: "SITE_URL",
|
|
2992
|
-
value: "http://localhost:3001",
|
|
2993
|
-
condition: true,
|
|
2994
|
-
comment: "Web app URL for authentication"
|
|
2995
|
-
});
|
|
2996
|
-
}
|
|
2997
|
-
if (convexBackendVars.length > 0) await addEnvVariablesToFile(envLocalPath, convexBackendVars);
|
|
2998
|
-
}
|
|
2999
|
-
return;
|
|
3000
|
-
}
|
|
3001
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
3002
|
-
let corsOrigin = "http://localhost:3001";
|
|
3003
|
-
if (backend === "self") corsOrigin = "http://localhost:3001";
|
|
3004
|
-
else if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
|
|
3005
|
-
let databaseUrl = null;
|
|
3006
|
-
if (database !== "none" && dbSetup === "none") switch (database) {
|
|
3007
|
-
case "postgres":
|
|
3008
|
-
databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
|
|
2636
|
+
//#region src/utils/env-utils.ts
|
|
2637
|
+
async function addEnvVariablesToFile(envPath, variables) {
|
|
2638
|
+
let content = "";
|
|
2639
|
+
if (fs.existsSync(envPath)) content = await fs.readFile(envPath, "utf-8");
|
|
2640
|
+
else await fs.ensureFile(envPath);
|
|
2641
|
+
const existingLines = content.split("\n");
|
|
2642
|
+
const newLines = [];
|
|
2643
|
+
const keysToAdd = /* @__PURE__ */ new Map();
|
|
2644
|
+
for (const variable of variables) {
|
|
2645
|
+
if (variable.condition === false || !variable.key) continue;
|
|
2646
|
+
keysToAdd.set(variable.key, variable.value);
|
|
2647
|
+
}
|
|
2648
|
+
let foundKeys = /* @__PURE__ */ new Set();
|
|
2649
|
+
for (const line of existingLines) {
|
|
2650
|
+
const trimmedLine = line.trim();
|
|
2651
|
+
let lineProcessed = false;
|
|
2652
|
+
for (const [key, value] of keysToAdd) if (trimmedLine.startsWith(`${key}=`)) {
|
|
2653
|
+
newLines.push(`${key}=${value}`);
|
|
2654
|
+
foundKeys.add(key);
|
|
2655
|
+
lineProcessed = true;
|
|
3009
2656
|
break;
|
|
3010
|
-
case "mysql":
|
|
3011
|
-
databaseUrl = "mysql://root:password@localhost:3306/mydb";
|
|
3012
|
-
break;
|
|
3013
|
-
case "mongodb":
|
|
3014
|
-
databaseUrl = "mongodb://localhost:27017/mydatabase";
|
|
3015
|
-
break;
|
|
3016
|
-
case "sqlite":
|
|
3017
|
-
if (config.runtime === "workers" || webDeploy === "cloudflare" || serverDeploy === "cloudflare") databaseUrl = "http://127.0.0.1:8080";
|
|
3018
|
-
else {
|
|
3019
|
-
const dbAppDir = backend === "self" ? "apps/web" : "apps/server";
|
|
3020
|
-
databaseUrl = `file:${path.join(config.projectDir, dbAppDir, "local.db")}`;
|
|
3021
|
-
}
|
|
3022
|
-
break;
|
|
3023
|
-
}
|
|
3024
|
-
const serverVars = [
|
|
3025
|
-
{
|
|
3026
|
-
key: "BETTER_AUTH_SECRET",
|
|
3027
|
-
value: generateAuthSecret(),
|
|
3028
|
-
condition: !!auth
|
|
3029
|
-
},
|
|
3030
|
-
{
|
|
3031
|
-
key: "BETTER_AUTH_URL",
|
|
3032
|
-
value: backend === "self" ? "http://localhost:3001" : "http://localhost:3000",
|
|
3033
|
-
condition: !!auth
|
|
3034
|
-
},
|
|
3035
|
-
{
|
|
3036
|
-
key: "POLAR_ACCESS_TOKEN",
|
|
3037
|
-
value: "",
|
|
3038
|
-
condition: config.payments === "polar"
|
|
3039
|
-
},
|
|
3040
|
-
{
|
|
3041
|
-
key: "POLAR_SUCCESS_URL",
|
|
3042
|
-
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
|
|
3043
|
-
condition: config.payments === "polar"
|
|
3044
|
-
},
|
|
3045
|
-
{
|
|
3046
|
-
key: "CORS_ORIGIN",
|
|
3047
|
-
value: corsOrigin,
|
|
3048
|
-
condition: true
|
|
3049
|
-
},
|
|
3050
|
-
{
|
|
3051
|
-
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
3052
|
-
value: "",
|
|
3053
|
-
condition: examples?.includes("ai") || false
|
|
3054
|
-
},
|
|
3055
|
-
{
|
|
3056
|
-
key: "DATABASE_URL",
|
|
3057
|
-
value: databaseUrl,
|
|
3058
|
-
condition: database !== "none" && dbSetup === "none"
|
|
3059
2657
|
}
|
|
3060
|
-
|
|
3061
|
-
if (backend === "self") {
|
|
3062
|
-
const webDir = path.join(projectDir, "apps/web");
|
|
3063
|
-
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverVars);
|
|
3064
|
-
} else if (await fs.pathExists(serverDir)) await addEnvVariablesToFile(path.join(serverDir, ".env"), serverVars);
|
|
3065
|
-
if (webDeploy === "cloudflare" && serverDeploy === "cloudflare" || webDeploy === "cloudflare" || serverDeploy === "cloudflare") {
|
|
3066
|
-
const infraDir = path.join(projectDir, "packages/infra");
|
|
3067
|
-
if (await fs.pathExists(infraDir)) await addEnvVariablesToFile(path.join(infraDir, ".env"), [{
|
|
3068
|
-
key: "ALCHEMY_PASSWORD",
|
|
3069
|
-
value: "please-change-this",
|
|
3070
|
-
condition: true
|
|
3071
|
-
}]);
|
|
2658
|
+
if (!lineProcessed) newLines.push(line);
|
|
3072
2659
|
}
|
|
2660
|
+
for (const [key, value] of keysToAdd) if (!foundKeys.has(key)) newLines.push(`${key}=${value}`);
|
|
2661
|
+
if (newLines.length > 0 && newLines[newLines.length - 1] === "") newLines.pop();
|
|
2662
|
+
if (foundKeys.size > 0 || keysToAdd.size > foundKeys.size) await fs.writeFile(envPath, newLines.join("\n") + "\n");
|
|
3073
2663
|
}
|
|
3074
2664
|
|
|
3075
2665
|
//#endregion
|
|
@@ -4021,314 +3611,6 @@ async function setupDatabase(config, cliInput) {
|
|
|
4021
3611
|
}
|
|
4022
3612
|
}
|
|
4023
3613
|
|
|
4024
|
-
//#endregion
|
|
4025
|
-
//#region src/helpers/deployment/alchemy/alchemy-next-setup.ts
|
|
4026
|
-
/**
|
|
4027
|
-
* Alchemy Next.js setup - CLI-only operations
|
|
4028
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4029
|
-
* This only modifies config files for "add deploy" command
|
|
4030
|
-
*/
|
|
4031
|
-
async function setupNextAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4032
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4033
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4034
|
-
const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
|
|
4035
|
-
await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
|
4036
|
-
|
|
4037
|
-
export default defineCloudflareConfig({});
|
|
4038
|
-
`);
|
|
4039
|
-
const gitignorePath = path.join(webAppDir, ".gitignore");
|
|
4040
|
-
if (await fs.pathExists(gitignorePath)) {
|
|
4041
|
-
if (!(await fs.readFile(gitignorePath, "utf-8")).includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
|
4042
|
-
} else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
|
|
4043
|
-
}
|
|
4044
|
-
|
|
4045
|
-
//#endregion
|
|
4046
|
-
//#region src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts
|
|
4047
|
-
/**
|
|
4048
|
-
* Alchemy Nuxt setup - CLI-only operations
|
|
4049
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4050
|
-
* This only modifies config files for "add deploy" command
|
|
4051
|
-
*/
|
|
4052
|
-
async function setupNuxtAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4053
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4054
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4055
|
-
const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
|
|
4056
|
-
if (!await fs.pathExists(nuxtConfigPath)) return;
|
|
4057
|
-
try {
|
|
4058
|
-
const project = new Project({ manipulationSettings: {
|
|
4059
|
-
indentationText: IndentationText.TwoSpaces,
|
|
4060
|
-
quoteKind: QuoteKind.Double
|
|
4061
|
-
} });
|
|
4062
|
-
project.addSourceFileAtPath(nuxtConfigPath);
|
|
4063
|
-
const exportAssignment = project.getSourceFileOrThrow(nuxtConfigPath).getExportAssignment((d) => !d.isExportEquals());
|
|
4064
|
-
if (!exportAssignment) return;
|
|
4065
|
-
const defineConfigCall = exportAssignment.getExpression();
|
|
4066
|
-
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
|
|
4067
|
-
let configObject = defineConfigCall.getArguments()[0];
|
|
4068
|
-
if (!configObject) configObject = defineConfigCall.addArgument("{}");
|
|
4069
|
-
if (Node.isObjectLiteralExpression(configObject)) {
|
|
4070
|
-
if (!configObject.getProperty("nitro")) configObject.addPropertyAssignment({
|
|
4071
|
-
name: "nitro",
|
|
4072
|
-
initializer: `{
|
|
4073
|
-
preset: "cloudflare_module",
|
|
4074
|
-
cloudflare: {
|
|
4075
|
-
deployConfig: true,
|
|
4076
|
-
nodeCompat: true
|
|
4077
|
-
}
|
|
4078
|
-
}`
|
|
4079
|
-
});
|
|
4080
|
-
const modulesProperty = configObject.getProperty("modules");
|
|
4081
|
-
if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
|
|
4082
|
-
const initializer = modulesProperty.getInitializer();
|
|
4083
|
-
if (Node.isArrayLiteralExpression(initializer)) {
|
|
4084
|
-
if (!initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'")) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
4085
|
-
}
|
|
4086
|
-
} else if (!modulesProperty) configObject.addPropertyAssignment({
|
|
4087
|
-
name: "modules",
|
|
4088
|
-
initializer: "[\"nitro-cloudflare-dev\"]"
|
|
4089
|
-
});
|
|
4090
|
-
}
|
|
4091
|
-
await project.save();
|
|
4092
|
-
} catch (error) {
|
|
4093
|
-
console.warn("Failed to update nuxt.config.ts:", error);
|
|
4094
|
-
}
|
|
4095
|
-
}
|
|
4096
|
-
|
|
4097
|
-
//#endregion
|
|
4098
|
-
//#region src/helpers/deployment/alchemy/alchemy-svelte-setup.ts
|
|
4099
|
-
/**
|
|
4100
|
-
* Alchemy Svelte setup - CLI-only operations
|
|
4101
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4102
|
-
* This only modifies config files for "add deploy" command
|
|
4103
|
-
*/
|
|
4104
|
-
async function setupSvelteAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4105
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4106
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4107
|
-
const svelteConfigPath = path.join(webAppDir, "svelte.config.js");
|
|
4108
|
-
if (!await fs.pathExists(svelteConfigPath)) return;
|
|
4109
|
-
try {
|
|
4110
|
-
const project = new Project({ manipulationSettings: {
|
|
4111
|
-
indentationText: IndentationText.TwoSpaces,
|
|
4112
|
-
quoteKind: QuoteKind.Single
|
|
4113
|
-
} });
|
|
4114
|
-
project.addSourceFileAtPath(svelteConfigPath);
|
|
4115
|
-
const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
|
|
4116
|
-
const adapterImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
4117
|
-
if (adapterImport) {
|
|
4118
|
-
adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
|
|
4119
|
-
adapterImport.removeDefaultImport();
|
|
4120
|
-
adapterImport.setDefaultImport("alchemy");
|
|
4121
|
-
} else sourceFile.insertImportDeclaration(0, {
|
|
4122
|
-
moduleSpecifier: "alchemy/cloudflare/sveltekit",
|
|
4123
|
-
defaultImport: "alchemy"
|
|
4124
|
-
});
|
|
4125
|
-
const configVariable = sourceFile.getVariableDeclaration("config");
|
|
4126
|
-
if (configVariable) {
|
|
4127
|
-
const initializer = configVariable.getInitializer();
|
|
4128
|
-
if (Node.isObjectLiteralExpression(initializer)) updateAdapterInConfig(initializer);
|
|
4129
|
-
}
|
|
4130
|
-
await project.save();
|
|
4131
|
-
} catch (error) {
|
|
4132
|
-
console.warn("Failed to update svelte.config.js:", error);
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
function updateAdapterInConfig(configObject) {
|
|
4136
|
-
if (!Node.isObjectLiteralExpression(configObject)) return;
|
|
4137
|
-
const kitProperty = configObject.getProperty("kit");
|
|
4138
|
-
if (kitProperty && Node.isPropertyAssignment(kitProperty)) {
|
|
4139
|
-
const kitInitializer = kitProperty.getInitializer();
|
|
4140
|
-
if (Node.isObjectLiteralExpression(kitInitializer)) {
|
|
4141
|
-
const adapterProperty = kitInitializer.getProperty("adapter");
|
|
4142
|
-
if (adapterProperty && Node.isPropertyAssignment(adapterProperty)) {
|
|
4143
|
-
const initializer = adapterProperty.getInitializer();
|
|
4144
|
-
if (Node.isCallExpression(initializer)) {
|
|
4145
|
-
const expression = initializer.getExpression();
|
|
4146
|
-
if (Node.isIdentifier(expression) && expression.getText() === "adapter") expression.replaceWithText("alchemy");
|
|
4147
|
-
}
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
|
-
}
|
|
4151
|
-
}
|
|
4152
|
-
|
|
4153
|
-
//#endregion
|
|
4154
|
-
//#region src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts
|
|
4155
|
-
/**
|
|
4156
|
-
* Alchemy TanStack Start setup - CLI-only operations
|
|
4157
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4158
|
-
* This only modifies vite.config.ts for "add deploy" command
|
|
4159
|
-
*/
|
|
4160
|
-
async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4161
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4162
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4163
|
-
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
4164
|
-
if (await fs.pathExists(viteConfigPath)) try {
|
|
4165
|
-
const project = new Project({ manipulationSettings: {
|
|
4166
|
-
indentationText: IndentationText.TwoSpaces,
|
|
4167
|
-
quoteKind: QuoteKind.Double
|
|
4168
|
-
} });
|
|
4169
|
-
project.addSourceFileAtPath(viteConfigPath);
|
|
4170
|
-
const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
|
|
4171
|
-
const alchemyImport = sourceFile.getImportDeclaration("alchemy/cloudflare/tanstack-start");
|
|
4172
|
-
if (!alchemyImport) sourceFile.addImportDeclaration({
|
|
4173
|
-
moduleSpecifier: "alchemy/cloudflare/tanstack-start",
|
|
4174
|
-
defaultImport: "alchemy"
|
|
4175
|
-
});
|
|
4176
|
-
else alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
|
|
4177
|
-
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
4178
|
-
if (!exportAssignment) return;
|
|
4179
|
-
const defineConfigCall = exportAssignment.getExpression();
|
|
4180
|
-
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineConfig") return;
|
|
4181
|
-
let configObject = defineConfigCall.getArguments()[0];
|
|
4182
|
-
if (!configObject) configObject = defineConfigCall.addArgument("{}");
|
|
4183
|
-
if (Node.isObjectLiteralExpression(configObject)) {
|
|
4184
|
-
const pluginsProperty = configObject.getProperty("plugins");
|
|
4185
|
-
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
|
4186
|
-
const initializer = pluginsProperty.getInitializer();
|
|
4187
|
-
if (Node.isArrayLiteralExpression(initializer)) {
|
|
4188
|
-
if (!initializer.getElements().some((el) => el.getText().includes("alchemy("))) initializer.addElement("alchemy()");
|
|
4189
|
-
}
|
|
4190
|
-
} else configObject.addPropertyAssignment({
|
|
4191
|
-
name: "plugins",
|
|
4192
|
-
initializer: "[alchemy()]"
|
|
4193
|
-
});
|
|
4194
|
-
}
|
|
4195
|
-
await project.save();
|
|
4196
|
-
} catch (error) {
|
|
4197
|
-
console.warn("Failed to update vite.config.ts:", error);
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4200
|
-
|
|
4201
|
-
//#endregion
|
|
4202
|
-
//#region src/helpers/deployment/alchemy/alchemy-vite-setup.ts
|
|
4203
|
-
/**
|
|
4204
|
-
* Alchemy setup for Vite-based frontends (React Router, Solid, TanStack Router)
|
|
4205
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts
|
|
4206
|
-
* These frontends don't need config modifications - templates already have correct setup
|
|
4207
|
-
*/
|
|
4208
|
-
async function setupReactRouterAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4209
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4210
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4211
|
-
}
|
|
4212
|
-
async function setupSolidAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4213
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4214
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4215
|
-
}
|
|
4216
|
-
async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager, _options) {
|
|
4217
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
4218
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
4219
|
-
}
|
|
4220
|
-
|
|
4221
|
-
//#endregion
|
|
4222
|
-
//#region src/helpers/deployment/alchemy/alchemy-combined-setup.ts
|
|
4223
|
-
function getInfraFilter(packageManager, hasTurborepo, infraWorkspace) {
|
|
4224
|
-
if (hasTurborepo) return (script) => `turbo -F ${infraWorkspace} ${script}`;
|
|
4225
|
-
switch (packageManager) {
|
|
4226
|
-
case "pnpm": return (script) => `pnpm --filter ${infraWorkspace} ${script}`;
|
|
4227
|
-
case "npm": return (script) => `npm run ${script} --workspace ${infraWorkspace}`;
|
|
4228
|
-
case "bun": return (script) => `bun run --filter ${infraWorkspace} ${script}`;
|
|
4229
|
-
}
|
|
4230
|
-
}
|
|
4231
|
-
async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
|
|
4232
|
-
await setupInfraScripts(projectDir, packageManager, config);
|
|
4233
|
-
const frontend = config.frontend;
|
|
4234
|
-
const isNext = frontend.includes("next");
|
|
4235
|
-
const isNuxt = frontend.includes("nuxt");
|
|
4236
|
-
const isSvelte = frontend.includes("svelte");
|
|
4237
|
-
const isTanstackRouter = frontend.includes("tanstack-router");
|
|
4238
|
-
const isTanstackStart = frontend.includes("tanstack-start");
|
|
4239
|
-
const isReactRouter = frontend.includes("react-router");
|
|
4240
|
-
const isSolid = frontend.includes("solid");
|
|
4241
|
-
if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4242
|
-
else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4243
|
-
else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4244
|
-
else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4245
|
-
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4246
|
-
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4247
|
-
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
4248
|
-
}
|
|
4249
|
-
async function setupInfraScripts(projectDir, packageManager, config) {
|
|
4250
|
-
const projectName = config.projectName;
|
|
4251
|
-
const hasTurborepo = config.addons.includes("turborepo");
|
|
4252
|
-
const infraWorkspace = `@${projectName}/infra`;
|
|
4253
|
-
const rootPkgPath = path.join(projectDir, "package.json");
|
|
4254
|
-
if (await fs.pathExists(rootPkgPath)) {
|
|
4255
|
-
const pkg = await fs.readJson(rootPkgPath);
|
|
4256
|
-
const filter = getInfraFilter(packageManager, hasTurborepo, infraWorkspace);
|
|
4257
|
-
pkg.scripts = {
|
|
4258
|
-
...pkg.scripts,
|
|
4259
|
-
deploy: filter("deploy"),
|
|
4260
|
-
destroy: filter("destroy")
|
|
4261
|
-
};
|
|
4262
|
-
await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
|
|
4263
|
-
}
|
|
4264
|
-
if (config.serverDeploy === "cloudflare") {
|
|
4265
|
-
const serverPkgPath = path.join(projectDir, "apps/server/package.json");
|
|
4266
|
-
if (await fs.pathExists(serverPkgPath)) {
|
|
4267
|
-
const serverPkg = await fs.readJson(serverPkgPath);
|
|
4268
|
-
if (serverPkg.scripts?.dev) {
|
|
4269
|
-
serverPkg.scripts["dev:bare"] = serverPkg.scripts.dev;
|
|
4270
|
-
delete serverPkg.scripts.dev;
|
|
4271
|
-
await fs.writeJson(serverPkgPath, serverPkg, { spaces: 2 });
|
|
4272
|
-
}
|
|
4273
|
-
}
|
|
4274
|
-
}
|
|
4275
|
-
if (config.webDeploy === "cloudflare") {
|
|
4276
|
-
const webPkgPath = path.join(projectDir, "apps/web/package.json");
|
|
4277
|
-
if (await fs.pathExists(webPkgPath)) {
|
|
4278
|
-
const webPkg = await fs.readJson(webPkgPath);
|
|
4279
|
-
if (webPkg.scripts?.dev) {
|
|
4280
|
-
webPkg.scripts["dev:bare"] = webPkg.scripts.dev;
|
|
4281
|
-
delete webPkg.scripts.dev;
|
|
4282
|
-
await fs.writeJson(webPkgPath, webPkg, { spaces: 2 });
|
|
4283
|
-
}
|
|
4284
|
-
}
|
|
4285
|
-
}
|
|
4286
|
-
}
|
|
4287
|
-
|
|
4288
|
-
//#endregion
|
|
4289
|
-
//#region src/helpers/deployment/server-deploy-setup.ts
|
|
4290
|
-
/**
|
|
4291
|
-
* Server deploy setup - CLI-only operations
|
|
4292
|
-
* NOTE: Dependencies are handled by template-generator's deploy-deps.ts processor
|
|
4293
|
-
* This file only handles external CLI calls for "add deploy" command
|
|
4294
|
-
*/
|
|
4295
|
-
async function setupServerDeploy(config) {
|
|
4296
|
-
const { serverDeploy, webDeploy, projectDir, packageManager } = config;
|
|
4297
|
-
if (serverDeploy === "none") return;
|
|
4298
|
-
if (serverDeploy === "cloudflare" && webDeploy === "cloudflare") return;
|
|
4299
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
4300
|
-
if (!await fs.pathExists(serverDir)) return;
|
|
4301
|
-
if (serverDeploy === "cloudflare") await setupInfraScripts(projectDir, packageManager, config);
|
|
4302
|
-
}
|
|
4303
|
-
|
|
4304
|
-
//#endregion
|
|
4305
|
-
//#region src/helpers/deployment/web-deploy-setup.ts
|
|
4306
|
-
async function setupWebDeploy(config) {
|
|
4307
|
-
const { webDeploy, serverDeploy, frontend, projectDir } = config;
|
|
4308
|
-
const { packageManager } = config;
|
|
4309
|
-
if (webDeploy === "none") return;
|
|
4310
|
-
if (webDeploy !== "cloudflare") return;
|
|
4311
|
-
if (webDeploy === "cloudflare" && serverDeploy === "cloudflare") {
|
|
4312
|
-
await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
|
|
4313
|
-
return;
|
|
4314
|
-
}
|
|
4315
|
-
await setupInfraScripts(projectDir, packageManager, config);
|
|
4316
|
-
const isNext = frontend.includes("next");
|
|
4317
|
-
const isNuxt = frontend.includes("nuxt");
|
|
4318
|
-
const isSvelte = frontend.includes("svelte");
|
|
4319
|
-
const isTanstackRouter = frontend.includes("tanstack-router");
|
|
4320
|
-
const isTanstackStart = frontend.includes("tanstack-start");
|
|
4321
|
-
const isReactRouter = frontend.includes("react-router");
|
|
4322
|
-
const isSolid = frontend.includes("solid");
|
|
4323
|
-
if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager);
|
|
4324
|
-
else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
|
4325
|
-
else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
|
4326
|
-
else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
|
4327
|
-
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
|
4328
|
-
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
|
4329
|
-
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
|
|
4330
|
-
}
|
|
4331
|
-
|
|
4332
3614
|
//#endregion
|
|
4333
3615
|
//#region src/helpers/core/git.ts
|
|
4334
3616
|
async function initializeGit(projectDir, useGit) {
|
|
@@ -4599,13 +3881,6 @@ async function createProject(options, cliInput = {}) {
|
|
|
4599
3881
|
await setPackageManagerVersion(projectDir, options.packageManager);
|
|
4600
3882
|
if (!isConvex && options.database !== "none") await setupDatabase(options, cliInput);
|
|
4601
3883
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
4602
|
-
if (options.auth === "better-auth" && !isConvex) {
|
|
4603
|
-
const authPackageDir = `${projectDir}/packages/auth`;
|
|
4604
|
-
if (await fs.pathExists(authPackageDir)) await setupBetterAuthPlugins(projectDir, options);
|
|
4605
|
-
}
|
|
4606
|
-
await setupEnvironmentVariables(options);
|
|
4607
|
-
await setupWebDeploy(options);
|
|
4608
|
-
await setupServerDeploy(options);
|
|
4609
3884
|
await writeBtsConfig(options);
|
|
4610
3885
|
await formatProject(projectDir);
|
|
4611
3886
|
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "3.13.2-dev.
|
|
3
|
+
"version": "3.13.2-dev.e295faf",
|
|
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": "3.13.2-dev.
|
|
74
|
-
"@better-t-stack/types": "3.13.2-dev.
|
|
73
|
+
"@better-t-stack/template-generator": "3.13.2-dev.e295faf",
|
|
74
|
+
"@better-t-stack/types": "3.13.2-dev.e295faf",
|
|
75
75
|
"@clack/core": "^0.5.0",
|
|
76
76
|
"@clack/prompts": "^1.0.0-alpha.8",
|
|
77
77
|
"@orpc/server": "^1.13.0",
|