From 463709f5675a7d58d9b8b8e72a353058697dd56d Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Tue, 1 Oct 2024 16:56:49 +0200 Subject: [PATCH] refactor: use Node.js built-in CLI parser BREAKING CHANGES: require Node.js 20.0.0 or above This allows us to remove the Commander.js dependency. This reduces the standalone binary size from 77KB to 45KB (58%). I disabled TypeScript's `exactOptionalPropertyTypes` because Node.js `parseArgs` return type allow `undefined` to every possible fields. And we pass the returned object to a function taking a `Partial`. `Partial` makes optional the fields, but doesn't allow `undefined`. We could fix the issue by changing the ret type of Node.js `parseArgs`. --- CHANGELOG.md | 13 ++-- package-lock.json | 19 +---- package.json | 5 +- src/bin/cli.ts | 179 ++++++++++++++++++++++++++------------------- tsconfig-base.json | 1 - 5 files changed, 118 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a6a4a2..9b3c45af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,14 @@ New entries must be placed in a section entitled `Unreleased`. ## Unreleased -- BREAKING CHANGES: require NodeJS 16.11.0 or above +- BREAKING CHANGES: require Node.js 20.0.0 or above - This will allow us to use the `cause` property of `Error`. + This allows us to use the built-in Node.js CLI parser and then to remove the [Commander.js](https://www.npmjs.com/package/commander) dependency. + This reduces the standalone binary size from 77KB to 45KB (42%). ## 0.15.0 (2023-10-19) -- BREAKING CHANGES: require NodeJS 16.9.0 or above +- BREAKING CHANGES: require Node.js 16.9.0 or above - BREAKING CHANGES: promote regular comments to doc-comments @@ -330,7 +331,7 @@ This release **widely improves the usage of unions and flat unions**. bare-ts now publishes _ES2020_ builds. This outputs smaller builds. - This should cause no issue since we require a NodeJS version `^14.18` or `>=16`. + This should cause no issue since we require a Node.js version `^14.18` or `>=16`. - Add option `--lib` to prevent `decode` and `encode` generation @@ -369,7 +370,7 @@ This release **widely improves the usage of unions and flat unions**. type Message union { Person } ``` -- BREAKING CHANGES: Require NodeJS `>=14.18.0` +- BREAKING CHANGES: Require Node.js `>=14.18.0` This enables _bare-ts_ to internally use `node:` prefixes for importing nodes' built-ins. @@ -586,7 +587,7 @@ This release **widely improves the usage of unions and flat unions**. ## 0.7.0 (2022-04-24) -- BREAKING CHANGES: require NodeJS versions that support _ESM_ +- BREAKING CHANGES: require Node.js versions that support _ESM_ _bare-ts_ requires now a node versions that support _ECMAScript Modules_. diff --git a/package-lock.json b/package-lock.json index 0c11943b..918cf931 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,7 @@ "devDependencies": { "@bare-ts/lib": "~0.4.0", "@biomejs/biome": "1.9.2", - "@types/node": "16.11.68", - "commander": "^12.1.0", + "@types/node": "20.0.0", "esbuild": "0.23.1", "oletus": "4.0.0", "typescript": "5.6.2", @@ -101,22 +100,12 @@ } }, "node_modules/@types/node": { - "version": "16.11.68", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.68.tgz", - "integrity": "sha512-JkRpuVz3xCNCWaeQ5EHLR/6woMbHZz/jZ7Kmc63AkU+1HxnoUugzSWMck7dsR4DvNYX8jp9wTi9K7WvnxOIQZQ==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.0.0.tgz", + "integrity": "sha512-cD2uPTDnQQCVpmRefonO98/PPijuOnnEy5oytWJFPY1N9aJCz2wJ5kSGWO+zJoed2cY2JxQh6yBuUq4vIn61hw==", "dev": true, "license": "MIT" }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", diff --git a/package.json b/package.json index 14836ee1..1c82aa03 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "url": "https://github.com/bare-ts/tools/issues" }, "engines": { - "node": ">=16.11.0" + "node": ">=20.0.0" }, "type": "module", "bin": { @@ -66,8 +66,7 @@ "devDependencies": { "@bare-ts/lib": "~0.4.0", "@biomejs/biome": "1.9.2", - "@types/node": "16.11.68", - "commander": "^12.1.0", + "@types/node": "20.0.0", "esbuild": "0.23.1", "oletus": "4.0.0", "typescript": "5.6.2", diff --git a/src/bin/cli.ts b/src/bin/cli.ts index 63d6bb3d..1f57dea9 100644 --- a/src/bin/cli.ts +++ b/src/bin/cli.ts @@ -5,27 +5,38 @@ import * as fs from "node:fs" import * as process from "node:process" -import { Argument, Option, program } from "commander" +import * as util from "node:util" import { CompilerError, Config, transform } from "../index.js" // WARNING: This constant MUST be defined at build time. declare const VERSION: string -const REPOSITORY_HELP = `Repository: - https://github.com/bare-ts/tools` +const HELP_TEXT = ` +Usage: bare [options] [schema] + +Compile a BARE (Binary Application Record Encoding) schema into a TypeScript or JavaScript file + +Arguments: + schema BARE schema file (default: stdin) + +Options: + -o, --out destination of output (default: stdout) + --generator output generator (choices: "bare", "dts", "js", "ts") + --legacy allow legacy BARE syntax and features + --lib do not generate decoders and encoders of root types + --pedantic require enum and union types to set all tags in-order + --use-class use classes instead of interfaces for structs + --use-generic-array use generic arrays instead of typed arrays + --use-int-enum use integers for enum values instead of strings + --use-int-tag always use integers for union tags instead of strings + --use-mutable use mutable types + --use-primitive-flat-union use flat unions instead of tagged unions for unions of primitive types + --use-safe-int use safe integers instead of bigint + --use-struct-flat-union use flat unions instead of tagged unions for unions of anonymous and aliased structs + --use-undefined use undefined instead of null for optional types + --version output the version number and exit + -h, --help display help for command -const EXTRA_HELP = ` -Examples: - # Compile schema.bare into Typescript - bare compile schema.bare -o output.ts - - # more examples - bare --help - -${REPOSITORY_HELP} -` - -const COMPILE_EXTRA_HELP = ` Examples: # Compile schema.bare into Typescript, TypeScript Declaration, or JavaScript bare compile schema.bare -o output.ts @@ -37,67 +48,87 @@ Examples: bare compile --generator dts < schema.bare > output.d.ts bare compile --generator js < schema.bare > output.js -${REPOSITORY_HELP} +Repository: + https://github.com/bare-ts/tools ` -program - .name("bare") - .description("Tools for BARE (Binary Application Record Encoding)") - .version(VERSION, "--version", "output the version number and exit") - .addHelpText("after", EXTRA_HELP) - .action(() => program.help()) - -program - .command("compile") - .description("Compile a BARE schema into a TypeScript or JavaScript file") - .addArgument( - new Argument("[schema]", "BARE schema file").default(0, "stdin"), - ) - .addOption( - new Option("-o, --out ", "destination of output").default( - 1, - "stdout", - ), - ) - .addOption( - new Option("--generator ", "output generator").choices([ - "bare", - "dts", - "js", - "ts", - ]), - ) - .option("--legacy", "allow legacy BARE syntax and features") - .option("--lib", "do not generate decoders and encoders of root types") - .option( - "--pedantic", - "require enum and union types to set all tags in-order", - ) - .option("--use-class", "use classes instead of interfaces for structs") - .option("--use-generic-array", "use generic arrays instead of typed arrays") - .option("--use-int-enum", "use integers for enum values instead of strings") - .option( - "--use-int-tag", - "always use integers for union tags instead of strings", - ) - .option("--use-mutable", "use mutable types") - .option( - "--use-primitive-flat-union", - "use flat unions instead of tagged unions for unions of primitive types", - ) - .option("--use-safe-int", "use safe integers instead of bigint") - .option( - "--use-struct-flat-union", - "use flat unions instead of tagged unions for unions of anonymous and aliased structs", - ) - .option( - "--use-undefined", - "use undefined instead of null for optional types", - ) - .addHelpText("after", COMPILE_EXTRA_HELP) - .action(compileAction) - -program.parse() +const PARSE_CONFIG = { + allowPositionals: true, + options: { + help: { short: "h", type: "boolean" }, + version: { type: "boolean" }, + out: { short: "o", type: "string" }, + generator: { type: "string" }, + legacy: { type: "boolean" }, + lib: { type: "boolean" }, + pedantic: { type: "boolean" }, + "use-int-tag": { type: "boolean" }, + "use-class": { type: "boolean" }, + "use-generic-array": { type: "boolean" }, + "use-int-enum": { type: "boolean" }, + "use-mutable": { type: "boolean" }, + "use-primitive-flat-union": { type: "boolean" }, + "use-safe-int": { type: "boolean" }, + "use-struct-flat-union": { type: "boolean" }, + "use-undefined": { type: "boolean" }, + }, +} as const + +main() + +function main(): void { + try { + const { values, positionals } = util.parseArgs(PARSE_CONFIG) + if (values.help) { + console.info(HELP_TEXT) + } else if (values.version) { + console.info(VERSION) + } else { + if (positionals.length > 1 && positionals[0] === "compile") { + positionals.pop() + } + if (positionals.length > 1) { + console.error("only one argument is expected") + process.exit(1) + } + const schema = positionals.length > 0 ? positionals[0] : 0 + const out = values.out ?? 1 + const generator = values.generator + if ( + generator != null && + generator !== "bare" && + generator !== "dts" && + generator !== "js" && + generator !== "ts" + ) { + console.error("Invalid value") + process.exit(1) + } + compileAction(out, { + generator, + legacy: values.legacy, + lib: values.lib, + out: values.out, + pedantic: values.pedantic, + schema, + useClass: values["use-class"], + useGenericArray: values["use-generic-array"], + useIntEnum: values["use-int-enum"], + useIntTag: values["use-int-tag"], + useMutable: values["use-mutable"], + usePrimitiveFlatUnion: values["use-primitive-flat-union"], + useSafeInt: values["use-safe-int"], + useStructFlatUnion: values["use-struct-flat-union"], + useUndefined: values["use-undefined"], + }) + } + } catch (error) { + if (error instanceof Error) { + console.error(error.message) + } + process.exit(1) + } +} function compileAction(schema: string | number, opts: Partial): void { let config: Config | null = null diff --git a/tsconfig-base.json b/tsconfig-base.json index 5cc27d50..b0302f6d 100644 --- a/tsconfig-base.json +++ b/tsconfig-base.json @@ -19,7 +19,6 @@ "allowUnreachableCode": false, "checkJs": true, - "exactOptionalPropertyTypes": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true,