Skip to content

Commit

Permalink
chore: migrate to stack v2
Browse files Browse the repository at this point in the history
  • Loading branch information
adbayb committed Nov 4, 2024
1 parent 80f2f8a commit 488dcf3
Show file tree
Hide file tree
Showing 23 changed files with 4,231 additions and 2,556 deletions.
10 changes: 5 additions & 5 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

Please include a concise summary of the change with the relevant context and main highlights:

- [ ] What I've done 1
- [ ] What I've done 2...
- [ ] What I've done 1
- [ ] What I've done 2...

## Screenshots

Expand All @@ -24,6 +24,6 @@ Please include a concise summary of the change with the relevant context and mai

## Resources

- Issue (GitHub, ...)
- Specification (ADRs, RFCs, ...)
- ...
- Issue (GitHub, ...)
- Specification (ADRs, RFCs, ...)
- ...
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "@adbayb/stack/eslint";
2 changes: 2 additions & 0 deletions examples/default/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import process from "node:process";

import { helpers, termost } from "termost";

type ProgramContext = {
Expand Down
20 changes: 5 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "root",
"private": true,
"version": "0.0.0",
"type": "module",
Expand All @@ -17,24 +18,13 @@
},
"packageManager": "[email protected]",
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0",
"node": ">=22.0.0",
"pnpm": ">=9.0.0",
"npm": "please-use-pnpm",
"yarn": "please-use-pnpm"
},
"prettier": "@adbayb/prettier-config",
"eslintConfig": {
"extends": "@adbayb"
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"prettier": "@adbayb/stack/prettier",
"devDependencies": {
"@adbayb/eslint-config": "1.12.0",
"@adbayb/prettier-config": "1.5.0",
"@adbayb/stack": "1.16.1",
"@adbayb/ts-config": "1.0.0"
"@adbayb/stack": "2.0.0"
}
}
6,549 changes: 4,125 additions & 2,424 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

31 changes: 15 additions & 16 deletions termost/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@

Termost allows building command line tools in a minute thanks to its:

