create-better-t-stack 2.32.1 → 2.33.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.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-DJffJtr1.js → src-DdLzqWNh.js} +484 -508
- package/package.json +1 -1
- package/templates/addons/vibe-rules/.ruler/mcp.json.hbs +18 -0
- package/templates/addons/vibe-rules/.ruler/ruler.toml.hbs +5 -0
- /package/templates/addons/vibe-rules/{.bts/rules.md.hbs → .ruler/bts.md.hbs} +0 -0
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -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 { globby } from "globby";
|
|
13
14
|
import handlebars from "handlebars";
|
|
14
15
|
import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
|
|
15
|
-
import { globby } from "globby";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
|
|
18
18
|
//#region src/utils/get-package-manager.ts
|
|
@@ -1942,373 +1942,143 @@ handlebars.registerHelper("or", (a, b) => a || b);
|
|
|
1942
1942
|
handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
|
|
1943
1943
|
|
|
1944
1944
|
//#endregion
|
|
1945
|
-
//#region src/helpers/
|
|
1946
|
-
async function
|
|
1947
|
-
const
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1945
|
+
//#region src/helpers/project-generation/template-manager.ts
|
|
1946
|
+
async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true, ignorePatterns) {
|
|
1947
|
+
const sourceFiles = await globby(sourcePattern, {
|
|
1948
|
+
cwd: baseSourceDir,
|
|
1949
|
+
dot: true,
|
|
1950
|
+
onlyFiles: true,
|
|
1951
|
+
absolute: false,
|
|
1952
|
+
ignore: ignorePatterns
|
|
1953
|
+
});
|
|
1954
|
+
for (const relativeSrcPath of sourceFiles) {
|
|
1955
|
+
const srcPath = path.join(baseSourceDir, relativeSrcPath);
|
|
1956
|
+
let relativeDestPath = relativeSrcPath;
|
|
1957
|
+
if (relativeSrcPath.endsWith(".hbs")) relativeDestPath = relativeSrcPath.slice(0, -4);
|
|
1958
|
+
const basename = path.basename(relativeDestPath);
|
|
1959
|
+
if (basename === "_gitignore") relativeDestPath = path.join(path.dirname(relativeDestPath), ".gitignore");
|
|
1960
|
+
else if (basename === "_npmrc") relativeDestPath = path.join(path.dirname(relativeDestPath), ".npmrc");
|
|
1961
|
+
const destPath = path.join(destDir, relativeDestPath);
|
|
1962
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
1963
|
+
if (!overwrite && await fs.pathExists(destPath)) continue;
|
|
1964
|
+
if (srcPath.endsWith(".hbs")) await processTemplate(srcPath, destPath, context);
|
|
1965
|
+
else await fs.copy(srcPath, destPath, { overwrite: true });
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
async function copyBaseTemplate(projectDir, context) {
|
|
1969
|
+
const templateDir = path.join(PKG_ROOT, "templates/base");
|
|
1970
|
+
await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
|
|
1971
|
+
}
|
|
1972
|
+
async function setupFrontendTemplates(projectDir, context) {
|
|
1973
|
+
const hasReactWeb = context.frontend.some((f) => [
|
|
1974
|
+
"tanstack-router",
|
|
1975
|
+
"react-router",
|
|
1976
|
+
"tanstack-start",
|
|
1977
|
+
"next"
|
|
1978
|
+
].includes(f));
|
|
1979
|
+
const hasNuxtWeb = context.frontend.includes("nuxt");
|
|
1980
|
+
const hasSvelteWeb = context.frontend.includes("svelte");
|
|
1981
|
+
const hasSolidWeb = context.frontend.includes("solid");
|
|
1982
|
+
const hasNativeWind = context.frontend.includes("native-nativewind");
|
|
1983
|
+
const hasUnistyles = context.frontend.includes("native-unistyles");
|
|
1984
|
+
const isConvex = context.backend === "convex";
|
|
1985
|
+
if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) {
|
|
1986
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
1987
|
+
await fs.ensureDir(webAppDir);
|
|
1988
|
+
if (hasReactWeb) {
|
|
1989
|
+
const webBaseDir = path.join(PKG_ROOT, "templates/frontend/react/web-base");
|
|
1990
|
+
if (await fs.pathExists(webBaseDir)) await processAndCopyFiles("**/*", webBaseDir, webAppDir, context);
|
|
1991
|
+
const reactFramework = context.frontend.find((f) => [
|
|
1992
|
+
"tanstack-router",
|
|
1993
|
+
"react-router",
|
|
1994
|
+
"tanstack-start",
|
|
1995
|
+
"next"
|
|
1996
|
+
].includes(f));
|
|
1997
|
+
if (reactFramework) {
|
|
1998
|
+
const frameworkSrcDir = path.join(PKG_ROOT, `templates/frontend/react/${reactFramework}`);
|
|
1999
|
+
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, webAppDir, context);
|
|
2000
|
+
if (!isConvex && context.api !== "none") {
|
|
2001
|
+
const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
|
|
2002
|
+
if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
|
|
2003
|
+
}
|
|
1960
2004
|
}
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
windsurf: {
|
|
1968
|
-
label: "Windsurf",
|
|
1969
|
-
hint: ".windsurfrules"
|
|
1970
|
-
},
|
|
1971
|
-
"claude-code": {
|
|
1972
|
-
label: "Claude Code",
|
|
1973
|
-
hint: "CLAUDE.md"
|
|
1974
|
-
},
|
|
1975
|
-
vscode: {
|
|
1976
|
-
label: "VSCode",
|
|
1977
|
-
hint: ".github/instructions/*.instructions.md"
|
|
1978
|
-
},
|
|
1979
|
-
gemini: {
|
|
1980
|
-
label: "Gemini",
|
|
1981
|
-
hint: "GEMINI.md"
|
|
1982
|
-
},
|
|
1983
|
-
codex: {
|
|
1984
|
-
label: "Codex",
|
|
1985
|
-
hint: "AGENTS.md"
|
|
1986
|
-
},
|
|
1987
|
-
clinerules: {
|
|
1988
|
-
label: "Cline/Roo",
|
|
1989
|
-
hint: ".clinerules/*.md"
|
|
1990
|
-
},
|
|
1991
|
-
roo: {
|
|
1992
|
-
label: "Roo",
|
|
1993
|
-
hint: ".clinerules/*.md"
|
|
1994
|
-
},
|
|
1995
|
-
zed: {
|
|
1996
|
-
label: "Zed",
|
|
1997
|
-
hint: ".rules/*.md"
|
|
1998
|
-
},
|
|
1999
|
-
unified: {
|
|
2000
|
-
label: "Unified",
|
|
2001
|
-
hint: ".rules/*.md"
|
|
2005
|
+
} else if (hasNuxtWeb) {
|
|
2006
|
+
const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
|
|
2007
|
+
if (await fs.pathExists(nuxtBaseDir)) await processAndCopyFiles("**/*", nuxtBaseDir, webAppDir, context);
|
|
2008
|
+
if (!isConvex && context.api === "orpc") {
|
|
2009
|
+
const apiWebNuxtDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/nuxt`);
|
|
2010
|
+
if (await fs.pathExists(apiWebNuxtDir)) await processAndCopyFiles("**/*", apiWebNuxtDir, webAppDir, context);
|
|
2002
2011
|
}
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
s.start("Saving and applying BTS rules...");
|
|
2017
|
-
try {
|
|
2018
|
-
const saveCmd = getPackageExecutionCommand(packageManager, `vibe-rules@latest save bts -f ${JSON.stringify(path.relative(projectDir, ruleFile))}`);
|
|
2019
|
-
await execa(saveCmd, {
|
|
2020
|
-
cwd: projectDir,
|
|
2021
|
-
env: { CI: "true" },
|
|
2022
|
-
shell: true
|
|
2023
|
-
});
|
|
2024
|
-
for (const editor of selectedEditors) {
|
|
2025
|
-
const loadCmd = getPackageExecutionCommand(packageManager, `vibe-rules@latest load bts ${editor}`);
|
|
2026
|
-
await execa(loadCmd, {
|
|
2027
|
-
cwd: projectDir,
|
|
2028
|
-
env: { CI: "true" },
|
|
2029
|
-
shell: true
|
|
2030
|
-
});
|
|
2012
|
+
} else if (hasSvelteWeb) {
|
|
2013
|
+
const svelteBaseDir = path.join(PKG_ROOT, "templates/frontend/svelte");
|
|
2014
|
+
if (await fs.pathExists(svelteBaseDir)) await processAndCopyFiles("**/*", svelteBaseDir, webAppDir, context);
|
|
2015
|
+
if (!isConvex && context.api === "orpc") {
|
|
2016
|
+
const apiWebSvelteDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/svelte`);
|
|
2017
|
+
if (await fs.pathExists(apiWebSvelteDir)) await processAndCopyFiles("**/*", apiWebSvelteDir, webAppDir, context);
|
|
2018
|
+
}
|
|
2019
|
+
} else if (hasSolidWeb) {
|
|
2020
|
+
const solidBaseDir = path.join(PKG_ROOT, "templates/frontend/solid");
|
|
2021
|
+
if (await fs.pathExists(solidBaseDir)) await processAndCopyFiles("**/*", solidBaseDir, webAppDir, context);
|
|
2022
|
+
if (!isConvex && context.api === "orpc") {
|
|
2023
|
+
const apiWebSolidDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/solid`);
|
|
2024
|
+
if (await fs.pathExists(apiWebSolidDir)) await processAndCopyFiles("**/*", apiWebSolidDir, webAppDir, context);
|
|
2031
2025
|
}
|
|
2032
|
-
s.stop(`Applied BTS rules to: ${editorsArg}`);
|
|
2033
|
-
} catch (error) {
|
|
2034
|
-
s.stop(pc.red("Failed to apply BTS rules"));
|
|
2035
|
-
throw error;
|
|
2036
2026
|
}
|
|
2037
|
-
try {
|
|
2038
|
-
await fs.remove(rulesDir);
|
|
2039
|
-
} catch (_) {}
|
|
2040
|
-
log.success("vibe-rules setup successfully!");
|
|
2041
|
-
} catch (error) {
|
|
2042
|
-
log.error(pc.red("Failed to set up vibe-rules"));
|
|
2043
|
-
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2044
2027
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2028
|
+
if (hasNativeWind || hasUnistyles) {
|
|
2029
|
+
const nativeAppDir = path.join(projectDir, "apps/native");
|
|
2030
|
+
await fs.ensureDir(nativeAppDir);
|
|
2031
|
+
const nativeBaseCommonDir = path.join(PKG_ROOT, "templates/frontend/native/native-base");
|
|
2032
|
+
if (await fs.pathExists(nativeBaseCommonDir)) await processAndCopyFiles("**/*", nativeBaseCommonDir, nativeAppDir, context);
|
|
2033
|
+
let nativeFrameworkPath = "";
|
|
2034
|
+
if (hasNativeWind) nativeFrameworkPath = "nativewind";
|
|
2035
|
+
else if (hasUnistyles) nativeFrameworkPath = "unistyles";
|
|
2036
|
+
const nativeSpecificDir = path.join(PKG_ROOT, `templates/frontend/native/${nativeFrameworkPath}`);
|
|
2037
|
+
if (await fs.pathExists(nativeSpecificDir)) await processAndCopyFiles("**/*", nativeSpecificDir, nativeAppDir, context, true);
|
|
2038
|
+
if (!isConvex && (context.api === "trpc" || context.api === "orpc")) {
|
|
2039
|
+
const apiNativeSrcDir = path.join(PKG_ROOT, `templates/api/${context.api}/native`);
|
|
2040
|
+
if (await fs.pathExists(apiNativeSrcDir)) await processAndCopyFiles("**/*", apiNativeSrcDir, nativeAppDir, context);
|
|
2041
|
+
}
|
|
2055
2042
|
}
|
|
2056
|
-
});
|
|
2057
|
-
function ensureArrayProperty(obj, name) {
|
|
2058
|
-
return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
|
|
2059
|
-
name,
|
|
2060
|
-
initializer: "[]"
|
|
2061
|
-
}).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
//#endregion
|
|
2065
|
-
//#region src/helpers/setup/vite-pwa-setup.ts
|
|
2066
|
-
async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
2067
|
-
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2068
|
-
if (!sourceFile) throw new Error("vite config not found");
|
|
2069
|
-
const hasImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
|
|
2070
|
-
if (!hasImport) sourceFile.insertImportDeclaration(0, {
|
|
2071
|
-
namedImports: ["VitePWA"],
|
|
2072
|
-
moduleSpecifier: "vite-plugin-pwa"
|
|
2073
|
-
});
|
|
2074
|
-
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
2075
|
-
const expression = expr.getExpression();
|
|
2076
|
-
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
2077
|
-
});
|
|
2078
|
-
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
2079
|
-
const callExpr = defineCall;
|
|
2080
|
-
const configObject = callExpr.getArguments()[0];
|
|
2081
|
-
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
2082
|
-
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
2083
|
-
const alreadyPresent = pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("));
|
|
2084
|
-
if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
|
|
2085
|
-
registerType: "autoUpdate",
|
|
2086
|
-
manifest: {
|
|
2087
|
-
name: "${projectName}",
|
|
2088
|
-
short_name: "${projectName}",
|
|
2089
|
-
description: "${projectName} - PWA Application",
|
|
2090
|
-
theme_color: "#0c0c0c",
|
|
2091
|
-
},
|
|
2092
|
-
pwaAssets: { disabled: false, config: true },
|
|
2093
|
-
devOptions: { enabled: true },
|
|
2094
|
-
})`);
|
|
2095
|
-
await tsProject.save();
|
|
2096
2043
|
}
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
if (addons.includes("turborepo")) {
|
|
2108
|
-
await addPackageDependency({
|
|
2109
|
-
devDependencies: ["turbo"],
|
|
2110
|
-
projectDir
|
|
2111
|
-
});
|
|
2112
|
-
if (isAddCommand) log.info(`${pc.yellow("Update your package.json scripts:")}
|
|
2113
|
-
|
|
2114
|
-
${pc.dim("Replace:")} ${pc.yellow("\"pnpm -r dev\"")} ${pc.dim("→")} ${pc.green("\"turbo dev\"")}
|
|
2115
|
-
${pc.dim("Replace:")} ${pc.yellow("\"pnpm --filter web dev\"")} ${pc.dim("→")} ${pc.green("\"turbo -F web dev\"")}
|
|
2116
|
-
|
|
2117
|
-
${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
|
2118
|
-
`);
|
|
2044
|
+
async function setupBackendFramework(projectDir, context) {
|
|
2045
|
+
if (context.backend === "none") return;
|
|
2046
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2047
|
+
if (context.backend === "convex") {
|
|
2048
|
+
if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
|
|
2049
|
+
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2050
|
+
const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
|
|
2051
|
+
await fs.ensureDir(convexBackendDestDir);
|
|
2052
|
+
if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
|
|
2053
|
+
return;
|
|
2119
2054
|
}
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
const
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
if (
|
|
2130
|
-
let linter;
|
|
2131
|
-
if (hasOxlint) linter = "oxlint";
|
|
2132
|
-
else if (hasBiome) linter = "biome";
|
|
2133
|
-
await setupHusky(projectDir, linter);
|
|
2134
|
-
}
|
|
2055
|
+
await fs.ensureDir(serverAppDir);
|
|
2056
|
+
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/server-base");
|
|
2057
|
+
if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
|
|
2058
|
+
const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
|
|
2059
|
+
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
|
|
2060
|
+
if (context.api !== "none") {
|
|
2061
|
+
const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
|
|
2062
|
+
if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, serverAppDir, context, true);
|
|
2063
|
+
const apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
|
|
2064
|
+
if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, serverAppDir, context, true);
|
|
2135
2065
|
}
|
|
2136
|
-
if (addons.includes("oxlint")) await setupOxlint(projectDir, packageManager);
|
|
2137
|
-
if (addons.includes("starlight")) await setupStarlight(config);
|
|
2138
|
-
if (addons.includes("vibe-rules")) await setupVibeRules(config);
|
|
2139
|
-
if (addons.includes("fumadocs")) await setupFumadocs(config);
|
|
2140
2066
|
}
|
|
2141
|
-
function
|
|
2142
|
-
if (
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
"solid"
|
|
2148
|
-
].includes(f))) return path.join(projectDir, "apps/web");
|
|
2149
|
-
return path.join(projectDir, "apps/web");
|
|
2067
|
+
async function setupDbOrmTemplates(projectDir, context) {
|
|
2068
|
+
if (context.backend === "convex" || context.orm === "none" || context.database === "none") return;
|
|
2069
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2070
|
+
await fs.ensureDir(serverAppDir);
|
|
2071
|
+
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2072
|
+
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, serverAppDir, context);
|
|
2150
2073
|
}
|
|
2151
|
-
async function
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
packageJson.scripts = {
|
|
2160
|
-
...packageJson.scripts,
|
|
2161
|
-
check: "biome check --write ."
|
|
2162
|
-
};
|
|
2163
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
async function setupHusky(projectDir, linter) {
|
|
2167
|
-
await addPackageDependency({
|
|
2168
|
-
devDependencies: ["husky", "lint-staged"],
|
|
2169
|
-
projectDir
|
|
2170
|
-
});
|
|
2171
|
-
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2172
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
2173
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
2174
|
-
packageJson.scripts = {
|
|
2175
|
-
...packageJson.scripts,
|
|
2176
|
-
prepare: "husky"
|
|
2177
|
-
};
|
|
2178
|
-
if (linter === "oxlint") packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint" };
|
|
2179
|
-
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
2180
|
-
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
2181
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
async function setupPwa(projectDir, frontends) {
|
|
2185
|
-
const isCompatibleFrontend = frontends.some((f) => [
|
|
2186
|
-
"react-router",
|
|
2187
|
-
"tanstack-router",
|
|
2188
|
-
"solid"
|
|
2189
|
-
].includes(f));
|
|
2190
|
-
if (!isCompatibleFrontend) return;
|
|
2191
|
-
const clientPackageDir = getWebAppDir(projectDir, frontends);
|
|
2192
|
-
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2193
|
-
await addPackageDependency({
|
|
2194
|
-
dependencies: ["vite-plugin-pwa"],
|
|
2195
|
-
devDependencies: ["@vite-pwa/assets-generator"],
|
|
2196
|
-
projectDir: clientPackageDir
|
|
2197
|
-
});
|
|
2198
|
-
const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
|
|
2199
|
-
if (await fs.pathExists(clientPackageJsonPath)) {
|
|
2200
|
-
const packageJson = await fs.readJson(clientPackageJsonPath);
|
|
2201
|
-
packageJson.scripts = {
|
|
2202
|
-
...packageJson.scripts,
|
|
2203
|
-
"generate-pwa-assets": "pwa-assets-generator"
|
|
2204
|
-
};
|
|
2205
|
-
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
|
2206
|
-
}
|
|
2207
|
-
const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
|
|
2208
|
-
if (await fs.pathExists(viteConfigTs)) await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
|
|
2209
|
-
}
|
|
2210
|
-
async function setupOxlint(projectDir, packageManager) {
|
|
2211
|
-
await addPackageDependency({
|
|
2212
|
-
devDependencies: ["oxlint"],
|
|
2213
|
-
projectDir
|
|
2214
|
-
});
|
|
2215
|
-
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2216
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
2217
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
2218
|
-
packageJson.scripts = {
|
|
2219
|
-
...packageJson.scripts,
|
|
2220
|
-
check: "oxlint"
|
|
2221
|
-
};
|
|
2222
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2223
|
-
}
|
|
2224
|
-
const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
|
|
2225
|
-
await execa(oxlintInitCommand, {
|
|
2226
|
-
cwd: projectDir,
|
|
2227
|
-
env: { CI: "true" },
|
|
2228
|
-
shell: true
|
|
2229
|
-
});
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
|
-
//#endregion
|
|
2233
|
-
//#region src/helpers/project-generation/detect-project-config.ts
|
|
2234
|
-
async function detectProjectConfig(projectDir) {
|
|
2235
|
-
try {
|
|
2236
|
-
const btsConfig = await readBtsConfig(projectDir);
|
|
2237
|
-
if (btsConfig) return {
|
|
2238
|
-
projectDir,
|
|
2239
|
-
projectName: path.basename(projectDir),
|
|
2240
|
-
database: btsConfig.database,
|
|
2241
|
-
orm: btsConfig.orm,
|
|
2242
|
-
backend: btsConfig.backend,
|
|
2243
|
-
runtime: btsConfig.runtime,
|
|
2244
|
-
frontend: btsConfig.frontend,
|
|
2245
|
-
addons: btsConfig.addons,
|
|
2246
|
-
examples: btsConfig.examples,
|
|
2247
|
-
auth: btsConfig.auth,
|
|
2248
|
-
packageManager: btsConfig.packageManager,
|
|
2249
|
-
dbSetup: btsConfig.dbSetup,
|
|
2250
|
-
api: btsConfig.api,
|
|
2251
|
-
webDeploy: btsConfig.webDeploy
|
|
2252
|
-
};
|
|
2253
|
-
return null;
|
|
2254
|
-
} catch (_error) {
|
|
2255
|
-
return null;
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2258
|
-
async function isBetterTStackProject(projectDir) {
|
|
2259
|
-
try {
|
|
2260
|
-
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
|
|
2261
|
-
} catch (_error) {
|
|
2262
|
-
return false;
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
//#endregion
|
|
2267
|
-
//#region src/helpers/project-generation/install-dependencies.ts
|
|
2268
|
-
async function installDependencies({ projectDir, packageManager }) {
|
|
2269
|
-
const s = spinner();
|
|
2270
|
-
try {
|
|
2271
|
-
s.start(`Running ${packageManager} install...`);
|
|
2272
|
-
await $({
|
|
2273
|
-
cwd: projectDir,
|
|
2274
|
-
stderr: "inherit"
|
|
2275
|
-
})`${packageManager} install`;
|
|
2276
|
-
s.stop("Dependencies installed successfully");
|
|
2277
|
-
} catch (error) {
|
|
2278
|
-
s.stop(pc.red("Failed to install dependencies"));
|
|
2279
|
-
if (error instanceof Error) consola.error(pc.red(`Installation error: ${error.message}`));
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
//#endregion
|
|
2284
|
-
//#region src/helpers/project-generation/template-manager.ts
|
|
2285
|
-
async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true, ignorePatterns) {
|
|
2286
|
-
const sourceFiles = await globby(sourcePattern, {
|
|
2287
|
-
cwd: baseSourceDir,
|
|
2288
|
-
dot: true,
|
|
2289
|
-
onlyFiles: true,
|
|
2290
|
-
absolute: false,
|
|
2291
|
-
ignore: ignorePatterns
|
|
2292
|
-
});
|
|
2293
|
-
for (const relativeSrcPath of sourceFiles) {
|
|
2294
|
-
const srcPath = path.join(baseSourceDir, relativeSrcPath);
|
|
2295
|
-
let relativeDestPath = relativeSrcPath;
|
|
2296
|
-
if (relativeSrcPath.endsWith(".hbs")) relativeDestPath = relativeSrcPath.slice(0, -4);
|
|
2297
|
-
const basename = path.basename(relativeDestPath);
|
|
2298
|
-
if (basename === "_gitignore") relativeDestPath = path.join(path.dirname(relativeDestPath), ".gitignore");
|
|
2299
|
-
else if (basename === "_npmrc") relativeDestPath = path.join(path.dirname(relativeDestPath), ".npmrc");
|
|
2300
|
-
const destPath = path.join(destDir, relativeDestPath);
|
|
2301
|
-
await fs.ensureDir(path.dirname(destPath));
|
|
2302
|
-
if (!overwrite && await fs.pathExists(destPath)) continue;
|
|
2303
|
-
if (srcPath.endsWith(".hbs")) await processTemplate(srcPath, destPath, context);
|
|
2304
|
-
else await fs.copy(srcPath, destPath, { overwrite: true });
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
async function copyBaseTemplate(projectDir, context) {
|
|
2308
|
-
const templateDir = path.join(PKG_ROOT, "templates/base");
|
|
2309
|
-
await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
|
|
2310
|
-
}
|
|
2311
|
-
async function setupFrontendTemplates(projectDir, context) {
|
|
2074
|
+
async function setupAuthTemplate(projectDir, context) {
|
|
2075
|
+
if (context.backend === "convex" || !context.auth) return;
|
|
2076
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2077
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2078
|
+
const nativeAppDir = path.join(projectDir, "apps/native");
|
|
2079
|
+
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2080
|
+
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2081
|
+
const nativeAppDirExists = await fs.pathExists(nativeAppDir);
|
|
2312
2082
|
const hasReactWeb = context.frontend.some((f) => [
|
|
2313
2083
|
"tanstack-router",
|
|
2314
2084
|
"react-router",
|
|
@@ -2320,13 +2090,29 @@ async function setupFrontendTemplates(projectDir, context) {
|
|
|
2320
2090
|
const hasSolidWeb = context.frontend.includes("solid");
|
|
2321
2091
|
const hasNativeWind = context.frontend.includes("native-nativewind");
|
|
2322
2092
|
const hasUnistyles = context.frontend.includes("native-unistyles");
|
|
2323
|
-
const
|
|
2324
|
-
if (
|
|
2325
|
-
const
|
|
2326
|
-
await fs.
|
|
2093
|
+
const hasNative = hasNativeWind || hasUnistyles;
|
|
2094
|
+
if (serverAppDirExists) {
|
|
2095
|
+
const authServerBaseSrc = path.join(PKG_ROOT, "templates/auth/server/base");
|
|
2096
|
+
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
|
|
2097
|
+
if (context.backend === "next") {
|
|
2098
|
+
const authServerNextSrc = path.join(PKG_ROOT, "templates/auth/server/next");
|
|
2099
|
+
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, serverAppDir, context);
|
|
2100
|
+
}
|
|
2101
|
+
if (context.orm !== "none" && context.database !== "none") {
|
|
2102
|
+
const orm = context.orm;
|
|
2103
|
+
const db = context.database;
|
|
2104
|
+
let authDbSrc = "";
|
|
2105
|
+
if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/drizzle/${db}`);
|
|
2106
|
+
else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/prisma/${db}`);
|
|
2107
|
+
else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/mongoose/${db}`);
|
|
2108
|
+
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context);
|
|
2109
|
+
else if (authDbSrc) {}
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
|
|
2327
2113
|
if (hasReactWeb) {
|
|
2328
|
-
const
|
|
2329
|
-
if (await fs.pathExists(
|
|
2114
|
+
const authWebBaseSrc = path.join(PKG_ROOT, "templates/auth/web/react/base");
|
|
2115
|
+
if (await fs.pathExists(authWebBaseSrc)) await processAndCopyFiles("**/*", authWebBaseSrc, webAppDir, context);
|
|
2330
2116
|
const reactFramework = context.frontend.find((f) => [
|
|
2331
2117
|
"tanstack-router",
|
|
2332
2118
|
"react-router",
|
|
@@ -2334,154 +2120,29 @@ async function setupFrontendTemplates(projectDir, context) {
|
|
|
2334
2120
|
"next"
|
|
2335
2121
|
].includes(f));
|
|
2336
2122
|
if (reactFramework) {
|
|
2337
|
-
const
|
|
2338
|
-
if (await fs.pathExists(
|
|
2339
|
-
if (!isConvex && context.api !== "none") {
|
|
2340
|
-
const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
|
|
2341
|
-
if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
|
|
2342
|
-
}
|
|
2123
|
+
const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/web/react/${reactFramework}`);
|
|
2124
|
+
if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
|
|
2343
2125
|
}
|
|
2344
2126
|
} else if (hasNuxtWeb) {
|
|
2345
|
-
const
|
|
2346
|
-
if (await fs.pathExists(
|
|
2347
|
-
if (!isConvex && context.api === "orpc") {
|
|
2348
|
-
const apiWebNuxtDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/nuxt`);
|
|
2349
|
-
if (await fs.pathExists(apiWebNuxtDir)) await processAndCopyFiles("**/*", apiWebNuxtDir, webAppDir, context);
|
|
2350
|
-
}
|
|
2127
|
+
const authWebNuxtSrc = path.join(PKG_ROOT, "templates/auth/web/nuxt");
|
|
2128
|
+
if (await fs.pathExists(authWebNuxtSrc)) await processAndCopyFiles("**/*", authWebNuxtSrc, webAppDir, context);
|
|
2351
2129
|
} else if (hasSvelteWeb) {
|
|
2352
|
-
const
|
|
2353
|
-
if (await fs.pathExists(
|
|
2354
|
-
if (!isConvex && context.api === "orpc") {
|
|
2355
|
-
const apiWebSvelteDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/svelte`);
|
|
2356
|
-
if (await fs.pathExists(apiWebSvelteDir)) await processAndCopyFiles("**/*", apiWebSvelteDir, webAppDir, context);
|
|
2357
|
-
}
|
|
2130
|
+
const authWebSvelteSrc = path.join(PKG_ROOT, "templates/auth/web/svelte");
|
|
2131
|
+
if (await fs.pathExists(authWebSvelteSrc)) await processAndCopyFiles("**/*", authWebSvelteSrc, webAppDir, context);
|
|
2358
2132
|
} else if (hasSolidWeb) {
|
|
2359
|
-
const
|
|
2360
|
-
if (await fs.pathExists(
|
|
2361
|
-
if (!isConvex && context.api === "orpc") {
|
|
2362
|
-
const apiWebSolidDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/solid`);
|
|
2363
|
-
if (await fs.pathExists(apiWebSolidDir)) await processAndCopyFiles("**/*", apiWebSolidDir, webAppDir, context);
|
|
2364
|
-
}
|
|
2133
|
+
const authWebSolidSrc = path.join(PKG_ROOT, "templates/auth/web/solid");
|
|
2134
|
+
if (await fs.pathExists(authWebSolidSrc)) await processAndCopyFiles("**/*", authWebSolidSrc, webAppDir, context);
|
|
2365
2135
|
}
|
|
2366
2136
|
}
|
|
2367
|
-
if (
|
|
2368
|
-
const
|
|
2369
|
-
await fs.
|
|
2370
|
-
|
|
2371
|
-
if (
|
|
2372
|
-
|
|
2373
|
-
if (
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
if (await fs.pathExists(nativeSpecificDir)) await processAndCopyFiles("**/*", nativeSpecificDir, nativeAppDir, context, true);
|
|
2377
|
-
if (!isConvex && (context.api === "trpc" || context.api === "orpc")) {
|
|
2378
|
-
const apiNativeSrcDir = path.join(PKG_ROOT, `templates/api/${context.api}/native`);
|
|
2379
|
-
if (await fs.pathExists(apiNativeSrcDir)) await processAndCopyFiles("**/*", apiNativeSrcDir, nativeAppDir, context);
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
async function setupBackendFramework(projectDir, context) {
|
|
2384
|
-
if (context.backend === "none") return;
|
|
2385
|
-
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2386
|
-
if (context.backend === "convex") {
|
|
2387
|
-
if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
|
|
2388
|
-
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2389
|
-
const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
|
|
2390
|
-
await fs.ensureDir(convexBackendDestDir);
|
|
2391
|
-
if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
|
|
2392
|
-
return;
|
|
2393
|
-
}
|
|
2394
|
-
await fs.ensureDir(serverAppDir);
|
|
2395
|
-
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/server-base");
|
|
2396
|
-
if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
|
|
2397
|
-
const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
|
|
2398
|
-
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
|
|
2399
|
-
if (context.api !== "none") {
|
|
2400
|
-
const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
|
|
2401
|
-
if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, serverAppDir, context, true);
|
|
2402
|
-
const apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
|
|
2403
|
-
if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, serverAppDir, context, true);
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
async function setupDbOrmTemplates(projectDir, context) {
|
|
2407
|
-
if (context.backend === "convex" || context.orm === "none" || context.database === "none") return;
|
|
2408
|
-
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2409
|
-
await fs.ensureDir(serverAppDir);
|
|
2410
|
-
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2411
|
-
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, serverAppDir, context);
|
|
2412
|
-
}
|
|
2413
|
-
async function setupAuthTemplate(projectDir, context) {
|
|
2414
|
-
if (context.backend === "convex" || !context.auth) return;
|
|
2415
|
-
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2416
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
2417
|
-
const nativeAppDir = path.join(projectDir, "apps/native");
|
|
2418
|
-
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2419
|
-
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2420
|
-
const nativeAppDirExists = await fs.pathExists(nativeAppDir);
|
|
2421
|
-
const hasReactWeb = context.frontend.some((f) => [
|
|
2422
|
-
"tanstack-router",
|
|
2423
|
-
"react-router",
|
|
2424
|
-
"tanstack-start",
|
|
2425
|
-
"next"
|
|
2426
|
-
].includes(f));
|
|
2427
|
-
const hasNuxtWeb = context.frontend.includes("nuxt");
|
|
2428
|
-
const hasSvelteWeb = context.frontend.includes("svelte");
|
|
2429
|
-
const hasSolidWeb = context.frontend.includes("solid");
|
|
2430
|
-
const hasNativeWind = context.frontend.includes("native-nativewind");
|
|
2431
|
-
const hasUnistyles = context.frontend.includes("native-unistyles");
|
|
2432
|
-
const hasNative = hasNativeWind || hasUnistyles;
|
|
2433
|
-
if (serverAppDirExists) {
|
|
2434
|
-
const authServerBaseSrc = path.join(PKG_ROOT, "templates/auth/server/base");
|
|
2435
|
-
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
|
|
2436
|
-
if (context.backend === "next") {
|
|
2437
|
-
const authServerNextSrc = path.join(PKG_ROOT, "templates/auth/server/next");
|
|
2438
|
-
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, serverAppDir, context);
|
|
2439
|
-
}
|
|
2440
|
-
if (context.orm !== "none" && context.database !== "none") {
|
|
2441
|
-
const orm = context.orm;
|
|
2442
|
-
const db = context.database;
|
|
2443
|
-
let authDbSrc = "";
|
|
2444
|
-
if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/drizzle/${db}`);
|
|
2445
|
-
else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/prisma/${db}`);
|
|
2446
|
-
else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/mongoose/${db}`);
|
|
2447
|
-
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context);
|
|
2448
|
-
else if (authDbSrc) {}
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
|
|
2452
|
-
if (hasReactWeb) {
|
|
2453
|
-
const authWebBaseSrc = path.join(PKG_ROOT, "templates/auth/web/react/base");
|
|
2454
|
-
if (await fs.pathExists(authWebBaseSrc)) await processAndCopyFiles("**/*", authWebBaseSrc, webAppDir, context);
|
|
2455
|
-
const reactFramework = context.frontend.find((f) => [
|
|
2456
|
-
"tanstack-router",
|
|
2457
|
-
"react-router",
|
|
2458
|
-
"tanstack-start",
|
|
2459
|
-
"next"
|
|
2460
|
-
].includes(f));
|
|
2461
|
-
if (reactFramework) {
|
|
2462
|
-
const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/web/react/${reactFramework}`);
|
|
2463
|
-
if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
|
|
2464
|
-
}
|
|
2465
|
-
} else if (hasNuxtWeb) {
|
|
2466
|
-
const authWebNuxtSrc = path.join(PKG_ROOT, "templates/auth/web/nuxt");
|
|
2467
|
-
if (await fs.pathExists(authWebNuxtSrc)) await processAndCopyFiles("**/*", authWebNuxtSrc, webAppDir, context);
|
|
2468
|
-
} else if (hasSvelteWeb) {
|
|
2469
|
-
const authWebSvelteSrc = path.join(PKG_ROOT, "templates/auth/web/svelte");
|
|
2470
|
-
if (await fs.pathExists(authWebSvelteSrc)) await processAndCopyFiles("**/*", authWebSvelteSrc, webAppDir, context);
|
|
2471
|
-
} else if (hasSolidWeb) {
|
|
2472
|
-
const authWebSolidSrc = path.join(PKG_ROOT, "templates/auth/web/solid");
|
|
2473
|
-
if (await fs.pathExists(authWebSolidSrc)) await processAndCopyFiles("**/*", authWebSolidSrc, webAppDir, context);
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
if (hasNative && nativeAppDirExists) {
|
|
2477
|
-
const authNativeBaseSrc = path.join(PKG_ROOT, "templates/auth/native/native-base");
|
|
2478
|
-
if (await fs.pathExists(authNativeBaseSrc)) await processAndCopyFiles("**/*", authNativeBaseSrc, nativeAppDir, context);
|
|
2479
|
-
let nativeFrameworkAuthPath = "";
|
|
2480
|
-
if (hasNativeWind) nativeFrameworkAuthPath = "nativewind";
|
|
2481
|
-
else if (hasUnistyles) nativeFrameworkAuthPath = "unistyles";
|
|
2482
|
-
if (nativeFrameworkAuthPath) {
|
|
2483
|
-
const authNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/native/${nativeFrameworkAuthPath}`);
|
|
2484
|
-
if (await fs.pathExists(authNativeFrameworkSrc)) await processAndCopyFiles("**/*", authNativeFrameworkSrc, nativeAppDir, context);
|
|
2137
|
+
if (hasNative && nativeAppDirExists) {
|
|
2138
|
+
const authNativeBaseSrc = path.join(PKG_ROOT, "templates/auth/native/native-base");
|
|
2139
|
+
if (await fs.pathExists(authNativeBaseSrc)) await processAndCopyFiles("**/*", authNativeBaseSrc, nativeAppDir, context);
|
|
2140
|
+
let nativeFrameworkAuthPath = "";
|
|
2141
|
+
if (hasNativeWind) nativeFrameworkAuthPath = "nativewind";
|
|
2142
|
+
else if (hasUnistyles) nativeFrameworkAuthPath = "unistyles";
|
|
2143
|
+
if (nativeFrameworkAuthPath) {
|
|
2144
|
+
const authNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/native/${nativeFrameworkAuthPath}`);
|
|
2145
|
+
if (await fs.pathExists(authNativeFrameworkSrc)) await processAndCopyFiles("**/*", authNativeFrameworkSrc, nativeAppDir, context);
|
|
2485
2146
|
}
|
|
2486
2147
|
}
|
|
2487
2148
|
}
|
|
@@ -2539,9 +2200,6 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
2539
2200
|
const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
|
|
2540
2201
|
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, serverAppDir, context, false);
|
|
2541
2202
|
}
|
|
2542
|
-
const ignorePatterns = [`${context.orm}/**`];
|
|
2543
|
-
if (example === "ai" && context.backend === "next") ignorePatterns.push("next/**");
|
|
2544
|
-
await processAndCopyFiles(["**/*.ts", "**/*.hbs"], exampleServerSrc, serverAppDir, context, false, ignorePatterns);
|
|
2545
2203
|
}
|
|
2546
2204
|
if (webAppDirExists) {
|
|
2547
2205
|
if (hasReactWeb) {
|
|
@@ -2633,6 +2291,324 @@ async function setupDeploymentTemplates(projectDir, context) {
|
|
|
2633
2291
|
}
|
|
2634
2292
|
}
|
|
2635
2293
|
|
|
2294
|
+
//#endregion
|
|
2295
|
+
//#region src/helpers/setup/vibe-rules-setup.ts
|
|
2296
|
+
async function setupVibeRules(config) {
|
|
2297
|
+
const { packageManager, projectDir } = config;
|
|
2298
|
+
try {
|
|
2299
|
+
log.info("Setting up Ruler...");
|
|
2300
|
+
const rulerDir = path.join(projectDir, ".ruler");
|
|
2301
|
+
const rulerTemplateDir = path.join(PKG_ROOT, "templates", "addons", "vibe-rules", ".ruler");
|
|
2302
|
+
if (!await fs.pathExists(rulerDir)) if (await fs.pathExists(rulerTemplateDir)) await processAndCopyFiles("**/*", rulerTemplateDir, rulerDir, config);
|
|
2303
|
+
else {
|
|
2304
|
+
log.error(pc.red("Ruler template directory not found"));
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
const EDITORS$1 = {
|
|
2308
|
+
cursor: { label: "Cursor" },
|
|
2309
|
+
windsurf: { label: "Windsurf" },
|
|
2310
|
+
claude: { label: "Claude Code" },
|
|
2311
|
+
copilot: { label: "GitHub Copilot" },
|
|
2312
|
+
"gemini-cli": { label: "Gemini CLI" },
|
|
2313
|
+
codex: { label: "OpenAI Codex CLI" },
|
|
2314
|
+
jules: { label: "Jules" },
|
|
2315
|
+
cline: { label: "Cline" },
|
|
2316
|
+
aider: { label: "Aider" },
|
|
2317
|
+
firebase: { label: "Firebase Studio" },
|
|
2318
|
+
openhands: { label: "Open Hands" },
|
|
2319
|
+
junie: { label: "Junie" },
|
|
2320
|
+
augmentcode: { label: "AugmentCode" },
|
|
2321
|
+
kilocode: { label: "Kilo Code" },
|
|
2322
|
+
opencode: { label: "OpenCode" }
|
|
2323
|
+
};
|
|
2324
|
+
const selectedEditors = await multiselect({
|
|
2325
|
+
message: "Select AI assistants for Ruler",
|
|
2326
|
+
options: Object.entries(EDITORS$1).map(([key, v]) => ({
|
|
2327
|
+
value: key,
|
|
2328
|
+
label: v.label
|
|
2329
|
+
})),
|
|
2330
|
+
required: false
|
|
2331
|
+
});
|
|
2332
|
+
if (isCancel(selectedEditors)) return exitCancelled("Operation cancelled");
|
|
2333
|
+
if (selectedEditors.length === 0) {
|
|
2334
|
+
log.info("No AI assistants selected. To apply rules later, run:");
|
|
2335
|
+
log.info(pc.cyan(`${getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only")}`));
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
const configFile = path.join(rulerDir, "ruler.toml");
|
|
2339
|
+
const currentConfig = await fs.readFile(configFile, "utf-8");
|
|
2340
|
+
let updatedConfig = currentConfig;
|
|
2341
|
+
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
2342
|
+
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
2343
|
+
await fs.writeFile(configFile, updatedConfig);
|
|
2344
|
+
await addRulerScriptToPackageJson(projectDir, packageManager);
|
|
2345
|
+
const s = spinner();
|
|
2346
|
+
s.start("Applying rules with Ruler...");
|
|
2347
|
+
try {
|
|
2348
|
+
const rulerApplyCmd = getPackageExecutionCommand(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`);
|
|
2349
|
+
await execa(rulerApplyCmd, {
|
|
2350
|
+
cwd: projectDir,
|
|
2351
|
+
env: { CI: "true" },
|
|
2352
|
+
shell: true
|
|
2353
|
+
});
|
|
2354
|
+
s.stop("Applied rules with Ruler");
|
|
2355
|
+
} catch (_error) {
|
|
2356
|
+
s.stop(pc.red("Failed to apply rules"));
|
|
2357
|
+
}
|
|
2358
|
+
} catch (error) {
|
|
2359
|
+
log.error(pc.red("Failed to set up Ruler"));
|
|
2360
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
async function addRulerScriptToPackageJson(projectDir, packageManager) {
|
|
2364
|
+
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
2365
|
+
if (!await fs.pathExists(rootPackageJsonPath)) {
|
|
2366
|
+
log.warn("Root package.json not found, skipping ruler:apply script addition");
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
const packageJson = await fs.readJson(rootPackageJsonPath);
|
|
2370
|
+
if (!packageJson.scripts) packageJson.scripts = {};
|
|
2371
|
+
const rulerApplyCommand = getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only");
|
|
2372
|
+
packageJson.scripts["ruler:apply"] = rulerApplyCommand;
|
|
2373
|
+
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
//#endregion
|
|
2377
|
+
//#region src/utils/ts-morph.ts
|
|
2378
|
+
const tsProject = new Project({
|
|
2379
|
+
useInMemoryFileSystem: false,
|
|
2380
|
+
skipAddingFilesFromTsConfig: true,
|
|
2381
|
+
manipulationSettings: {
|
|
2382
|
+
quoteKind: QuoteKind.Single,
|
|
2383
|
+
indentationText: IndentationText.TwoSpaces
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2386
|
+
function ensureArrayProperty(obj, name) {
|
|
2387
|
+
return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
|
|
2388
|
+
name,
|
|
2389
|
+
initializer: "[]"
|
|
2390
|
+
}).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
//#endregion
|
|
2394
|
+
//#region src/helpers/setup/vite-pwa-setup.ts
|
|
2395
|
+
async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
2396
|
+
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2397
|
+
if (!sourceFile) throw new Error("vite config not found");
|
|
2398
|
+
const hasImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
|
|
2399
|
+
if (!hasImport) sourceFile.insertImportDeclaration(0, {
|
|
2400
|
+
namedImports: ["VitePWA"],
|
|
2401
|
+
moduleSpecifier: "vite-plugin-pwa"
|
|
2402
|
+
});
|
|
2403
|
+
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
2404
|
+
const expression = expr.getExpression();
|
|
2405
|
+
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
2406
|
+
});
|
|
2407
|
+
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
2408
|
+
const callExpr = defineCall;
|
|
2409
|
+
const configObject = callExpr.getArguments()[0];
|
|
2410
|
+
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
2411
|
+
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
2412
|
+
const alreadyPresent = pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("));
|
|
2413
|
+
if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
|
|
2414
|
+
registerType: "autoUpdate",
|
|
2415
|
+
manifest: {
|
|
2416
|
+
name: "${projectName}",
|
|
2417
|
+
short_name: "${projectName}",
|
|
2418
|
+
description: "${projectName} - PWA Application",
|
|
2419
|
+
theme_color: "#0c0c0c",
|
|
2420
|
+
},
|
|
2421
|
+
pwaAssets: { disabled: false, config: true },
|
|
2422
|
+
devOptions: { enabled: true },
|
|
2423
|
+
})`);
|
|
2424
|
+
await tsProject.save();
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
//#endregion
|
|
2428
|
+
//#region src/helpers/setup/addons-setup.ts
|
|
2429
|
+
async function setupAddons(config, isAddCommand = false) {
|
|
2430
|
+
const { addons, frontend, projectDir, packageManager } = config;
|
|
2431
|
+
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
|
|
2432
|
+
const hasNuxtFrontend = frontend.includes("nuxt");
|
|
2433
|
+
const hasSvelteFrontend = frontend.includes("svelte");
|
|
2434
|
+
const hasSolidFrontend = frontend.includes("solid");
|
|
2435
|
+
const hasNextFrontend = frontend.includes("next");
|
|
2436
|
+
if (addons.includes("turborepo")) {
|
|
2437
|
+
await addPackageDependency({
|
|
2438
|
+
devDependencies: ["turbo"],
|
|
2439
|
+
projectDir
|
|
2440
|
+
});
|
|
2441
|
+
if (isAddCommand) log.info(`${pc.yellow("Update your package.json scripts:")}
|
|
2442
|
+
|
|
2443
|
+
${pc.dim("Replace:")} ${pc.yellow("\"pnpm -r dev\"")} ${pc.dim("→")} ${pc.green("\"turbo dev\"")}
|
|
2444
|
+
${pc.dim("Replace:")} ${pc.yellow("\"pnpm --filter web dev\"")} ${pc.dim("→")} ${pc.green("\"turbo -F web dev\"")}
|
|
2445
|
+
|
|
2446
|
+
${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
|
2447
|
+
`);
|
|
2448
|
+
}
|
|
2449
|
+
if (addons.includes("pwa") && (hasReactWebFrontend || hasSolidFrontend)) await setupPwa(projectDir, frontend);
|
|
2450
|
+
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
|
|
2451
|
+
const hasUltracite = addons.includes("ultracite");
|
|
2452
|
+
const hasBiome = addons.includes("biome");
|
|
2453
|
+
const hasHusky = addons.includes("husky");
|
|
2454
|
+
const hasOxlint = addons.includes("oxlint");
|
|
2455
|
+
if (hasUltracite) await setupUltracite(config, hasHusky);
|
|
2456
|
+
else {
|
|
2457
|
+
if (hasBiome) await setupBiome(projectDir);
|
|
2458
|
+
if (hasHusky) {
|
|
2459
|
+
let linter;
|
|
2460
|
+
if (hasOxlint) linter = "oxlint";
|
|
2461
|
+
else if (hasBiome) linter = "biome";
|
|
2462
|
+
await setupHusky(projectDir, linter);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
if (addons.includes("oxlint")) await setupOxlint(projectDir, packageManager);
|
|
2466
|
+
if (addons.includes("starlight")) await setupStarlight(config);
|
|
2467
|
+
if (addons.includes("vibe-rules")) await setupVibeRules(config);
|
|
2468
|
+
if (addons.includes("fumadocs")) await setupFumadocs(config);
|
|
2469
|
+
}
|
|
2470
|
+
function getWebAppDir(projectDir, frontends) {
|
|
2471
|
+
if (frontends.some((f) => [
|
|
2472
|
+
"react-router",
|
|
2473
|
+
"tanstack-router",
|
|
2474
|
+
"nuxt",
|
|
2475
|
+
"svelte",
|
|
2476
|
+
"solid"
|
|
2477
|
+
].includes(f))) return path.join(projectDir, "apps/web");
|
|
2478
|
+
return path.join(projectDir, "apps/web");
|
|
2479
|
+
}
|
|
2480
|
+
async function setupBiome(projectDir) {
|
|
2481
|
+
await addPackageDependency({
|
|
2482
|
+
devDependencies: ["@biomejs/biome"],
|
|
2483
|
+
projectDir
|
|
2484
|
+
});
|
|
2485
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2486
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2487
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2488
|
+
packageJson.scripts = {
|
|
2489
|
+
...packageJson.scripts,
|
|
2490
|
+
check: "biome check --write ."
|
|
2491
|
+
};
|
|
2492
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
async function setupHusky(projectDir, linter) {
|
|
2496
|
+
await addPackageDependency({
|
|
2497
|
+
devDependencies: ["husky", "lint-staged"],
|
|
2498
|
+
projectDir
|
|
2499
|
+
});
|
|
2500
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2501
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2502
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2503
|
+
packageJson.scripts = {
|
|
2504
|
+
...packageJson.scripts,
|
|
2505
|
+
prepare: "husky"
|
|
2506
|
+
};
|
|
2507
|
+
if (linter === "oxlint") packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint" };
|
|
2508
|
+
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
2509
|
+
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
2510
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
async function setupPwa(projectDir, frontends) {
|
|
2514
|
+
const isCompatibleFrontend = frontends.some((f) => [
|
|
2515
|
+
"react-router",
|
|
2516
|
+
"tanstack-router",
|
|
2517
|
+
"solid"
|
|
2518
|
+
].includes(f));
|
|
2519
|
+
if (!isCompatibleFrontend) return;
|
|
2520
|
+
const clientPackageDir = getWebAppDir(projectDir, frontends);
|
|
2521
|
+
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2522
|
+
await addPackageDependency({
|
|
2523
|
+
dependencies: ["vite-plugin-pwa"],
|
|
2524
|
+
devDependencies: ["@vite-pwa/assets-generator"],
|
|
2525
|
+
projectDir: clientPackageDir
|
|
2526
|
+
});
|
|
2527
|
+
const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
|
|
2528
|
+
if (await fs.pathExists(clientPackageJsonPath)) {
|
|
2529
|
+
const packageJson = await fs.readJson(clientPackageJsonPath);
|
|
2530
|
+
packageJson.scripts = {
|
|
2531
|
+
...packageJson.scripts,
|
|
2532
|
+
"generate-pwa-assets": "pwa-assets-generator"
|
|
2533
|
+
};
|
|
2534
|
+
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
|
2535
|
+
}
|
|
2536
|
+
const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
|
|
2537
|
+
if (await fs.pathExists(viteConfigTs)) await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
|
|
2538
|
+
}
|
|
2539
|
+
async function setupOxlint(projectDir, packageManager) {
|
|
2540
|
+
await addPackageDependency({
|
|
2541
|
+
devDependencies: ["oxlint"],
|
|
2542
|
+
projectDir
|
|
2543
|
+
});
|
|
2544
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2545
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2546
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2547
|
+
packageJson.scripts = {
|
|
2548
|
+
...packageJson.scripts,
|
|
2549
|
+
check: "oxlint"
|
|
2550
|
+
};
|
|
2551
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2552
|
+
}
|
|
2553
|
+
const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
|
|
2554
|
+
await execa(oxlintInitCommand, {
|
|
2555
|
+
cwd: projectDir,
|
|
2556
|
+
env: { CI: "true" },
|
|
2557
|
+
shell: true
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
//#endregion
|
|
2562
|
+
//#region src/helpers/project-generation/detect-project-config.ts
|
|
2563
|
+
async function detectProjectConfig(projectDir) {
|
|
2564
|
+
try {
|
|
2565
|
+
const btsConfig = await readBtsConfig(projectDir);
|
|
2566
|
+
if (btsConfig) return {
|
|
2567
|
+
projectDir,
|
|
2568
|
+
projectName: path.basename(projectDir),
|
|
2569
|
+
database: btsConfig.database,
|
|
2570
|
+
orm: btsConfig.orm,
|
|
2571
|
+
backend: btsConfig.backend,
|
|
2572
|
+
runtime: btsConfig.runtime,
|
|
2573
|
+
frontend: btsConfig.frontend,
|
|
2574
|
+
addons: btsConfig.addons,
|
|
2575
|
+
examples: btsConfig.examples,
|
|
2576
|
+
auth: btsConfig.auth,
|
|
2577
|
+
packageManager: btsConfig.packageManager,
|
|
2578
|
+
dbSetup: btsConfig.dbSetup,
|
|
2579
|
+
api: btsConfig.api,
|
|
2580
|
+
webDeploy: btsConfig.webDeploy
|
|
2581
|
+
};
|
|
2582
|
+
return null;
|
|
2583
|
+
} catch (_error) {
|
|
2584
|
+
return null;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
async function isBetterTStackProject(projectDir) {
|
|
2588
|
+
try {
|
|
2589
|
+
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
|
|
2590
|
+
} catch (_error) {
|
|
2591
|
+
return false;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
//#endregion
|
|
2596
|
+
//#region src/helpers/project-generation/install-dependencies.ts
|
|
2597
|
+
async function installDependencies({ projectDir, packageManager }) {
|
|
2598
|
+
const s = spinner();
|
|
2599
|
+
try {
|
|
2600
|
+
s.start(`Running ${packageManager} install...`);
|
|
2601
|
+
await $({
|
|
2602
|
+
cwd: projectDir,
|
|
2603
|
+
stderr: "inherit"
|
|
2604
|
+
})`${packageManager} install`;
|
|
2605
|
+
s.stop("Dependencies installed successfully");
|
|
2606
|
+
} catch (error) {
|
|
2607
|
+
s.stop(pc.red("Failed to install dependencies"));
|
|
2608
|
+
if (error instanceof Error) consola.error(pc.red(`Installation error: ${error.message}`));
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2636
2612
|
//#endregion
|
|
2637
2613
|
//#region src/helpers/project-generation/add-addons.ts
|
|
2638
2614
|
async function addAddonsToProject(input) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.33.0",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"context7": {
|
|
4
|
+
"type": "stdio",
|
|
5
|
+
"command": "npx",
|
|
6
|
+
"args": ["-y", "@upstash/context7-mcp"]
|
|
7
|
+
}{{#if (or (eq runtime "workers") (eq webDeploy "workers"))}},
|
|
8
|
+
"cloudflare": {
|
|
9
|
+
"command": "npx",
|
|
10
|
+
"args": ["mcp-remote", "https://docs.mcp.cloudflare.com/sse"]
|
|
11
|
+
}{{/if}}{{#if (eq backend "convex")}},
|
|
12
|
+
"convex": {
|
|
13
|
+
"command": "npx",
|
|
14
|
+
"args": ["-y", "convex@latest", "mcp", "start"]
|
|
15
|
+
}
|
|
16
|
+
{{/if}}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
File without changes
|