create-moost
Version:
344 lines (336 loc) • 9.81 kB
JavaScript
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 `
[97m[1mSuccess! [22mYour new "${prompts.projectName}" project has been created successfully. [39m
Follow these next steps to start your development server:
1. Navigate to your new project:
[36mcd ${prompts.targetDir} [39m
2. Install the dependencies:
[36mnpm install [39m
${cli ? `
3. Make bin.js executable:
[36mchmod +x ./bin.js [39m
` : ""}
${cli ? "4" : "3"}. ${ws ? "Build and start" : "Start the development server"}:
[36mnpm run dev${cli ? " -- hello World" : ""}[39m
[32mYou're all set!${ws ? "" : " The development server will help you in building your application."}
Enjoy coding, and build something amazing![39m
${ssr ? `
[2mVue app: http://localhost:3000
API routes: http://localhost:3000/api/hello/World[39m
` : ""}`;
}
};
_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 };