@pierrehrt/create-content-builder
Version:
CLI tool to create content builder projects
405 lines (398 loc) • 14.1 kB
JavaScript
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();