- [Fluent](https://en.wikipedia.org/wiki/Fluent_interface) syntax to express your CLI configurations with instructions such as:
- [Subcommand](examples/command/src/index.ts) support
- Long and short [option](examples/option/src/index.ts) support
- [User input](examples/input/src/index.ts) support
- [Task](examples/task/src/index.ts) support
- Shareable output between instructions
- Auto-generated help and version metadata
- TypeScript support to foster a type-safe API
- Built-in helpers to make stdin/stdout management a breeze (including exec, and message helpers...)
- [Fluent](https://en.wikipedia.org/wiki/Fluent_interface) syntax to express your CLI configurations with instructions such as:
- [Subcommand](examples/command/src/index.ts) support
- Long and short [option](examples/option/src/index.ts) support
- [User input](examples/input/src/index.ts) support
- [Task](examples/task/src/index.ts) support
- Shareable output between instructions
- Auto-generated help and version metadata
- TypeScript support to foster a type-safe API
- Built-in helpers to make stdin/stdout management a breeze (including exec, and message helpers...)

<br>

Expand Down Expand Up @@ -64,8 +64,7 @@ program.option({
name: { long: "global", short: "g" },
description:
"A global flag/option example accessible by all commands (key is used to persist the value into the context object)",
defaultValue:
"A default value can be set if no flag is provided by the user",
defaultValue: "A default value can be set if no flag is provided by the user",
});

program
Expand Down Expand Up @@ -275,8 +274,8 @@ program
The `task` executes a handler (either a synchronous or an asynchronous one).
The output can be either:

- Displayed gradually if no `label` is provided
- Displayed until the promise is fulfilled if a `label` property is specified (in the meantime, a spinner with the label is showcased)
- Displayed gradually if no `label` is provided
- Displayed until the promise is fulfilled if a `label` property is specified (in the meantime, a spinner with the label is showcased)

```ts
#!/usr/bin/env node
Expand Down Expand Up @@ -396,16 +395,16 @@ const wait = (delay: number) => {

## 🤩 Built with Termost

- [Quickbundle](https://github.com/adbayb/quickbundle) The zero-configuration bundler powered by ESBuild
- [Quickbundle](https://github.com/adbayb/quickbundle) The zero-configuration bundler powered by ESBuild

<br>

## 💙 Acknowledgements

This project is built upon solid open-source foundations. We'd like to thank:

- [`enquirer`](https://www.npmjs.com/package/enquirer) for managing `input` internals
- [`listr2`](https://www.npmjs.com/package/listr2) for managing `task` internals
- [`enquirer`](https://www.npmjs.com/package/enquirer) for managing `input` internals
- [`listr2`](https://www.npmjs.com/package/listr2) for managing `task` internals

<br>

Expand Down
30 changes: 17 additions & 13 deletions termost/src/api/command/command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { format } from "../../helpers/stdout";
import type { ObjectLikeConstraint, ProgramMetadata } from "../../types";

import { format } from "../../helpers/stdout";
import {
createCommandController,
getCommandController,
Expand All @@ -24,20 +23,26 @@ export const createCommand = <Values extends ObjectLikeConstraint>(
const controller = createCommandController<Values>(name, description);
const rootController = getCommandController(rootCommandName);

// Timeout to force evaluating help output at the end of the program instructions chaining.
// It allows collecting all needed input to fill the output:
/*
* Timeout to force evaluating help output at the end of the program instructions chaining.
* It allows collecting all needed input to fill the output:
*/
setTimeout(() => {
// @note: By design, the root command instructions are always executed
// even with subcommands (to share options, messages...)
/**
* By design, the root command instructions are always executed
* even with subcommands (to share options, messages...).
*/
if (isRootCommand && !isActiveCommand) {
void rootController.enable();
}

// @note: enable the current active command instructions:
// Enable the current active command instructions:
if (isActiveCommand) {
// @note: setTimeout 0 allows to run activation logic in the next event loop iteration.
// It'll allow to make sure that the `metadata` is correctly filled with all commands
// metadata (especially to let the global help option to display all available commands):
/**
* SetTimeout 0 allows to run activation logic in the next event loop iteration.
* It'll allow to make sure that the `metadata` is correctly filled with all commands
* metadata (especially to let the global help option to display all available commands).
*/
const optionKeys = Object.keys(argv.options);

const help = () => {
Expand Down Expand Up @@ -93,6 +98,7 @@ const showHelp = ({
currentCommandName: string;
isRootCommand: boolean;
rootCommandName: string;
// eslint-disable-next-line sonarjs/cyclomatic-complexity
}) => {
const commandMetadata = controller.getMetadata(rootCommandName);
const { description, options } = commandMetadata;
Expand All @@ -111,9 +117,7 @@ const showHelp = ({
{
color: "green",
},
)} ${hasCommands ? "<command> " : ""}${
hasOptions ? "[...options]" : ""
}`,
)} ${hasCommands ? "<command> " : ""}${hasOptions ? "[...options]" : ""}`,
);

if (description) {
Expand Down
15 changes: 6 additions & 9 deletions termost/src/api/command/controller/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {
EmptyObject,
ObjectLikeConstraint,
} from "../../../types";

import { createQueue } from "./queue";

export type CommandController<
Expand Down Expand Up @@ -71,13 +70,13 @@ export const createCommandController = <Values extends ObjectLikeConstraint>(
}
},
getContext(rootCommandName) {
// @note: By design, global values are accessible to subcommands
// Consequently, root command values are merged with the current command ones:
/**
* By design, global values are accessible to subcommands.
* Consequently, root command values are merged with the current command ones.
*/
if (name !== rootCommandName) {
const rootController = getCommandController(rootCommandName);

const globalContext =
rootController.getContext(rootCommandName);
const globalContext = rootController.getContext(rootCommandName);

context = {
...globalContext,
Expand All @@ -90,9 +89,7 @@ export const createCommandController = <Values extends ObjectLikeConstraint>(
getMetadata(rootCommandName) {
if (name !== rootCommandName) {
const globalMetadata =
getCommandController(rootCommandName).getMetadata(
rootCommandName,
);
getCommandController(rootCommandName).getMetadata(rootCommandName);

metadata.options = {
...globalMetadata.options,
Expand Down
20 changes: 5 additions & 15 deletions termost/src/api/input/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ export const createInput: CreateInstruction<
title: option,
value: option,
...(isMultiSelect && {
selected: ((defaultValue || []) as string[]).includes(
option,
),
selected: ((defaultValue || []) as string[]).includes(option),
}),
}));

Expand All @@ -58,31 +56,23 @@ export type InputParameters<
(
| {
label: Label<Values>;
defaultValue?: Values[Key] extends boolean
? Values[Key]
: never;
defaultValue?: Values[Key] extends boolean ? Values[Key] : never;
type: "confirm";
}
| {
label: Label<Values>;
defaultValue?: Values[Key] extends string
? Values[Key]
: never;
defaultValue?: Values[Key] extends string ? Values[Key] : never;
options: Values[Key] extends string ? Values[Key][] : never;
type: "select";
}
| {
label: Label<Values>;
defaultValue?: Values[Key] extends string
? Values[Key]
: never;
defaultValue?: Values[Key] extends string ? Values[Key] : never;
type: "text";
}
| {
label: Label<Values>;
defaultValue?: Values[Key] extends string[]
? Values[Key]
: never;
defaultValue?: Values[Key] extends string[] ? Values[Key] : never;
options: Values[Key] extends string[] ? Values[Key] : never;
type: "multiselect";
}
Expand Down
6 changes: 2 additions & 4 deletions termost/src/api/option/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import type { CommandController } from "../command";
import type {
CreateInstruction,
InstructionKey,
InstructionParameters,
ObjectLikeConstraint,
ProgramMetadata,
} from "../../types";
import type { CommandController } from "../command";

export const createOption =
(
Expand All @@ -17,9 +17,7 @@ export const createOption =
> =>
(parameters) => {
const { key, name, description, defaultValue } = parameters;

const aliases =
typeof name === "string" ? [name] : [name.short, name.long];
const aliases = typeof name === "string" ? [name] : [name.short, name.long];

const metadataKey = aliases
.map(
Expand Down
5 changes: 1 addition & 4 deletions termost/src/api/task/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ export const createTask: CreateInstruction<
} else {
receiver.add({
...(label && {
title:
typeof label === "function"
? label(context, argv)
: label,
title: typeof label === "function" ? label(context, argv) : label,
}),
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
task: async () => (value = await handler(context, argv)),
Expand Down
6 changes: 3 additions & 3 deletions termost/src/helpers/package/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { accessSync, constants } from "node:fs";
import { createRequire } from "node:module";
import process from "node:process";
import { resolve } from "node:path";
import { createRequire } from "node:module";
import { accessSync, constants } from "node:fs";

import type { PackageMetadata } from "../../types";

Expand All @@ -14,7 +15,6 @@ export const getPackageMetadata = (
const packagePathname = resolve(pathname, "package.json");

if (isFileExists(packagePathname)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require(packagePathname) as PackageMetadata;
}

Expand Down
4 changes: 1 addition & 3 deletions termost/src/helpers/process/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ describe("process", () => {
});

test("should `exec` given error", async () => {
await expect(exec("unavailable_command12345")).rejects.toContain(
"not found",
);
await expect(exec("unavailable_command12345")).rejects.toThrow(/not found/);
});
});
11 changes: 7 additions & 4 deletions termost/src/helpers/process/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { spawn } from "child_process";
import process from "node:process";
import { spawn } from "node:child_process";

export const exec = async (
command: string,
Expand All @@ -9,9 +10,11 @@ export const exec = async (
let stderr = "";
const [bin, ...args] = command.split(" ") as [string, ...string[]];

// eslint-disable-next-line sonarjs/os-command
const childProcess = spawn(bin, args, {
cwd: options.cwd,
env: {
// eslint-disable-next-line n/no-process-env
...process.env,
// @note: make sure to force color display for spawned processes
FORCE_COLOR: "1",
Expand All @@ -20,19 +23,19 @@ export const exec = async (
stdio: options.hasLiveOutput ? "inherit" : "pipe",
});

childProcess.stdout?.on("data", (chunk) => {
childProcess.stdout?.on("data", (chunk: string) => {
stdout += chunk;
});

childProcess.stderr?.on("data", (chunk) => {
childProcess.stderr?.on("data", (chunk: string) => {
stderr += chunk;
});

childProcess.on("close", (exitCode) => {
if (exitCode !== 0) {
const output = `${stderr}${stdout}`;

reject(output.trim());
reject(new Error(output.trim()));
} else {
resolve(stdout.trim());
}
Expand Down
2 changes: 2 additions & 0 deletions termost/src/helpers/stdin/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import process from "node:process";

import { describe, expect, test } from "vitest";

import { getArguments } from ".";
Expand Down
Loading

0 comments on commit 488dcf3

Please sign in to comment.