UNPKG

@plugjs/plug

Version:
261 lines (246 loc) 8.7 kB
#!/usr/bin/env node // cli.mts import _fs from "node:fs"; import { main, yargsParser } from "@plugjs/tsrun"; import { BuildFailure } from "./asserts.mjs"; import { runAsync } from "./async.mjs"; import { invokeTasks, isBuild } from "./build.mjs"; import { $blu, $gry, $p, $red, $t, $und, $wht } from "./logging/colors.mjs"; import { logLevels } from "./logging/levels.mjs"; import { logOptions } from "./logging/options.mjs"; import { getCurrentWorkingDirectory, resolveAbsolutePath, resolveDirectory, resolveFile } from "./paths.mjs"; import { Context } from "./pipe.mjs"; var { TRACE, DEBUG, INFO, NOTICE, WARN, ERROR, OFF } = logLevels; var $bnd = (s) => $blu($und(s)); var $gnd = (s) => $gry($und(s)); var $wnd = (s) => $wht($und(s)); var version = true ? "0.6.43" : "0.0.0-dev"; function help() { console.log(`${$bnd("Usage:")} ${$wht("plugjs")} ${$gry("[")}--options${$gry("] [... ")}prop=val${$gry(" ...] [... ")}task${$gry(" ...]")} ${$bnd("Options:")} ${$wht(`-f --file ${$gnd("file")}`)} Specify the build file to use (default ${$wnd("./build.ts")}) ${$wht(`-w --watch ${$gnd("dir")}`)} Watch for changes on the specified directory and run ${$wht("-v --verbose")} Increase logging verbosity ${$wht("-q --quiet")} Decrease logging verbosity ${$wht("-c --colors")} Force colorful output (use ${$wnd("--no-colors")} to force plain text) ${$wht("-l --list")} Only list the tasks defined by the build, nothing more! ${$wht("-h --help")} Help! You're reading it now! ${$wht(" --version")} Version! This one: ${version}! ${$bnd("Properties:")} Any argument in the format ${$wnd("key=value")} will be interpeted as a property to be injected in the build process (e.g. ${$wnd("mode=production")}). ${$bnd("Tasks:")} Any other argument will be treated as a task name. If no task names are specified, the ${$t("default")} task will be executed. ${$bnd("Watch Mode:")} The ${$wnd("--watch")} option can be specified multiple times, and each single directory specified will be watched for changes. Note that Plug's own watch mode is incredibly basic, for more complex scenarios use something more advanced like nodemon ${$gry("(")}${$gnd("https://www.npmjs.com/package/nodemon")}${$gry(")")}. ${$bnd("TypeScript module format:")} Normally our TypeScript loader will transpile ${$wnd(".ts")} files to the type specified in ${$wnd("package.json")}, either ${$wnd("commonjs")} (the default) or ${$wnd("module")}. To force a specific module format use one of the following flags: ${$wht("--force-esm")} Force transpilation of ${$wnd(".ts")} files to EcmaScript modules ${$wht("--force-cjs")} Force transpilation of ${$wnd(".ts")} files to CommonJS modules `); } function parseCommandLine(args) { const parsed = yargsParser(args, { configuration: { "camel-case-expansion": false, "strip-aliased": true, "strip-dashed": true }, alias: { "verbose": ["v"], "quiet": ["q"], "colors": ["c"], "file": ["f"], "list": ["l"], "watch": ["w"], "help": ["h"] }, string: ["file", "watch"], boolean: ["help", "colors", "list", "force-esm", "force-cjs", "version"], count: ["verbose", "quiet"] }); const tasks = []; const props = {}; const watchDirs = []; let verbosity = 0; let colors = void 0; let file = void 0; let listOnly = false; for (const [key, value] of Object.entries(parsed)) { switch (key) { case "_": value.forEach((current) => { const [key2, val] = current.split(/=(.*)/, 2); if (key2 && val) props[key2] = val; else tasks.push(current); }); break; case "verbose": verbosity = verbosity + value; break; case "quiet": verbosity = verbosity - value; break; case "file": file = value; break; case "watch": if (Array.isArray(value)) watchDirs.push(...value); else if (value) watchDirs.push(value); break; case "colors": colors = !!value; break; case "list": listOnly = !!value; break; case "help": help(); process.exit(0); case "version": console.log(`PlugJS ${$gry("ver.")} ${$wnd(version)}`); process.exit(0); default: console.log(`Unsupported option ${$wnd(key)} (try ${$wnd("--help")})`); process.exit(1); } } if (colors !== void 0) logOptions.colors = colors; if (verbosity) { const levels = [TRACE, DEBUG, INFO, NOTICE, WARN, ERROR, OFF]; let level = levels.indexOf(logLevels.NOTICE) - verbosity; if (level >= levels.length) level = levels.length - 1; else if (level < 0) level = 0; logOptions.level = levels[level]; } const cwd = getCurrentWorkingDirectory(); const exts = ["ts", "mts", "mjs", "js", "mjs", "cjs"]; let buildFile = void 0; if (file) { const absolute = resolveFile(cwd, file); if (!absolute) { console.log(`Specified build file "${file}" was not found`); process.exit(1); } else { buildFile = absolute; } } else { for (const ext of exts) { const absolute = resolveFile(cwd, `build.${ext}`); if (!absolute) continue; buildFile = absolute; break; } } if (!buildFile) { console.log(`${$red("Unable to find build file")} ${$wht(`./build.[${exts.join("|")}]`)}`); process.exit(1); } watchDirs.forEach((watchDir) => { const absolute = resolveDirectory(cwd, watchDir); if (!absolute) { const path = resolveAbsolutePath(cwd, watchDir); console.log(`Specified watch directory "${$p(path)}" was not found`); process.exit(1); } else { watchDir = absolute; } }); return { buildFile, watchDirs, tasks, props, listOnly }; } main(import.meta.url, async (args) => { const { buildFile, watchDirs, tasks, props, listOnly } = parseCommandLine(args); if (tasks.length === 0) tasks.push("default"); const initialContext = new Context(buildFile, ""); const maybeBuild = await runAsync(initialContext, async () => { let maybeBuild2 = await import(buildFile); while (maybeBuild2) { if (isBuild(maybeBuild2)) return maybeBuild2; maybeBuild2 = maybeBuild2.default; } }); if (!isBuild(maybeBuild)) { console.log($red("Build file did not export a proper build")); console.log(); console.log('- If using CommonJS export your build as "module.exports"'); console.log(` e.g.: ${$wht("module.exports = build({ ... })")}`); console.log(); console.log('- If using ESM modules export your build as "default"'); console.log(` e.g.: ${$wht("export default build({ ... })")}`); console.log(); process.exit(1); } const build = maybeBuild; if (listOnly) { const taskNames = []; const propNames = []; for (const [key, value] of Object.entries(build)) { (typeof value === "string" ? propNames : taskNames).push(key); } console.log(` ${$gry("Outline of")} ${$p(buildFile)}`); console.log("\nKnown tasks:\n"); for (const taskName of taskNames.sort()) { console.log(` ${$gry("\u25A0")} ${$t(taskName)}`); } console.log("\nKnown properties:\n"); for (const propName of propNames.sort()) { const value = build[propName] ? ` ${$gry("(default")} ${$und(build[propName])}${$gry(")")}` : ""; console.log(` ${$gry("\u25A1")} ${$blu(propName)}${value}`); } console.log(); return; } if (watchDirs.length) { return new Promise((_, reject) => { let timeout = void 0; const runme = () => { invokeTasks(build, tasks, props).then(() => { console.log(` ${$gry("Watching for files change...")} `); }, (error) => { if (error instanceof BuildFailure) { console.log(` ${$gry("Watching for files change...")} `); } else { watchers.forEach((watcher) => watcher.close()); reject(error); } }).finally(() => { timeout = void 0; }); }; const watchers = watchDirs.map((watchDir) => { return _fs.watch(watchDir, { recursive: true }, () => { if (!timeout) timeout = setTimeout(runme, 250); }); }); runme(); }); } try { await invokeTasks(build, tasks, props); } catch (error) { if (!(error instanceof BuildFailure)) console.log(error); process.exitCode = 1; } }); export { parseCommandLine }; //# sourceMappingURL=cli.mjs.map