Skip to content

Commit

Permalink
refactor: use Node.js built-in CLI parser
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
Conaclos committed Oct 1, 2024
1 parent 1b14ff5 commit 463709f
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 99 deletions.
13 changes: 7 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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_.
Expand Down
19 changes: 4 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"url": "https://github.com/bare-ts/tools/issues"
},
"engines": {
"node": ">=16.11.0"
"node": ">=20.0.0"
},
"type": "module",
"bin": {
Expand Down Expand Up @@ -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",
Expand Down
179 changes: 105 additions & 74 deletions src/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <file> destination of output (default: stdout)
--generator <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 <command> --help
${REPOSITORY_HELP}
`

const COMPILE_EXTRA_HELP = `
Examples:
# Compile schema.bare into Typescript, TypeScript Declaration, or JavaScript
bare compile schema.bare -o output.ts
Expand All @@ -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 <file>", "destination of output").default(
1,
"stdout",
),
)
.addOption(
new Option("--generator <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 <generator> 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<Config>): void {
let config: Config | null = null
Expand Down
1 change: 0 additions & 1 deletion tsconfig-base.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

"allowUnreachableCode": false,
"checkJs": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
Expand Down

0 comments on commit 463709f

Please sign in to comment.