UNPKG

@pierrehrt/create-content-builder

Version:

CLI tool to create content builder projects

405 lines (398 loc) 14.1 kB
#!/usr/bin/env node var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/index.ts import { Command } from "commander"; import chalk3 from "chalk"; import ora3 from "ora"; import fs3 from "fs-extra"; import path4 from "path"; import { fileURLToPath as fileURLToPath3 } from "url"; // src/commands/create.ts import inquirer from "inquirer"; import chalk from "chalk"; import ora from "ora"; import path2 from "path"; import { fileURLToPath as fileURLToPath2 } from "url"; // src/utils/template.ts import fs from "fs-extra"; import path from "path"; import { fileURLToPath } from "url"; var __filename = fileURLToPath(import.meta.url); var __dirname = path.dirname(__filename); async function createProjectFromTemplate(options) { const possibleTemplatePaths = [ path.join(__dirname, "../templates/base"), // If we're in dist/utils path.join(__dirname, "templates/base") // If we're in dist ]; console.log("Running from directory:", __dirname); console.log("Checking template paths:"); possibleTemplatePaths.forEach((p) => console.log("- ", p)); let templateDir; for (const templatePath of possibleTemplatePaths) { console.log("Checking path:", templatePath); console.log("Path exists:", fs.existsSync(templatePath)); if (fs.existsSync(templatePath)) { templateDir = templatePath; console.log("Found templates at:", templatePath); break; } } if (!templateDir) { throw new Error( "Templates directory not found. Available paths were:\n" + possibleTemplatePaths.map((p) => `- ${p}`).join("\n") ); } const targetDir = path.join( process.cwd(), options.name ?? "content-builder-app" ); console.log("Creating project at:", targetDir); if (fs.existsSync(targetDir)) { throw new Error( `Directory ${targetDir} already exists. Please choose a different name or remove the existing directory.` ); } await fs.copy(templateDir, targetDir); const packageJsonPath = path.join(targetDir, "package.json"); const packageJson = await fs.readJson(packageJsonPath); packageJson.name = options.name; switch (options.database) { case "postgres": packageJson.dependencies["@prisma/client"] = "^5.0.0"; packageJson.devDependencies["prisma"] = "^5.0.0"; break; case "firebase": packageJson.dependencies["firebase-admin"] = "^11.0.0"; packageJson.dependencies["firebase"] = "^10.0.0"; break; case "sqlite": packageJson.dependencies["better-sqlite3"] = "^8.0.0"; break; } switch (options.auth) { case "firebase-auth": if (!packageJson.dependencies["firebase"]) { packageJson.dependencies["firebase"] = "^10.0.0"; } break; case "clerk": packageJson.dependencies["@clerk/nextjs"] = "^4.23.2"; break; case "auth0": packageJson.dependencies["@auth0/nextjs-auth0"] = "^3.0.0"; break; } await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); const envExample = path.join(targetDir, ".env.example"); const envLocal = path.join(targetDir, ".env.local"); await fs.copy(envExample, envLocal); try { const { execSync: execSync3 } = __require("child_process"); execSync3("git init", { cwd: targetDir }); await fs.writeFile( path.join(targetDir, ".gitignore"), `.env* .env.local node_modules .next ` ); } catch (error) { console.warn("Failed to initialize git repository"); } return targetDir; } // src/commands/create.ts var __filename2 = fileURLToPath2(import.meta.url); var __dirname2 = path2.dirname(__filename2); async function createProject(name) { console.log(chalk.bold("\n\u{1F680} Welcome to Content Builder Setup!\n")); if (!name) { const response = await inquirer.prompt([ { type: "input", name: "projectName", message: "What is your project named?", default: "content-builder-app" } ]); name = response.projectName; } const { database } = await inquirer.prompt({ type: "list", name: "database", message: "Select your database:", choices: [ { name: chalk.blue("PostgreSQL"), value: "postgres" }, { name: chalk.yellow("Firebase"), value: "firebase" }, { name: chalk.green("SQLite"), value: "sqlite" } ] }); const { auth } = await inquirer.prompt({ type: "list", name: "auth", message: "Select your authentication provider:", choices: [ { name: chalk.yellow("Firebase Auth"), value: "firebase-auth" }, { name: chalk.blue("Clerk"), value: "clerk" }, { name: chalk.cyan("Auth0"), value: "auth0" } ] }); const spinner = ora("Creating your project...").start(); try { const projectDir = await createProjectFromTemplate({ name, database, auth }); spinner.succeed(chalk.green("Project created successfully! \u{1F389}")); console.log("\nNext steps:"); console.log(chalk.cyan(` cd ${name}`)); console.log(chalk.cyan(" npm install")); console.log(chalk.cyan(" npm run dev")); console.log(chalk.cyan("\nDon't forget to:")); console.log(chalk.cyan(" 1. Update .env.local with your credentials")); console.log(chalk.cyan(" 2. Follow the setup guide in README.md")); } catch (error) { spinner.fail(chalk.red("Failed to create project")); console.error(error); process.exit(1); } } // src/commands/update.ts import chalk2 from "chalk"; import ora2 from "ora"; import fs2 from "fs-extra"; import path3 from "path"; import semver from "semver"; import { execSync } from "child_process"; import { diffLines } from "diff"; async function updateProject(options = {}) { const spinner = ora2("Checking for updates...").start(); try { const projectRoot = process.cwd(); const configPath = path3.join(projectRoot, "content-builder.config.ts"); if (!fs2.existsSync(configPath)) { spinner.fail("No content-builder project found in current directory"); process.exit(1); } const currentConfig = await fs2.readJson(configPath); const currentVersion = currentConfig.version; const latestVersion = await getLatestVersion(); if (!options.force && !semver.gt(latestVersion, currentVersion)) { spinner.succeed("Project is already up to date"); return; } spinner.text = "Fetching update manifest..."; const updates = await getUpdatesForVersion(currentVersion, latestVersion); if (options.dryRun) { spinner.stop(); displayUpdatePreview(updates); return; } spinner.text = "Creating backup..."; await createBackup(projectRoot); spinner.text = "Applying updates..."; await applyUpdates(updates, projectRoot); spinner.text = "Updating dependencies..."; await updateDependencies(projectRoot); currentConfig.version = latestVersion; await fs2.writeJson(configPath, currentConfig, { spaces: 2 }); spinner.succeed( chalk2.green(`Successfully updated to version ${latestVersion}`) ); console.log("\nChanges made:"); displayChangeSummary(updates); console.log("\nNext steps:"); console.log(chalk2.cyan("1. Review the changes in your project")); console.log( chalk2.cyan( "2. Check the backup folder if you need to restore any customizations" ) ); console.log(chalk2.cyan("3. Run your tests to ensure everything works")); console.log( chalk2.cyan("4. Commit the changes to your version control system\n") ); } catch (error) { spinner.fail(chalk2.red("Update failed")); console.error(error); console.log("\nTo recover:"); console.log(chalk2.cyan("1. Check the error message above")); console.log(chalk2.cyan("2. Restore from backup if needed: .-backup")); console.log( chalk2.cyan("3. Run update again with --force flag if necessary") ); process.exit(1); } } async function getLatestVersion() { const result = execSync("npm show create-content-builder version").toString().trim(); return result; } async function getUpdatesForVersion(currentVersion, targetVersion) { return [ { path: "components/ContentBuilder/index.tsx", content: "// Updated content builder component", type: "modify" }, { path: "lib/database/types.ts", content: "// Updated database types", type: "modify" } // Add more files that need updating ]; } async function createBackup(projectRoot) { const backupDir = path3.join(projectRoot, "content-builder-backup"); const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"); const backupPath = path3.join(backupDir, timestamp); await fs2.ensureDir(backupPath); await fs2.copy(projectRoot, backupPath, { filter: (src) => { return !src.includes("node_modules") && !src.includes(".git") && !src.includes("content-builder-backup"); } }); } async function applyUpdates(updates, projectRoot) { for (const update of updates) { const filePath = path3.join(projectRoot, update.path); switch (update.type) { case "add": await fs2.ensureDir(path3.dirname(filePath)); await fs2.writeFile(filePath, update.content); break; case "modify": if (fs2.existsSync(filePath)) { const currentContent = await fs2.readFile(filePath, "utf8"); const diff = diffLines(currentContent, update.content); if (diff.some((part) => part.added || part.removed)) { await fs2.writeFile(filePath, update.content); } } break; case "delete": if (fs2.existsSync(filePath)) { await fs2.remove(filePath); } break; } } } async function updateDependencies(projectRoot) { const packageJsonPath = path3.join(projectRoot, "package.json"); const packageJson = await fs2.readJson(packageJsonPath); packageJson.dependencies = { ...packageJson.dependencies // Add or update core dependencies }; await fs2.writeJson(packageJsonPath, packageJson, { spaces: 2 }); execSync("npm install", { stdio: "inherit", cwd: projectRoot }); } function displayUpdatePreview(updates) { console.log(chalk2.bold("\nUpdate Preview:\n")); const counts = { add: 0, modify: 0, delete: 0 }; updates.forEach((update) => { counts[update.type]++; console.log(`${getUpdateSymbol(update.type)} ${update.path}`); }); console.log("\nSummary:"); console.log(`Files to add: ${chalk2.green(counts.add)}`); console.log(`Files to modify: ${chalk2.yellow(counts.modify)}`); console.log(`Files to delete: ${chalk2.red(counts.delete)}`); console.log(chalk2.cyan("\nRun without --dry-run to apply these changes")); } function displayChangeSummary(updates) { updates.forEach((update) => { console.log(`${getUpdateSymbol(update.type)} ${update.path}`); }); } function getUpdateSymbol(type) { switch (type) { case "add": return chalk2.green("+"); case "modify": return chalk2.yellow("\u25CF"); case "delete": return chalk2.red("-"); } } // src/index.ts import { execSync as execSync2 } from "child_process"; var __filename3 = fileURLToPath3(import.meta.url); var __dirname3 = path4.dirname(__filename3); var program = new Command(); program.name("create-content-builder").description("CLI to create and manage content builder projects").version("1.0.0"); program.command("create [name]").description("Create a new content builder project").action(async (name) => { console.log(chalk3.bold("\n\u{1F680} Welcome to Content Builder Setup!\n")); await createProject(name); }); program.command("update").description("Update an existing content builder project").option("-f, --force", "Force update even if already up to date").option("--dry-run", "Show what would be updated without making changes").action(async (options) => { await updateProject(options); }); program.command("status").description("Check the status of your content builder project").action(async () => { const spinner = ora3("Checking project status...").start(); try { const status = await checkProjectStatus(); spinner.stop(); displayProjectStatus(status); } catch (error) { spinner.fail(chalk3.red("Failed to check project status")); console.error(error); } }); async function checkProjectStatus() { const configPath = path4.join(process.cwd(), "content-builder.config.ts"); const packageJsonPath = path4.join(process.cwd(), "package.json"); if (!fs3.existsSync(configPath)) { throw new Error("No content-builder project found in current directory"); } const config = await fs3.readJson(configPath); const packageJson = await fs3.readJson(packageJsonPath); return { version: packageJson.version, currentConfig: config, latestVersion: await getLatestVersion2(), dependencies: packageJson.dependencies }; } function displayProjectStatus(status) { console.log(chalk3.bold("\nContent Builder Status:\n")); console.log(`Current version: ${chalk3.cyan(status.version)}`); console.log(`Latest version: ${chalk3.cyan(status.latestVersion)}`); console.log( `Database: ${chalk3.cyan(status.currentConfig.database.type)}` ); console.log(`Auth: ${chalk3.cyan(status.currentConfig.auth.type)}`); if (status.version !== status.latestVersion) { console.log( chalk3.yellow("\nUpdate available! Run the following to update:") ); console.log(chalk3.cyan(" npx create-content-builder update")); } else { console.log(chalk3.green("\nProject is up to date!")); } } async function getLatestVersion2() { try { const result = execSync2("npm show create-content-builder version").toString().trim(); return result; } catch (error) { return "unknown"; } } program.parse();