initial commit
This commit is contained in:
commit
db4a1e24ca
10 changed files with 1977 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.emacs*
|
||||
.oc
|
||||
dist
|
||||
node_modules
|
||||
result
|
||||
47
README.md
Normal file
47
README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# CLI Template
|
||||
|
||||
A minimal CLI template using Stricli and Dax.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
nix develop # optional: provides Node.js, TypeScript, biome
|
||||
npm install
|
||||
npm run build
|
||||
node dist/index.js --help
|
||||
```
|
||||
|
||||
Alternatively, use Nix directly:
|
||||
|
||||
```bash
|
||||
nix run . -- --help # runs the CLI directly
|
||||
nix shell . --command name-placeholder --help # provides CLI in PATH
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Uses TypeScript and Biome for checks:
|
||||
|
||||
```bash
|
||||
npm run check
|
||||
```
|
||||
|
||||
## Renaming
|
||||
|
||||
To rename the CLI from `name-placeholder` to your desired name:
|
||||
|
||||
```bash
|
||||
sed -i 's/name-placeholder/your-new-name/g' package.json src/index.ts flake.nix
|
||||
```
|
||||
|
||||
Then rebuild:
|
||||
|
||||
```bash
|
||||
nix build .
|
||||
```
|
||||
|
||||
In case Nix complains about the hash, run this to get the correct one and replace in flake.nix:
|
||||
|
||||
```bash
|
||||
nix run nixpkgs#prefetch-npm-deps -- package-lock.json
|
||||
```
|
||||
34
biome.json
Normal file
34
biome.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"includes": ["**", "!!**/dist"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab"
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"enabled": true,
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
flake.lock
generated
Normal file
135
flake.lock
generated
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769996383,
|
||||
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770726378,
|
||||
"narHash": "sha256-kck+vIbGOaM/dHea7aTBxdFYpeUl/jHOy5W3eyRvVx8=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1770073757,
|
||||
"narHash": "sha256-Vy+G+F+3E/Tl+GMNgiHl9Pah2DgShmIUBJXmbiQPHbI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "47472570b1e607482890801aeaf29bfb749884f6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1769909678,
|
||||
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "72716169fe93074c333e8d0173151350670b824c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1771008912,
|
||||
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"git-hooks": "git-hooks",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
34
flake.nix
Normal file
34
flake.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
description = "name-placeholder";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
git-hooks.url = "github:cachix/git-hooks.nix";
|
||||
};
|
||||
|
||||
outputs = inputs:
|
||||
inputs.flake-parts.lib.mkFlake { inherit inputs; } (
|
||||
{
|
||||
systems = [ "x86_64-linux" ];
|
||||
|
||||
perSystem = { config, system, pkgs, ... }:
|
||||
{
|
||||
packages.default = pkgs.buildNpmPackage {
|
||||
pname = "name-placeholder";
|
||||
version = "0.1.0";
|
||||
src = ./.;
|
||||
npmDepsHash = "sha256-+30FObMk/z+ondarQzErirWkefJZR9dNGO6JPdbwSZI=";
|
||||
npmBuildScript = "build";
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = with pkgs;
|
||||
[ nodejs typescript-language-server biome ];
|
||||
};
|
||||
|
||||
formatter = pkgs.nixpkgs-fmt;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
1562
package-lock.json
generated
Normal file
1562
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
21
package.json
Normal file
21
package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "name-placeholder",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"name-placeholder": "./dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stricli/core": "^1.2.5",
|
||||
"dax": "^0.45.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.2.3",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts",
|
||||
"check": "tsc --noEmit && biome check ."
|
||||
}
|
||||
}
|
||||
115
src/index.ts
Normal file
115
src/index.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import {
|
||||
buildApplication,
|
||||
buildCommand,
|
||||
buildRouteMap,
|
||||
numberParser,
|
||||
run,
|
||||
} from "@stricli/core";
|
||||
import { $, CommandBuilder } from "dax";
|
||||
|
||||
$.setPrintCommand(true);
|
||||
|
||||
interface EchoFlags {
|
||||
count: number;
|
||||
verbose: boolean;
|
||||
name: string | undefined;
|
||||
level: "info" | "warn" | "error" | undefined;
|
||||
timeout: number | undefined;
|
||||
}
|
||||
|
||||
const echo = buildCommand<EchoFlags, [string]>({
|
||||
docs: {
|
||||
brief: "Echo a message with optional flags",
|
||||
fullDescription:
|
||||
"This command echoes a message and demonstrates various flag types.",
|
||||
},
|
||||
parameters: {
|
||||
aliases: {
|
||||
c: "count",
|
||||
v: "verbose",
|
||||
n: "name",
|
||||
t: "timeout",
|
||||
},
|
||||
flags: {
|
||||
count: { kind: "counter", brief: "Number of times to repeat" },
|
||||
verbose: { kind: "boolean", brief: "Enable verbose output" },
|
||||
name: {
|
||||
kind: "parsed",
|
||||
parse: (s: string) => s,
|
||||
brief: "Name to include",
|
||||
optional: true,
|
||||
},
|
||||
level: {
|
||||
kind: "enum",
|
||||
values: ["info", "warn", "error"] as const,
|
||||
brief: "Log level",
|
||||
optional: true,
|
||||
},
|
||||
timeout: {
|
||||
kind: "parsed",
|
||||
parse: numberParser,
|
||||
brief: "Timeout in ms",
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
positional: {
|
||||
kind: "tuple",
|
||||
parameters: [
|
||||
{
|
||||
parse: (s: string) => s,
|
||||
placeholder: "MESSAGE",
|
||||
brief: "Message to echo",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
func: async (flags, message) => {
|
||||
$.logStep("Starting echo command");
|
||||
$.log("Flags:", flags);
|
||||
$.log("Message:", message);
|
||||
|
||||
$.logStep("Executing echo via $");
|
||||
const result = await $`echo ${message}`.stdout("piped").stderr("piped");
|
||||
$.logWarn("Stderr:", result.stderr);
|
||||
$.log("Stdout:", result.stdout);
|
||||
|
||||
if (flags.count > 0) {
|
||||
$.logStep(`Repeating ${flags.count} times via commandBuilder`);
|
||||
const cmd = new CommandBuilder()
|
||||
.command(["echo", message])
|
||||
.env({ MY_VAR: "test" })
|
||||
.timeout(flags.timeout ?? 30 * 1000);
|
||||
for (let i = 0; i < flags.count; i++) {
|
||||
await cmd;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.name) {
|
||||
$.log("Name flag was:", flags.name);
|
||||
}
|
||||
|
||||
if (flags.level) {
|
||||
$.logError(`Level set to: ${flags.level}`);
|
||||
}
|
||||
|
||||
$.logStep("Done!");
|
||||
},
|
||||
});
|
||||
|
||||
const root = buildRouteMap({
|
||||
docs: {
|
||||
brief: "My CLI tool",
|
||||
},
|
||||
routes: {
|
||||
echo,
|
||||
},
|
||||
});
|
||||
|
||||
const app = buildApplication(root, { name: "name-placeholder" });
|
||||
|
||||
await run(app, process.argv.slice(2), {
|
||||
process:
|
||||
process as /* biome-ignore lint/suspicious/noExplicitAny: Stricli types don't align with @types/node */ any,
|
||||
});
|
||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
8
tsup.config.ts
Normal file
8
tsup.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm"],
|
||||
clean: true,
|
||||
shims: true, // Add shims for __dirname/__filename in ESM
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue