Skip to content

Commit

Permalink
initial commit - wip
Browse files Browse the repository at this point in the history
  • Loading branch information
roaminro committed Oct 31, 2024
0 parents commit caf2721
Show file tree
Hide file tree
Showing 20 changed files with 3,394 additions and 0 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

on:
push:
branches: ["main"]
pull_request

jobs:
build:
name: Build and Test
timeout-minutes: 15
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['18.x', '20.x']

steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 2

- uses: pnpm/action-setup@v3
with:
version: 8

- name: Setup Node.js ${{ matrix.node-version }} environment
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

- name: Install dependencies
run: pnpm install

- name: Build
run: pnpm build

- name: Test
run: pnpm test
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
dist
.DS_STORE

.env*

.turbo
coverage
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["orta.vscode-twoslash-queries", "biomejs.biome"]
}
21 changes: 21 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
118 changes: 118 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
STATUS: 🚧 Work in progress.

USAGE RIGHTS: None granted; until a license is added, all rights are reserved.

-------------------------------------------

# **CLIME**
[![CI](https://github.com/roaminro/clime/actions/workflows/ci.yml/badge.svg)](https://github.com/roaminro/clime/actions/workflows/ci.yml)

Node.js **CLI**s **M**ade **E**asy

## Features
- Arguments and options parsing powered by Node.js's [parseArgs](https://nodejs.org/api/util.html#utilparseargsconfig) util
- Powerful arguments and options validation powered by [ArkType](https://arktype.io/)
- Nested sub-commands
- Auto-generated usage and help
- Commands easily testable

## Usage

```ts
// 1. Create a cli.ts file.

// 2. Import modules.
import {
defineCommand,
isErrorResult,
isHelpResult,
isVersionResult,
runCommand,
} from "TBD";

// 3. Define your commands.
const cli = defineCommand({
name: "calculator",
description: "a simple calculator",
subCommands: [
defineCommand({
name: "add",
description: "add number a to number b",
args: {
a: {
description: "number a",
type: "number",
},
b: {
description: "number b",
type: "number",
},
},
options: {
print: {
description: "print the equation",
type: "boolean|undefined",
short: 'p'
},
},
run({ args, options }) {
const result = args.a + args.b;

if (options.print) {
console.log(`${args.a} + ${args.b} = ${result}`)
}

return result;
},
}),
defineCommand({
name: "sub",
description: "subtract number b from number a",
args: {
a: {
description: "number a",
type: "number",
},
b: {
description: "number b",
type: "number",
},
},
run({ args }) {
return args.a - args.b;
},
})
]
});

// 4. Call runCommand to process arguments and options.
const result = await runCommand(cli);

// 5. Process result.
// if cli was called with --help or -h
if (isHelpResult(result)) {
// use provided formatter to print help
// or, provide your own formatter
printCommandHelp(result.help);
}
// if cli was called with --version or -help
else if (isVersionResult(result)) {
console.log(result.version);
}
// if errors occured during the execution of the command
else if (isErrorResult(result)) {
console.log(result.errors.join("\n"));
}
// otherwise, we can print the result of the command
else {
console.log(result.data);
}

// 6. Run your cli (e.g.: using tsx)
// display general help: tsx cli.ts -h
// display command specific help: tsx cli.ts add -h
// display version: tsx cli.ts -v
// call "add" command: tsx cli.ts add 4 2
// call "add" command: tsx cli.ts add 4 2 -p
// call "add" command: tsx cli.ts add 4 2 --print
```
28 changes: 28 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.2/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "root",
"type": "module",
"scripts": {
"build": "turbo build",
"test": "turbo test",
"test:watch": "turbo test:watch",
"clean": "turbo clean",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/node": "^22.8.4",
"turbo": "^2.2.3",
"typescript": "^5.6.3"
},
"packageManager": "[email protected]"
}
33 changes: 33 additions & 0 deletions packages/clime/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@roamin/clime",
"version": "0.0.0",
"description": "",
"author": "Roamin Ro",
"keywords": [],
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": ["dist"],
"scripts": {
"test": "vitest run --coverage",
"test:watch": "vitest watch",
"build": "tsup",
"clean": "rm -rf .turbo && rm -rf dist"
},
"dependencies": {
"arktype": "2.0.0-rc.12"
},
"devDependencies": {
"@vitest/coverage-v8": "^2.1.4",
"tsup": "^8.3.5",
"vitest": "^2.1.4"
}
}
42 changes: 42 additions & 0 deletions packages/clime/src/defineCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Command, VerifiedCommand } from "./types.js";

function verifyCommand<const Args, const Opts, const RunRet>(
command: Command<Args, Opts, RunRet>,
isSubCommand = false,
): string[] {
const errors: string[] = [];

if (command.run && command.subCommands?.length) {
errors.push(
`${isSubCommand ? "subCommand" : "command"} "${
command.name
}" can only have "run" OR "subCommands" defined at the same time.`,
);
}

if (command.args && command.subCommands?.length) {
errors.push(
`${isSubCommand ? "subCommand" : "command"} "${
command.name
}" can only have "arguments" OR "subCommands" defined at the same time.`,
);
}

for (const subCommand of command.subCommands || []) {
errors.push(...verifyCommand(subCommand));
}

return errors;
}

export function defineCommand<const Args, const Opts, const RunRet>(
command: Command<Args, Opts, RunRet>,
): VerifiedCommand<Args, Opts, RunRet> {
const errors = verifyCommand(command);

if (errors.length) {
throw new Error(errors.join("\n"));
}

return command as VerifiedCommand<Args, Opts, RunRet>;
}
4 changes: 4 additions & 0 deletions packages/clime/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./types.js";
export * from "./utils.js";
export * from "./defineCommand.js";
export * from "./runCommand.js";
Loading

0 comments on commit caf2721

Please sign in to comment.