node/setup-commander
Configures a Typescript project to include dependencies and scaffolding setup for a commander CLI
Usage
npx @lukasbach/scripts node/setup-commander
You can call the script directly if you have installed it globally:
npm i -g @lukasbach/scripts
ldo node/setup-commander
Options
--bin-folder
: Where should the binary be stored?--bin-folder
: What is the name of the CLI?--commander-type
: Which commander setup do you want?-v
,--verbose
: Verbose logging
You can also omit options, and will be asked for them interactively.
Add --yes
to skip all confirmations.
Script source
/** Configures a Typescript project to include dependencies and scaffolding setup for a commander CLI */
await utils.cd(await utils.node.getPackageRoot());
if (!(await fs.exists("tsconfig.json"))) {
log.exit("No tsconfig.json found. Please run `node/setup-tsconfig` first.");
}
const simpleType = "Single Command";
const complexType = "Complex Setup with multiple commands";
const { main, exports, name } = await utils.node.getPackageJson();
const binFolder = await ask.text("bin-folder", "Where should the binary be stored?", main ?? exports);
const binName = await ask.text("bin-folder", "What is the name of the CLI?", name);
const setupType = await ask.choice(
"commander-type",
"Which commander setup do you want?",
[simpleType, complexType],
simpleType
);
await utils.node.amendPackageJson({
bin: { [binName]: binFolder },
scripts: {
start: "esr src/index.ts",
},
});
await utils.node.addDependency("commander");
await utils.node.addDevDependency("esbuild esbuild-runner");
if (setupType === simpleType) {
await fs.ensureDir("src");
const mainFileContents = utils.noindent(`
#!/usr/bin/env node
import { program } from "commander";
import * as fs from "fs";
import * as path from "path";
interface Options {
small?: boolean;
pizzaType: string;
}
let cliVersion: string;
try {
cliVersion = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), { encoding: "utf-8" })).version;
} catch (e) {
cliVersion = "unknown";
}
program
.version(cliVersion)
.option("-s, --small", "small pizza size")
.requiredOption("-p, --pizza-type <type>", "flavour of pizza");
program.parse(process.argv);
const options = program.opts() as Options;
console.log(options);
`);
await fs.writeFile("src/index.ts", mainFileContents);
}
if (setupType === complexType) {
await fs.ensureDir("src/commands");
const mainFileContents = utils.noindent(`
#!/usr/bin/env node
import * as fs from "fs";
import * as path from "path";
import { Command } from "commander";
import { sampleCommand } from "./commands/sample";
const program = new Command();
let cliVersion: string;
try {
cliVersion = JSON.parse(
fs.readFileSync(path.join(__dirname, "../package.json"), {
encoding: "utf-8",
})
).version;
} catch (e) {
cliVersion = "unknown";
}
program.version(cliVersion).addCommand(sampleCommand);
program.parse();
`);
const commandFileContents = utils.noindent(`
import { Command, Option } from "commander";
interface Options {
flag?: boolean;
str?: string;
number2?: number;
}
export const sampleCommand = new Command("sample");
sampleCommand.argument("<name>", "Name of the sample");
sampleCommand.option("-f, --flag", "Flag");
sampleCommand.option("-s, --str <string>", "String");
sampleCommand.addOption(
new Option("--number <number>").argParser((v) => parseInt(v, 10))
);
sampleCommand.action((name, options: Options) => {});
`);
await fs.writeFile("src/index.ts", mainFileContents);
await fs.writeFile("src/commands/sample.ts", commandFileContents);
}