UNPKG

@akrc/fnpm

Version:
723 lines (701 loc) 17.5 kB
import consola, { consola as consola$1 } from "consola"; import { commands } from "pm-combo"; import { resolveContext } from "fnpm-context"; import parser from "fnpm-parse"; import { packageDirectory } from "package-directory"; import { x } from "tinyexec"; import { hideBin } from "yargs/helpers"; import colors from "picocolors"; import { readPackage } from "read-pkg"; import * as doctor from "fnpm-doctor"; import { start } from "fnpm-ui"; import { getPort } from "get-port-please"; import gitUrlParse from "git-url-parse"; import open from "open"; //#region package.json var name = "@akrc/fnpm"; var version = "1.13.0"; var description = ""; var type = "module"; var scripts = { "build": "tsdown", "link:fnpm": "pnpm link . -g", "unlink:fnpm": "pnpm remove @akrc/fnpm -g", "dev": "tsdown --watch" }; var keywords = ["fnpm"]; var author = "AkaraChen"; var homepage = "https://github.com/akarachen/fnpm"; var repository = { "type": "git", "url": "git+https://github.com/akarachen/fnpm.git" }; var license = "ISC"; var devDependencies = { "@akrc/monorepo-tools": "^4.1.0", "@effect/platform": "^0.84.9", "@effect/platform-node": "^0.85.11", "@types/yargs": "^17.0.33", "effect": "^3.16.5", "fnpm-test-suite": "workspace:*", "nanoid": "^5.1.5", "type-fest": "^4.41.0" }; var dependencies = { "consola": "^3.4.2", "fnpm-context": "workspace:*", "fnpm-doctor": "workspace:*", "fnpm-parse": "workspace:*", "fnpm-ui": "workspace:*", "get-port-please": "^3.1.2", "git-url-parse": "^16.1.0", "open": "^10.1.2", "package-directory": "^8.1.0", "picocolors": "^1.1.1", "pm-combo": "workspace:*", "read-pkg": "^9.0.1", "tinyexec": "^1.0.1", "yargs": "^18.0.0" }; var bin = { "fnpm": "./dist/fnpm.js", "fnpx": "./dist/fnpx.js" }; var files = ["dist"]; var package_default = { name, version, description, type, scripts, keywords, author, homepage, repository, license, devDependencies, dependencies, bin, files }; //#endregion //#region src/commands/base.ts /** * Base command class that all command classes can extend * * @template T The command options type */ var BaseCommand = class { /** * Context object */ ctx; constructor(ctx) { this.ctx = ctx; } }; var CommandFactory = class { ctx; constructor(ctx) { this.ctx = ctx; } create(Command, ctx = this.ctx) { const original = new Command(ctx); const command = { ...original }; command.builder = original.builder?.bind(original); command.handler = original.handler?.bind(original); return command; } }; //#endregion //#region src/util.ts function exec(shell, opts = {}) { const { cwd } = opts; const [command, ...args] = shell; return x(command, args, { nodeOptions: { cwd, stdio: "inherit" } }); } function error(message) { consola$1.error(message); process.exit(1); } async function getContext(cwd) { const ctx = await resolveContext(cwd); const hasWFlag = process.argv[2] === "-w"; if (hasWFlag) process.argv.splice(2, 1); const args = hideBin(process.argv); const root = hasWFlag ? ctx.root : await packageDirectory({ cwd }) || cwd; return { pm: ctx.pm, args, root }; } function normalizePackageVersion(input) { const parsed = parser.parse(input); return `${parsed.fullName}@${parsed.version || "latest"}`; } //#endregion //#region src/commands/add.ts var Add = class extends BaseCommand { command = [ "add [packages..]", "a", "install", "i" ]; describe = "add packages"; builder = (args) => { return args.positional("packages", { type: "string", array: true, description: "Packages to install" }).option("save", { alias: ["d"], type: "boolean", description: "Save to dependencies", conflicts: [ "save-dev", "save-exact", "save-peer", "save-optional" ] }).option("save-dev", { alias: ["D"], type: "boolean", description: "Save as devDependencies", conflicts: [ "save", "save-exact", "save-peer", "save-optional" ] }).option("save-exact", { alias: ["E", "exact"], type: "boolean", description: "Save exact version", conflicts: [ "save", "save-dev", "save-peer", "save-optional" ] }).option("save-peer", { alias: ["P", "peer"], type: "boolean", description: "Save as peerDependencies", conflicts: [ "save", "save-dev", "save-exact", "save-optional" ] }).option("save-optional", { alias: ["O", "optional"], type: "boolean", description: "Save as optionalDependencies", conflicts: [ "save", "save-dev", "save-exact", "save-peer" ] }).option("fixed", { alias: ["F"], type: "boolean", description: "Use fixed version", default: false }).option("workspace", { alias: ["W", "w"], type: "boolean", description: "Add packages to workspace root", default: false }).option("global", { alias: ["G", "g"], type: "boolean", description: "Install packages globally", default: false }); }; async handler(args) { const { packages, saveOptional = false, saveExact = false, saveDev = false, savePeer = false, fixed = false, workspace = false, global = false, save = true } = args; consola.info(`Installing packages with ${this.ctx.pm}`); const options = { packages, save, saveDev, savePeer, saveOptional, exact: saveExact, global, fixed, allowRoot: workspace }; const command = options.packages ? commands.add.concat(this.ctx.pm, options) : commands.install.concat(this.ctx.pm, options); await exec(command, { cwd: this.ctx.root }); } }; var add_default = Add; //#endregion //#region src/commands/ci.ts var CI = class extends BaseCommand { command = "ci"; describe = "run continuous integration"; builder(args) { return args; } async handler() { const command = commands.install.concat(this.ctx.pm, { fixed: true }); consola.info(`Running CI with ${this.ctx.pm}`); await exec(command, { cwd: this.ctx.root }); } }; var ci_default = CI; //#endregion //#region src/commands/config.ts const verbsMap = { list: ["list", "ls"], get: ["get", "g"], set: ["set", "s"], delete: [ "delete", "d", "rm", "del", "remove", "unset" ] }; const verbs = Object.values(verbsMap).flat(); var Config = class extends BaseCommand { command = ["config [verb] [key] [value]", "c"]; describe = "Manage the npm configuration files"; builder = (args) => { return args.positional("verb", { type: "string", description: "Verb to use", demandOption: false, default: "list", choices: verbs }).positional("key", { type: "string", description: "Key to get or set", demandOption: false }).positional("value", { type: "string", description: "Value to set", demandOption: false }).option("global", { alias: ["G", "g"], type: "boolean", description: "Update packages globally" }).option("json", { alias: ["j"], type: "boolean", description: "Output json" }); }; async handler(args) { const { global, json, verb, key, value } = args; if (!verbs.includes(verb)) error(`Invalid verb ${verb}`); const command = commands.config.concat(this.ctx.pm, { verb: Object.entries(verbsMap).find(([_k, v]) => v.includes(verb))[0], global, json, key, value }); await exec(command, { cwd: this.ctx.root }); } }; var config_default = Config; //#endregion //#region src/commands/default.ts var Default = class extends BaseCommand { command = "*"; describe = "run a script"; builder(args) { return args; } async handler(args) { if (args._.length === 0) { consola.info("Installing dependencies"); const shell$1 = commands.install.concat(this.ctx.pm, {}); await exec(shell$1, { cwd: this.ctx.root }); return; } const inputs = this.ctx.args; let pkg; try { pkg = await readPackage({ cwd: this.ctx.root }); } catch { error("Not in a package workspace, you may running fnpm at incorrect place."); } const scripts$1 = pkg.scripts || {}; const [script, ...otherArgs] = inputs; if (script && scripts$1[script]) { const shell$1 = [ "node", "--run", script, "--", ...otherArgs ]; await exec(shell$1, { cwd: this.ctx.root }); return; } const shell = commands.exec.concat(this.ctx.pm, { args: inputs }); consola.info(`Running ${colors.green(shell.join(" "))}`); await exec(shell, { cwd: this.ctx.root }); } }; var default_default = Default; //#endregion //#region src/commands/dlx.ts var Dlx = class extends BaseCommand { command = "dlx"; describe = "download and exec"; builder(args) { return args; } async handler(args) { const pkg = args._[0] === "dlx" ? args._[1] : args._[0]; const rest = this.ctx.args.slice(this.ctx.args.indexOf(pkg) + 1); if (!pkg) error("No package specified"); const command = commands.dlx.concat(this.ctx.pm, { package: normalizePackageVersion(pkg), args: rest }); consola.info(`Running ${command.join(" ")}`); await exec(command); } }; var dlx_default = Dlx; //#endregion //#region src/commands/doctor.ts var Doctor = class extends BaseCommand { command = "doctor"; describe = "diagnose common issues"; builder(args) { return args; } async handler() { const result = await doctor.scan(this.ctx.root); if (result.diagnoses.length === 0) consola.success("No issues found"); else result.diagnoses.forEach(doctor.writeToConsole); } }; var doctor_default = Doctor; //#endregion //#region src/commands/init.ts var Init = class extends BaseCommand { command = "init"; describe = "initialize a new project"; builder = (args) => { return args.option("y", { type: "boolean", default: true }); }; async handler(args) { const { y } = args; const command = commands.init.concat(this.ctx.pm, { interactively: !y }); consola.info(`Initializing project with ${this.ctx.pm}`); await exec(command); } }; var init_default = Init; //#endregion //#region src/commands/publish.ts var Publish = class extends BaseCommand { command = "publish"; describe = "publish package"; builder(args) { return args; } async handler() { const command = ["npm", "publish"]; await exec(command, { cwd: this.ctx.root }); } }; var publish_default = Publish; //#endregion //#region src/commands/registry.ts var Registry = class extends BaseCommand { command = "registry <registry>"; describe = "Manage the npm registry"; builder = (args) => { return args.positional("registry", { type: "string", description: "Registry URL", demandOption: true }).option("global", { alias: ["G", "g"], type: "boolean", description: "Install packages globally" }); }; async handler(args) { const { global, registry } = args; if (!URL.canParse(registry)) error(`Invalid registry URL ${registry}`); const command = commands.config.concat(this.ctx.pm, { verb: "set", key: "registry", value: registry, global }); await exec(command, { cwd: this.ctx.root }); } }; var registry_default = Registry; //#endregion //#region src/commands/remove.ts var Remove = class extends BaseCommand { command = [ "remove <packages..>", "rm", "uninstall", "un" ]; describe = "remove packages"; builder = (args) => { return args.positional("packages", { type: "string", array: true, demandOption: true, description: "Packages to remove" }).option("save", { alias: ["S"], type: "boolean", description: "Remove from dependencies", conflicts: [ "save-dev", "save-peer", "save-optional", "global" ] }).option("save-dev", { alias: ["D"], type: "boolean", description: "Remove from devDependencies", conflicts: [ "save", "save-peer", "save-optional", "global" ] }).option("save-peer", { alias: ["P"], type: "boolean", description: "Remove from peerDependencies", conflicts: [ "save", "save-dev", "save-optional", "global" ] }).option("save-optional", { alias: ["O"], type: "boolean", description: "Remove from optionalDependencies", conflicts: [ "save", "save-dev", "save-peer", "global" ] }).option("global", { alias: ["G", "g"], type: "boolean", description: "Remove packages globally", conflicts: [ "save", "save-dev", "save-peer", "save-optional" ] }); }; async handler(args) { const { packages, saveDev, savePeer, saveOptional, global } = args; const options = { packages, saveDev, savePeer, saveOptional, global }; const command = commands.remove.concat(this.ctx.pm, options); consola.info(`Removing packages with ${this.ctx.pm}`); await exec(command, { cwd: this.ctx.root }); } }; var remove_default = Remove; //#endregion //#region src/commands/scaffold.ts var Scaffold = class extends BaseCommand { command = ["scaffold", "sc"]; describe = "Scaffold a new project"; builder(args) { return args; } handler = () => { exec(commands.create.concat(this.ctx.pm, { name: "akrc", args: [] }), { cwd: this.ctx.root }); }; }; var scaffold_default = Scaffold; //#endregion //#region src/commands/test.ts var Test = class extends BaseCommand { command = ["test", "t"]; describe = "run tests"; builder(args) { return args; } async handler() { const command = commands.test.concat(this.ctx.pm, { args: this.ctx.args.slice(1) }); consola.info(`Running tests with ${this.ctx.pm}`); await exec(command, { cwd: this.ctx.root }); } }; var test_default = Test; //#endregion //#region src/commands/ui.ts var UI = class extends BaseCommand { command = "ui"; describe = "open the package manager UI"; builder = (args) => { return args.option("port", { alias: ["p", "P"], type: "number", description: "Port to use" }); }; async handler(args) { const port = args.port || await getPort({ port: 13131 }); consola.info(`Starting UI on http://localhost:${port}`); await start(port, this.ctx.root); } }; var ui_default = UI; //#endregion //#region src/commands/update.ts var Update = class extends BaseCommand { command = ["update [packages..]", "up"]; describe = "update packages"; builder = (args) => { return args.positional("packages", { type: "string", array: true, description: "Packages to update" }).option("global", { alias: ["G", "g"], type: "boolean", description: "Update packages globally" }); }; async handler(args) { const { packages, global } = args; const options = { packages, global }; const command = commands.update.concat(this.ctx.pm, options); await exec(command, { cwd: this.ctx.root }); } }; var update_default = Update; //#endregion //#region src/commands/use.ts var Use = class extends BaseCommand { command = "use <pattern>"; describe = "use a different package manager"; builder = (args) => { return args.positional("pattern", { type: "string", description: "Pattern to match package manager", demandOption: true }); }; async handler(args) { const { pattern } = args; if (pattern === "latest") { const shell$1 = [ "corepack", "use", `${this.ctx.pm}@latest` ]; await exec(shell$1, { cwd: this.ctx.root }); return; } const shell = [ "corepack", "use", pattern ]; await exec(shell, { cwd: this.ctx.root }); } }; var use_default = Use; //#endregion //#region src/commands/view.ts var View = class extends BaseCommand { command = ["view [platform]", "v"]; describe = "View in other platform"; builder = (args) => { return args.positional("platform", { choices: ["repo", "npm"], default: "npm" }); }; async handler(args) { const { platform } = args; const pkgJson = await readPackage({ cwd: this.ctx.root }); switch (platform) { case "repo": { if (pkgJson.repository?.type !== "git") error(`The package's repository is hosted on ${pkgJson.repository?.type} which is not supported yet.`); const url = gitUrlParse(pkgJson.repository.url).toString("https"); open(url); break; } case "npm": { open(new URL(pkgJson.name, "https://npm.im").href); break; } default: error(`The requested platform ${platform} is not supported`); } } }; var view_default = View; //#endregion //#region src/commands/why.ts var Why = class extends BaseCommand { command = ["why <query>", "explain"]; describe = "explain why a package is installed"; builder = (args) => { return args.positional("query", { type: "string", description: "Shared to explain", demandOption: true }); }; async handler(args) { const { query } = args; const command = commands.why.concat(this.ctx.pm, { query }); await exec(command, { cwd: this.ctx.root }); } }; var why_default = Why; //#endregion //#region src/commands/index.ts function mount(argv, ctx) { const factory = new CommandFactory(ctx); return argv.command(factory.create(add_default)).command(factory.create(dlx_default)).command(factory.create(remove_default)).command(factory.create(init_default)).command(factory.create(test_default)).command(factory.create(ci_default)).command(factory.create(doctor_default)).command(factory.create(ui_default)).command(factory.create(default_default)).command(factory.create(use_default)).command(factory.create(update_default)).command(factory.create(publish_default)).command(factory.create(why_default)).command(factory.create(config_default)).command(factory.create(registry_default)).command(factory.create(scaffold_default)).command(factory.create(view_default)); } //#endregion export { CommandFactory, dlx_default, getContext, mount, package_default };