@vlandoss/run-run 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # 🦊 run-run
2
+
3
+ CLI toolbox to fullstack common scripts in Variable Land 👊
4
+
5
+ ## Prerequisites
6
+
7
+ - Bun >= 1.2.4
8
+
9
+ ## Toolbox
10
+
11
+ - [Biome](https://biomejs.dev)
12
+ - [TSC](https://www.typescriptlang.org)
13
+ - [rimraf](https://www.npmjs.com/package/rimraf)
14
+
15
+ ## Installation
16
+
17
+ ```sh
18
+ pnpm add @vlandoss/run-run
19
+ ```
20
+
21
+ It will adds the `rr` and `run-run` command to your project
22
+
23
+ ## Usage
24
+
25
+ > [!NOTE]
26
+ > The documentation is WIP
27
+
28
+ Run the help command:
29
+
30
+ ```sh
31
+ rr help
32
+ ```
33
+
34
+ ## Troubleshooting
35
+
36
+ To enable debug mode, set the `DEBUG` environment variable to `run-run:*` before running *any* command.
37
+
38
+ ```sh
39
+ DEBUG=run-run:* rr help
40
+ ```
41
+
42
+ Additionally, there is an special command to display `package.json` information:
43
+
44
+ ```sh
45
+ rr info:pkg --help
46
+ ```
package/bin.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { main } from "./src/main";
3
+
4
+ main({
5
+ binDir: __dirname,
6
+ });
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@vlandoss/run-run",
3
+ "version": "0.0.1",
4
+ "description": "The CLI toolbox to fullstack common scripts in Variable Land",
5
+ "homepage": "https://github.com/variableland/dx/tree/main/packages/run-run#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/variableland/dx/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/variableland/dx.git"
12
+ },
13
+ "license": "MIT",
14
+ "author": "rcrd <rcrd@variable.land>",
15
+ "type": "module",
16
+ "module": "src/main.ts",
17
+ "bin": {
18
+ "rr": "./bin.ts",
19
+ "run-run": "./bin.ts"
20
+ },
21
+ "files": [
22
+ "bin",
23
+ "src",
24
+ "plopfiles",
25
+ "tsconfig.json"
26
+ ],
27
+ "dependencies": {
28
+ "@biomejs/biome": "1.9.4",
29
+ "commander": "13.1.0",
30
+ "is-ci": "4.1.0",
31
+ "rimraf": "6.0.1",
32
+ "typescript": "5.8.2",
33
+ "@vlandoss/clibuddy": "0.0.1",
34
+ "@vlandoss/loggy": "0.0.1"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "engines": {
40
+ "bun": ">=1.0.0"
41
+ },
42
+ "scripts": {
43
+ "test": "bun test",
44
+ "typecheck": "rr tsc"
45
+ }
46
+ }
package/src/main.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { type Options, createProgram } from "./program";
2
+ import { logger } from "./services/logger";
3
+
4
+ export async function main(options: Options) {
5
+ try {
6
+ const { cmd } = await createProgram(options);
7
+ await cmd.parseAsync();
8
+ } catch (error) {
9
+ logger.error("Cannot run main successfully", error);
10
+ process.exit(1);
11
+ }
12
+ }
@@ -0,0 +1,154 @@
1
+ // Bun Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`should match command: "help" 1`] = `
4
+ "🦊 R U N - R U N: The CLI toolbox to fullstack common scripts in Variable Land 👊
5
+
6
+ Usage: rr|run-run [options] [command]
7
+
8
+ Options:
9
+ -v, --version output the version number
10
+ -h, --help display help for command
11
+
12
+ Commands:
13
+ format|fmt [options] format the code 🎨
14
+ lint [options] lint the code 🧹
15
+ test:static [options] check format and lint issues ✅
16
+ clean [options] delete dirty folders or files such as node_modules, etc
17
+ 🗑️
18
+ typecheck|tsc check if TypeScript code is well typed 🎨
19
+ info:pkg [options] display run-run package.json ℹ️
20
+ help [command] display help for command
21
+
22
+ Acknowledgment:
23
+ - kcd-scripts: for main inspiration
24
+ https://github.com/kentcdodds/kcd-scripts
25
+
26
+ - peruvian news: in honor to Run Run
27
+ https://es.wikipedia.org/wiki/Run_Run
28
+ "
29
+ `;
30
+
31
+ exports[`should match command: "--help" 1`] = `
32
+ "🦊 R U N - R U N: The CLI toolbox to fullstack common scripts in Variable Land 👊
33
+
34
+ Usage: rr|run-run [options] [command]
35
+
36
+ Options:
37
+ -v, --version output the version number
38
+ -h, --help display help for command
39
+
40
+ Commands:
41
+ format|fmt [options] format the code 🎨
42
+ lint [options] lint the code 🧹
43
+ test:static [options] check format and lint issues ✅
44
+ clean [options] delete dirty folders or files such as node_modules, etc
45
+ 🗑️
46
+ typecheck|tsc check if TypeScript code is well typed 🎨
47
+ info:pkg [options] display run-run package.json ℹ️
48
+ help [command] display help for command
49
+
50
+ Acknowledgment:
51
+ - kcd-scripts: for main inspiration
52
+ https://github.com/kentcdodds/kcd-scripts
53
+
54
+ - peruvian news: in honor to Run Run
55
+ https://es.wikipedia.org/wiki/Run_Run
56
+ "
57
+ `;
58
+
59
+ exports[`should match command: "--version" 1`] = `
60
+ "0.0.0-test
61
+ "
62
+ `;
63
+
64
+ exports[`should match command: "-v" 1`] = `
65
+ "0.0.0-test
66
+ "
67
+ `;
68
+
69
+ exports[`should match help message for command "format" 1`] = `
70
+ "Usage: rr format|fmt [options]
71
+
72
+ format the code 🎨
73
+
74
+ Options:
75
+ -c, --check check if the code is formatted (default: true)
76
+ -f, --fix format all the code
77
+ -h, --help display help for command
78
+
79
+ Under the hood, this command uses the biome CLI to format the code.
80
+ "
81
+ `;
82
+
83
+ exports[`should match help message for command "lint" 1`] = `
84
+ "Usage: rr lint [options]
85
+
86
+ lint the code 🧹
87
+
88
+ Options:
89
+ -c, --check check if the code is valid (default: true)
90
+ -f, --fix try to fix all the code
91
+ -h, --help display help for command
92
+
93
+ Under the hood, this command uses the biome CLI to lint the code.
94
+ "
95
+ `;
96
+
97
+ exports[`should match help message for command "test:static" 1`] = `
98
+ "Usage: rr test:static [options]
99
+
100
+ check format and lint issues ✅
101
+
102
+ Options:
103
+ -f, --fix try to fix issues automatically
104
+ --fix-staged try to fix staged files only
105
+ -h, --help display help for command
106
+
107
+ Under the hood, this command uses the biome CLI to check the code.
108
+ "
109
+ `;
110
+
111
+ exports[`should match help message for command "clean" 1`] = `
112
+ "Usage: rr clean [options]
113
+
114
+ delete dirty folders or files such as node_modules, etc 🗑️
115
+
116
+ Options:
117
+ --only-dist delete 'dist' folders only
118
+ -h, --help display help for command
119
+
120
+ Under the hood, this command uses the rimraf.js to delete dirty folders or files.
121
+ "
122
+ `;
123
+
124
+ exports[`should match help message for command "typecheck" 1`] = `
125
+ "Usage: rr typecheck|tsc [options]
126
+
127
+ check if TypeScript code is well typed 🎨
128
+
129
+ Options:
130
+ -h, --help display help for command
131
+
132
+ Under the hood, this command uses the TSC CLI to check the code.
133
+ "
134
+ `;
135
+
136
+ exports[`should match help message for command "info:pkg" 1`] = `
137
+ "Usage: rr info:pkg [options]
138
+
139
+ display run-run package.json ℹ️
140
+
141
+ Options:
142
+ -f, --filter <filter> lodash get id like to filter info by
143
+ -c, --current display package.json where run-run will be executed
144
+ -h, --help display help for command
145
+ "
146
+ `;
147
+
148
+ exports[`should match "format" command 1`] = `"biome format --no-errors-on-unmatched --colors=force"`;
149
+
150
+ exports[`should match "lint" command 1`] = `"biome check --colors=force --formatter-enabled=false"`;
151
+
152
+ exports[`should match "test:static" command 1`] = `"biome check --colors=force"`;
153
+
154
+ exports[`should match "typecheck" command 1`] = `"tsc --noEmit"`;
@@ -0,0 +1,48 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { createTestProgram, execCli, mocked, parseProgram } from "test/helpers";
3
+
4
+ const { cmd, ctx } = await createTestProgram();
5
+ const $ = ctx.shell.$;
6
+
7
+ const rootCommands = ["help", "--help", "--version", "-v"];
8
+
9
+ afterEach(() => {
10
+ mocked($).mockClear();
11
+ });
12
+
13
+ for (const cmd of rootCommands) {
14
+ test(`should match command: "${cmd}"`, async () => {
15
+ const { stdout } = await execCli(cmd);
16
+
17
+ expect(stdout).toMatchSnapshot();
18
+ });
19
+ }
20
+
21
+ for (const command of cmd.commands) {
22
+ const cmd = command.name();
23
+
24
+ test(`should match help message for command "${cmd}"`, async () => {
25
+ const { stdout } = await execCli(`${cmd} --help`);
26
+
27
+ expect(stdout).toMatchSnapshot();
28
+ });
29
+ }
30
+
31
+ // these command don't use shell ($) instance
32
+ const hardCommands = ["info:pkg", "clean"];
33
+
34
+ const easyTesteableCommands = cmd.commands.filter((command) => {
35
+ const isHard = hardCommands.some((cmd) => command.name() === cmd);
36
+ return !isHard;
37
+ });
38
+
39
+ for (const command of easyTesteableCommands) {
40
+ const cmd = command.name();
41
+
42
+ test(`should match "${cmd}" command`, async () => {
43
+ await parseProgram([cmd]);
44
+
45
+ expect($).toHaveBeenCalledTimes(1);
46
+ expect(mocked($).mock.results[0]?.value).toMatchSnapshot();
47
+ });
48
+ }
@@ -0,0 +1,46 @@
1
+ import { cwd } from "@vlandoss/clibuddy";
2
+ import { createCommand } from "commander";
3
+ import { rimraf } from "rimraf";
4
+ import { logger } from "~/services/logger";
5
+
6
+ export function createCleanCommand() {
7
+ return createCommand("clean")
8
+ .description("delete dirty folders or files such as node_modules, etc 🗑️")
9
+ .option("--only-dist", "delete 'dist' folders only")
10
+ .action(async function cleanCommandAction(options) {
11
+ try {
12
+ if (options.onlyDist) {
13
+ logger.info("Cleaning only 'dist' folders... ⌛");
14
+
15
+ await rimraf("**/dist", {
16
+ glob: {
17
+ cwd,
18
+ ignore: ["**/node_modules/**"],
19
+ },
20
+ });
21
+
22
+ logger.info("Done ✅");
23
+
24
+ return;
25
+ }
26
+
27
+ logger.info("Cleaning all... ⌛");
28
+
29
+ const dirtyPaths = ["**/.turbo", "**/dist", "**/node_modules", "pnpm-lock.yaml", "bun.lock"];
30
+
31
+ logger.info(dirtyPaths.join("\n"));
32
+
33
+ await rimraf(dirtyPaths, {
34
+ glob: {
35
+ cwd,
36
+ },
37
+ });
38
+
39
+ logger.info("Done ✅");
40
+ } catch (error) {
41
+ logger.error(error);
42
+ process.exit(1);
43
+ }
44
+ })
45
+ .addHelpText("afterAll", "\nUnder the hood, this command uses the rimraf.js to delete dirty folders or files.");
46
+ }
@@ -0,0 +1,30 @@
1
+ import { createCommand } from "commander";
2
+ import type { Context } from "~/services/ctx";
3
+ import { logger } from "~/services/logger";
4
+
5
+ export function createFormatCommand(ctx: Context) {
6
+ return createCommand("format")
7
+ .alias("fmt")
8
+ .description("format the code 🎨")
9
+ .option("-c, --check", "check if the code is formatted", true)
10
+ .option("-f, --fix", "format all the code")
11
+ .action(async function formatAction(options) {
12
+ const $ = ctx.shell.$;
13
+ const toolCmd = "biome format --no-errors-on-unmatched --colors=force";
14
+
15
+ try {
16
+ if (options.fix) {
17
+ await $`${toolCmd} --fix`;
18
+ return;
19
+ }
20
+
21
+ if (options.check) {
22
+ await $`${toolCmd}`;
23
+ }
24
+ } catch (error) {
25
+ logger.error(error);
26
+ process.exit(1);
27
+ }
28
+ })
29
+ .addHelpText("afterAll", "\nUnder the hood, this command uses the biome CLI to format the code.");
30
+ }
@@ -0,0 +1,36 @@
1
+ import { createCommand } from "commander";
2
+ import type { Context } from "~/services/ctx";
3
+ import { logger } from "~/services/logger";
4
+ import { get } from "~/utils/get";
5
+
6
+ export function createInfoPkgCommand(ctx: Context) {
7
+ return createCommand("info:pkg")
8
+ .description("display run-run package.json ℹ️")
9
+ .option("-f, --filter <filter>", "lodash get id like to filter info by")
10
+ .option("-c, --current", "display package.json where run-run will be executed")
11
+ .action(async function pkgAction(options) {
12
+ try {
13
+ const { appPkg, binPkg } = ctx;
14
+
15
+ const infoObject = options.current ? appPkg.info() : binPkg.info();
16
+
17
+ if (!options.filter) {
18
+ logger.info("%O", infoObject);
19
+ return;
20
+ }
21
+
22
+ const { filter } = options;
23
+ const subInfoObject = get(infoObject.packageJson, filter);
24
+
25
+ if (!subInfoObject) {
26
+ logger.info("No info found");
27
+ return;
28
+ }
29
+
30
+ logger.info("%O", { [filter]: subInfoObject });
31
+ } catch (error) {
32
+ logger.error(error);
33
+ process.exit(1);
34
+ }
35
+ });
36
+ }
@@ -0,0 +1,29 @@
1
+ import { createCommand } from "commander";
2
+ import type { Context } from "~/services/ctx";
3
+ import { logger } from "~/services/logger";
4
+
5
+ export function createLintCommand(ctx: Context) {
6
+ return createCommand("lint")
7
+ .description("lint the code 🧹")
8
+ .option("-c, --check", "check if the code is valid", true)
9
+ .option("-f, --fix", "try to fix all the code")
10
+ .action(async function lintAction(options) {
11
+ const $ = ctx.shell.$;
12
+ const toolCmd = "biome check --colors=force --formatter-enabled=false";
13
+
14
+ try {
15
+ if (options.fix) {
16
+ await $`${toolCmd} --fix --unsafe`;
17
+ return;
18
+ }
19
+
20
+ if (options.check) {
21
+ await $`${toolCmd}`;
22
+ }
23
+ } catch (error) {
24
+ logger.error(error);
25
+ process.exit(1);
26
+ }
27
+ })
28
+ .addHelpText("afterAll", "\nUnder the hood, this command uses the biome CLI to lint the code.");
29
+ }
@@ -0,0 +1,33 @@
1
+ import { createCommand } from "commander";
2
+ import isCI from "is-ci";
3
+ import type { Context } from "~/services/ctx";
4
+ import { logger } from "~/services/logger";
5
+
6
+ export function createTestStaticCommand(ctx: Context) {
7
+ return createCommand("test:static")
8
+ .description("check format and lint issues ✅")
9
+ .option("-f, --fix", "try to fix issues automatically")
10
+ .option("--fix-staged", "try to fix staged files only")
11
+ .action(async function testStaticAction(options) {
12
+ const $ = ctx.shell.$;
13
+ const toolCmd = (cmd = "check") => `biome ${cmd} --colors=force`;
14
+
15
+ try {
16
+ if (options.fix) {
17
+ await $`${toolCmd()} --fix --unsafe`;
18
+ return;
19
+ }
20
+
21
+ if (options.fixStaged) {
22
+ await $`${toolCmd()} --no-errors-on-unmatched --fix --unsafe --staged`;
23
+ return;
24
+ }
25
+
26
+ await $`${toolCmd(isCI ? "ci" : "check")}`;
27
+ } catch (error) {
28
+ logger.error(error);
29
+ process.exit(1);
30
+ }
31
+ })
32
+ .addHelpText("afterAll", "\nUnder the hood, this command uses the biome CLI to check the code.");
33
+ }
@@ -0,0 +1,25 @@
1
+ import { createCommand } from "commander";
2
+ import type { Context } from "~/services/ctx";
3
+ import { logger } from "~/services/logger";
4
+
5
+ export function createTypecheckCommand(ctx: Context) {
6
+ return createCommand("typecheck")
7
+ .alias("tsc")
8
+ .description("check if TypeScript code is well typed 🎨")
9
+ .action(async function typecheckAction() {
10
+ const { appPkg } = ctx;
11
+ const $ = ctx.shell.$;
12
+
13
+ try {
14
+ if (appPkg?.hasFile("tsconfig.json")) {
15
+ await $`tsc --noEmit`;
16
+ } else {
17
+ logger.info("No tsconfig.json found. Skipping type checking.");
18
+ }
19
+ } catch (error) {
20
+ logger.error(error);
21
+ process.exit(1);
22
+ }
23
+ })
24
+ .addHelpText("afterAll", "\nUnder the hood, this command uses the TSC CLI to check the code.");
25
+ }
@@ -0,0 +1,32 @@
1
+ import { getVersion } from "@vlandoss/clibuddy";
2
+ import { Command } from "commander";
3
+ import { createContext } from "~/services/ctx";
4
+ import { createCleanCommand } from "./commands/clean";
5
+ import { createFormatCommand } from "./commands/format";
6
+ import { createInfoPkgCommand } from "./commands/info-pkg";
7
+ import { createLintCommand } from "./commands/lint";
8
+ import { createTestStaticCommand } from "./commands/test-static";
9
+ import { createTypecheckCommand } from "./commands/typecheck";
10
+ import { BANNER_TEXT, CREDITS_TEXT } from "./ui";
11
+
12
+ export type Options = {
13
+ binDir: string;
14
+ };
15
+
16
+ export async function createProgram(options: Options) {
17
+ const ctx = await createContext(options.binDir);
18
+
19
+ const cmd = new Command("rr")
20
+ .alias("run-run")
21
+ .version(getVersion(ctx.binPkg), "-v, --version")
22
+ .addHelpText("before", BANNER_TEXT)
23
+ .addHelpText("after", CREDITS_TEXT)
24
+ .addCommand(createFormatCommand(ctx))
25
+ .addCommand(createLintCommand(ctx))
26
+ .addCommand(createTestStaticCommand(ctx))
27
+ .addCommand(createCleanCommand())
28
+ .addCommand(createTypecheckCommand(ctx))
29
+ .addCommand(createInfoPkgCommand(ctx));
30
+
31
+ return { cmd, ctx };
32
+ }
@@ -0,0 +1,14 @@
1
+ import { colors } from "@vlandoss/clibuddy";
2
+
3
+ const UI_LOGO = `🦊 ${colors.blueBright("R")} ${colors.redBright("U")} ${colors.greenBright("N")} - ${colors.blueBright("R")} ${colors.redBright("U")} ${colors.greenBright("N")}`;
4
+
5
+ const COMPANY_LOGO = `${colors.redBright("Variable Land")} 👊`;
6
+
7
+ export const BANNER_TEXT = `${UI_LOGO}: The CLI toolbox to fullstack common scripts in ${COMPANY_LOGO}\n`;
8
+
9
+ export const CREDITS_TEXT = `\nAcknowledgment:
10
+ - kcd-scripts: for main inspiration
11
+ https://github.com/kentcdodds/kcd-scripts
12
+
13
+ - peruvian news: in honor to Run Run
14
+ https://es.wikipedia.org/wiki/Run_Run`;
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import { type PkgService, type ShellService, createPkgService, createShellService, cwd } from "@vlandoss/clibuddy";
3
+ import { logger } from "./logger";
4
+
5
+ export type Context = {
6
+ binPkg: PkgService;
7
+ appPkg: PkgService;
8
+ shell: ShellService;
9
+ };
10
+
11
+ export async function createContext(binDir: string): Promise<Context> {
12
+ const debug = logger.subdebug("create-context-value");
13
+
14
+ const binPath = fs.realpathSync(binDir);
15
+
16
+ debug("bin path:", binPath);
17
+ debug("process cwd:", process.cwd());
18
+
19
+ const [appPkg, binPkg] = await Promise.all([createPkgService(cwd), createPkgService(binPath)]);
20
+
21
+ if (!binPkg) {
22
+ throw new Error("Could not find bin package.json");
23
+ }
24
+
25
+ if (!appPkg) {
26
+ throw new Error("Could not find app package.json");
27
+ }
28
+
29
+ debug("app pkg info: %O", appPkg.info());
30
+ debug("bin pkg info: %O", binPkg.info());
31
+
32
+ const shell = createShellService({
33
+ localBaseBinPath: [binDir],
34
+ });
35
+
36
+ return {
37
+ appPkg,
38
+ binPkg,
39
+ shell,
40
+ };
41
+ }
@@ -0,0 +1,5 @@
1
+ import { createLoggy } from "@vlandoss/loggy";
2
+
3
+ export const logger = createLoggy({
4
+ namespace: "run-run",
5
+ });
@@ -0,0 +1,22 @@
1
+ import { expect, it } from "bun:test";
2
+ import { get } from "../get";
3
+
4
+ it("should get the value when the key is found", () => {
5
+ const obj = { a: 1 };
6
+ expect(get(obj, "a")).toBe(1);
7
+ });
8
+
9
+ it("should get the value when the nested key is found", () => {
10
+ const obj = { a: { b: { c: 1 } } };
11
+ expect(get(obj, "a.b.c")).toBe(1);
12
+ });
13
+
14
+ it("should get undefined when the key is not found", () => {
15
+ const obj = { a: 1 };
16
+ expect(get(obj, "b")).toBeUndefined();
17
+ });
18
+
19
+ it("should get undefined when the nested key is not found", () => {
20
+ const obj = { a: { b: { c: 1 } } };
21
+ expect(get(obj, "a.b.d")).toBeUndefined();
22
+ });
@@ -0,0 +1,30 @@
1
+ export function get(obj: unknown, key: string) {
2
+ if (typeof obj !== "object" || obj === null) {
3
+ return undefined;
4
+ }
5
+
6
+ if (typeof key !== "string") {
7
+ throw new Error("Key must be a string");
8
+ }
9
+
10
+ const parts = key.split(".");
11
+
12
+ let value = {
13
+ ...obj,
14
+ };
15
+
16
+ for (let i = 0; i < parts.length; i++) {
17
+ const key = parts[i] as string;
18
+
19
+ // @ts-expect-error value is type to {}
20
+ const subValue = value[key];
21
+
22
+ if (!subValue) {
23
+ return undefined;
24
+ }
25
+
26
+ value = subValue;
27
+ }
28
+
29
+ return value;
30
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": ["@total-typescript/tsconfig/bundler/no-dom/app"],
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "~/*": ["./src/*"],
7
+ "test/*": ["./test/*"],
8
+ "@vlandoss/*": ["../../packages/*/src"]
9
+ }
10
+ }
11
+ }