UNPKG

create-moost

Version:
344 lines (336 loc) 9.81 kB
import { Cli, CliApp, Param } from "@moostjs/event-cli"; import { useAutoHelp, useCliOption } from "@wooksjs/event-cli"; import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmdirSync, unlinkSync } from "fs"; import prompts from "prompts"; import { ProstoRewrite } from "@prostojs/rewrite"; import { join } from "path"; //#region packages/create-moost/src/prompts.ts const defaultProjectName = "moost-app"; async function getPrompts(inputs) { const predefined = { targetDir: inputs.name || "", projectName: inputs.name || "", packageName: inputs.name || "" }; if (inputs.ws) predefined.ws = true; if (inputs.wf) predefined.wf = true; if (inputs.ssr) predefined.ssr = true; if (inputs.oxc) predefined.oxc = true; if (inputs.force) predefined.overwrite = true; try { const results = await prompts([ { name: "projectName", type: predefined.targetDir ? null : "text", message: "Project name:", initial: defaultProjectName, onState: (state) => predefined.targetDir = String(state.value).trim() || defaultProjectName }, { name: "overwrite", type: () => canSkipEmptying(predefined.targetDir) || inputs.force ? null : "confirm", message: () => { return `${predefined.targetDir === "." ? "Current directory" : `Target directory "${predefined.targetDir}"`} is not empty. Remove existing files and continue?`; } }, { name: "overwriteChecker", type: (prev, values) => { if (values.overwrite === false) throw new Error("Operation cancelled"); return null; } }, { name: "type", type: () => { const selected = [ "http", "cli", "ws", "ssr" ].filter((t) => inputs[t] && !(t === "ws" && inputs.http)); if (selected.length === 1) { predefined.type = selected[0]; return null; } return "select"; }, message: "Moost Adapter:", choices: [ { title: "HTTP (Web) Application", value: "http" }, { title: "Vue + Moost (SSR/SPA)", value: "ssr" }, { title: "WebSocket Application", value: "ws" }, { title: "CLI Application", value: "cli" } ] }, { name: "packageName", type: () => isValidPackageName(predefined.targetDir) ? null : "text", message: "Package name:", initial: () => toValidPackageName(predefined.targetDir), validate: (dir) => isValidPackageName(dir) || "Invalid package.json name" }, { name: "ssr", type: (prev, values) => { if ((values.type || predefined.type) !== "ssr" || inputs.ssr) return null; return "toggle"; }, message: "Enable SSR (Server-Side Rendering)?", initial: true, active: "Yes", inactive: "No (SPA only)" }, { name: "ws", type: (prev, values) => { if ((values.type || predefined.type) !== "http" || inputs.ws) return null; return "toggle"; }, message: "Add WebSockets?", initial: false, active: "Yes", inactive: "No" }, { name: "wf", type: (prev, values) => { const type = values.type || predefined.type; if (inputs.wf || type === "cli" || type === "ws") return null; return "toggle"; }, message: "Add Moost Workflows Example?", initial: false, active: "Yes", inactive: "No" }, { name: "oxc", type: () => inputs.oxc ? null : "toggle", message: "Add OXC lint and formatter (oxlint + oxfmt)?", initial: false, active: "Yes", inactive: "No" } ], { onCancel: () => { throw new Error("Operation cancelled"); } }); return { ...predefined, ...results, packageName: results.packageName || results.targetDir || predefined.targetDir }; } catch (error) { console.log(error.message); process.exit(1); } } function canSkipEmptying(dir) { if (!existsSync(dir)) return true; const files = readdirSync(dir); if (files.length === 0) return true; if (files.length === 1 && files[0] === ".git") return true; return false; } function isValidPackageName(projectName) { return /^(?:@[\d*a-z~-][\d*._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*$/.test(projectName); } function toValidPackageName(projectName) { return projectName.trim().toLowerCase().replaceAll(/\s+/g, "-").replace(/^[._]/, "").replaceAll(/[^\da-z~-]+/g, "-"); } //#endregion //#region packages/create-moost/src/scaffold.ts const rw = new ProstoRewrite({ textPattern: [ "*.{js,jsx,ts,tsx,txt,json,jsonc,yml,yaml,md,ini,css,html}", "Dockerfile", "*config", ".gitignore", ".oxlintrc.json", ".oxfmtrc.json" ] }); const root = process.cwd(); const { version } = JSON.parse(readFileSync(join(__dirname, "../package.json")).toString()); async function scaffold(data) { const projectDir = join(root, data.targetDir); if (existsSync(projectDir)) { if (data.overwrite) emptyDirectorySync(projectDir); } else mkdirSync(projectDir); const templatePath = join(__dirname, "../templates", data.type); const commonPath = join(__dirname, "../templates/common"); const wfPath = join(__dirname, "../templates/wf"); const wsAddonPath = join(__dirname, "../templates/ws-addon"); const context = { ...data, version }; const excludeCommon = []; if (!data.oxc) { excludeCommon.push(".oxlintrc.json"); excludeCommon.push(".oxfmtrc.json"); } if (data.type === "ssr") excludeCommon.push("tsconfig.json"); const renameFile = (filename) => { if (filename.endsWith(".jsonc")) return filename.replace(/c$/, ""); return filename; }; const excludeTemplate = []; if (data.type === "ssr" && !data.ssr) excludeTemplate.push("src/entry-server.ts"); await rw.rewriteDir({ baseDir: templatePath, output: projectDir, exclude: excludeTemplate, renameFile }, context); await rw.rewriteDir({ baseDir: commonPath, output: projectDir, exclude: excludeCommon, renameFile }, context); if (data.wf && data.type === "http") await rw.rewriteDir({ baseDir: wfPath, output: projectDir, renameFile }, context); if (data.ws && data.type === "http") await rw.rewriteDir({ baseDir: wsAddonPath, output: projectDir, renameFile }, context); } function emptyDirectorySync(directory) { if (existsSync(directory)) readdirSync(directory).forEach((file) => { const currentPath = join(directory, file); if (lstatSync(currentPath).isDirectory()) { emptyDirectorySync(currentPath); rmdirSync(currentPath); } else unlinkSync(currentPath); }); } //#endregion //#region packages/create-moost/src/index.ts function _ts_decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function _ts_metadata(k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); } function _ts_param(paramIndex, decorator) { return function(target, key) { decorator(target, key, paramIndex); }; } let Commands = class Commands extends CliApp { root() { return this.execute(); } withName(name) { return this.execute(name); } async execute(name) { if (useAutoHelp()) process.exit(0); const prompts = await getPrompts({ name, http: !!useCliOption("http"), cli: !!useCliOption("cli"), ws: !!useCliOption("ws"), wf: !!useCliOption("wf"), ssr: !!useCliOption("ssr"), oxc: !!useCliOption("oxc"), force: !!useCliOption("force") }); console.log("\nScaffolding a new project..."); await scaffold(prompts); const cli = prompts.type === "cli"; const ws = prompts.type === "ws"; const ssr = prompts.type === "ssr"; return ` Success! Your new "${prompts.projectName}" project has been created successfully.  Follow these next steps to start your development server: 1. Navigate to your new project: cd ${prompts.targetDir}  2. Install the dependencies: npm install  ${cli ? ` 3. Make bin.js executable: chmod +x ./bin.js  ` : ""} ${cli ? "4" : "3"}. ${ws ? "Build and start" : "Start the development server"}: npm run dev${cli ? " -- hello World" : ""} You're all set!${ws ? "" : " The development server will help you in building your application."} Enjoy coding, and build something amazing! ${ssr ? ` Vue app: http://localhost:3000 API routes: http://localhost:3000/api/hello/World ` : ""}`; } }; _ts_decorate([ Cli(""), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", []), _ts_metadata("design:returntype", void 0) ], Commands.prototype, "root", null); _ts_decorate([ Cli(":name"), _ts_param(0, Param("name")), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [String]), _ts_metadata("design:returntype", void 0) ], Commands.prototype, "withName", null); function run() { new Commands().useOptions([ { keys: ["http"], description: "Use Moost HTTP", type: Boolean }, { keys: ["cli"], description: "Use Moost CLI", type: Boolean }, { keys: ["ws"], description: "Use Moost WebSocket", type: Boolean }, { keys: ["wf"], description: "Add Workflow Adapter", type: Boolean }, { keys: ["ssr"], description: "Vue + Moost (SSR/SPA)", type: Boolean }, { keys: ["oxc"], description: "Add OXC lint and formatter (oxlint + oxfmt)", type: Boolean }, { keys: ["force"], description: "Force Overwrite", type: Boolean } ]).start(); } //#endregion export { run };