mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): add ultracite, oxlint, fumadocs addons (#427)
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
type EnvVariable,
|
||||
} from "../project-generation/env-setup";
|
||||
|
||||
export async function setupCloudflareD1(config: ProjectConfig): Promise<void> {
|
||||
export async function setupCloudflareD1(config: ProjectConfig) {
|
||||
const { projectDir } = config;
|
||||
|
||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type EnvVariable,
|
||||
} from "../project-generation/env-setup";
|
||||
|
||||
export async function setupDockerCompose(config: ProjectConfig): Promise<void> {
|
||||
export async function setupDockerCompose(config: ProjectConfig) {
|
||||
const { database, projectDir, projectName } = config;
|
||||
|
||||
if (database === "none" || database === "sqlite") {
|
||||
|
||||
@@ -156,7 +156,7 @@ function displayManualSetupInstructions() {
|
||||
DATABASE_URL="your_connection_string"`);
|
||||
}
|
||||
|
||||
export async function setupNeonPostgres(config: ProjectConfig): Promise<void> {
|
||||
export async function setupNeonPostgres(config: ProjectConfig) {
|
||||
const { packageManager, projectDir } = config;
|
||||
|
||||
try {
|
||||
|
||||
@@ -206,7 +206,7 @@ DATABASE_URL=your_database_url
|
||||
DATABASE_AUTH_TOKEN=your_auth_token`);
|
||||
}
|
||||
|
||||
export async function setupTurso(config: ProjectConfig): Promise<void> {
|
||||
export async function setupTurso(config: ProjectConfig) {
|
||||
const { orm, projectDir } = config;
|
||||
const _isDrizzle = orm === "drizzle";
|
||||
const setupSpinner = spinner();
|
||||
|
||||
@@ -19,7 +19,7 @@ function exitWithError(message: string): never {
|
||||
|
||||
export async function addAddonsToProject(
|
||||
input: AddInput & { addons: Addons[]; suppressInstallMessage?: boolean },
|
||||
): Promise<void> {
|
||||
) {
|
||||
try {
|
||||
const projectDir = input.projectDir || process.cwd();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ function exitWithError(message: string): never {
|
||||
|
||||
export async function addDeploymentToProject(
|
||||
input: AddInput & { webDeploy: WebDeploy; suppressInstallMessage?: boolean },
|
||||
): Promise<void> {
|
||||
) {
|
||||
try {
|
||||
const projectDir = input.projectDir || process.cwd();
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ export async function createProjectHandler(
|
||||
}
|
||||
}
|
||||
|
||||
export async function addAddonsHandler(input: AddInput): Promise<void> {
|
||||
export async function addAddonsHandler(input: AddInput) {
|
||||
try {
|
||||
const projectDir = input.projectDir || process.cwd();
|
||||
const detectedConfig = await detectProjectConfig(projectDir);
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface EnvVariable {
|
||||
export async function addEnvVariablesToFile(
|
||||
filePath: string,
|
||||
variables: EnvVariable[],
|
||||
): Promise<void> {
|
||||
) {
|
||||
await fs.ensureDir(path.dirname(filePath));
|
||||
|
||||
let envContent = "";
|
||||
@@ -84,9 +84,7 @@ export async function addEnvVariablesToFile(
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupEnvironmentVariables(
|
||||
config: ProjectConfig,
|
||||
): Promise<void> {
|
||||
export async function setupEnvironmentVariables(config: ProjectConfig) {
|
||||
const { backend, frontend, database, auth, examples, dbSetup, projectDir } =
|
||||
config;
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@ import { log } from "@clack/prompts";
|
||||
import { $ } from "execa";
|
||||
import pc from "picocolors";
|
||||
|
||||
export async function initializeGit(
|
||||
projectDir: string,
|
||||
useGit: boolean,
|
||||
): Promise<void> {
|
||||
export async function initializeGit(projectDir: string, useGit: boolean) {
|
||||
if (!useGit) return;
|
||||
|
||||
const gitVersionResult = await $({
|
||||
|
||||
@@ -142,6 +142,10 @@ export async function displayPostInstallInstructions(
|
||||
output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
|
||||
}
|
||||
|
||||
if (addons?.includes("fumadocs")) {
|
||||
output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
|
||||
}
|
||||
|
||||
if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
|
||||
if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
|
||||
if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { ProjectConfig } from "../../types";
|
||||
export async function updatePackageConfigurations(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
await updateRootPackageJson(projectDir, options);
|
||||
if (options.backend !== "convex") {
|
||||
await updateServerPackageJson(projectDir, options);
|
||||
@@ -19,7 +19,7 @@ export async function updatePackageConfigurations(
|
||||
async function updateRootPackageJson(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
||||
if (!(await fs.pathExists(rootPackageJsonPath))) return;
|
||||
|
||||
@@ -185,18 +185,6 @@ async function updateRootPackageJson(
|
||||
}
|
||||
}
|
||||
|
||||
if (options.addons.includes("biome")) {
|
||||
scripts.check = "biome check --write .";
|
||||
}
|
||||
if (options.addons.includes("husky")) {
|
||||
scripts.prepare = "husky";
|
||||
packageJson["lint-staged"] = {
|
||||
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
|
||||
"biome check --write .",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execa(options.packageManager, ["-v"], {
|
||||
cwd: projectDir,
|
||||
@@ -235,7 +223,7 @@ async function updateRootPackageJson(
|
||||
async function updateServerPackageJson(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const serverPackageJsonPath = path.join(
|
||||
projectDir,
|
||||
"apps/server/package.json",
|
||||
@@ -287,7 +275,7 @@ async function updateServerPackageJson(
|
||||
async function updateConvexPackageJson(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const convexPackageJsonPath = path.join(
|
||||
projectDir,
|
||||
"packages/backend/package.json",
|
||||
|
||||
@@ -11,7 +11,7 @@ async function processAndCopyFiles(
|
||||
destDir: string,
|
||||
context: ProjectConfig,
|
||||
overwrite = true,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const sourceFiles = await globby(sourcePattern, {
|
||||
cwd: baseSourceDir,
|
||||
dot: true,
|
||||
@@ -54,7 +54,7 @@ async function processAndCopyFiles(
|
||||
export async function copyBaseTemplate(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const templateDir = path.join(PKG_ROOT, "templates/base");
|
||||
await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export async function copyBaseTemplate(
|
||||
export async function setupFrontendTemplates(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const hasReactWeb = context.frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start", "next"].includes(f),
|
||||
);
|
||||
@@ -241,7 +241,7 @@ export async function setupFrontendTemplates(
|
||||
export async function setupBackendFramework(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (context.backend === "none") {
|
||||
return;
|
||||
}
|
||||
@@ -332,7 +332,7 @@ export async function setupBackendFramework(
|
||||
export async function setupDbOrmTemplates(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (
|
||||
context.backend === "convex" ||
|
||||
context.orm === "none" ||
|
||||
@@ -357,7 +357,7 @@ export async function setupDbOrmTemplates(
|
||||
export async function setupAuthTemplate(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (context.backend === "convex" || !context.auth) return;
|
||||
|
||||
const serverAppDir = path.join(projectDir, "apps/server");
|
||||
@@ -529,7 +529,7 @@ export async function setupAuthTemplate(
|
||||
export async function setupAddonsTemplate(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (!context.addons || context.addons.length === 0) return;
|
||||
|
||||
for (const addon of context.addons) {
|
||||
@@ -567,7 +567,7 @@ export async function setupAddonsTemplate(
|
||||
export async function setupExamplesTemplate(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (
|
||||
!context.examples ||
|
||||
context.examples.length === 0 ||
|
||||
@@ -773,10 +773,7 @@ export async function setupExamplesTemplate(
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleExtras(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
export async function handleExtras(projectDir: string, context: ProjectConfig) {
|
||||
const extrasDir = path.join(PKG_ROOT, "templates/extras");
|
||||
const hasNativeWind = context.frontend.includes("native-nativewind");
|
||||
const hasUnistyles = context.frontend.includes("native-unistyles");
|
||||
@@ -790,6 +787,14 @@ export async function handleExtras(
|
||||
}
|
||||
}
|
||||
|
||||
if (context.packageManager === "bun") {
|
||||
const bunfigSrc = path.join(extrasDir, "bunfig.toml");
|
||||
const bunfigDest = path.join(projectDir, "bunfig.toml");
|
||||
if (await fs.pathExists(bunfigSrc)) {
|
||||
await fs.copy(bunfigSrc, bunfigDest);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
context.packageManager === "pnpm" &&
|
||||
(hasNative || context.frontend.includes("nuxt"))
|
||||
@@ -818,7 +823,7 @@ export async function handleExtras(
|
||||
export async function setupDockerComposeTemplates(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (context.dbSetup !== "docker" || context.database === "none") {
|
||||
return;
|
||||
}
|
||||
@@ -838,7 +843,7 @@ export async function setupDockerComposeTemplates(
|
||||
export async function setupDeploymentTemplates(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (context.webDeploy === "none") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import path from "node:path";
|
||||
import { log } from "@clack/prompts";
|
||||
import { execa } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { Frontend, ProjectConfig } from "../../types";
|
||||
import type { Frontend, PackageManager, ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||
import { setupFumadocs } from "./fumadocs-setup";
|
||||
import { setupStarlight } from "./starlight-setup";
|
||||
import { setupTauri } from "./tauri-setup";
|
||||
import { setupUltracite } from "./ultracite-setup";
|
||||
import { addPwaToViteConfig } from "./vite-pwa-setup";
|
||||
|
||||
export async function setupAddons(config: ProjectConfig, isAddCommand = false) {
|
||||
const { addons, frontend, projectDir } = config;
|
||||
const { addons, frontend, projectDir, packageManager } = config;
|
||||
const hasReactWebFrontend =
|
||||
frontend.includes("react-router") ||
|
||||
frontend.includes("tanstack-router") ||
|
||||
@@ -53,15 +57,37 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
||||
) {
|
||||
await setupTauri(config);
|
||||
}
|
||||
if (addons.includes("biome")) {
|
||||
await setupBiome(projectDir);
|
||||
const hasUltracite = addons.includes("ultracite");
|
||||
const hasBiome = addons.includes("biome");
|
||||
const hasHusky = addons.includes("husky");
|
||||
const hasOxlint = addons.includes("oxlint");
|
||||
|
||||
if (hasUltracite) {
|
||||
await setupUltracite(config, hasHusky);
|
||||
} else {
|
||||
if (hasBiome) {
|
||||
await setupBiome(projectDir);
|
||||
}
|
||||
if (hasHusky) {
|
||||
let linter: "biome" | "oxlint" | undefined;
|
||||
if (hasOxlint) {
|
||||
linter = "oxlint";
|
||||
} else if (hasBiome) {
|
||||
linter = "biome";
|
||||
}
|
||||
await setupHusky(projectDir, linter);
|
||||
}
|
||||
}
|
||||
if (addons.includes("husky")) {
|
||||
await setupHusky(projectDir);
|
||||
|
||||
if (addons.includes("oxlint")) {
|
||||
await setupOxlint(projectDir, packageManager);
|
||||
}
|
||||
if (addons.includes("starlight")) {
|
||||
await setupStarlight(config);
|
||||
}
|
||||
if (addons.includes("fumadocs")) {
|
||||
await setupFumadocs(config);
|
||||
}
|
||||
}
|
||||
|
||||
function getWebAppDir(projectDir: string, frontends: Frontend[]): string {
|
||||
@@ -77,7 +103,7 @@ function getWebAppDir(projectDir: string, frontends: Frontend[]): string {
|
||||
return path.join(projectDir, "apps/web");
|
||||
}
|
||||
|
||||
async function setupBiome(projectDir: string) {
|
||||
export async function setupBiome(projectDir: string) {
|
||||
await addPackageDependency({
|
||||
devDependencies: ["@biomejs/biome"],
|
||||
projectDir,
|
||||
@@ -96,7 +122,10 @@ async function setupBiome(projectDir: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function setupHusky(projectDir: string) {
|
||||
export async function setupHusky(
|
||||
projectDir: string,
|
||||
linter?: "biome" | "oxlint",
|
||||
) {
|
||||
await addPackageDependency({
|
||||
devDependencies: ["husky", "lint-staged"],
|
||||
projectDir,
|
||||
@@ -111,11 +140,21 @@ async function setupHusky(projectDir: string) {
|
||||
prepare: "husky",
|
||||
};
|
||||
|
||||
packageJson["lint-staged"] = {
|
||||
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
|
||||
"biome check --write .",
|
||||
],
|
||||
};
|
||||
if (linter === "oxlint") {
|
||||
packageJson["lint-staged"] = {
|
||||
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint",
|
||||
};
|
||||
} else if (linter === "biome") {
|
||||
packageJson["lint-staged"] = {
|
||||
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
|
||||
"biome check --write .",
|
||||
],
|
||||
};
|
||||
} else {
|
||||
packageJson["lint-staged"] = {
|
||||
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "",
|
||||
};
|
||||
}
|
||||
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
@@ -157,3 +196,33 @@ async function setupPwa(projectDir: string, frontends: Frontend[]) {
|
||||
await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
|
||||
}
|
||||
}
|
||||
|
||||
async function setupOxlint(projectDir: string, packageManager: PackageManager) {
|
||||
await addPackageDependency({
|
||||
devDependencies: ["oxlint"],
|
||||
projectDir,
|
||||
});
|
||||
|
||||
const packageJsonPath = path.join(projectDir, "package.json");
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
check: "oxlint",
|
||||
};
|
||||
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
|
||||
const oxlintInitCommand = getPackageExecutionCommand(
|
||||
packageManager,
|
||||
"oxlint@latest --init",
|
||||
);
|
||||
|
||||
await execa(oxlintInitCommand, {
|
||||
cwd: projectDir,
|
||||
env: { CI: "true" },
|
||||
shell: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { AvailableDependencies } from "../../constants";
|
||||
import type { Frontend, ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
|
||||
export async function setupApi(config: ProjectConfig): Promise<void> {
|
||||
export async function setupApi(config: ProjectConfig) {
|
||||
const { api, projectName, frontend, backend, packageManager, projectDir } =
|
||||
config;
|
||||
const isConvex = backend === "convex";
|
||||
|
||||
@@ -5,7 +5,7 @@ import pc from "picocolors";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
|
||||
export async function setupAuth(config: ProjectConfig): Promise<void> {
|
||||
export async function setupAuth(config: ProjectConfig) {
|
||||
const { auth, frontend, backend, projectDir } = config;
|
||||
if (backend === "convex" || !auth) {
|
||||
return;
|
||||
|
||||
@@ -3,9 +3,7 @@ import type { AvailableDependencies } from "../../constants";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
|
||||
export async function setupBackendDependencies(
|
||||
config: ProjectConfig,
|
||||
): Promise<void> {
|
||||
export async function setupBackendDependencies(config: ProjectConfig) {
|
||||
const { backend, runtime, api, projectDir } = config;
|
||||
|
||||
if (backend === "convex") {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { setupPrismaPostgres } from "../database-providers/prisma-postgres-setup
|
||||
import { setupSupabase } from "../database-providers/supabase-setup";
|
||||
import { setupTurso } from "../database-providers/turso-setup";
|
||||
|
||||
export async function setupDatabase(config: ProjectConfig): Promise<void> {
|
||||
export async function setupDatabase(config: ProjectConfig) {
|
||||
const { database, orm, dbSetup, backend, projectDir } = config;
|
||||
|
||||
if (backend === "convex" || database === "none") {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { AvailableDependencies } from "../../constants";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
|
||||
export async function setupExamples(config: ProjectConfig): Promise<void> {
|
||||
export async function setupExamples(config: ProjectConfig) {
|
||||
const { examples, frontend, backend, projectDir } = config;
|
||||
|
||||
if (
|
||||
|
||||
96
apps/cli/src/helpers/setup/fumadocs-setup.ts
Normal file
96
apps/cli/src/helpers/setup/fumadocs-setup.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import path from "node:path";
|
||||
import { cancel, isCancel, log, select } from "@clack/prompts";
|
||||
import consola from "consola";
|
||||
import { execa } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||
|
||||
type FumadocsTemplate =
|
||||
| "next-mdx"
|
||||
| "next-content-collections"
|
||||
| "react-router-mdx-remote"
|
||||
| "tanstack-start-mdx-remote";
|
||||
|
||||
const TEMPLATES = {
|
||||
"next-mdx": {
|
||||
label: "Next.js: Fumadocs MDX",
|
||||
hint: "Recommended template with MDX support",
|
||||
value: "+next+fuma-docs-mdx",
|
||||
},
|
||||
"next-content-collections": {
|
||||
label: "Next.js: Content Collections",
|
||||
hint: "Template using Next.js content collections",
|
||||
value: "+next+content-collections",
|
||||
},
|
||||
"react-router-mdx-remote": {
|
||||
label: "React Router: MDX Remote",
|
||||
hint: "Template for React Router with MDX remote",
|
||||
value: "react-router",
|
||||
},
|
||||
"tanstack-start-mdx-remote": {
|
||||
label: "Tanstack Start: MDX Remote",
|
||||
hint: "Template for Tanstack Start with MDX remote",
|
||||
value: "tanstack-start",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export async function setupFumadocs(config: ProjectConfig) {
|
||||
const { packageManager, projectDir } = config;
|
||||
|
||||
try {
|
||||
log.info("Setting up Fumadocs...");
|
||||
|
||||
const template = await select<FumadocsTemplate>({
|
||||
message: "Choose a template",
|
||||
options: Object.entries(TEMPLATES).map(([key, template]) => ({
|
||||
value: key as FumadocsTemplate,
|
||||
label: template.label,
|
||||
hint: template.hint,
|
||||
})),
|
||||
initialValue: "next-mdx",
|
||||
});
|
||||
|
||||
if (isCancel(template)) {
|
||||
cancel(pc.red("Operation cancelled"));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const templateArg = TEMPLATES[template].value;
|
||||
|
||||
const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --no-install --pm ${packageManager} --no-eslint`;
|
||||
|
||||
const fumadocsInitCommand = getPackageExecutionCommand(
|
||||
packageManager,
|
||||
commandWithArgs,
|
||||
);
|
||||
|
||||
await execa(fumadocsInitCommand, {
|
||||
cwd: path.join(projectDir, "apps"),
|
||||
env: { CI: "true" },
|
||||
shell: true,
|
||||
});
|
||||
|
||||
const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
|
||||
const packageJsonPath = path.join(fumadocsDir, "package.json");
|
||||
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
packageJson.name = "fumadocs";
|
||||
|
||||
if (packageJson.scripts?.dev) {
|
||||
packageJson.scripts.dev = `${packageJson.scripts.dev} --port=4000`;
|
||||
}
|
||||
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
|
||||
log.success("Fumadocs setup successfully!");
|
||||
} catch (error) {
|
||||
log.error(pc.red("Failed to set up Fumadocs"));
|
||||
if (error instanceof Error) {
|
||||
consola.error(pc.red(error.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import pc from "picocolors";
|
||||
import type { Backend, ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
|
||||
export async function setupRuntime(config: ProjectConfig): Promise<void> {
|
||||
export async function setupRuntime(config: ProjectConfig) {
|
||||
const { runtime, backend, projectDir } = config;
|
||||
|
||||
if (backend === "convex" || backend === "next" || runtime === "none") {
|
||||
@@ -28,9 +28,7 @@ export async function setupRuntime(config: ProjectConfig): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateCloudflareWorkerTypes(
|
||||
config: ProjectConfig,
|
||||
): Promise<void> {
|
||||
export async function generateCloudflareWorkerTypes(config: ProjectConfig) {
|
||||
if (config.runtime !== "workers") {
|
||||
return;
|
||||
}
|
||||
@@ -65,10 +63,7 @@ export async function generateCloudflareWorkerTypes(
|
||||
}
|
||||
}
|
||||
|
||||
async function setupBunRuntime(
|
||||
serverDir: string,
|
||||
_backend: Backend,
|
||||
): Promise<void> {
|
||||
async function setupBunRuntime(serverDir: string, _backend: Backend) {
|
||||
const packageJsonPath = path.join(serverDir, "package.json");
|
||||
if (!(await fs.pathExists(packageJsonPath))) return;
|
||||
|
||||
@@ -88,10 +83,7 @@ async function setupBunRuntime(
|
||||
});
|
||||
}
|
||||
|
||||
async function setupNodeRuntime(
|
||||
serverDir: string,
|
||||
backend: Backend,
|
||||
): Promise<void> {
|
||||
async function setupNodeRuntime(serverDir: string, backend: Backend) {
|
||||
const packageJsonPath = path.join(serverDir, "package.json");
|
||||
if (!(await fs.pathExists(packageJsonPath))) return;
|
||||
|
||||
@@ -123,7 +115,7 @@ async function setupNodeRuntime(
|
||||
}
|
||||
}
|
||||
|
||||
async function setupWorkersRuntime(serverDir: string): Promise<void> {
|
||||
async function setupWorkersRuntime(serverDir: string) {
|
||||
const packageJsonPath = path.join(serverDir, "package.json");
|
||||
if (!(await fs.pathExists(packageJsonPath))) return;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import pc from "picocolors";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||
|
||||
export async function setupStarlight(config: ProjectConfig): Promise<void> {
|
||||
export async function setupStarlight(config: ProjectConfig) {
|
||||
const { packageManager, projectDir } = config;
|
||||
const s = spinner();
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||
|
||||
export async function setupTauri(config: ProjectConfig): Promise<void> {
|
||||
export async function setupTauri(config: ProjectConfig) {
|
||||
const { packageManager, frontend, projectDir } = config;
|
||||
const s = spinner();
|
||||
const clientPackageDir = path.join(projectDir, "apps/web");
|
||||
|
||||
141
apps/cli/src/helpers/setup/ultracite-setup.ts
Normal file
141
apps/cli/src/helpers/setup/ultracite-setup.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { cancel, isCancel, log, multiselect } from "@clack/prompts";
|
||||
import { execa } from "execa";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||
import { setupBiome } from "./addons-setup";
|
||||
|
||||
type UltraciteEditor = "vscode" | "zed";
|
||||
type UltraciteRule =
|
||||
| "vscode-copilot"
|
||||
| "cursor"
|
||||
| "windsurf"
|
||||
| "zed"
|
||||
| "claude"
|
||||
| "codex";
|
||||
|
||||
const EDITORS = {
|
||||
vscode: {
|
||||
label: "VSCode / Cursor / Windsurf",
|
||||
hint: "Visual Studio Code editor configuration",
|
||||
},
|
||||
zed: {
|
||||
label: "Zed",
|
||||
hint: "Zed editor configuration",
|
||||
},
|
||||
} as const;
|
||||
|
||||
const RULES = {
|
||||
"vscode-copilot": {
|
||||
label: "VS Code Copilot",
|
||||
hint: "GitHub Copilot integration for VS Code",
|
||||
},
|
||||
cursor: {
|
||||
label: "Cursor",
|
||||
hint: "Cursor AI editor configuration",
|
||||
},
|
||||
windsurf: {
|
||||
label: "Windsurf",
|
||||
hint: "Windsurf editor configuration",
|
||||
},
|
||||
zed: {
|
||||
label: "Zed",
|
||||
hint: "Zed editor rules",
|
||||
},
|
||||
claude: {
|
||||
label: "Claude",
|
||||
hint: "Claude AI integration",
|
||||
},
|
||||
codex: {
|
||||
label: "Codex",
|
||||
hint: "Codex AI integration",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export async function setupUltracite(config: ProjectConfig, hasHusky: boolean) {
|
||||
const { packageManager, projectDir } = config;
|
||||
|
||||
try {
|
||||
log.info("Setting up Ultracite...");
|
||||
|
||||
await setupBiome(projectDir);
|
||||
|
||||
const editors = await multiselect<UltraciteEditor>({
|
||||
message: "Choose editors",
|
||||
options: Object.entries(EDITORS).map(([key, editor]) => ({
|
||||
value: key as UltraciteEditor,
|
||||
label: editor.label,
|
||||
hint: editor.hint,
|
||||
})),
|
||||
required: false,
|
||||
});
|
||||
|
||||
if (isCancel(editors)) {
|
||||
cancel(pc.red("Operation cancelled"));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const rules = await multiselect<UltraciteRule>({
|
||||
message: "Choose rules",
|
||||
options: Object.entries(RULES).map(([key, rule]) => ({
|
||||
value: key as UltraciteRule,
|
||||
label: rule.label,
|
||||
hint: rule.hint,
|
||||
})),
|
||||
required: false,
|
||||
});
|
||||
|
||||
if (isCancel(rules)) {
|
||||
cancel(pc.red("Operation cancelled"));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const ultraciteArgs = ["init", "--pm", packageManager];
|
||||
|
||||
if (editors.length > 0) {
|
||||
ultraciteArgs.push("--editors", ...editors);
|
||||
}
|
||||
|
||||
if (rules.length > 0) {
|
||||
ultraciteArgs.push("--rules", ...rules);
|
||||
}
|
||||
|
||||
if (hasHusky) {
|
||||
ultraciteArgs.push("--features", "husky", "lint-staged");
|
||||
}
|
||||
|
||||
const ultraciteArgsString = ultraciteArgs.join(" ");
|
||||
const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
|
||||
|
||||
const ultraciteInitCommand = getPackageExecutionCommand(
|
||||
packageManager,
|
||||
commandWithArgs,
|
||||
);
|
||||
|
||||
await execa(ultraciteInitCommand, {
|
||||
cwd: projectDir,
|
||||
env: { CI: "true" },
|
||||
shell: true,
|
||||
});
|
||||
|
||||
if (hasHusky) {
|
||||
await addPackageDependency({
|
||||
devDependencies: ["husky", "lint-staged"],
|
||||
projectDir,
|
||||
});
|
||||
}
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["ultracite"],
|
||||
projectDir,
|
||||
});
|
||||
|
||||
log.success("Ultracite setup successfully!");
|
||||
} catch (error) {
|
||||
log.error(pc.red("Failed to set up Ultracite"));
|
||||
if (error instanceof Error) {
|
||||
console.error(pc.red(error.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { ensureArrayProperty, tsProject } from "../../utils/ts-morph";
|
||||
export async function addPwaToViteConfig(
|
||||
viteConfigPath: string,
|
||||
projectName: string,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
||||
if (!sourceFile) {
|
||||
throw new Error("vite config not found");
|
||||
|
||||
@@ -7,7 +7,7 @@ import { setupSvelteWorkersDeploy } from "./workers-svelte-setup";
|
||||
import { setupTanstackStartWorkersDeploy } from "./workers-tanstack-start-setup";
|
||||
import { setupWorkersVitePlugin } from "./workers-vite-setup";
|
||||
|
||||
export async function setupWebDeploy(config: ProjectConfig): Promise<void> {
|
||||
export async function setupWebDeploy(config: ProjectConfig) {
|
||||
const { webDeploy, frontend, projectDir } = config;
|
||||
const { packageManager } = config;
|
||||
|
||||
@@ -39,7 +39,7 @@ export async function setupWebDeploy(config: ProjectConfig): Promise<void> {
|
||||
async function setupWorkersWebDeploy(
|
||||
projectDir: string,
|
||||
pkgManager: PackageManager,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
|
||||
if (!(await fs.pathExists(webAppDir))) {
|
||||
@@ -65,7 +65,7 @@ async function setupWorkersWebDeploy(
|
||||
async function setupNextWorkersDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { tsProject } from "../../utils/ts-morph";
|
||||
export async function setupNuxtWorkersDeploy(
|
||||
projectDir: string,
|
||||
packageManager: PackageManager,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { tsProject } from "../../utils/ts-morph";
|
||||
export async function setupSvelteWorkersDeploy(
|
||||
projectDir: string,
|
||||
packageManager: PackageManager,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ensureArrayProperty, tsProject } from "../../utils/ts-morph";
|
||||
export async function setupTanstackStartWorkersDeploy(
|
||||
projectDir: string,
|
||||
packageManager: PackageManager,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ import {
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { ensureArrayProperty, tsProject } from "../../utils/ts-morph";
|
||||
|
||||
export async function setupWorkersVitePlugin(
|
||||
projectDir: string,
|
||||
): Promise<void> {
|
||||
export async function setupWorkersVitePlugin(projectDir: string) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user