create-better-t-stack 2.41.2 → 2.41.4
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.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-DVRX65N3.js → src-CGOvCEq2.js} +858 -860
- package/package.json +1 -1
- package/templates/addons/ruler/.ruler/ruler.toml.hbs +60 -1
- package/templates/backend/server/server-base/_gitignore +2 -0
- package/templates/base/package.json.hbs +1 -0
- package/templates/addons/ruler/.ruler/mcp.json.hbs +0 -18
|
@@ -10,9 +10,9 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import gradient from "gradient-string";
|
|
11
11
|
import * as JSONC from "jsonc-parser";
|
|
12
12
|
import { $, execa } from "execa";
|
|
13
|
+
import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
|
|
13
14
|
import { glob } from "tinyglobby";
|
|
14
15
|
import handlebars from "handlebars";
|
|
15
|
-
import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
|
|
18
18
|
//#region src/utils/get-package-manager.ts
|
|
@@ -139,7 +139,7 @@ const dependencyVersionMap = {
|
|
|
139
139
|
"nitro-cloudflare-dev": "^0.2.2",
|
|
140
140
|
"@sveltejs/adapter-cloudflare": "^7.2.1",
|
|
141
141
|
"@cloudflare/workers-types": "^4.20250822.0",
|
|
142
|
-
alchemy: "^0.65.
|
|
142
|
+
alchemy: "^0.65.1",
|
|
143
143
|
nitropack: "^2.12.4",
|
|
144
144
|
dotenv: "^17.2.1"
|
|
145
145
|
};
|
|
@@ -2038,927 +2038,925 @@ async function setupFumadocs(config) {
|
|
|
2038
2038
|
}
|
|
2039
2039
|
|
|
2040
2040
|
//#endregion
|
|
2041
|
-
//#region src/
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
* @param srcPath Path to the source .hbs template file.
|
|
2045
|
-
* @param destPath Path to write the processed file.
|
|
2046
|
-
* @param context Data to be passed to the Handlebars template.
|
|
2047
|
-
*/
|
|
2048
|
-
async function processTemplate(srcPath, destPath, context) {
|
|
2041
|
+
//#region src/helpers/addons/ruler-setup.ts
|
|
2042
|
+
async function setupRuler(config) {
|
|
2043
|
+
const { packageManager, projectDir } = config;
|
|
2049
2044
|
try {
|
|
2050
|
-
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2045
|
+
log.info("Setting up Ruler...");
|
|
2046
|
+
const rulerDir = path.join(projectDir, ".ruler");
|
|
2047
|
+
if (!await fs.pathExists(rulerDir)) {
|
|
2048
|
+
log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
const EDITORS$1 = {
|
|
2052
|
+
amp: { label: "AMP" },
|
|
2053
|
+
copilot: { label: "GitHub Copilot" },
|
|
2054
|
+
claude: { label: "Claude Code" },
|
|
2055
|
+
codex: { label: "OpenAI Codex CLI" },
|
|
2056
|
+
cursor: { label: "Cursor" },
|
|
2057
|
+
windsurf: { label: "Windsurf" },
|
|
2058
|
+
cline: { label: "Cline" },
|
|
2059
|
+
aider: { label: "Aider" },
|
|
2060
|
+
firebase: { label: "Firebase Studio" },
|
|
2061
|
+
"gemini-cli": { label: "Gemini CLI" },
|
|
2062
|
+
junie: { label: "Junie" },
|
|
2063
|
+
kilocode: { label: "Kilo Code" },
|
|
2064
|
+
opencode: { label: "OpenCode" },
|
|
2065
|
+
crush: { label: "Crush" },
|
|
2066
|
+
zed: { label: "Zed" },
|
|
2067
|
+
qwen: { label: "Qwen" }
|
|
2068
|
+
};
|
|
2069
|
+
const selectedEditors = await autocompleteMultiselect({
|
|
2070
|
+
message: "Select AI assistants for Ruler",
|
|
2071
|
+
options: Object.entries(EDITORS$1).map(([key, v]) => ({
|
|
2072
|
+
value: key,
|
|
2073
|
+
label: v.label
|
|
2074
|
+
})),
|
|
2075
|
+
required: false
|
|
2076
|
+
});
|
|
2077
|
+
if (isCancel(selectedEditors)) return exitCancelled("Operation cancelled");
|
|
2078
|
+
if (selectedEditors.length === 0) {
|
|
2079
|
+
log.info("No AI assistants selected. To apply rules later, run:");
|
|
2080
|
+
log.info(pc.cyan(`${getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only")}`));
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
const configFile = path.join(rulerDir, "ruler.toml");
|
|
2084
|
+
const currentConfig = await fs.readFile(configFile, "utf-8");
|
|
2085
|
+
let updatedConfig = currentConfig;
|
|
2086
|
+
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
2087
|
+
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
2088
|
+
await fs.writeFile(configFile, updatedConfig);
|
|
2089
|
+
await addRulerScriptToPackageJson(projectDir, packageManager);
|
|
2090
|
+
const s = spinner();
|
|
2091
|
+
s.start("Applying rules with Ruler...");
|
|
2092
|
+
try {
|
|
2093
|
+
const rulerApplyCmd = getPackageExecutionCommand(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`);
|
|
2094
|
+
await execa(rulerApplyCmd, {
|
|
2095
|
+
cwd: projectDir,
|
|
2096
|
+
env: { CI: "true" },
|
|
2097
|
+
shell: true
|
|
2098
|
+
});
|
|
2099
|
+
s.stop("Applied rules with Ruler");
|
|
2100
|
+
} catch (_error) {
|
|
2101
|
+
s.stop(pc.red("Failed to apply rules"));
|
|
2102
|
+
}
|
|
2055
2103
|
} catch (error) {
|
|
2056
|
-
|
|
2057
|
-
|
|
2104
|
+
log.error(pc.red("Failed to set up Ruler"));
|
|
2105
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2058
2106
|
}
|
|
2059
2107
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2108
|
+
async function addRulerScriptToPackageJson(projectDir, packageManager) {
|
|
2109
|
+
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
2110
|
+
if (!await fs.pathExists(rootPackageJsonPath)) {
|
|
2111
|
+
log.warn("Root package.json not found, skipping ruler:apply script addition");
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
const packageJson = await fs.readJson(rootPackageJsonPath);
|
|
2115
|
+
if (!packageJson.scripts) packageJson.scripts = {};
|
|
2116
|
+
const rulerApplyCommand = getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only");
|
|
2117
|
+
packageJson.scripts["ruler:apply"] = rulerApplyCommand;
|
|
2118
|
+
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
|
2119
|
+
}
|
|
2065
2120
|
|
|
2066
2121
|
//#endregion
|
|
2067
|
-
//#region src/helpers/
|
|
2068
|
-
async function
|
|
2069
|
-
const
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
const
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2122
|
+
//#region src/helpers/addons/starlight-setup.ts
|
|
2123
|
+
async function setupStarlight(config) {
|
|
2124
|
+
const { packageManager, projectDir } = config;
|
|
2125
|
+
const s = spinner();
|
|
2126
|
+
try {
|
|
2127
|
+
s.start("Setting up Starlight docs...");
|
|
2128
|
+
const starlightArgs = [
|
|
2129
|
+
"docs",
|
|
2130
|
+
"--template",
|
|
2131
|
+
"starlight",
|
|
2132
|
+
"--no-install",
|
|
2133
|
+
"--add",
|
|
2134
|
+
"tailwind",
|
|
2135
|
+
"--no-git",
|
|
2136
|
+
"--skip-houston"
|
|
2137
|
+
];
|
|
2138
|
+
const starlightArgsString = starlightArgs.join(" ");
|
|
2139
|
+
const commandWithArgs = `create-astro@latest ${starlightArgsString}`;
|
|
2140
|
+
const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2141
|
+
await execa(starlightInitCommand, {
|
|
2142
|
+
cwd: path.join(projectDir, "apps"),
|
|
2143
|
+
env: { CI: "true" },
|
|
2144
|
+
shell: true
|
|
2145
|
+
});
|
|
2146
|
+
s.stop("Starlight docs setup successfully!");
|
|
2147
|
+
} catch (error) {
|
|
2148
|
+
s.stop(pc.red("Failed to set up Starlight docs"));
|
|
2149
|
+
if (error instanceof Error) consola.error(pc.red(error.message));
|
|
2088
2150
|
}
|
|
2089
2151
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
const
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
"
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
"tanstack-router",
|
|
2115
|
-
"react-router",
|
|
2116
|
-
"tanstack-start",
|
|
2117
|
-
"next"
|
|
2118
|
-
].includes(f));
|
|
2119
|
-
if (reactFramework) {
|
|
2120
|
-
const frameworkSrcDir = path.join(PKG_ROOT, `templates/frontend/react/${reactFramework}`);
|
|
2121
|
-
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, webAppDir, context);
|
|
2122
|
-
if (!isConvex && context.api !== "none") {
|
|
2123
|
-
const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
|
|
2124
|
-
if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
} else if (hasNuxtWeb) {
|
|
2128
|
-
const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
|
|
2129
|
-
if (await fs.pathExists(nuxtBaseDir)) await processAndCopyFiles("**/*", nuxtBaseDir, webAppDir, context);
|
|
2130
|
-
if (!isConvex && context.api === "orpc") {
|
|
2131
|
-
const apiWebNuxtDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/nuxt`);
|
|
2132
|
-
if (await fs.pathExists(apiWebNuxtDir)) await processAndCopyFiles("**/*", apiWebNuxtDir, webAppDir, context);
|
|
2133
|
-
}
|
|
2134
|
-
} else if (hasSvelteWeb) {
|
|
2135
|
-
const svelteBaseDir = path.join(PKG_ROOT, "templates/frontend/svelte");
|
|
2136
|
-
if (await fs.pathExists(svelteBaseDir)) await processAndCopyFiles("**/*", svelteBaseDir, webAppDir, context);
|
|
2137
|
-
if (!isConvex && context.api === "orpc") {
|
|
2138
|
-
const apiWebSvelteDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/svelte`);
|
|
2139
|
-
if (await fs.pathExists(apiWebSvelteDir)) await processAndCopyFiles("**/*", apiWebSvelteDir, webAppDir, context);
|
|
2140
|
-
}
|
|
2141
|
-
} else if (hasSolidWeb) {
|
|
2142
|
-
const solidBaseDir = path.join(PKG_ROOT, "templates/frontend/solid");
|
|
2143
|
-
if (await fs.pathExists(solidBaseDir)) await processAndCopyFiles("**/*", solidBaseDir, webAppDir, context);
|
|
2144
|
-
if (!isConvex && context.api === "orpc") {
|
|
2145
|
-
const apiWebSolidDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/solid`);
|
|
2146
|
-
if (await fs.pathExists(apiWebSolidDir)) await processAndCopyFiles("**/*", apiWebSolidDir, webAppDir, context);
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
if (hasNativeWind || hasUnistyles) {
|
|
2151
|
-
const nativeAppDir = path.join(projectDir, "apps/native");
|
|
2152
|
-
await fs.ensureDir(nativeAppDir);
|
|
2153
|
-
const nativeBaseCommonDir = path.join(PKG_ROOT, "templates/frontend/native/native-base");
|
|
2154
|
-
if (await fs.pathExists(nativeBaseCommonDir)) await processAndCopyFiles("**/*", nativeBaseCommonDir, nativeAppDir, context);
|
|
2155
|
-
let nativeFrameworkPath = "";
|
|
2156
|
-
if (hasNativeWind) nativeFrameworkPath = "nativewind";
|
|
2157
|
-
else if (hasUnistyles) nativeFrameworkPath = "unistyles";
|
|
2158
|
-
const nativeSpecificDir = path.join(PKG_ROOT, `templates/frontend/native/${nativeFrameworkPath}`);
|
|
2159
|
-
if (await fs.pathExists(nativeSpecificDir)) await processAndCopyFiles("**/*", nativeSpecificDir, nativeAppDir, context, true);
|
|
2160
|
-
if (!isConvex && (context.api === "trpc" || context.api === "orpc")) {
|
|
2161
|
-
const apiNativeSrcDir = path.join(PKG_ROOT, `templates/api/${context.api}/native`);
|
|
2162
|
-
if (await fs.pathExists(apiNativeSrcDir)) await processAndCopyFiles("**/*", apiNativeSrcDir, nativeAppDir, context);
|
|
2152
|
+
|
|
2153
|
+
//#endregion
|
|
2154
|
+
//#region src/helpers/addons/tauri-setup.ts
|
|
2155
|
+
async function setupTauri(config) {
|
|
2156
|
+
const { packageManager, frontend, projectDir } = config;
|
|
2157
|
+
const s = spinner();
|
|
2158
|
+
const clientPackageDir = path.join(projectDir, "apps/web");
|
|
2159
|
+
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2160
|
+
try {
|
|
2161
|
+
s.start("Setting up Tauri desktop app support...");
|
|
2162
|
+
await addPackageDependency({
|
|
2163
|
+
devDependencies: ["@tauri-apps/cli"],
|
|
2164
|
+
projectDir: clientPackageDir
|
|
2165
|
+
});
|
|
2166
|
+
const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
|
|
2167
|
+
if (await fs.pathExists(clientPackageJsonPath)) {
|
|
2168
|
+
const packageJson = await fs.readJson(clientPackageJsonPath);
|
|
2169
|
+
packageJson.scripts = {
|
|
2170
|
+
...packageJson.scripts,
|
|
2171
|
+
tauri: "tauri",
|
|
2172
|
+
"desktop:dev": "tauri dev",
|
|
2173
|
+
"desktop:build": "tauri build"
|
|
2174
|
+
};
|
|
2175
|
+
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
|
2163
2176
|
}
|
|
2177
|
+
frontend.includes("tanstack-router");
|
|
2178
|
+
const hasReactRouter = frontend.includes("react-router");
|
|
2179
|
+
const hasNuxt = frontend.includes("nuxt");
|
|
2180
|
+
const hasSvelte = frontend.includes("svelte");
|
|
2181
|
+
frontend.includes("solid");
|
|
2182
|
+
const hasNext = frontend.includes("next");
|
|
2183
|
+
const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
|
|
2184
|
+
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
|
|
2185
|
+
const tauriArgs = [
|
|
2186
|
+
"init",
|
|
2187
|
+
`--app-name=${path.basename(projectDir)}`,
|
|
2188
|
+
`--window-title=${path.basename(projectDir)}`,
|
|
2189
|
+
`--frontend-dist=${frontendDist}`,
|
|
2190
|
+
`--dev-url=${devUrl}`,
|
|
2191
|
+
`--before-dev-command="${packageManager} run dev"`,
|
|
2192
|
+
`--before-build-command="${packageManager} run build"`
|
|
2193
|
+
];
|
|
2194
|
+
const tauriArgsString = tauriArgs.join(" ");
|
|
2195
|
+
const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
|
|
2196
|
+
const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2197
|
+
await execa(tauriInitCommand, {
|
|
2198
|
+
cwd: clientPackageDir,
|
|
2199
|
+
env: { CI: "true" },
|
|
2200
|
+
shell: true
|
|
2201
|
+
});
|
|
2202
|
+
s.stop("Tauri desktop app support configured successfully!");
|
|
2203
|
+
} catch (error) {
|
|
2204
|
+
s.stop(pc.red("Failed to set up Tauri"));
|
|
2205
|
+
if (error instanceof Error) consola$1.error(pc.red(error.message));
|
|
2164
2206
|
}
|
|
2165
2207
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
const
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
const
|
|
2219
|
-
const
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
if (
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
else if (hasUnistyles$1) nativeFrameworkPath = "unistyles";
|
|
2244
|
-
if (nativeFrameworkPath) {
|
|
2245
|
-
const convexClerkNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/clerk/convex/native/${nativeFrameworkPath}`);
|
|
2246
|
-
if (await fs.pathExists(convexClerkNativeFrameworkSrc)) await processAndCopyFiles("**/*", convexClerkNativeFrameworkSrc, nativeAppDir, context);
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
return;
|
|
2250
|
-
}
|
|
2251
|
-
if (serverAppDirExists && context.backend !== "convex") {
|
|
2252
|
-
const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
|
|
2253
|
-
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
|
|
2254
|
-
if (context.backend === "next") {
|
|
2255
|
-
const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
|
|
2256
|
-
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, serverAppDir, context);
|
|
2257
|
-
}
|
|
2258
|
-
if (context.orm !== "none" && context.database !== "none") {
|
|
2259
|
-
const orm = context.orm;
|
|
2260
|
-
const db = context.database;
|
|
2261
|
-
let authDbSrc = "";
|
|
2262
|
-
if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
|
|
2263
|
-
else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
|
|
2264
|
-
else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/mongoose/${db}`);
|
|
2265
|
-
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context);
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
|
|
2269
|
-
if (hasReactWeb) {
|
|
2270
|
-
const authWebBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/base`);
|
|
2271
|
-
if (await fs.pathExists(authWebBaseSrc)) await processAndCopyFiles("**/*", authWebBaseSrc, webAppDir, context);
|
|
2272
|
-
const reactFramework = context.frontend.find((f) => [
|
|
2273
|
-
"tanstack-router",
|
|
2274
|
-
"react-router",
|
|
2275
|
-
"tanstack-start",
|
|
2276
|
-
"next"
|
|
2277
|
-
].includes(f));
|
|
2278
|
-
if (reactFramework) {
|
|
2279
|
-
const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/${reactFramework}`);
|
|
2280
|
-
if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
|
|
2281
|
-
}
|
|
2282
|
-
} else if (hasNuxtWeb) {
|
|
2283
|
-
const authWebNuxtSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/nuxt`);
|
|
2284
|
-
if (await fs.pathExists(authWebNuxtSrc)) await processAndCopyFiles("**/*", authWebNuxtSrc, webAppDir, context);
|
|
2285
|
-
} else if (hasSvelteWeb) {
|
|
2286
|
-
const authWebSvelteSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/svelte`);
|
|
2287
|
-
if (await fs.pathExists(authWebSvelteSrc)) await processAndCopyFiles("**/*", authWebSvelteSrc, webAppDir, context);
|
|
2288
|
-
} else if (hasSolidWeb) {
|
|
2289
|
-
const authWebSolidSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/solid`);
|
|
2290
|
-
if (await fs.pathExists(authWebSolidSrc)) await processAndCopyFiles("**/*", authWebSolidSrc, webAppDir, context);
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
if (hasNative && nativeAppDirExists) {
|
|
2294
|
-
const authNativeBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/native/native-base`);
|
|
2295
|
-
if (await fs.pathExists(authNativeBaseSrc)) await processAndCopyFiles("**/*", authNativeBaseSrc, nativeAppDir, context);
|
|
2296
|
-
let nativeFrameworkAuthPath = "";
|
|
2297
|
-
if (hasNativeWind) nativeFrameworkAuthPath = "nativewind";
|
|
2298
|
-
else if (hasUnistyles) nativeFrameworkAuthPath = "unistyles";
|
|
2299
|
-
if (nativeFrameworkAuthPath) {
|
|
2300
|
-
const authNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/native/${nativeFrameworkAuthPath}`);
|
|
2301
|
-
if (await fs.pathExists(authNativeFrameworkSrc)) await processAndCopyFiles("**/*", authNativeFrameworkSrc, nativeAppDir, context);
|
|
2302
|
-
}
|
|
2208
|
+
|
|
2209
|
+
//#endregion
|
|
2210
|
+
//#region src/helpers/addons/ultracite-setup.ts
|
|
2211
|
+
const EDITORS = {
|
|
2212
|
+
vscode: { label: "VSCode / Cursor / Windsurf" },
|
|
2213
|
+
zed: { label: "Zed" }
|
|
2214
|
+
};
|
|
2215
|
+
const RULES = {
|
|
2216
|
+
"vscode-copilot": { label: "VS Code Copilot" },
|
|
2217
|
+
cursor: { label: "Cursor" },
|
|
2218
|
+
windsurf: { label: "Windsurf" },
|
|
2219
|
+
zed: { label: "Zed" },
|
|
2220
|
+
claude: { label: "Claude" },
|
|
2221
|
+
codex: { label: "Codex" },
|
|
2222
|
+
kiro: { label: "Kiro" },
|
|
2223
|
+
cline: { label: "Cline" },
|
|
2224
|
+
amp: { label: "Amp" },
|
|
2225
|
+
aider: { label: "Aider" },
|
|
2226
|
+
"firebase-studio": { label: "Firebase Studio" },
|
|
2227
|
+
"open-hands": { label: "Open Hands" },
|
|
2228
|
+
"gemini-cli": { label: "Gemini CLI" },
|
|
2229
|
+
junie: { label: "Junie" },
|
|
2230
|
+
augmentcode: { label: "AugmentCode" },
|
|
2231
|
+
"kilo-code": { label: "Kilo Code" },
|
|
2232
|
+
goose: { label: "Goose" }
|
|
2233
|
+
};
|
|
2234
|
+
async function setupUltracite(config, hasHusky) {
|
|
2235
|
+
const { packageManager, projectDir } = config;
|
|
2236
|
+
try {
|
|
2237
|
+
log.info("Setting up Ultracite...");
|
|
2238
|
+
await setupBiome(projectDir);
|
|
2239
|
+
const result = await group({
|
|
2240
|
+
editors: () => multiselect({
|
|
2241
|
+
message: "Choose editors",
|
|
2242
|
+
options: Object.entries(EDITORS).map(([key, editor]) => ({
|
|
2243
|
+
value: key,
|
|
2244
|
+
label: editor.label
|
|
2245
|
+
})),
|
|
2246
|
+
required: true
|
|
2247
|
+
}),
|
|
2248
|
+
rules: () => autocompleteMultiselect({
|
|
2249
|
+
message: "Choose rules",
|
|
2250
|
+
options: Object.entries(RULES).map(([key, rule]) => ({
|
|
2251
|
+
value: key,
|
|
2252
|
+
label: rule.label
|
|
2253
|
+
})),
|
|
2254
|
+
required: true
|
|
2255
|
+
})
|
|
2256
|
+
}, { onCancel: () => {
|
|
2257
|
+
exitCancelled("Operation cancelled");
|
|
2258
|
+
} });
|
|
2259
|
+
const editors = result.editors;
|
|
2260
|
+
const rules = result.rules;
|
|
2261
|
+
const ultraciteArgs = [
|
|
2262
|
+
"init",
|
|
2263
|
+
"--pm",
|
|
2264
|
+
packageManager
|
|
2265
|
+
];
|
|
2266
|
+
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
2267
|
+
if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
|
|
2268
|
+
if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
|
|
2269
|
+
const ultraciteArgsString = ultraciteArgs.join(" ");
|
|
2270
|
+
const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
|
|
2271
|
+
const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2272
|
+
await execa(ultraciteInitCommand, {
|
|
2273
|
+
cwd: projectDir,
|
|
2274
|
+
env: { CI: "true" },
|
|
2275
|
+
shell: true
|
|
2276
|
+
});
|
|
2277
|
+
if (hasHusky) await addPackageDependency({
|
|
2278
|
+
devDependencies: ["husky", "lint-staged"],
|
|
2279
|
+
projectDir
|
|
2280
|
+
});
|
|
2281
|
+
log.success("Ultracite setup successfully!");
|
|
2282
|
+
} catch (error) {
|
|
2283
|
+
log.error(pc.red("Failed to set up Ultracite"));
|
|
2284
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2303
2285
|
}
|
|
2304
2286
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
if (!await fs.pathExists(webAppDir)) continue;
|
|
2315
|
-
addonDestDir = webAppDir;
|
|
2316
|
-
if (context.frontend.includes("next")) addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/next");
|
|
2317
|
-
else if (context.frontend.some((f) => [
|
|
2318
|
-
"tanstack-router",
|
|
2319
|
-
"react-router",
|
|
2320
|
-
"solid"
|
|
2321
|
-
].includes(f))) addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/vite");
|
|
2322
|
-
else continue;
|
|
2323
|
-
}
|
|
2324
|
-
if (await fs.pathExists(addonSrcDir)) await processAndCopyFiles("**/*", addonSrcDir, addonDestDir, context);
|
|
2287
|
+
|
|
2288
|
+
//#endregion
|
|
2289
|
+
//#region src/utils/ts-morph.ts
|
|
2290
|
+
const tsProject = new Project({
|
|
2291
|
+
useInMemoryFileSystem: false,
|
|
2292
|
+
skipAddingFilesFromTsConfig: true,
|
|
2293
|
+
manipulationSettings: {
|
|
2294
|
+
quoteKind: QuoteKind.Single,
|
|
2295
|
+
indentationText: IndentationText.TwoSpaces
|
|
2325
2296
|
}
|
|
2297
|
+
});
|
|
2298
|
+
function ensureArrayProperty(obj, name) {
|
|
2299
|
+
return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
|
|
2300
|
+
name,
|
|
2301
|
+
initializer: "[]"
|
|
2302
|
+
}).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
2326
2303
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
"
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
const
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
if (webAppDirExists) {
|
|
2361
|
-
if (hasReactWeb) {
|
|
2362
|
-
const exampleWebSrc = path.join(exampleBaseDir, "web/react");
|
|
2363
|
-
if (await fs.pathExists(exampleWebSrc)) {
|
|
2364
|
-
if (example === "ai") {
|
|
2365
|
-
const exampleWebBaseSrc = path.join(exampleWebSrc, "base");
|
|
2366
|
-
if (await fs.pathExists(exampleWebBaseSrc)) await processAndCopyFiles("**/*", exampleWebBaseSrc, webAppDir, context, false);
|
|
2367
|
-
}
|
|
2368
|
-
const reactFramework = context.frontend.find((f) => [
|
|
2369
|
-
"next",
|
|
2370
|
-
"react-router",
|
|
2371
|
-
"tanstack-router",
|
|
2372
|
-
"tanstack-start"
|
|
2373
|
-
].includes(f));
|
|
2374
|
-
if (reactFramework) {
|
|
2375
|
-
const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
|
|
2376
|
-
if (await fs.pathExists(exampleWebFrameworkSrc)) await processAndCopyFiles("**/*", exampleWebFrameworkSrc, webAppDir, context, false);
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
} else if (hasNuxtWeb) {
|
|
2380
|
-
const exampleWebNuxtSrc = path.join(exampleBaseDir, "web/nuxt");
|
|
2381
|
-
if (await fs.pathExists(exampleWebNuxtSrc)) await processAndCopyFiles("**/*", exampleWebNuxtSrc, webAppDir, context, false);
|
|
2382
|
-
} else if (hasSvelteWeb) {
|
|
2383
|
-
const exampleWebSvelteSrc = path.join(exampleBaseDir, "web/svelte");
|
|
2384
|
-
if (await fs.pathExists(exampleWebSvelteSrc)) await processAndCopyFiles("**/*", exampleWebSvelteSrc, webAppDir, context, false);
|
|
2385
|
-
} else if (hasSolidWeb) {
|
|
2386
|
-
const exampleWebSolidSrc = path.join(exampleBaseDir, "web/solid");
|
|
2387
|
-
if (await fs.pathExists(exampleWebSolidSrc)) await processAndCopyFiles("**/*", exampleWebSolidSrc, webAppDir, context, false);
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
if (nativeAppDirExists) {
|
|
2391
|
-
const hasNativeWind = context.frontend.includes("native-nativewind");
|
|
2392
|
-
const hasUnistyles = context.frontend.includes("native-unistyles");
|
|
2393
|
-
if (hasNativeWind || hasUnistyles) {
|
|
2394
|
-
let nativeFramework = "";
|
|
2395
|
-
if (hasNativeWind) nativeFramework = "nativewind";
|
|
2396
|
-
else if (hasUnistyles) nativeFramework = "unistyles";
|
|
2397
|
-
const exampleNativeSrc = path.join(exampleBaseDir, `native/${nativeFramework}`);
|
|
2398
|
-
if (await fs.pathExists(exampleNativeSrc)) await processAndCopyFiles("**/*", exampleNativeSrc, nativeAppDir, context, false);
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2304
|
+
|
|
2305
|
+
//#endregion
|
|
2306
|
+
//#region src/helpers/addons/vite-pwa-setup.ts
|
|
2307
|
+
async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
2308
|
+
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2309
|
+
if (!sourceFile) throw new Error("vite config not found");
|
|
2310
|
+
const hasImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
|
|
2311
|
+
if (!hasImport) sourceFile.insertImportDeclaration(0, {
|
|
2312
|
+
namedImports: ["VitePWA"],
|
|
2313
|
+
moduleSpecifier: "vite-plugin-pwa"
|
|
2314
|
+
});
|
|
2315
|
+
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
2316
|
+
const expression = expr.getExpression();
|
|
2317
|
+
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
2318
|
+
});
|
|
2319
|
+
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
2320
|
+
const callExpr = defineCall;
|
|
2321
|
+
const configObject = callExpr.getArguments()[0];
|
|
2322
|
+
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
2323
|
+
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
2324
|
+
const alreadyPresent = pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("));
|
|
2325
|
+
if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
|
|
2326
|
+
registerType: "autoUpdate",
|
|
2327
|
+
manifest: {
|
|
2328
|
+
name: "${projectName}",
|
|
2329
|
+
short_name: "${projectName}",
|
|
2330
|
+
description: "${projectName} - PWA Application",
|
|
2331
|
+
theme_color: "#0c0c0c",
|
|
2332
|
+
},
|
|
2333
|
+
pwaAssets: { disabled: false, config: true },
|
|
2334
|
+
devOptions: { enabled: true },
|
|
2335
|
+
})`);
|
|
2336
|
+
await tsProject.save();
|
|
2402
2337
|
}
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
const
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
if (
|
|
2414
|
-
|
|
2415
|
-
|
|
2338
|
+
|
|
2339
|
+
//#endregion
|
|
2340
|
+
//#region src/helpers/addons/addons-setup.ts
|
|
2341
|
+
async function setupAddons(config, isAddCommand = false) {
|
|
2342
|
+
const { addons, frontend, projectDir, packageManager } = config;
|
|
2343
|
+
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
|
|
2344
|
+
const hasNuxtFrontend = frontend.includes("nuxt");
|
|
2345
|
+
const hasSvelteFrontend = frontend.includes("svelte");
|
|
2346
|
+
const hasSolidFrontend = frontend.includes("solid");
|
|
2347
|
+
const hasNextFrontend = frontend.includes("next");
|
|
2348
|
+
if (addons.includes("turborepo")) {
|
|
2349
|
+
await addPackageDependency({
|
|
2350
|
+
devDependencies: ["turbo"],
|
|
2351
|
+
projectDir
|
|
2352
|
+
});
|
|
2353
|
+
if (isAddCommand) log.info(`${pc.yellow("Update your package.json scripts:")}
|
|
2354
|
+
|
|
2355
|
+
${pc.dim("Replace:")} ${pc.yellow("\"pnpm -r dev\"")} ${pc.dim("→")} ${pc.green("\"turbo dev\"")}
|
|
2356
|
+
${pc.dim("Replace:")} ${pc.yellow("\"pnpm --filter web dev\"")} ${pc.dim("→")} ${pc.green("\"turbo -F web dev\"")}
|
|
2357
|
+
|
|
2358
|
+
${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
|
2359
|
+
`);
|
|
2416
2360
|
}
|
|
2417
|
-
if (
|
|
2418
|
-
|
|
2419
|
-
|
|
2361
|
+
if (addons.includes("pwa") && (hasReactWebFrontend || hasSolidFrontend)) await setupPwa(projectDir, frontend);
|
|
2362
|
+
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
|
|
2363
|
+
const hasUltracite = addons.includes("ultracite");
|
|
2364
|
+
const hasBiome = addons.includes("biome");
|
|
2365
|
+
const hasHusky = addons.includes("husky");
|
|
2366
|
+
const hasOxlint = addons.includes("oxlint");
|
|
2367
|
+
if (hasUltracite) await setupUltracite(config, hasHusky);
|
|
2368
|
+
else {
|
|
2369
|
+
if (hasBiome) await setupBiome(projectDir);
|
|
2370
|
+
if (hasHusky) {
|
|
2371
|
+
let linter;
|
|
2372
|
+
if (hasOxlint) linter = "oxlint";
|
|
2373
|
+
else if (hasBiome) linter = "biome";
|
|
2374
|
+
await setupHusky(projectDir, linter);
|
|
2375
|
+
}
|
|
2420
2376
|
}
|
|
2377
|
+
if (addons.includes("oxlint")) await setupOxlint(projectDir, packageManager);
|
|
2378
|
+
if (addons.includes("starlight")) await setupStarlight(config);
|
|
2379
|
+
if (addons.includes("ruler")) await setupRuler(config);
|
|
2380
|
+
if (addons.includes("fumadocs")) await setupFumadocs(config);
|
|
2421
2381
|
}
|
|
2422
|
-
|
|
2423
|
-
if (
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2382
|
+
function getWebAppDir(projectDir, frontends) {
|
|
2383
|
+
if (frontends.some((f) => [
|
|
2384
|
+
"react-router",
|
|
2385
|
+
"tanstack-router",
|
|
2386
|
+
"nuxt",
|
|
2387
|
+
"svelte",
|
|
2388
|
+
"solid"
|
|
2389
|
+
].includes(f))) return path.join(projectDir, "apps/web");
|
|
2390
|
+
return path.join(projectDir, "apps/web");
|
|
2427
2391
|
}
|
|
2428
|
-
async function
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
}
|
|
2442
|
-
if (context.serverDeploy === "alchemy") {
|
|
2443
|
-
const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
|
|
2444
|
-
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2445
|
-
if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(serverAppDir)) {
|
|
2446
|
-
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
2447
|
-
await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
2448
|
-
}
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
if (context.webDeploy !== "none" && context.webDeploy !== "alchemy") {
|
|
2452
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
2453
|
-
if (await fs.pathExists(webAppDir)) {
|
|
2454
|
-
const frontends = context.frontend;
|
|
2455
|
-
const templateMap = {
|
|
2456
|
-
"tanstack-router": "react/tanstack-router",
|
|
2457
|
-
"tanstack-start": "react/tanstack-start",
|
|
2458
|
-
"react-router": "react/react-router",
|
|
2459
|
-
solid: "solid",
|
|
2460
|
-
next: "react/next",
|
|
2461
|
-
nuxt: "nuxt",
|
|
2462
|
-
svelte: "svelte"
|
|
2463
|
-
};
|
|
2464
|
-
for (const f of frontends) if (templateMap[f]) {
|
|
2465
|
-
const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.webDeploy}/web/${templateMap[f]}`);
|
|
2466
|
-
if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, webAppDir, context);
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2392
|
+
async function setupBiome(projectDir) {
|
|
2393
|
+
await addPackageDependency({
|
|
2394
|
+
devDependencies: ["@biomejs/biome"],
|
|
2395
|
+
projectDir
|
|
2396
|
+
});
|
|
2397
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2398
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2399
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2400
|
+
packageJson.scripts = {
|
|
2401
|
+
...packageJson.scripts,
|
|
2402
|
+
check: "biome check --write ."
|
|
2403
|
+
};
|
|
2404
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2469
2405
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2406
|
+
}
|
|
2407
|
+
async function setupHusky(projectDir, linter) {
|
|
2408
|
+
await addPackageDependency({
|
|
2409
|
+
devDependencies: ["husky", "lint-staged"],
|
|
2410
|
+
projectDir
|
|
2411
|
+
});
|
|
2412
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2413
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2414
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2415
|
+
packageJson.scripts = {
|
|
2416
|
+
...packageJson.scripts,
|
|
2417
|
+
prepare: "husky"
|
|
2418
|
+
};
|
|
2419
|
+
if (linter === "oxlint") packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint" };
|
|
2420
|
+
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
2421
|
+
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
2422
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2476
2423
|
}
|
|
2477
2424
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
"
|
|
2498
|
-
codex: { label: "OpenAI Codex CLI" },
|
|
2499
|
-
jules: { label: "Jules" },
|
|
2500
|
-
cline: { label: "Cline" },
|
|
2501
|
-
aider: { label: "Aider" },
|
|
2502
|
-
firebase: { label: "Firebase Studio" },
|
|
2503
|
-
openhands: { label: "Open Hands" },
|
|
2504
|
-
junie: { label: "Junie" },
|
|
2505
|
-
augmentcode: { label: "AugmentCode" },
|
|
2506
|
-
kilocode: { label: "Kilo Code" },
|
|
2507
|
-
opencode: { label: "OpenCode" }
|
|
2425
|
+
async function setupPwa(projectDir, frontends) {
|
|
2426
|
+
const isCompatibleFrontend = frontends.some((f) => [
|
|
2427
|
+
"react-router",
|
|
2428
|
+
"tanstack-router",
|
|
2429
|
+
"solid"
|
|
2430
|
+
].includes(f));
|
|
2431
|
+
if (!isCompatibleFrontend) return;
|
|
2432
|
+
const clientPackageDir = getWebAppDir(projectDir, frontends);
|
|
2433
|
+
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2434
|
+
await addPackageDependency({
|
|
2435
|
+
dependencies: ["vite-plugin-pwa"],
|
|
2436
|
+
devDependencies: ["@vite-pwa/assets-generator"],
|
|
2437
|
+
projectDir: clientPackageDir
|
|
2438
|
+
});
|
|
2439
|
+
const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
|
|
2440
|
+
if (await fs.pathExists(clientPackageJsonPath)) {
|
|
2441
|
+
const packageJson = await fs.readJson(clientPackageJsonPath);
|
|
2442
|
+
packageJson.scripts = {
|
|
2443
|
+
...packageJson.scripts,
|
|
2444
|
+
"generate-pwa-assets": "pwa-assets-generator"
|
|
2508
2445
|
};
|
|
2509
|
-
|
|
2510
|
-
message: "Select AI assistants for Ruler",
|
|
2511
|
-
options: Object.entries(EDITORS$1).map(([key, v]) => ({
|
|
2512
|
-
value: key,
|
|
2513
|
-
label: v.label
|
|
2514
|
-
})),
|
|
2515
|
-
required: false
|
|
2516
|
-
});
|
|
2517
|
-
if (isCancel(selectedEditors)) return exitCancelled("Operation cancelled");
|
|
2518
|
-
if (selectedEditors.length === 0) {
|
|
2519
|
-
log.info("No AI assistants selected. To apply rules later, run:");
|
|
2520
|
-
log.info(pc.cyan(`${getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only")}`));
|
|
2521
|
-
return;
|
|
2522
|
-
}
|
|
2523
|
-
const configFile = path.join(rulerDir, "ruler.toml");
|
|
2524
|
-
const currentConfig = await fs.readFile(configFile, "utf-8");
|
|
2525
|
-
let updatedConfig = currentConfig;
|
|
2526
|
-
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
2527
|
-
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
2528
|
-
await fs.writeFile(configFile, updatedConfig);
|
|
2529
|
-
await addRulerScriptToPackageJson(projectDir, packageManager);
|
|
2530
|
-
const s = spinner();
|
|
2531
|
-
s.start("Applying rules with Ruler...");
|
|
2532
|
-
try {
|
|
2533
|
-
const rulerApplyCmd = getPackageExecutionCommand(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`);
|
|
2534
|
-
await execa(rulerApplyCmd, {
|
|
2535
|
-
cwd: projectDir,
|
|
2536
|
-
env: { CI: "true" },
|
|
2537
|
-
shell: true
|
|
2538
|
-
});
|
|
2539
|
-
s.stop("Applied rules with Ruler");
|
|
2540
|
-
} catch (_error) {
|
|
2541
|
-
s.stop(pc.red("Failed to apply rules"));
|
|
2542
|
-
}
|
|
2543
|
-
} catch (error) {
|
|
2544
|
-
log.error(pc.red("Failed to set up Ruler"));
|
|
2545
|
-
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2446
|
+
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
|
2546
2447
|
}
|
|
2448
|
+
const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
|
|
2449
|
+
if (await fs.pathExists(viteConfigTs)) await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
|
|
2547
2450
|
}
|
|
2548
|
-
async function
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2451
|
+
async function setupOxlint(projectDir, packageManager) {
|
|
2452
|
+
await addPackageDependency({
|
|
2453
|
+
devDependencies: ["oxlint"],
|
|
2454
|
+
projectDir
|
|
2455
|
+
});
|
|
2456
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2457
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2458
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2459
|
+
packageJson.scripts = {
|
|
2460
|
+
...packageJson.scripts,
|
|
2461
|
+
check: "oxlint"
|
|
2462
|
+
};
|
|
2463
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2553
2464
|
}
|
|
2554
|
-
const
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2465
|
+
const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
|
|
2466
|
+
await execa(oxlintInitCommand, {
|
|
2467
|
+
cwd: projectDir,
|
|
2468
|
+
env: { CI: "true" },
|
|
2469
|
+
shell: true
|
|
2470
|
+
});
|
|
2559
2471
|
}
|
|
2560
2472
|
|
|
2561
2473
|
//#endregion
|
|
2562
|
-
//#region src/helpers/
|
|
2563
|
-
async function
|
|
2564
|
-
const { packageManager, projectDir } = config;
|
|
2565
|
-
const s = spinner();
|
|
2474
|
+
//#region src/helpers/core/detect-project-config.ts
|
|
2475
|
+
async function detectProjectConfig(projectDir) {
|
|
2566
2476
|
try {
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2477
|
+
const btsConfig = await readBtsConfig(projectDir);
|
|
2478
|
+
if (btsConfig) return {
|
|
2479
|
+
projectDir,
|
|
2480
|
+
projectName: path.basename(projectDir),
|
|
2481
|
+
database: btsConfig.database,
|
|
2482
|
+
orm: btsConfig.orm,
|
|
2483
|
+
backend: btsConfig.backend,
|
|
2484
|
+
runtime: btsConfig.runtime,
|
|
2485
|
+
frontend: btsConfig.frontend,
|
|
2486
|
+
addons: btsConfig.addons,
|
|
2487
|
+
examples: btsConfig.examples,
|
|
2488
|
+
auth: btsConfig.auth,
|
|
2489
|
+
packageManager: btsConfig.packageManager,
|
|
2490
|
+
dbSetup: btsConfig.dbSetup,
|
|
2491
|
+
api: btsConfig.api,
|
|
2492
|
+
webDeploy: btsConfig.webDeploy,
|
|
2493
|
+
serverDeploy: btsConfig.serverDeploy
|
|
2494
|
+
};
|
|
2495
|
+
return null;
|
|
2496
|
+
} catch (_error) {
|
|
2497
|
+
return null;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
async function isBetterTStackProject(projectDir) {
|
|
2501
|
+
try {
|
|
2502
|
+
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
|
|
2503
|
+
} catch (_error) {
|
|
2504
|
+
return false;
|
|
2590
2505
|
}
|
|
2591
2506
|
}
|
|
2592
2507
|
|
|
2593
2508
|
//#endregion
|
|
2594
|
-
//#region src/helpers/
|
|
2595
|
-
async function
|
|
2596
|
-
const { packageManager, frontend, projectDir } = config;
|
|
2509
|
+
//#region src/helpers/core/install-dependencies.ts
|
|
2510
|
+
async function installDependencies({ projectDir, packageManager }) {
|
|
2597
2511
|
const s = spinner();
|
|
2598
|
-
const clientPackageDir = path.join(projectDir, "apps/web");
|
|
2599
|
-
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2600
2512
|
try {
|
|
2601
|
-
s.start(
|
|
2602
|
-
await
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
})
|
|
2606
|
-
|
|
2607
|
-
if (await fs.pathExists(clientPackageJsonPath)) {
|
|
2608
|
-
const packageJson = await fs.readJson(clientPackageJsonPath);
|
|
2609
|
-
packageJson.scripts = {
|
|
2610
|
-
...packageJson.scripts,
|
|
2611
|
-
tauri: "tauri",
|
|
2612
|
-
"desktop:dev": "tauri dev",
|
|
2613
|
-
"desktop:build": "tauri build"
|
|
2614
|
-
};
|
|
2615
|
-
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
|
2616
|
-
}
|
|
2617
|
-
frontend.includes("tanstack-router");
|
|
2618
|
-
const hasReactRouter = frontend.includes("react-router");
|
|
2619
|
-
const hasNuxt = frontend.includes("nuxt");
|
|
2620
|
-
const hasSvelte = frontend.includes("svelte");
|
|
2621
|
-
frontend.includes("solid");
|
|
2622
|
-
const hasNext = frontend.includes("next");
|
|
2623
|
-
const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
|
|
2624
|
-
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
|
|
2625
|
-
const tauriArgs = [
|
|
2626
|
-
"init",
|
|
2627
|
-
`--app-name=${path.basename(projectDir)}`,
|
|
2628
|
-
`--window-title=${path.basename(projectDir)}`,
|
|
2629
|
-
`--frontend-dist=${frontendDist}`,
|
|
2630
|
-
`--dev-url=${devUrl}`,
|
|
2631
|
-
`--before-dev-command="${packageManager} run dev"`,
|
|
2632
|
-
`--before-build-command="${packageManager} run build"`
|
|
2633
|
-
];
|
|
2634
|
-
const tauriArgsString = tauriArgs.join(" ");
|
|
2635
|
-
const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
|
|
2636
|
-
const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2637
|
-
await execa(tauriInitCommand, {
|
|
2638
|
-
cwd: clientPackageDir,
|
|
2639
|
-
env: { CI: "true" },
|
|
2640
|
-
shell: true
|
|
2641
|
-
});
|
|
2642
|
-
s.stop("Tauri desktop app support configured successfully!");
|
|
2513
|
+
s.start(`Running ${packageManager} install...`);
|
|
2514
|
+
await $({
|
|
2515
|
+
cwd: projectDir,
|
|
2516
|
+
stderr: "inherit"
|
|
2517
|
+
})`${packageManager} install`;
|
|
2518
|
+
s.stop("Dependencies installed successfully");
|
|
2643
2519
|
} catch (error) {
|
|
2644
|
-
s.stop(pc.red("Failed to
|
|
2645
|
-
if (error instanceof Error) consola
|
|
2520
|
+
s.stop(pc.red("Failed to install dependencies"));
|
|
2521
|
+
if (error instanceof Error) consola.error(pc.red(`Installation error: ${error.message}`));
|
|
2646
2522
|
}
|
|
2647
2523
|
}
|
|
2648
2524
|
|
|
2649
2525
|
//#endregion
|
|
2650
|
-
//#region src/
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
windsurf: { label: "Windsurf" },
|
|
2659
|
-
zed: { label: "Zed" },
|
|
2660
|
-
claude: { label: "Claude" },
|
|
2661
|
-
codex: { label: "Codex" },
|
|
2662
|
-
kiro: { label: "Kiro" },
|
|
2663
|
-
cline: { label: "Cline" },
|
|
2664
|
-
amp: { label: "Amp" },
|
|
2665
|
-
aider: { label: "Aider" },
|
|
2666
|
-
"firebase-studio": { label: "Firebase Studio" },
|
|
2667
|
-
"open-hands": { label: "Open Hands" },
|
|
2668
|
-
"gemini-cli": { label: "Gemini CLI" },
|
|
2669
|
-
junie: { label: "Junie" },
|
|
2670
|
-
augmentcode: { label: "AugmentCode" },
|
|
2671
|
-
"kilo-code": { label: "Kilo Code" },
|
|
2672
|
-
goose: { label: "Goose" }
|
|
2673
|
-
};
|
|
2674
|
-
async function setupUltracite(config, hasHusky) {
|
|
2675
|
-
const { packageManager, projectDir } = config;
|
|
2526
|
+
//#region src/utils/template-processor.ts
|
|
2527
|
+
/**
|
|
2528
|
+
* Processes a Handlebars template file and writes the output to the destination.
|
|
2529
|
+
* @param srcPath Path to the source .hbs template file.
|
|
2530
|
+
* @param destPath Path to write the processed file.
|
|
2531
|
+
* @param context Data to be passed to the Handlebars template.
|
|
2532
|
+
*/
|
|
2533
|
+
async function processTemplate(srcPath, destPath, context) {
|
|
2676
2534
|
try {
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
const
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
options: Object.entries(EDITORS).map(([key, editor]) => ({
|
|
2683
|
-
value: key,
|
|
2684
|
-
label: editor.label
|
|
2685
|
-
})),
|
|
2686
|
-
required: true
|
|
2687
|
-
}),
|
|
2688
|
-
rules: () => autocompleteMultiselect({
|
|
2689
|
-
message: "Choose rules",
|
|
2690
|
-
options: Object.entries(RULES).map(([key, rule]) => ({
|
|
2691
|
-
value: key,
|
|
2692
|
-
label: rule.label
|
|
2693
|
-
})),
|
|
2694
|
-
required: true
|
|
2695
|
-
})
|
|
2696
|
-
}, { onCancel: () => {
|
|
2697
|
-
exitCancelled("Operation cancelled");
|
|
2698
|
-
} });
|
|
2699
|
-
const editors = result.editors;
|
|
2700
|
-
const rules = result.rules;
|
|
2701
|
-
const ultraciteArgs = [
|
|
2702
|
-
"init",
|
|
2703
|
-
"--pm",
|
|
2704
|
-
packageManager
|
|
2705
|
-
];
|
|
2706
|
-
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
2707
|
-
if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
|
|
2708
|
-
if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
|
|
2709
|
-
const ultraciteArgsString = ultraciteArgs.join(" ");
|
|
2710
|
-
const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
|
|
2711
|
-
const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2712
|
-
await execa(ultraciteInitCommand, {
|
|
2713
|
-
cwd: projectDir,
|
|
2714
|
-
env: { CI: "true" },
|
|
2715
|
-
shell: true
|
|
2716
|
-
});
|
|
2717
|
-
if (hasHusky) await addPackageDependency({
|
|
2718
|
-
devDependencies: ["husky", "lint-staged"],
|
|
2719
|
-
projectDir
|
|
2720
|
-
});
|
|
2721
|
-
log.success("Ultracite setup successfully!");
|
|
2535
|
+
const templateContent = await fs.readFile(srcPath, "utf-8");
|
|
2536
|
+
const template = handlebars.compile(templateContent);
|
|
2537
|
+
const processedContent = template(context);
|
|
2538
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
2539
|
+
await fs.writeFile(destPath, processedContent);
|
|
2722
2540
|
} catch (error) {
|
|
2723
|
-
|
|
2724
|
-
|
|
2541
|
+
consola.error(`Error processing template ${srcPath}:`, error);
|
|
2542
|
+
throw new Error(`Failed to process template ${srcPath}`);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
handlebars.registerHelper("eq", (a, b) => a === b);
|
|
2546
|
+
handlebars.registerHelper("ne", (a, b) => a !== b);
|
|
2547
|
+
handlebars.registerHelper("and", (a, b) => a && b);
|
|
2548
|
+
handlebars.registerHelper("or", (a, b) => a || b);
|
|
2549
|
+
handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
|
|
2550
|
+
|
|
2551
|
+
//#endregion
|
|
2552
|
+
//#region src/helpers/core/template-manager.ts
|
|
2553
|
+
async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true, ignorePatterns) {
|
|
2554
|
+
const sourceFiles = await glob(sourcePattern, {
|
|
2555
|
+
cwd: baseSourceDir,
|
|
2556
|
+
dot: true,
|
|
2557
|
+
onlyFiles: true,
|
|
2558
|
+
absolute: false,
|
|
2559
|
+
ignore: ignorePatterns
|
|
2560
|
+
});
|
|
2561
|
+
for (const relativeSrcPath of sourceFiles) {
|
|
2562
|
+
const srcPath = path.join(baseSourceDir, relativeSrcPath);
|
|
2563
|
+
let relativeDestPath = relativeSrcPath;
|
|
2564
|
+
if (relativeSrcPath.endsWith(".hbs")) relativeDestPath = relativeSrcPath.slice(0, -4);
|
|
2565
|
+
const basename = path.basename(relativeDestPath);
|
|
2566
|
+
if (basename === "_gitignore") relativeDestPath = path.join(path.dirname(relativeDestPath), ".gitignore");
|
|
2567
|
+
else if (basename === "_npmrc") relativeDestPath = path.join(path.dirname(relativeDestPath), ".npmrc");
|
|
2568
|
+
const destPath = path.join(destDir, relativeDestPath);
|
|
2569
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
2570
|
+
if (!overwrite && await fs.pathExists(destPath)) continue;
|
|
2571
|
+
if (srcPath.endsWith(".hbs")) await processTemplate(srcPath, destPath, context);
|
|
2572
|
+
else await fs.copy(srcPath, destPath, { overwrite: true });
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
async function copyBaseTemplate(projectDir, context) {
|
|
2576
|
+
const templateDir = path.join(PKG_ROOT, "templates/base");
|
|
2577
|
+
await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
|
|
2578
|
+
}
|
|
2579
|
+
async function setupFrontendTemplates(projectDir, context) {
|
|
2580
|
+
const hasReactWeb = context.frontend.some((f) => [
|
|
2581
|
+
"tanstack-router",
|
|
2582
|
+
"react-router",
|
|
2583
|
+
"tanstack-start",
|
|
2584
|
+
"next"
|
|
2585
|
+
].includes(f));
|
|
2586
|
+
const hasNuxtWeb = context.frontend.includes("nuxt");
|
|
2587
|
+
const hasSvelteWeb = context.frontend.includes("svelte");
|
|
2588
|
+
const hasSolidWeb = context.frontend.includes("solid");
|
|
2589
|
+
const hasNativeWind = context.frontend.includes("native-nativewind");
|
|
2590
|
+
const hasUnistyles = context.frontend.includes("native-unistyles");
|
|
2591
|
+
const isConvex = context.backend === "convex";
|
|
2592
|
+
if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) {
|
|
2593
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2594
|
+
await fs.ensureDir(webAppDir);
|
|
2595
|
+
if (hasReactWeb) {
|
|
2596
|
+
const webBaseDir = path.join(PKG_ROOT, "templates/frontend/react/web-base");
|
|
2597
|
+
if (await fs.pathExists(webBaseDir)) await processAndCopyFiles("**/*", webBaseDir, webAppDir, context);
|
|
2598
|
+
const reactFramework = context.frontend.find((f) => [
|
|
2599
|
+
"tanstack-router",
|
|
2600
|
+
"react-router",
|
|
2601
|
+
"tanstack-start",
|
|
2602
|
+
"next"
|
|
2603
|
+
].includes(f));
|
|
2604
|
+
if (reactFramework) {
|
|
2605
|
+
const frameworkSrcDir = path.join(PKG_ROOT, `templates/frontend/react/${reactFramework}`);
|
|
2606
|
+
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, webAppDir, context);
|
|
2607
|
+
if (!isConvex && context.api !== "none") {
|
|
2608
|
+
const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
|
|
2609
|
+
if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
} else if (hasNuxtWeb) {
|
|
2613
|
+
const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
|
|
2614
|
+
if (await fs.pathExists(nuxtBaseDir)) await processAndCopyFiles("**/*", nuxtBaseDir, webAppDir, context);
|
|
2615
|
+
if (!isConvex && context.api === "orpc") {
|
|
2616
|
+
const apiWebNuxtDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/nuxt`);
|
|
2617
|
+
if (await fs.pathExists(apiWebNuxtDir)) await processAndCopyFiles("**/*", apiWebNuxtDir, webAppDir, context);
|
|
2618
|
+
}
|
|
2619
|
+
} else if (hasSvelteWeb) {
|
|
2620
|
+
const svelteBaseDir = path.join(PKG_ROOT, "templates/frontend/svelte");
|
|
2621
|
+
if (await fs.pathExists(svelteBaseDir)) await processAndCopyFiles("**/*", svelteBaseDir, webAppDir, context);
|
|
2622
|
+
if (!isConvex && context.api === "orpc") {
|
|
2623
|
+
const apiWebSvelteDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/svelte`);
|
|
2624
|
+
if (await fs.pathExists(apiWebSvelteDir)) await processAndCopyFiles("**/*", apiWebSvelteDir, webAppDir, context);
|
|
2625
|
+
}
|
|
2626
|
+
} else if (hasSolidWeb) {
|
|
2627
|
+
const solidBaseDir = path.join(PKG_ROOT, "templates/frontend/solid");
|
|
2628
|
+
if (await fs.pathExists(solidBaseDir)) await processAndCopyFiles("**/*", solidBaseDir, webAppDir, context);
|
|
2629
|
+
if (!isConvex && context.api === "orpc") {
|
|
2630
|
+
const apiWebSolidDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/solid`);
|
|
2631
|
+
if (await fs.pathExists(apiWebSolidDir)) await processAndCopyFiles("**/*", apiWebSolidDir, webAppDir, context);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
if (hasNativeWind || hasUnistyles) {
|
|
2636
|
+
const nativeAppDir = path.join(projectDir, "apps/native");
|
|
2637
|
+
await fs.ensureDir(nativeAppDir);
|
|
2638
|
+
const nativeBaseCommonDir = path.join(PKG_ROOT, "templates/frontend/native/native-base");
|
|
2639
|
+
if (await fs.pathExists(nativeBaseCommonDir)) await processAndCopyFiles("**/*", nativeBaseCommonDir, nativeAppDir, context);
|
|
2640
|
+
let nativeFrameworkPath = "";
|
|
2641
|
+
if (hasNativeWind) nativeFrameworkPath = "nativewind";
|
|
2642
|
+
else if (hasUnistyles) nativeFrameworkPath = "unistyles";
|
|
2643
|
+
const nativeSpecificDir = path.join(PKG_ROOT, `templates/frontend/native/${nativeFrameworkPath}`);
|
|
2644
|
+
if (await fs.pathExists(nativeSpecificDir)) await processAndCopyFiles("**/*", nativeSpecificDir, nativeAppDir, context, true);
|
|
2645
|
+
if (!isConvex && (context.api === "trpc" || context.api === "orpc")) {
|
|
2646
|
+
const apiNativeSrcDir = path.join(PKG_ROOT, `templates/api/${context.api}/native`);
|
|
2647
|
+
if (await fs.pathExists(apiNativeSrcDir)) await processAndCopyFiles("**/*", apiNativeSrcDir, nativeAppDir, context);
|
|
2648
|
+
}
|
|
2725
2649
|
}
|
|
2726
2650
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2651
|
+
async function setupBackendFramework(projectDir, context) {
|
|
2652
|
+
if (context.backend === "none") return;
|
|
2653
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2654
|
+
if (context.backend === "convex") {
|
|
2655
|
+
if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
|
|
2656
|
+
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2657
|
+
const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
|
|
2658
|
+
await fs.ensureDir(convexBackendDestDir);
|
|
2659
|
+
if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
await fs.ensureDir(serverAppDir);
|
|
2663
|
+
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/server-base");
|
|
2664
|
+
if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
|
|
2665
|
+
const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
|
|
2666
|
+
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
|
|
2667
|
+
if (context.api !== "none") {
|
|
2668
|
+
const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
|
|
2669
|
+
if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, serverAppDir, context, true);
|
|
2670
|
+
const apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
|
|
2671
|
+
if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, serverAppDir, context, true);
|
|
2736
2672
|
}
|
|
2737
|
-
});
|
|
2738
|
-
function ensureArrayProperty(obj, name) {
|
|
2739
|
-
return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
|
|
2740
|
-
name,
|
|
2741
|
-
initializer: "[]"
|
|
2742
|
-
}).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
2743
2673
|
}
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
const
|
|
2749
|
-
if (
|
|
2750
|
-
const hasImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
|
|
2751
|
-
if (!hasImport) sourceFile.insertImportDeclaration(0, {
|
|
2752
|
-
namedImports: ["VitePWA"],
|
|
2753
|
-
moduleSpecifier: "vite-plugin-pwa"
|
|
2754
|
-
});
|
|
2755
|
-
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
2756
|
-
const expression = expr.getExpression();
|
|
2757
|
-
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
2758
|
-
});
|
|
2759
|
-
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
2760
|
-
const callExpr = defineCall;
|
|
2761
|
-
const configObject = callExpr.getArguments()[0];
|
|
2762
|
-
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
2763
|
-
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
2764
|
-
const alreadyPresent = pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("));
|
|
2765
|
-
if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
|
|
2766
|
-
registerType: "autoUpdate",
|
|
2767
|
-
manifest: {
|
|
2768
|
-
name: "${projectName}",
|
|
2769
|
-
short_name: "${projectName}",
|
|
2770
|
-
description: "${projectName} - PWA Application",
|
|
2771
|
-
theme_color: "#0c0c0c",
|
|
2772
|
-
},
|
|
2773
|
-
pwaAssets: { disabled: false, config: true },
|
|
2774
|
-
devOptions: { enabled: true },
|
|
2775
|
-
})`);
|
|
2776
|
-
await tsProject.save();
|
|
2674
|
+
async function setupDbOrmTemplates(projectDir, context) {
|
|
2675
|
+
if (context.backend === "convex" || context.orm === "none" || context.database === "none") return;
|
|
2676
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2677
|
+
await fs.ensureDir(serverAppDir);
|
|
2678
|
+
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2679
|
+
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, serverAppDir, context);
|
|
2777
2680
|
}
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
const
|
|
2783
|
-
const
|
|
2784
|
-
const
|
|
2785
|
-
const
|
|
2786
|
-
const
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2681
|
+
async function setupAuthTemplate(projectDir, context) {
|
|
2682
|
+
if (!context.auth || context.auth === "none") return;
|
|
2683
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2684
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2685
|
+
const nativeAppDir = path.join(projectDir, "apps/native");
|
|
2686
|
+
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2687
|
+
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2688
|
+
const nativeAppDirExists = await fs.pathExists(nativeAppDir);
|
|
2689
|
+
const hasReactWeb = context.frontend.some((f) => [
|
|
2690
|
+
"tanstack-router",
|
|
2691
|
+
"react-router",
|
|
2692
|
+
"tanstack-start",
|
|
2693
|
+
"next"
|
|
2694
|
+
].includes(f));
|
|
2695
|
+
const hasNuxtWeb = context.frontend.includes("nuxt");
|
|
2696
|
+
const hasSvelteWeb = context.frontend.includes("svelte");
|
|
2697
|
+
const hasSolidWeb = context.frontend.includes("solid");
|
|
2698
|
+
const hasNativeWind = context.frontend.includes("native-nativewind");
|
|
2699
|
+
const hasUnistyles = context.frontend.includes("native-unistyles");
|
|
2700
|
+
const hasNative = hasNativeWind || hasUnistyles;
|
|
2701
|
+
const authProvider = context.auth;
|
|
2702
|
+
if (context.backend === "convex" && authProvider === "clerk") {
|
|
2703
|
+
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2704
|
+
const convexClerkBackendSrc = path.join(PKG_ROOT, "templates/auth/clerk/convex/backend");
|
|
2705
|
+
if (await fs.pathExists(convexClerkBackendSrc)) {
|
|
2706
|
+
await fs.ensureDir(convexBackendDestDir);
|
|
2707
|
+
await processAndCopyFiles("**/*", convexClerkBackendSrc, convexBackendDestDir, context);
|
|
2708
|
+
}
|
|
2709
|
+
if (webAppDirExists) {
|
|
2710
|
+
const reactFramework = context.frontend.find((f) => [
|
|
2711
|
+
"tanstack-router",
|
|
2712
|
+
"react-router",
|
|
2713
|
+
"tanstack-start",
|
|
2714
|
+
"next"
|
|
2715
|
+
].includes(f));
|
|
2716
|
+
if (reactFramework) {
|
|
2717
|
+
const convexClerkWebSrc = path.join(PKG_ROOT, `templates/auth/clerk/convex/web/react/${reactFramework}`);
|
|
2718
|
+
if (await fs.pathExists(convexClerkWebSrc)) await processAndCopyFiles("**/*", convexClerkWebSrc, webAppDir, context);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
if (nativeAppDirExists) {
|
|
2722
|
+
const convexClerkNativeBaseSrc = path.join(PKG_ROOT, "templates/auth/clerk/convex/native/base");
|
|
2723
|
+
if (await fs.pathExists(convexClerkNativeBaseSrc)) await processAndCopyFiles("**/*", convexClerkNativeBaseSrc, nativeAppDir, context);
|
|
2724
|
+
const hasNativeWind$1 = context.frontend.includes("native-nativewind");
|
|
2725
|
+
const hasUnistyles$1 = context.frontend.includes("native-unistyles");
|
|
2726
|
+
let nativeFrameworkPath = "";
|
|
2727
|
+
if (hasNativeWind$1) nativeFrameworkPath = "nativewind";
|
|
2728
|
+
else if (hasUnistyles$1) nativeFrameworkPath = "unistyles";
|
|
2729
|
+
if (nativeFrameworkPath) {
|
|
2730
|
+
const convexClerkNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/clerk/convex/native/${nativeFrameworkPath}`);
|
|
2731
|
+
if (await fs.pathExists(convexClerkNativeFrameworkSrc)) await processAndCopyFiles("**/*", convexClerkNativeFrameworkSrc, nativeAppDir, context);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
return;
|
|
2800
2735
|
}
|
|
2801
|
-
if (
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
let
|
|
2812
|
-
if (
|
|
2813
|
-
else if (
|
|
2814
|
-
|
|
2736
|
+
if (serverAppDirExists && context.backend !== "convex") {
|
|
2737
|
+
const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
|
|
2738
|
+
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
|
|
2739
|
+
if (context.backend === "next") {
|
|
2740
|
+
const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
|
|
2741
|
+
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, serverAppDir, context);
|
|
2742
|
+
}
|
|
2743
|
+
if (context.orm !== "none" && context.database !== "none") {
|
|
2744
|
+
const orm = context.orm;
|
|
2745
|
+
const db = context.database;
|
|
2746
|
+
let authDbSrc = "";
|
|
2747
|
+
if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
|
|
2748
|
+
else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
|
|
2749
|
+
else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/mongoose/${db}`);
|
|
2750
|
+
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context);
|
|
2815
2751
|
}
|
|
2816
2752
|
}
|
|
2817
|
-
if (
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
...packageJson.scripts,
|
|
2842
|
-
check: "biome check --write ."
|
|
2843
|
-
};
|
|
2844
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2753
|
+
if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
|
|
2754
|
+
if (hasReactWeb) {
|
|
2755
|
+
const authWebBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/base`);
|
|
2756
|
+
if (await fs.pathExists(authWebBaseSrc)) await processAndCopyFiles("**/*", authWebBaseSrc, webAppDir, context);
|
|
2757
|
+
const reactFramework = context.frontend.find((f) => [
|
|
2758
|
+
"tanstack-router",
|
|
2759
|
+
"react-router",
|
|
2760
|
+
"tanstack-start",
|
|
2761
|
+
"next"
|
|
2762
|
+
].includes(f));
|
|
2763
|
+
if (reactFramework) {
|
|
2764
|
+
const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/${reactFramework}`);
|
|
2765
|
+
if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
|
|
2766
|
+
}
|
|
2767
|
+
} else if (hasNuxtWeb) {
|
|
2768
|
+
const authWebNuxtSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/nuxt`);
|
|
2769
|
+
if (await fs.pathExists(authWebNuxtSrc)) await processAndCopyFiles("**/*", authWebNuxtSrc, webAppDir, context);
|
|
2770
|
+
} else if (hasSvelteWeb) {
|
|
2771
|
+
const authWebSvelteSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/svelte`);
|
|
2772
|
+
if (await fs.pathExists(authWebSvelteSrc)) await processAndCopyFiles("**/*", authWebSvelteSrc, webAppDir, context);
|
|
2773
|
+
} else if (hasSolidWeb) {
|
|
2774
|
+
const authWebSolidSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/solid`);
|
|
2775
|
+
if (await fs.pathExists(authWebSolidSrc)) await processAndCopyFiles("**/*", authWebSolidSrc, webAppDir, context);
|
|
2776
|
+
}
|
|
2845
2777
|
}
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
...packageJson.scripts,
|
|
2857
|
-
prepare: "husky"
|
|
2858
|
-
};
|
|
2859
|
-
if (linter === "oxlint") packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint" };
|
|
2860
|
-
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
2861
|
-
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
2862
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2778
|
+
if (hasNative && nativeAppDirExists) {
|
|
2779
|
+
const authNativeBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/native/native-base`);
|
|
2780
|
+
if (await fs.pathExists(authNativeBaseSrc)) await processAndCopyFiles("**/*", authNativeBaseSrc, nativeAppDir, context);
|
|
2781
|
+
let nativeFrameworkAuthPath = "";
|
|
2782
|
+
if (hasNativeWind) nativeFrameworkAuthPath = "nativewind";
|
|
2783
|
+
else if (hasUnistyles) nativeFrameworkAuthPath = "unistyles";
|
|
2784
|
+
if (nativeFrameworkAuthPath) {
|
|
2785
|
+
const authNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/native/${nativeFrameworkAuthPath}`);
|
|
2786
|
+
if (await fs.pathExists(authNativeFrameworkSrc)) await processAndCopyFiles("**/*", authNativeFrameworkSrc, nativeAppDir, context);
|
|
2787
|
+
}
|
|
2863
2788
|
}
|
|
2864
2789
|
}
|
|
2865
|
-
async function
|
|
2866
|
-
|
|
2867
|
-
|
|
2790
|
+
async function setupAddonsTemplate(projectDir, context) {
|
|
2791
|
+
if (!context.addons || context.addons.length === 0) return;
|
|
2792
|
+
for (const addon of context.addons) {
|
|
2793
|
+
if (addon === "none") continue;
|
|
2794
|
+
let addonSrcDir = path.join(PKG_ROOT, `templates/addons/${addon}`);
|
|
2795
|
+
let addonDestDir = projectDir;
|
|
2796
|
+
if (addon === "pwa") {
|
|
2797
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2798
|
+
if (!await fs.pathExists(webAppDir)) continue;
|
|
2799
|
+
addonDestDir = webAppDir;
|
|
2800
|
+
if (context.frontend.includes("next")) addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/next");
|
|
2801
|
+
else if (context.frontend.some((f) => [
|
|
2802
|
+
"tanstack-router",
|
|
2803
|
+
"react-router",
|
|
2804
|
+
"solid"
|
|
2805
|
+
].includes(f))) addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/vite");
|
|
2806
|
+
else continue;
|
|
2807
|
+
}
|
|
2808
|
+
if (await fs.pathExists(addonSrcDir)) await processAndCopyFiles("**/*", addonSrcDir, addonDestDir, context);
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
async function setupExamplesTemplate(projectDir, context) {
|
|
2812
|
+
if (!context.examples || context.examples.length === 0 || context.examples[0] === "none") return;
|
|
2813
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2814
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2815
|
+
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2816
|
+
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2817
|
+
const nativeAppDir = path.join(projectDir, "apps/native");
|
|
2818
|
+
const nativeAppDirExists = await fs.pathExists(nativeAppDir);
|
|
2819
|
+
const hasReactWeb = context.frontend.some((f) => [
|
|
2868
2820
|
"tanstack-router",
|
|
2869
|
-
"
|
|
2821
|
+
"react-router",
|
|
2822
|
+
"tanstack-start",
|
|
2823
|
+
"next"
|
|
2870
2824
|
].includes(f));
|
|
2871
|
-
|
|
2872
|
-
const
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2825
|
+
const hasNuxtWeb = context.frontend.includes("nuxt");
|
|
2826
|
+
const hasSvelteWeb = context.frontend.includes("svelte");
|
|
2827
|
+
const hasSolidWeb = context.frontend.includes("solid");
|
|
2828
|
+
for (const example of context.examples) {
|
|
2829
|
+
if (example === "none") continue;
|
|
2830
|
+
const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
|
|
2831
|
+
if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
|
|
2832
|
+
const exampleServerSrc = path.join(exampleBaseDir, "server");
|
|
2833
|
+
if (example === "ai" && context.backend === "next") {
|
|
2834
|
+
const aiNextServerSrc = path.join(exampleServerSrc, "next");
|
|
2835
|
+
if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
|
|
2836
|
+
}
|
|
2837
|
+
if (context.orm !== "none" && context.database !== "none") {
|
|
2838
|
+
const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
|
|
2839
|
+
if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, serverAppDir, context, false);
|
|
2840
|
+
const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
|
|
2841
|
+
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, serverAppDir, context, false);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
if (webAppDirExists) {
|
|
2845
|
+
if (hasReactWeb) {
|
|
2846
|
+
const exampleWebSrc = path.join(exampleBaseDir, "web/react");
|
|
2847
|
+
if (await fs.pathExists(exampleWebSrc)) {
|
|
2848
|
+
if (example === "ai") {
|
|
2849
|
+
const exampleWebBaseSrc = path.join(exampleWebSrc, "base");
|
|
2850
|
+
if (await fs.pathExists(exampleWebBaseSrc)) await processAndCopyFiles("**/*", exampleWebBaseSrc, webAppDir, context, false);
|
|
2851
|
+
}
|
|
2852
|
+
const reactFramework = context.frontend.find((f) => [
|
|
2853
|
+
"next",
|
|
2854
|
+
"react-router",
|
|
2855
|
+
"tanstack-router",
|
|
2856
|
+
"tanstack-start"
|
|
2857
|
+
].includes(f));
|
|
2858
|
+
if (reactFramework) {
|
|
2859
|
+
const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
|
|
2860
|
+
if (await fs.pathExists(exampleWebFrameworkSrc)) await processAndCopyFiles("**/*", exampleWebFrameworkSrc, webAppDir, context, false);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
} else if (hasNuxtWeb) {
|
|
2864
|
+
const exampleWebNuxtSrc = path.join(exampleBaseDir, "web/nuxt");
|
|
2865
|
+
if (await fs.pathExists(exampleWebNuxtSrc)) await processAndCopyFiles("**/*", exampleWebNuxtSrc, webAppDir, context, false);
|
|
2866
|
+
} else if (hasSvelteWeb) {
|
|
2867
|
+
const exampleWebSvelteSrc = path.join(exampleBaseDir, "web/svelte");
|
|
2868
|
+
if (await fs.pathExists(exampleWebSvelteSrc)) await processAndCopyFiles("**/*", exampleWebSvelteSrc, webAppDir, context, false);
|
|
2869
|
+
} else if (hasSolidWeb) {
|
|
2870
|
+
const exampleWebSolidSrc = path.join(exampleBaseDir, "web/solid");
|
|
2871
|
+
if (await fs.pathExists(exampleWebSolidSrc)) await processAndCopyFiles("**/*", exampleWebSolidSrc, webAppDir, context, false);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
if (nativeAppDirExists) {
|
|
2875
|
+
const hasNativeWind = context.frontend.includes("native-nativewind");
|
|
2876
|
+
const hasUnistyles = context.frontend.includes("native-unistyles");
|
|
2877
|
+
if (hasNativeWind || hasUnistyles) {
|
|
2878
|
+
let nativeFramework = "";
|
|
2879
|
+
if (hasNativeWind) nativeFramework = "nativewind";
|
|
2880
|
+
else if (hasUnistyles) nativeFramework = "unistyles";
|
|
2881
|
+
const exampleNativeSrc = path.join(exampleBaseDir, `native/${nativeFramework}`);
|
|
2882
|
+
if (await fs.pathExists(exampleNativeSrc)) await processAndCopyFiles("**/*", exampleNativeSrc, nativeAppDir, context, false);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2887
2885
|
}
|
|
2888
|
-
const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
|
|
2889
|
-
if (await fs.pathExists(viteConfigTs)) await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
|
|
2890
2886
|
}
|
|
2891
|
-
async function
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
const
|
|
2899
|
-
|
|
2900
|
-
...packageJson.scripts,
|
|
2901
|
-
check: "oxlint"
|
|
2902
|
-
};
|
|
2903
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2887
|
+
async function handleExtras(projectDir, context) {
|
|
2888
|
+
const extrasDir = path.join(PKG_ROOT, "templates/extras");
|
|
2889
|
+
const hasNativeWind = context.frontend.includes("native-nativewind");
|
|
2890
|
+
const hasUnistyles = context.frontend.includes("native-unistyles");
|
|
2891
|
+
const hasNative = hasNativeWind || hasUnistyles;
|
|
2892
|
+
if (context.packageManager === "pnpm") {
|
|
2893
|
+
const pnpmWorkspaceSrc = path.join(extrasDir, "pnpm-workspace.yaml");
|
|
2894
|
+
const pnpmWorkspaceDest = path.join(projectDir, "pnpm-workspace.yaml");
|
|
2895
|
+
if (await fs.pathExists(pnpmWorkspaceSrc)) await fs.copy(pnpmWorkspaceSrc, pnpmWorkspaceDest);
|
|
2904
2896
|
}
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
env: { CI: "true" },
|
|
2909
|
-
shell: true
|
|
2910
|
-
});
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
|
-
//#endregion
|
|
2914
|
-
//#region src/helpers/core/detect-project-config.ts
|
|
2915
|
-
async function detectProjectConfig(projectDir) {
|
|
2916
|
-
try {
|
|
2917
|
-
const btsConfig = await readBtsConfig(projectDir);
|
|
2918
|
-
if (btsConfig) return {
|
|
2919
|
-
projectDir,
|
|
2920
|
-
projectName: path.basename(projectDir),
|
|
2921
|
-
database: btsConfig.database,
|
|
2922
|
-
orm: btsConfig.orm,
|
|
2923
|
-
backend: btsConfig.backend,
|
|
2924
|
-
runtime: btsConfig.runtime,
|
|
2925
|
-
frontend: btsConfig.frontend,
|
|
2926
|
-
addons: btsConfig.addons,
|
|
2927
|
-
examples: btsConfig.examples,
|
|
2928
|
-
auth: btsConfig.auth,
|
|
2929
|
-
packageManager: btsConfig.packageManager,
|
|
2930
|
-
dbSetup: btsConfig.dbSetup,
|
|
2931
|
-
api: btsConfig.api,
|
|
2932
|
-
webDeploy: btsConfig.webDeploy,
|
|
2933
|
-
serverDeploy: btsConfig.serverDeploy
|
|
2934
|
-
};
|
|
2935
|
-
return null;
|
|
2936
|
-
} catch (_error) {
|
|
2937
|
-
return null;
|
|
2897
|
+
if (context.packageManager === "bun") {
|
|
2898
|
+
const bunfigSrc = path.join(extrasDir, "bunfig.toml.hbs");
|
|
2899
|
+
if (await fs.pathExists(bunfigSrc)) await processAndCopyFiles("bunfig.toml.hbs", extrasDir, projectDir, context);
|
|
2938
2900
|
}
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
|
|
2943
|
-
} catch (_error) {
|
|
2944
|
-
return false;
|
|
2901
|
+
if (context.packageManager === "pnpm" && (hasNative || context.frontend.includes("nuxt"))) {
|
|
2902
|
+
const npmrcTemplateSrc = path.join(extrasDir, "_npmrc.hbs");
|
|
2903
|
+
if (await fs.pathExists(npmrcTemplateSrc)) await processAndCopyFiles("_npmrc.hbs", extrasDir, projectDir, context);
|
|
2945
2904
|
}
|
|
2946
2905
|
}
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2906
|
+
async function setupDockerComposeTemplates(projectDir, context) {
|
|
2907
|
+
if (context.dbSetup !== "docker" || context.database === "none") return;
|
|
2908
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2909
|
+
const dockerSrcDir = path.join(PKG_ROOT, `templates/db-setup/docker-compose/${context.database}`);
|
|
2910
|
+
if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, serverAppDir, context);
|
|
2911
|
+
}
|
|
2912
|
+
async function setupDeploymentTemplates(projectDir, context) {
|
|
2913
|
+
if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") if (context.webDeploy === "alchemy" && context.serverDeploy === "alchemy") {
|
|
2914
|
+
const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
|
|
2915
|
+
if (await fs.pathExists(alchemyTemplateSrc)) {
|
|
2916
|
+
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, projectDir, context);
|
|
2917
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2918
|
+
if (await fs.pathExists(serverAppDir)) await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
2919
|
+
}
|
|
2920
|
+
} else {
|
|
2921
|
+
if (context.webDeploy === "alchemy") {
|
|
2922
|
+
const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
|
|
2923
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2924
|
+
if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(webAppDir)) await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, webAppDir, context);
|
|
2925
|
+
}
|
|
2926
|
+
if (context.serverDeploy === "alchemy") {
|
|
2927
|
+
const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
|
|
2928
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2929
|
+
if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(serverAppDir)) {
|
|
2930
|
+
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
2931
|
+
await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
if (context.webDeploy !== "none" && context.webDeploy !== "alchemy") {
|
|
2936
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2937
|
+
if (await fs.pathExists(webAppDir)) {
|
|
2938
|
+
const frontends = context.frontend;
|
|
2939
|
+
const templateMap = {
|
|
2940
|
+
"tanstack-router": "react/tanstack-router",
|
|
2941
|
+
"tanstack-start": "react/tanstack-start",
|
|
2942
|
+
"react-router": "react/react-router",
|
|
2943
|
+
solid: "solid",
|
|
2944
|
+
next: "react/next",
|
|
2945
|
+
nuxt: "nuxt",
|
|
2946
|
+
svelte: "svelte"
|
|
2947
|
+
};
|
|
2948
|
+
for (const f of frontends) if (templateMap[f]) {
|
|
2949
|
+
const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.webDeploy}/web/${templateMap[f]}`);
|
|
2950
|
+
if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, webAppDir, context);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
if (context.serverDeploy !== "none" && context.serverDeploy !== "alchemy") {
|
|
2955
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2956
|
+
if (await fs.pathExists(serverAppDir)) {
|
|
2957
|
+
const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.serverDeploy}/server`);
|
|
2958
|
+
if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, serverAppDir, context);
|
|
2959
|
+
}
|
|
2962
2960
|
}
|
|
2963
2961
|
}
|
|
2964
2962
|
|