mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat: add command (#337)
This commit is contained in:
104
apps/cli/src/helpers/project-generation/add-addons.ts
Normal file
104
apps/cli/src/helpers/project-generation/add-addons.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import path from "node:path";
|
||||
import { cancel, log } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import type { AddInput, Addons, ProjectConfig } from "../../types";
|
||||
import { validateAddonCompatibility } from "../../utils/addon-compatibility";
|
||||
import { updateBtsConfig } from "../../utils/bts-config";
|
||||
import { setupAddons } from "../setup/addons-setup";
|
||||
import {
|
||||
detectProjectConfig,
|
||||
isBetterTStackProject,
|
||||
} from "./detect-project-config";
|
||||
import { installDependencies } from "./install-dependencies";
|
||||
import { setupAddonsTemplate } from "./template-manager";
|
||||
|
||||
function exitWithError(message: string): never {
|
||||
cancel(pc.red(message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export async function addAddonsToProject(
|
||||
input: AddInput & { addons: Addons[] },
|
||||
): Promise<void> {
|
||||
try {
|
||||
const projectDir = input.projectDir || process.cwd();
|
||||
|
||||
const isBetterTStack = await isBetterTStackProject(projectDir);
|
||||
if (!isBetterTStack) {
|
||||
exitWithError(
|
||||
"This doesn't appear to be a Better-T Stack project. Please run this command from the root of a Better-T Stack project.",
|
||||
);
|
||||
}
|
||||
|
||||
const detectedConfig = await detectProjectConfig(projectDir);
|
||||
if (!detectedConfig) {
|
||||
exitWithError(
|
||||
"Could not detect the project configuration. Please ensure this is a valid Better-T Stack project.",
|
||||
);
|
||||
}
|
||||
|
||||
const config: ProjectConfig = {
|
||||
projectName: detectedConfig.projectName || path.basename(projectDir),
|
||||
projectDir,
|
||||
relativePath: ".",
|
||||
database: detectedConfig.database || "none",
|
||||
orm: detectedConfig.orm || "none",
|
||||
backend: detectedConfig.backend || "none",
|
||||
runtime: detectedConfig.runtime || "none",
|
||||
frontend: detectedConfig.frontend || [],
|
||||
addons: input.addons,
|
||||
examples: detectedConfig.examples || [],
|
||||
auth: detectedConfig.auth || false,
|
||||
git: false,
|
||||
packageManager:
|
||||
input.packageManager || detectedConfig.packageManager || "npm",
|
||||
install: input.install || false,
|
||||
dbSetup: detectedConfig.dbSetup || "none",
|
||||
api: detectedConfig.api || "none",
|
||||
};
|
||||
|
||||
for (const addon of input.addons) {
|
||||
const { isCompatible, reason } = validateAddonCompatibility(
|
||||
addon,
|
||||
config.frontend,
|
||||
);
|
||||
if (!isCompatible) {
|
||||
exitWithError(
|
||||
reason ||
|
||||
`${addon} addon is not compatible with current frontend configuration`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log.info(
|
||||
pc.green(
|
||||
`Adding ${input.addons.join(", ")} to ${config.frontend.join("/")}`,
|
||||
),
|
||||
);
|
||||
|
||||
await setupAddonsTemplate(projectDir, config);
|
||||
await setupAddons(config, true);
|
||||
|
||||
const currentAddons = detectedConfig.addons || [];
|
||||
const mergedAddons = [...new Set([...currentAddons, ...input.addons])];
|
||||
await updateBtsConfig(projectDir, { addons: mergedAddons });
|
||||
|
||||
if (config.install) {
|
||||
await installDependencies({
|
||||
projectDir,
|
||||
packageManager: config.packageManager,
|
||||
});
|
||||
} else {
|
||||
log.info(
|
||||
pc.yellow(
|
||||
`Run ${pc.bold(
|
||||
`${config.packageManager} install`,
|
||||
)} to install dependencies`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
exitWithError(`Error adding addons: ${message}`);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { cancel, log } from "@clack/prompts";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { writeBtsConfig } from "../../utils/bts-config";
|
||||
import { setupAddons } from "../setup/addons-setup";
|
||||
import { setupApi } from "../setup/api-setup";
|
||||
import { setupAuth } from "../setup/auth-setup";
|
||||
@@ -71,6 +72,9 @@ export async function createProject(options: ProjectConfig) {
|
||||
await setupEnvironmentVariables(options);
|
||||
await updatePackageConfigurations(projectDir, options);
|
||||
await createReadme(projectDir, options);
|
||||
|
||||
await writeBtsConfig(options);
|
||||
|
||||
await initializeGit(projectDir, options.git);
|
||||
|
||||
log.success("Project template successfully scaffolded!");
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { readBtsConfig } from "../../utils/bts-config";
|
||||
|
||||
export async function detectProjectConfig(
|
||||
projectDir: string,
|
||||
): Promise<Partial<ProjectConfig> | null> {
|
||||
try {
|
||||
const btsConfig = await readBtsConfig(projectDir);
|
||||
if (btsConfig) {
|
||||
return {
|
||||
projectDir,
|
||||
projectName: path.basename(projectDir),
|
||||
database: btsConfig.database,
|
||||
orm: btsConfig.orm,
|
||||
backend: btsConfig.backend,
|
||||
runtime: btsConfig.runtime,
|
||||
frontend: btsConfig.frontend,
|
||||
addons: btsConfig.addons,
|
||||
examples: btsConfig.examples,
|
||||
auth: btsConfig.auth,
|
||||
packageManager: btsConfig.packageManager,
|
||||
dbSetup: btsConfig.dbSetup,
|
||||
api: btsConfig.api,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function isBetterTStackProject(
|
||||
projectDir: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import path from "node:path";
|
||||
import { log } from "@clack/prompts";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { Frontend, ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { setupStarlight } from "./starlight-setup";
|
||||
import { setupTauri } from "./tauri-setup";
|
||||
|
||||
export async function setupAddons(config: ProjectConfig) {
|
||||
export async function setupAddons(config: ProjectConfig, isAddCommand = false) {
|
||||
const { addons, frontend, projectDir } = config;
|
||||
const hasReactWebFrontend =
|
||||
frontend.includes("react-router") ||
|
||||
@@ -21,6 +23,20 @@ export async function setupAddons(config: ProjectConfig) {
|
||||
devDependencies: ["turbo"],
|
||||
projectDir,
|
||||
});
|
||||
|
||||
if (isAddCommand) {
|
||||
log.info(`${pc.yellow("Update your package.json scripts:")}
|
||||
|
||||
${pc.dim("Replace:")} ${pc.yellow('"pnpm -r dev"')} ${pc.dim("→")} ${pc.green(
|
||||
'"turbo dev"',
|
||||
)}
|
||||
${pc.dim("Replace:")} ${pc.yellow('"pnpm --filter web dev"')} ${pc.dim(
|
||||
"→",
|
||||
)} ${pc.green('"turbo -F web dev"')}
|
||||
|
||||
${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
if (addons.includes("pwa") && (hasReactWebFrontend || hasSolidFrontend)) {
|
||||
|
||||
Reference in New Issue
Block a user