UNPKG

@kevinmarrec/create-cloudstack-app

Version:

CLI that scaffolds an opinionated Bun & Vue fullstack application.

150 lines (141 loc) 4.56 kB
#!/usr/bin/env node // src/run.ts import process from "node:process"; import { parseArgs } from "node:util"; import { cancel, confirm, intro, isCancel, log, note, outro, tasks, text } from "@clack/prompts"; import c from "ansis"; import { resolve as resolve2 } from "pathe"; import { x } from "tinyexec"; // package.json var version = "1.0.0-rc.8"; // src/scaffold.ts import { join } from "pathe"; import { glob } from "tinyglobby"; // src/utils/fs.ts import fs from "node:fs/promises"; import { resolve } from "pathe"; var ignorePredicate = (filename) => [".git"].includes(filename); async function empty(dir) { for (const entry of await fs.readdir(dir)) { if (ignorePredicate(entry)) { continue; } await fs.rm(resolve(dir, entry), { recursive: true }); } } async function emptyCheck(path) { return fs.readdir(path).then((files) => files.every(ignorePredicate)).catch(() => true); } async function exists(path) { return fs.access(path).then(() => true).catch(() => false); } var fs_default = { ...fs, empty, emptyCheck, exists }; // src/scaffold.ts async function scaffold(root) { await fs_default.exists(root) ? await fs_default.empty(root) : await fs_default.mkdir(root); await fs_default.cp(join(import.meta.dirname, "../template"), root, { recursive: true }); await fs_default.rename(join(root, "gitignore"), join(root, ".gitignore")); const files = await glob("**/*.{json,ts,vue}", { cwd: root, absolute: true }); await Promise.all(files.map(async (file) => { const content = await fs_default.readFile(file, "utf-8"); if (!content.includes("@kevinmarrec/cloudstack-")) { return; } await fs_default.writeFile( file, file.includes("package.json") ? content.replace(/"(@kevinmarrec\/cloudstack-(.*))": "workspace:\*"/g, `"@cloudstack/$2": "npm:$1@^${version}"`) : content.replace(/@kevinmarrec\/cloudstack-(.*)/g, "@cloudstack/$1") ); })); } // src/run.ts function maybeCancel(value, options) { if (isCancel(value) || options?.strict && !value) { cancel("Operation cancelled"); process.exit(1); } } async function run() { const { values: options, positionals } = parseArgs({ args: process.argv.slice(2), allowPositionals: true, options: { force: { type: "boolean", short: "f" }, help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" } } }); if (options.help) { process.stdout.write(`Usage: create-app [OPTIONS...] [DIRECTORY] Options: -f, --force Create the project even if the directory is not empty. -h, --help Display this help message. --version Display the version number of this CLI. `); process.exit(0); } if (options.version) { process.stdout.write(`${version} `); process.exit(0); } process.stdout.write("\n"); intro(`Cloudstack ${c.dim(`v${version}`)}`); let projectName = positionals[0] || await text({ message: "Project name", placeholder: "my-app", validate: (value) => { if (!value.trim()) return "Project name cannot be empty"; } }); maybeCancel(projectName); projectName = projectName.trim(); const cwd = process.cwd(); const targetDir = resolve2(cwd, projectName); if (!(await fs_default.emptyCheck(targetDir) || options.force)) { await log.warn(`${targetDir === cwd ? "Current directory" : `Target directory ${c.blue(targetDir)}`} is not empty`); const shouldOverwrite = await confirm({ message: "Remove existing files and continue?", initialValue: true, active: "Yes", inactive: "No" }); maybeCancel(shouldOverwrite, { strict: true }); } await tasks([{ title: `Scaffolding project in ${c.blue(targetDir)}`, task: async () => { await scaffold(targetDir); return `Scaffolded project in ${c.blue(targetDir)}`; } }]); const shouldInstall = await confirm({ message: "Install dependencies?", initialValue: true, active: "Yes", inactive: "No" }); maybeCancel(shouldInstall); await tasks([{ title: "Installing with bun", enabled: shouldInstall, task: async () => { await x("bun", ["install", "--cwd", targetDir, "--force"]); return "Installed with bun"; } }]); await note([ targetDir !== cwd && `cd ${c.reset.blue(projectName)}`, `bun run dev` ].filter(Boolean).join("\n"), "Next steps"); await outro(`Problems? ${c.cyan("https://github.com/kevinmarrec/cloudstack/issues")}`); } // src/index.ts run().catch((error) => { console.error(error); });