create-better-t-stack 3.13.2-pr788.6feedca → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-DtAyzhjL.mjs → src-BRi63IDG.mjs} +253 -930
- package/package.json +4 -4
|
@@ -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,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;
|
|
@@ -616,29 +614,29 @@ function getAddonDisplay(addon) {
|
|
|
616
614
|
};
|
|
617
615
|
}
|
|
618
616
|
const ADDON_GROUPS = {
|
|
619
|
-
|
|
620
|
-
|
|
617
|
+
Tooling: [
|
|
618
|
+
"turborepo",
|
|
621
619
|
"biome",
|
|
622
620
|
"oxlint",
|
|
623
|
-
"ultracite"
|
|
621
|
+
"ultracite",
|
|
622
|
+
"husky"
|
|
624
623
|
],
|
|
625
|
-
|
|
626
|
-
|
|
624
|
+
Documentation: ["starlight", "fumadocs"],
|
|
625
|
+
Extensions: [
|
|
627
626
|
"pwa",
|
|
628
627
|
"tauri",
|
|
629
|
-
"husky",
|
|
630
628
|
"opentui",
|
|
631
629
|
"wxt",
|
|
632
|
-
"
|
|
630
|
+
"ruler"
|
|
633
631
|
]
|
|
634
632
|
};
|
|
635
633
|
async function getAddonsChoice(addons, frontends, auth) {
|
|
636
634
|
if (addons !== void 0) return addons;
|
|
637
635
|
const allAddons = types_exports.AddonsSchema.options.filter((addon) => addon !== "none");
|
|
638
636
|
const groupedOptions = {
|
|
637
|
+
Tooling: [],
|
|
639
638
|
Documentation: [],
|
|
640
|
-
|
|
641
|
-
Other: []
|
|
639
|
+
Extensions: []
|
|
642
640
|
};
|
|
643
641
|
const frontendsArray = frontends || [];
|
|
644
642
|
for (const addon of allAddons) {
|
|
@@ -650,9 +648,9 @@ async function getAddonsChoice(addons, frontends, auth) {
|
|
|
650
648
|
label,
|
|
651
649
|
hint
|
|
652
650
|
};
|
|
653
|
-
if (ADDON_GROUPS.
|
|
654
|
-
else if (ADDON_GROUPS.
|
|
655
|
-
else if (ADDON_GROUPS.
|
|
651
|
+
if (ADDON_GROUPS.Tooling.includes(addon)) groupedOptions.Tooling.push(option);
|
|
652
|
+
else if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
|
|
653
|
+
else if (ADDON_GROUPS.Extensions.includes(addon)) groupedOptions.Extensions.push(option);
|
|
656
654
|
}
|
|
657
655
|
Object.keys(groupedOptions).forEach((group$1) => {
|
|
658
656
|
if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
|
|
@@ -1444,7 +1442,7 @@ const getLatestCLIVersion = () => {
|
|
|
1444
1442
|
*/
|
|
1445
1443
|
function isTelemetryEnabled() {
|
|
1446
1444
|
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
1447
|
-
const BTS_TELEMETRY = "
|
|
1445
|
+
const BTS_TELEMETRY = "1";
|
|
1448
1446
|
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
1449
1447
|
if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
|
|
1450
1448
|
return true;
|
|
@@ -1452,7 +1450,16 @@ function isTelemetryEnabled() {
|
|
|
1452
1450
|
|
|
1453
1451
|
//#endregion
|
|
1454
1452
|
//#region src/utils/analytics.ts
|
|
1455
|
-
|
|
1453
|
+
const CONVEX_INGEST_URL = "https://striped-seahorse-863.convex.site/api/analytics/ingest";
|
|
1454
|
+
async function sendConvexEvent(payload) {
|
|
1455
|
+
try {
|
|
1456
|
+
await fetch(CONVEX_INGEST_URL, {
|
|
1457
|
+
method: "POST",
|
|
1458
|
+
headers: { "Content-Type": "application/json" },
|
|
1459
|
+
body: JSON.stringify(payload)
|
|
1460
|
+
});
|
|
1461
|
+
} catch {}
|
|
1462
|
+
}
|
|
1456
1463
|
async function trackProjectCreation(config, disableAnalytics = false) {
|
|
1457
1464
|
if (!isTelemetryEnabled() || disableAnalytics) return;
|
|
1458
1465
|
const { projectName: _projectName, projectDir: _projectDir, relativePath: _relativePath, ...safeConfig } = config;
|
|
@@ -2029,63 +2036,6 @@ function validateConfigCompatibility(config, providedFlags, options) {
|
|
|
2029
2036
|
else validateConfigForProgrammaticUse(config);
|
|
2030
2037
|
}
|
|
2031
2038
|
|
|
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
2039
|
//#endregion
|
|
2090
2040
|
//#region src/utils/bts-config.ts
|
|
2091
2041
|
const BTS_CONFIG_FILE = "bts.jsonc";
|
|
@@ -2148,7 +2098,7 @@ const formatOptions = {
|
|
|
2148
2098
|
experimentalSortPackageJson: true,
|
|
2149
2099
|
experimentalSortImports: { order: "asc" }
|
|
2150
2100
|
};
|
|
2151
|
-
async function
|
|
2101
|
+
async function formatCode(filePath, content) {
|
|
2152
2102
|
try {
|
|
2153
2103
|
const result = await format(path.basename(filePath), content, formatOptions);
|
|
2154
2104
|
if (result.errors && result.errors.length > 0) return null;
|
|
@@ -2157,45 +2107,47 @@ async function formatFile(filePath, content) {
|
|
|
2157
2107
|
return null;
|
|
2158
2108
|
}
|
|
2159
2109
|
}
|
|
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
|
-
}));
|
|
2110
|
+
async function formatProject(projectDir) {
|
|
2111
|
+
async function formatDirectory(dir) {
|
|
2112
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2113
|
+
await Promise.all(entries.map(async (entry) => {
|
|
2114
|
+
const fullPath = path.join(dir, entry.name);
|
|
2115
|
+
if (entry.isDirectory()) await formatDirectory(fullPath);
|
|
2116
|
+
else if (entry.isFile()) try {
|
|
2117
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
2118
|
+
const formatted = await formatCode(fullPath, content);
|
|
2119
|
+
if (formatted && formatted !== content) await fs.writeFile(fullPath, formatted, "utf-8");
|
|
2120
|
+
} catch {}
|
|
2121
|
+
}));
|
|
2122
|
+
}
|
|
2123
|
+
await formatDirectory(projectDir);
|
|
2180
2124
|
}
|
|
2181
2125
|
|
|
2182
2126
|
//#endregion
|
|
2183
|
-
//#region src/utils/package-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
case "pnpm": return `pnpm dlx ${commandWithArgs}`;
|
|
2195
|
-
case "bun": return `bunx ${commandWithArgs}`;
|
|
2196
|
-
default: return `npx ${commandWithArgs}`;
|
|
2127
|
+
//#region src/utils/add-package-deps.ts
|
|
2128
|
+
const addPackageDependency = async (opts) => {
|
|
2129
|
+
const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
|
|
2130
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
2131
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
2132
|
+
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
2133
|
+
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
2134
|
+
for (const pkgName of dependencies) {
|
|
2135
|
+
const version = dependencyVersionMap[pkgName];
|
|
2136
|
+
if (version) pkgJson.dependencies[pkgName] = version;
|
|
2137
|
+
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
2197
2138
|
}
|
|
2198
|
-
|
|
2139
|
+
for (const pkgName of devDependencies) {
|
|
2140
|
+
const version = dependencyVersionMap[pkgName];
|
|
2141
|
+
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
2142
|
+
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
2143
|
+
}
|
|
2144
|
+
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
2145
|
+
for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
|
|
2146
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2147
|
+
};
|
|
2148
|
+
|
|
2149
|
+
//#endregion
|
|
2150
|
+
//#region src/utils/package-runner.ts
|
|
2199
2151
|
/**
|
|
2200
2152
|
* Returns the command and arguments as an array for use with execa's $ template syntax.
|
|
2201
2153
|
* This avoids the need for shell: true and provides better escaping.
|
|
@@ -2305,15 +2257,23 @@ async function setupFumadocs(config) {
|
|
|
2305
2257
|
|
|
2306
2258
|
//#endregion
|
|
2307
2259
|
//#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
2260
|
async function setupOxlint(projectDir, packageManager) {
|
|
2261
|
+
await addPackageDependency({
|
|
2262
|
+
devDependencies: ["oxlint", "oxfmt"],
|
|
2263
|
+
projectDir
|
|
2264
|
+
});
|
|
2265
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2266
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2267
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2268
|
+
packageJson.scripts = {
|
|
2269
|
+
...packageJson.scripts,
|
|
2270
|
+
check: "oxlint && oxfmt --write"
|
|
2271
|
+
};
|
|
2272
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2273
|
+
}
|
|
2314
2274
|
const s = spinner();
|
|
2315
|
-
s.start("Initializing oxlint and oxfmt...");
|
|
2316
2275
|
const oxlintArgs = getPackageExecutionArgs(packageManager, "oxlint@latest --init");
|
|
2276
|
+
s.start("Initializing oxlint and oxfmt...");
|
|
2317
2277
|
await $({
|
|
2318
2278
|
cwd: projectDir,
|
|
2319
2279
|
env: { CI: "true" }
|
|
@@ -2326,94 +2286,6 @@ async function setupOxlint(projectDir, packageManager) {
|
|
|
2326
2286
|
s.stop("oxlint and oxfmt initialized successfully!");
|
|
2327
2287
|
}
|
|
2328
2288
|
|
|
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
2289
|
//#endregion
|
|
2418
2290
|
//#region src/helpers/addons/starlight-setup.ts
|
|
2419
2291
|
async function setupStarlight(config) {
|
|
@@ -2529,38 +2401,57 @@ async function setupTui(config) {
|
|
|
2529
2401
|
|
|
2530
2402
|
//#endregion
|
|
2531
2403
|
//#region src/helpers/addons/ultracite-setup.ts
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2404
|
+
const LINTERS = {
|
|
2405
|
+
biome: {
|
|
2406
|
+
label: "Biome",
|
|
2407
|
+
hint: "Fast formatter and linter"
|
|
2408
|
+
},
|
|
2409
|
+
eslint: {
|
|
2410
|
+
label: "ESLint",
|
|
2411
|
+
hint: "Traditional JavaScript linter"
|
|
2412
|
+
},
|
|
2413
|
+
oxlint: {
|
|
2414
|
+
label: "Oxlint",
|
|
2415
|
+
hint: "Oxidation compiler linter"
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2537
2418
|
const EDITORS = {
|
|
2538
|
-
vscode: { label: "
|
|
2419
|
+
vscode: { label: "VS Code" },
|
|
2420
|
+
cursor: { label: "Cursor" },
|
|
2421
|
+
windsurf: { label: "Windsurf" },
|
|
2422
|
+
antigravity: { label: "Antigravity" },
|
|
2423
|
+
kiro: { label: "Kiro" },
|
|
2424
|
+
trae: { label: "Trae" },
|
|
2425
|
+
void: { label: "Void" },
|
|
2539
2426
|
zed: { label: "Zed" }
|
|
2540
2427
|
};
|
|
2541
2428
|
const AGENTS = {
|
|
2542
|
-
"vscode-copilot": { label: "VS Code Copilot" },
|
|
2543
|
-
cursor: { label: "Cursor" },
|
|
2544
|
-
windsurf: { label: "Windsurf" },
|
|
2545
|
-
zed: { label: "Zed" },
|
|
2546
2429
|
claude: { label: "Claude" },
|
|
2547
2430
|
codex: { label: "Codex" },
|
|
2548
|
-
|
|
2431
|
+
jules: { label: "Jules" },
|
|
2432
|
+
copilot: { label: "GitHub Copilot" },
|
|
2549
2433
|
cline: { label: "Cline" },
|
|
2550
2434
|
amp: { label: "Amp" },
|
|
2551
2435
|
aider: { label: "Aider" },
|
|
2552
2436
|
"firebase-studio": { label: "Firebase Studio" },
|
|
2553
2437
|
"open-hands": { label: "Open Hands" },
|
|
2554
|
-
|
|
2438
|
+
gemini: { label: "Gemini" },
|
|
2555
2439
|
junie: { label: "Junie" },
|
|
2556
2440
|
augmentcode: { label: "AugmentCode" },
|
|
2557
2441
|
"kilo-code": { label: "Kilo Code" },
|
|
2558
2442
|
goose: { label: "Goose" },
|
|
2559
|
-
"roo-code": { label: "Roo Code" }
|
|
2443
|
+
"roo-code": { label: "Roo Code" },
|
|
2444
|
+
warp: { label: "Warp" },
|
|
2445
|
+
droid: { label: "Droid" },
|
|
2446
|
+
opencode: { label: "OpenCode" },
|
|
2447
|
+
crush: { label: "Crush" },
|
|
2448
|
+
qwen: { label: "Qwen" },
|
|
2449
|
+
"amazon-q-cli": { label: "Amazon Q CLI" },
|
|
2450
|
+
firebender: { label: "Firebender" }
|
|
2560
2451
|
};
|
|
2561
2452
|
const HOOKS = {
|
|
2562
2453
|
cursor: { label: "Cursor" },
|
|
2563
|
-
|
|
2454
|
+
windsurf: { label: "Windsurf" }
|
|
2564
2455
|
};
|
|
2565
2456
|
function getFrameworksFromFrontend(frontend) {
|
|
2566
2457
|
const frameworkMap = {
|
|
@@ -2584,6 +2475,15 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2584
2475
|
try {
|
|
2585
2476
|
log.info("Setting up Ultracite...");
|
|
2586
2477
|
const result = await group({
|
|
2478
|
+
linter: () => select({
|
|
2479
|
+
message: "Choose linter/formatter",
|
|
2480
|
+
options: Object.entries(LINTERS).map(([key, linter$1]) => ({
|
|
2481
|
+
value: key,
|
|
2482
|
+
label: linter$1.label,
|
|
2483
|
+
hint: linter$1.hint
|
|
2484
|
+
})),
|
|
2485
|
+
initialValue: "biome"
|
|
2486
|
+
}),
|
|
2587
2487
|
editors: () => multiselect({
|
|
2588
2488
|
message: "Choose editors",
|
|
2589
2489
|
options: Object.entries(EDITORS).map(([key, editor]) => ({
|
|
@@ -2592,7 +2492,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2592
2492
|
})),
|
|
2593
2493
|
required: true
|
|
2594
2494
|
}),
|
|
2595
|
-
agents: () =>
|
|
2495
|
+
agents: () => multiselect({
|
|
2596
2496
|
message: "Choose agents",
|
|
2597
2497
|
options: Object.entries(AGENTS).map(([key, agent]) => ({
|
|
2598
2498
|
value: key,
|
|
@@ -2600,7 +2500,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2600
2500
|
})),
|
|
2601
2501
|
required: true
|
|
2602
2502
|
}),
|
|
2603
|
-
hooks: () =>
|
|
2503
|
+
hooks: () => multiselect({
|
|
2604
2504
|
message: "Choose hooks",
|
|
2605
2505
|
options: Object.entries(HOOKS).map(([key, hook]) => ({
|
|
2606
2506
|
value: key,
|
|
@@ -2610,6 +2510,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2610
2510
|
}, { onCancel: () => {
|
|
2611
2511
|
exitCancelled("Operation cancelled");
|
|
2612
2512
|
} });
|
|
2513
|
+
const linter = result.linter;
|
|
2613
2514
|
const editors = result.editors;
|
|
2614
2515
|
const agents = result.agents;
|
|
2615
2516
|
const hooks = result.hooks;
|
|
@@ -2617,7 +2518,9 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2617
2518
|
const ultraciteArgs = [
|
|
2618
2519
|
"init",
|
|
2619
2520
|
"--pm",
|
|
2620
|
-
packageManager
|
|
2521
|
+
packageManager,
|
|
2522
|
+
"--linter",
|
|
2523
|
+
linter
|
|
2621
2524
|
];
|
|
2622
2525
|
if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
|
|
2623
2526
|
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
@@ -2702,360 +2605,96 @@ async function setupWxt(config) {
|
|
|
2702
2605
|
|
|
2703
2606
|
//#endregion
|
|
2704
2607
|
//#region src/helpers/addons/addons-setup.ts
|
|
2705
|
-
async function setupAddons(config
|
|
2706
|
-
const { addons, frontend, projectDir
|
|
2608
|
+
async function setupAddons(config) {
|
|
2609
|
+
const { addons, frontend, projectDir } = config;
|
|
2707
2610
|
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
|
|
2708
2611
|
const hasNuxtFrontend = frontend.includes("nuxt");
|
|
2709
2612
|
const hasSvelteFrontend = frontend.includes("svelte");
|
|
2710
2613
|
const hasSolidFrontend = frontend.includes("solid");
|
|
2711
2614
|
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
2615
|
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
|
|
2720
2616
|
const hasUltracite = addons.includes("ultracite");
|
|
2617
|
+
const hasBiome = addons.includes("biome");
|
|
2721
2618
|
const hasHusky = addons.includes("husky");
|
|
2722
2619
|
const hasOxlint = addons.includes("oxlint");
|
|
2723
|
-
if (hasUltracite)
|
|
2724
|
-
|
|
2620
|
+
if (!hasUltracite) {
|
|
2621
|
+
if (hasBiome) await setupBiome(projectDir);
|
|
2622
|
+
if (hasOxlint) await setupOxlint(projectDir, config.packageManager);
|
|
2623
|
+
if (hasHusky) {
|
|
2624
|
+
let linter;
|
|
2625
|
+
if (hasOxlint) linter = "oxlint";
|
|
2626
|
+
else if (hasBiome) linter = "biome";
|
|
2627
|
+
await setupHusky(projectDir, linter);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2725
2630
|
if (addons.includes("starlight")) await setupStarlight(config);
|
|
2726
|
-
if (addons.includes("ruler")) await setupRuler(config);
|
|
2727
2631
|
if (addons.includes("fumadocs")) await setupFumadocs(config);
|
|
2728
2632
|
if (addons.includes("opentui")) await setupTui(config);
|
|
2729
2633
|
if (addons.includes("wxt")) await setupWxt(config);
|
|
2634
|
+
if (hasUltracite) await setupUltracite(config, hasHusky);
|
|
2730
2635
|
}
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
const
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
for (const pkgName of dependencies) {
|
|
2745
|
-
const version = dependencyVersionMap[pkgName];
|
|
2746
|
-
if (version) pkgJson.dependencies[pkgName] = version;
|
|
2747
|
-
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
2636
|
+
async function setupBiome(projectDir) {
|
|
2637
|
+
await addPackageDependency({
|
|
2638
|
+
devDependencies: ["@biomejs/biome"],
|
|
2639
|
+
projectDir
|
|
2640
|
+
});
|
|
2641
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2642
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2643
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2644
|
+
packageJson.scripts = {
|
|
2645
|
+
...packageJson.scripts,
|
|
2646
|
+
check: "biome check --write ."
|
|
2647
|
+
};
|
|
2648
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2748
2649
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2650
|
+
}
|
|
2651
|
+
async function setupHusky(projectDir, linter) {
|
|
2652
|
+
await addPackageDependency({
|
|
2653
|
+
devDependencies: ["husky", "lint-staged"],
|
|
2654
|
+
projectDir
|
|
2655
|
+
});
|
|
2656
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2657
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2658
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2659
|
+
packageJson.scripts = {
|
|
2660
|
+
...packageJson.scripts,
|
|
2661
|
+
prepare: "husky"
|
|
2662
|
+
};
|
|
2663
|
+
if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
|
|
2664
|
+
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
2665
|
+
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
2666
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2753
2667
|
}
|
|
2754
|
-
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
2755
|
-
for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
|
|
2756
|
-
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2757
|
-
};
|
|
2758
|
-
|
|
2759
|
-
//#endregion
|
|
2760
|
-
//#region src/helpers/core/auth-setup.ts
|
|
2761
|
-
function generateAuthSecret(length = 32) {
|
|
2762
|
-
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
2763
|
-
let result = "";
|
|
2764
|
-
const charactersLength = 62;
|
|
2765
|
-
for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
2766
|
-
return result;
|
|
2767
2668
|
}
|
|
2768
2669
|
|
|
2769
2670
|
//#endregion
|
|
2770
|
-
//#region src/
|
|
2771
|
-
function
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
const
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
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";
|
|
2995
|
-
break;
|
|
2996
|
-
case "mysql":
|
|
2997
|
-
databaseUrl = "mysql://root:password@localhost:3306/mydb";
|
|
2998
|
-
break;
|
|
2999
|
-
case "mongodb":
|
|
3000
|
-
databaseUrl = "mongodb://localhost:27017/mydatabase";
|
|
2671
|
+
//#region src/utils/env-utils.ts
|
|
2672
|
+
async function addEnvVariablesToFile(envPath, variables) {
|
|
2673
|
+
let content = "";
|
|
2674
|
+
if (fs.existsSync(envPath)) content = await fs.readFile(envPath, "utf-8");
|
|
2675
|
+
else await fs.ensureFile(envPath);
|
|
2676
|
+
const existingLines = content.split("\n");
|
|
2677
|
+
const newLines = [];
|
|
2678
|
+
const keysToAdd = /* @__PURE__ */ new Map();
|
|
2679
|
+
for (const variable of variables) {
|
|
2680
|
+
if (variable.condition === false || !variable.key) continue;
|
|
2681
|
+
keysToAdd.set(variable.key, variable.value);
|
|
2682
|
+
}
|
|
2683
|
+
let foundKeys = /* @__PURE__ */ new Set();
|
|
2684
|
+
for (const line of existingLines) {
|
|
2685
|
+
const trimmedLine = line.trim();
|
|
2686
|
+
let lineProcessed = false;
|
|
2687
|
+
for (const [key, value] of keysToAdd) if (trimmedLine.startsWith(`${key}=`)) {
|
|
2688
|
+
newLines.push(`${key}=${value}`);
|
|
2689
|
+
foundKeys.add(key);
|
|
2690
|
+
lineProcessed = true;
|
|
3001
2691
|
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
2692
|
}
|
|
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
|
-
}]);
|
|
2693
|
+
if (!lineProcessed) newLines.push(line);
|
|
3058
2694
|
}
|
|
2695
|
+
for (const [key, value] of keysToAdd) if (!foundKeys.has(key)) newLines.push(`${key}=${value}`);
|
|
2696
|
+
if (newLines.length > 0 && newLines[newLines.length - 1] === "") newLines.pop();
|
|
2697
|
+
if (foundKeys.size > 0 || keysToAdd.size > foundKeys.size) await fs.writeFile(envPath, newLines.join("\n") + "\n");
|
|
3059
2698
|
}
|
|
3060
2699
|
|
|
3061
2700
|
//#endregion
|
|
@@ -4007,314 +3646,6 @@ async function setupDatabase(config, cliInput) {
|
|
|
4007
3646
|
}
|
|
4008
3647
|
}
|
|
4009
3648
|
|
|
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
3649
|
//#endregion
|
|
4319
3650
|
//#region src/helpers/core/git.ts
|
|
4320
3651
|
async function initializeGit(projectDir, useGit) {
|
|
@@ -4582,19 +3913,11 @@ async function createProject(options, cliInput = {}) {
|
|
|
4582
3913
|
});
|
|
4583
3914
|
if (!result.success || !result.tree) throw new Error(result.error || "Failed to generate project templates");
|
|
4584
3915
|
await writeTreeToFilesystem(result.tree, projectDir);
|
|
4585
|
-
await formatProjectFiles(projectDir);
|
|
4586
3916
|
await setPackageManagerVersion(projectDir, options.packageManager);
|
|
4587
3917
|
if (!isConvex && options.database !== "none") await setupDatabase(options, cliInput);
|
|
4588
|
-
if (options.examples.length > 0 && options.examples[0] !== "none") await /* @__PURE__ */ setupExamples(options);
|
|
4589
3918
|
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
3919
|
await writeBtsConfig(options);
|
|
3920
|
+
await formatProject(projectDir);
|
|
4598
3921
|
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
|
4599
3922
|
if (options.install) await installDependencies({
|
|
4600
3923
|
projectDir,
|