@vlandoss/run-run 0.0.14-git-40ae44d.0 → 0.0.14-git-cf575e8.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vlandoss/run-run",
3
- "version": "0.0.14-git-40ae44d.0",
3
+ "version": "0.0.14-git-cf575e8.0",
4
4
  "description": "The CLI toolbox to fullstack common scripts in Variable Land",
5
5
  "homepage": "https://github.com/variableland/dx/tree/main/packages/run-run#readme",
6
6
  "bugs": {
@@ -19,28 +19,29 @@
19
19
  "bin": {
20
20
  "rr": "./bin.ts",
21
21
  "run-run": "./bin.ts",
22
- "biome": "./tools/biome"
22
+ "biome": "./tools/biome",
23
+ "oxfmt": "./tools/oxfmt",
24
+ "oxlint": "./tools/oxlint"
23
25
  },
24
26
  "files": [
25
- "bin",
27
+ "bin.ts",
26
28
  "src",
27
29
  "tools",
28
- "plopfiles",
29
30
  "tsconfig.json"
30
31
  ],
31
32
  "dependencies": {
32
33
  "@biomejs/biome": "2.1.4",
33
- "c12": "4.0.0-beta.3",
34
34
  "commander": "13.1.0",
35
35
  "glob": "^11.0.2",
36
36
  "is-ci": "4.1.0",
37
+ "lilconfig": "^3.1.3",
37
38
  "memoize": "^10.2.0",
38
39
  "oxfmt": "^0.35.0",
39
40
  "oxlint": "^1.50.0",
40
41
  "oxlint-tsgolint": "^0.15.0",
41
42
  "rimraf": "6.0.1",
42
43
  "typescript": "5.8.2",
43
- "@vlandoss/clibuddy": "0.0.5",
44
+ "@vlandoss/clibuddy": "0.0.6-git-cf575e8.0",
44
45
  "@vlandoss/loggy": "0.0.5"
45
46
  },
46
47
  "publishConfig": {
@@ -10,20 +10,16 @@ export function createCheckCommand(ctx: Context) {
10
10
  .option("-f, --fix", "try to fix issues automatically")
11
11
  .option("--fix-staged", "try to fix staged files only")
12
12
  .action(async function checkAction(options) {
13
- const { $ } = new BiomeService(ctx.shell);
14
- const toolCmd = (cmd = "check") => `biome ${cmd} --colors=force`;
13
+ const biome = new BiomeService(ctx.shell);
14
+ const toolCmd = (cmd = "check") => `${cmd} --colors=force`;
15
15
 
16
16
  if (options.fix) {
17
- await $`${toolCmd()} --fix`;
18
- return;
17
+ await biome.exec(`${toolCmd()} --fix`);
18
+ } else if (options.fixStaged) {
19
+ await biome.exec(`${toolCmd()} --no-errors-on-unmatched --fix --staged`);
20
+ } else {
21
+ await biome.exec(`${toolCmd(isCI ? "ci" : "check")}`);
19
22
  }
20
-
21
- if (options.fixStaged) {
22
- await $`${toolCmd()} --no-errors-on-unmatched --fix --staged`;
23
- return;
24
- }
25
-
26
- await $`${toolCmd(isCI ? "ci" : "check")}`;
27
23
  })
28
24
  .addHelpText("afterAll", "\nUnder the hood, this command uses the biome CLI to check the code.");
29
25
  }
@@ -1,3 +1,4 @@
1
+ import { colors } from "@vlandoss/clibuddy";
1
2
  import { createCommand } from "commander";
2
3
  import type { Context } from "#/services/ctx";
3
4
 
@@ -5,7 +6,10 @@ export function createConfigCommand(ctx: Context) {
5
6
  return createCommand("config")
6
7
  .alias("cfg")
7
8
  .description("display the current config 🛠️")
8
- .action(function configAction() {
9
- console.log(ctx.config);
9
+ .action(async function configAction() {
10
+ const { config, meta } = ctx.config;
11
+ console.log(colors.muted("Config:"));
12
+ console.log(config);
13
+ console.log(colors.muted(`Loaded from ${meta.filepath ? colors.link(meta.filepath) : "n/a"}`));
10
14
  });
11
15
  }
@@ -2,52 +2,33 @@ import { createCommand } from "commander";
2
2
  import { BiomeService } from "#/services/biome";
3
3
  import type { Context } from "#/services/ctx";
4
4
  import { OxfmtService } from "#/services/oxfmt";
5
+ import type { Formatter } from "#/types/tool";
5
6
 
6
7
  type ActionOptions = {
7
8
  check?: boolean;
8
9
  fix?: boolean;
9
10
  };
10
11
 
11
- export function createFormatCommand(ctx: Context) {
12
- const fmtCommand = createCommand("fmt")
13
- .alias("format")
14
- .description("format the code 🎨")
15
- .option("-c, --check", "check if the code is formatted", true)
16
- .option("-f, --fix", "format all the code");
12
+ function getToolService(ctx: Context): Formatter {
13
+ const { config } = ctx.config;
17
14
 
18
- if (ctx.config.future?.oxc) {
19
- fmtCommand
20
- .action(async function formatAction(options: ActionOptions) {
21
- const { $ } = new OxfmtService(ctx.shell);
22
- const toolCmd = "oxfmt --no-error-on-unmatched-pattern";
23
-
24
- if (options.fix) {
25
- await $`${toolCmd} --fix`;
26
- return;
27
- }
28
-
29
- if (options.check) {
30
- await $`${toolCmd} --check`;
31
- }
32
- })
33
- .addHelpText("afterAll", "\nUnder the hood, this command uses the oxfmt CLI to format the code.");
34
- } else {
35
- fmtCommand
36
- .action(async function formatAction(options: ActionOptions) {
37
- const { $ } = new BiomeService(ctx.shell);
38
- const toolCmd = "biome format --no-errors-on-unmatched --colors=force";
15
+ if (config.future?.oxc) {
16
+ return new OxfmtService(ctx.shell);
17
+ }
39
18
 
40
- if (options.fix) {
41
- await $`${toolCmd} --fix`;
42
- return;
43
- }
19
+ return new BiomeService(ctx.shell);
20
+ }
44
21
 
45
- if (options.check) {
46
- await $`${toolCmd}`;
47
- }
48
- })
49
- .addHelpText("afterAll", "\nUnder the hood, this command uses the biome CLI to format the code.");
50
- }
22
+ export function createFormatCommand(ctx: Context) {
23
+ const toolService = getToolService(ctx);
51
24
 
52
- return fmtCommand;
25
+ return createCommand("fmt")
26
+ .alias("format")
27
+ .description("format the code 🎨")
28
+ .option("-c, --check", "check if the code is formatted", true)
29
+ .option("-f, --fix", "format all the code")
30
+ .action(async function formatAction(options: ActionOptions) {
31
+ await toolService.format(options);
32
+ })
33
+ .addHelpText("afterAll", `\nUnder the hood, this command uses the ${toolService.bin} CLI to format the code.`);
53
34
  }
@@ -2,51 +2,32 @@ import { createCommand } from "commander";
2
2
  import { BiomeService } from "#/services/biome";
3
3
  import type { Context } from "#/services/ctx";
4
4
  import { OxlintService } from "#/services/oxlint";
5
+ import type { Linter } from "#/types/tool";
5
6
 
6
7
  type ActionOptions = {
7
8
  check?: boolean;
8
9
  fix?: boolean;
9
10
  };
10
11
 
11
- export function createLintCommand(ctx: Context) {
12
- const lintCommand = createCommand("lint")
13
- .description("lint the code 🧹")
14
- .option("-c, --check", "check if the code is valid", true)
15
- .option("-f, --fix", "try to fix all the code");
12
+ function getToolService(ctx: Context): Linter {
13
+ const { config } = ctx.config;
16
14
 
17
- if (ctx.config.future?.oxc) {
18
- lintCommand
19
- .action(async function lintAction(options: ActionOptions) {
20
- const { $ } = new OxlintService(ctx.shell);
21
- const toolCmd = "oxlint --report-unused-disable-directives";
22
-
23
- if (options.fix) {
24
- await $`${toolCmd} --fix`;
25
- return;
26
- }
27
-
28
- if (options.check) {
29
- await $`${toolCmd} --check`;
30
- }
31
- })
32
- .addHelpText("afterAll", "\nUnder the hood, this command uses the oxlint CLI to lint the code.");
33
- } else {
34
- lintCommand
35
- .action(async function lintAction(options: ActionOptions) {
36
- const { $ } = new BiomeService(ctx.shell);
37
- const toolCmd = "biome check --colors=force --formatter-enabled=false";
15
+ if (config.future?.oxc) {
16
+ return new OxlintService(ctx.shell);
17
+ }
38
18
 
39
- if (options.fix) {
40
- await $`${toolCmd} --fix --unsafe`;
41
- return;
42
- }
19
+ return new BiomeService(ctx.shell);
20
+ }
43
21
 
44
- if (options.check) {
45
- await $`${toolCmd}`;
46
- }
47
- })
48
- .addHelpText("afterAll", "\nUnder the hood, this command uses the biome CLI to lint the code.");
49
- }
22
+ export function createLintCommand(ctx: Context) {
23
+ const toolService = getToolService(ctx);
50
24
 
51
- return lintCommand;
25
+ return createCommand("lint")
26
+ .description("lint the code 🧹")
27
+ .option("-c, --check", "check if the code is valid", true)
28
+ .option("-f, --fix", "try to fix all the code")
29
+ .action(async function lintAction(options: ActionOptions) {
30
+ await toolService.lint(options);
31
+ })
32
+ .addHelpText("afterAll", `\nUnder the hood, this command uses the ${toolService.bin} CLI to lint the code.`);
52
33
  }
@@ -1,41 +1,45 @@
1
+ import type { ShellService } from "@vlandoss/clibuddy";
1
2
  import { createCommand } from "commander";
2
3
  import { BiomeService } from "#/services/biome";
3
4
  import type { Context } from "#/services/ctx";
4
5
  import { OxfmtService } from "#/services/oxfmt";
5
6
  import { OxlintService } from "#/services/oxlint";
7
+ import type { ToolService } from "#/services/tool";
6
8
 
7
9
  type ActionParams = {
8
10
  args: string[];
9
11
  };
10
12
 
11
- function createToolCommand(toolBin: string) {
12
- // biome-ignore format: I prefer multi-line here
13
- return createCommand(toolBin)
13
+ function getToolService(bin: string, shell: ShellService): ToolService {
14
+ switch (bin) {
15
+ case "biome":
16
+ return new BiomeService(shell);
17
+ case "oxfmt":
18
+ return new OxfmtService(shell);
19
+ case "oxlint":
20
+ return new OxlintService(shell);
21
+ default:
22
+ throw new Error(`Unknown tool: ${bin}`);
23
+ }
24
+ }
25
+
26
+ function createToolCommand(bin: string, shell: ShellService) {
27
+ const tool = getToolService(bin, shell);
28
+
29
+ return createCommand(tool.bin)
14
30
  .helpCommand(false)
15
31
  .helpOption(false)
16
32
  .allowExcessArguments(true)
17
- .allowUnknownOption(true);
33
+ .allowUnknownOption(true)
34
+ .action(async (_: unknown, { args }: ActionParams) => {
35
+ await tool.exec(args);
36
+ });
18
37
  }
19
38
 
20
39
  export function createToolsCommand(ctx: Context) {
21
40
  return createCommand("tools")
22
41
  .description("expose the internal tools 🛠️")
23
- .addCommand(
24
- createToolCommand("biome").action((_: unknown, { args }: ActionParams) => {
25
- const biomeService = new BiomeService(ctx.shell);
26
- biomeService.execute(args);
27
- }),
28
- )
29
- .addCommand(
30
- createToolCommand("oxfmt").action((_: unknown, { args }: ActionParams) => {
31
- const oxfmtService = new OxfmtService(ctx.shell);
32
- oxfmtService.execute(args);
33
- }),
34
- )
35
- .addCommand(
36
- createToolCommand("oxlint").action((_: unknown, { args }: ActionParams) => {
37
- const oxlintService = new OxlintService(ctx.shell);
38
- oxlintService.execute(args);
39
- }),
40
- );
42
+ .addCommand(createToolCommand("biome", ctx.shell))
43
+ .addCommand(createToolCommand("oxfmt", ctx.shell))
44
+ .addCommand(createToolCommand("oxlint", ctx.shell));
41
45
  }
@@ -1,4 +1,4 @@
1
- import { cwd } from "@vlandoss/clibuddy";
1
+ import { cwd, type ShellService } from "@vlandoss/clibuddy";
2
2
  import type { AnyLogger } from "@vlandoss/loggy";
3
3
  import { createCommand } from "commander";
4
4
  import type { Context } from "#/services/ctx";
@@ -9,86 +9,96 @@ type TypecheckAtOptions = {
9
9
  dir: string;
10
10
  scripts: Record<string, string | undefined> | undefined;
11
11
  log: AnyLogger;
12
+ shell: ShellService;
13
+ run: (shell: ShellService) => Promise<void>;
12
14
  };
13
15
 
14
- export function createTypecheckCommand(ctx: Context) {
15
- const typecheckCommand = createCommand("tsc").alias("typecheck").description("check if TypeScript code is well typed 🎨");
16
+ const getPreScript = (scripts: Record<string, string | undefined> | undefined) => scripts?.pretsc ?? scripts?.pretypecheck;
16
17
 
17
- if (ctx.config.future?.oxc) {
18
- typecheckCommand
19
- .action(async function typecheckAction() {
20
- const { $ } = new OxlintService(ctx.shell);
21
- await $`oxlint --type-aware --type-check --report-unused-disable-directives`;
22
- })
23
- .addHelpText("afterAll", "\nUnder the hood, this command uses the oxlint CLI to check the code.");
24
- } else {
25
- typecheckCommand
26
- .action(async function typecheckAction() {
27
- const { appPkg, shell } = ctx;
18
+ async function typecheckAt({ dir, scripts, log, shell, run }: TypecheckAtOptions) {
19
+ const shellAt = cwd === dir ? shell : shell.at(dir);
28
20
 
29
- const isTsProject = (dir: string) => appPkg.hasFile("tsconfig.json", dir);
21
+ try {
22
+ const preScript = getPreScript(scripts);
23
+ if (preScript) {
24
+ log.start(`Running pre-script: ${preScript}`);
25
+ await shellAt.$`${preScript}`;
26
+ log.success("Pre-script completed");
27
+ }
30
28
 
31
- const getPreScript = (scripts: Record<string, string | undefined> | undefined) =>
32
- scripts?.pretsc ?? scripts?.pretypecheck;
29
+ log.start("Type checking started");
30
+ await run(shellAt);
31
+ log.success("Typecheck completed");
32
+ } catch (error) {
33
+ log.error("Typecheck failed");
34
+ throw error;
35
+ }
36
+ }
33
37
 
34
- async function typecheckAt({ dir, scripts, log }: TypecheckAtOptions) {
35
- const shellAt = cwd === dir ? shell : shell.at(dir);
38
+ export function createTypecheckCommand(ctx: Context) {
39
+ const {
40
+ appPkg,
41
+ shell,
42
+ config: { config },
43
+ } = ctx;
36
44
 
37
- try {
38
- const preScript = getPreScript(scripts);
39
- if (preScript) {
40
- log.start(`Running pre-script: ${preScript}`);
41
- await shellAt.$`${preScript}`;
42
- log.success("Pre-script completed");
43
- }
45
+ return createCommand("tsc")
46
+ .alias("typecheck")
47
+ .description("check if TypeScript code is well typed 🎨")
48
+ .addHelpText(
49
+ "afterAll",
50
+ `\nUnder the hood, this command uses the ${config.future?.oxc ? "oxlint" : "TypeScript"} CLI to check the code.`,
51
+ )
52
+ .action(async function typecheckAction() {
53
+ const isTsProject = (dir: string) => appPkg.hasFile("tsconfig.json", dir);
44
54
 
45
- log.start("Type checking started");
46
- await shellAt.$`tsc --noEmit`;
47
- log.success("Typecheck completed");
48
- } catch (error) {
49
- log.error("Typecheck failed");
50
- throw error;
51
- }
55
+ const runTypecheck = async (shell: ShellService) => {
56
+ if (config.future?.oxc) {
57
+ const oxlint = new OxlintService(shell);
58
+ await oxlint.exec(`--type-aware --type-check --report-unused-disable-directives`);
59
+ } else {
60
+ await shell.$`tsc --noEmit`;
52
61
  }
62
+ };
53
63
 
54
- if (!appPkg.isMonorepo()) {
55
- if (!isTsProject(appPkg.dirPath)) {
56
- logger.info("No tsconfig.json found, skipping typecheck");
57
- return;
58
- }
59
-
60
- await typecheckAt({
61
- dir: appPkg.dirPath,
62
- scripts: appPkg.packageJson.scripts,
63
- log: logger,
64
- });
65
-
64
+ if (!appPkg.isMonorepo()) {
65
+ if (!isTsProject(appPkg.dirPath)) {
66
+ logger.info("No tsconfig.json found, skipping typecheck");
66
67
  return;
67
68
  }
68
69
 
69
- const projects = await appPkg.getWorkspaceProjects();
70
- const tsProjects = projects.filter((project) => isTsProject(project.rootDir));
70
+ await typecheckAt({
71
+ shell,
72
+ run: runTypecheck,
73
+ dir: appPkg.dirPath,
74
+ scripts: appPkg.packageJson.scripts,
75
+ log: logger,
76
+ });
71
77
 
72
- if (!tsProjects.length) {
73
- logger.warn("No TypeScript projects found in the monorepo, skipping typecheck");
74
- return;
75
- }
78
+ return;
79
+ }
76
80
 
77
- await Promise.all(
78
- tsProjects.map((p) =>
79
- typecheckAt({
80
- dir: p.rootDir,
81
- scripts: p.manifest.scripts,
82
- log: logger.child({
83
- tag: p.manifest.name,
84
- namespace: "typecheck",
85
- }),
86
- }),
87
- ),
88
- );
89
- })
90
- .addHelpText("afterAll", "\nUnder the hood, this command uses the TypeScript CLI to check the code.");
91
- }
81
+ const projects = await appPkg.getWorkspaceProjects();
82
+ const tsProjects = projects.filter((project) => isTsProject(project.rootDir));
83
+
84
+ if (!tsProjects.length) {
85
+ logger.warn("No TypeScript projects found in the monorepo, skipping typecheck");
86
+ return;
87
+ }
92
88
 
93
- return typecheckCommand;
89
+ await Promise.all(
90
+ tsProjects.map((p) =>
91
+ typecheckAt({
92
+ shell,
93
+ run: runTypecheck,
94
+ dir: p.rootDir,
95
+ scripts: p.manifest.scripts,
96
+ log: logger.child({
97
+ tag: p.manifest.name,
98
+ namespace: "typecheck",
99
+ }),
100
+ }),
101
+ ),
102
+ );
103
+ });
94
104
  }
@@ -1,12 +1,33 @@
1
1
  import type { ShellService } from "@vlandoss/clibuddy";
2
+ import type { FormatOptions, Formatter, Linter, LintOptions } from "#/types/tool";
2
3
  import { ToolService } from "./tool";
3
4
 
4
- export class BiomeService extends ToolService {
5
+ export class BiomeService extends ToolService implements Formatter, Linter {
5
6
  constructor(shellService: ShellService) {
6
- super({ cmd: "biome", shellService });
7
+ super({ bin: "biome", shellService });
7
8
  }
8
9
 
9
10
  getBinDir() {
10
11
  return require.resolve("@biomejs/biome/bin/biome");
11
12
  }
13
+
14
+ async format(options: FormatOptions) {
15
+ const commonOptions = "format --no-errors-on-unmatched --colors=force";
16
+
17
+ if (options.fix) {
18
+ await this.exec(`${commonOptions} --fix`);
19
+ } else if (options.check) {
20
+ await this.exec(`${commonOptions}`);
21
+ }
22
+ }
23
+
24
+ async lint(options: LintOptions) {
25
+ const commonOptions = "check --colors=force --formatter-enabled=false";
26
+
27
+ if (options.fix) {
28
+ await this.exec(`${commonOptions} --fix --unsafe`);
29
+ } else if (options.check) {
30
+ await this.exec(`${commonOptions}`);
31
+ }
32
+ }
12
33
  }
@@ -1,5 +1,6 @@
1
- import { loadConfig } from "c12";
2
- import type { UserConfig } from "#/types/config";
1
+ import os from "node:os";
2
+ import { type AsyncSearcher, lilconfig } from "lilconfig";
3
+ import type { ExportedConfig, UserConfig } from "#/types/config";
3
4
  import { logger } from "./logger";
4
5
 
5
6
  const DEFAULT_CONFIG: UserConfig = {
@@ -9,19 +10,46 @@ const DEFAULT_CONFIG: UserConfig = {
9
10
  };
10
11
 
11
12
  export class ConfigService {
12
- async load(cwd?: string) {
13
- const debug = logger.subdebug("load-config");
13
+ #searcher: AsyncSearcher;
14
14
 
15
- const result = await loadConfig<UserConfig>({
16
- cwd,
17
- name: "run-run",
18
- defaultConfig: DEFAULT_CONFIG,
15
+ constructor() {
16
+ this.#searcher = lilconfig("run-run", {
17
+ searchPlaces: ["run-run.config.ts"],
18
+ cache: true,
19
+ stopDir: os.homedir(),
20
+ loaders: {
21
+ ".ts": (filepath: string) => import(filepath).then((mod) => mod.default),
22
+ },
19
23
  });
24
+ }
25
+
26
+ async load(): Promise<ExportedConfig> {
27
+ const debug = logger.subdebug("load-config");
28
+
29
+ const searchResult = await this.#searcher.search();
30
+
31
+ if (!searchResult || searchResult?.isEmpty) {
32
+ debug("loaded default config: %O", DEFAULT_CONFIG);
33
+ return {
34
+ config: DEFAULT_CONFIG,
35
+ meta: {
36
+ isDefault: true,
37
+ filepath: undefined,
38
+ },
39
+ };
40
+ }
41
+
42
+ const config = searchResult.config as UserConfig;
20
43
 
21
- debug("loaded config: %O", result.config);
22
- debug("config cwd: %s", result.cwd);
23
- debug("config file: %s", result.configFile);
44
+ debug("loaded config: %O", config);
45
+ debug("config file: %s", searchResult.filepath);
24
46
 
25
- return result.config;
47
+ return {
48
+ config,
49
+ meta: {
50
+ isDefault: false,
51
+ filepath: searchResult.filepath,
52
+ },
53
+ };
26
54
  }
27
55
  }
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import { createPkgService, createShellService, cwd, type PkgService, type ShellService } from "@vlandoss/clibuddy";
3
- import type { UserConfig } from "#/types/config";
3
+ import type { ExportedConfig } from "#/types/config";
4
4
  import { ConfigService } from "./config";
5
5
  import { logger } from "./logger";
6
6
 
@@ -8,7 +8,7 @@ export type Context = {
8
8
  binPkg: PkgService;
9
9
  appPkg: PkgService;
10
10
  shell: ShellService;
11
- config: UserConfig;
11
+ config: ExportedConfig;
12
12
  };
13
13
 
14
14
  export async function createContext(binDir: string): Promise<Context> {
@@ -1,12 +1,23 @@
1
1
  import type { ShellService } from "@vlandoss/clibuddy";
2
+ import type { FormatOptions, Formatter } from "#/types/tool";
2
3
  import { ToolService } from "./tool";
3
4
 
4
- export class OxfmtService extends ToolService {
5
+ export class OxfmtService extends ToolService implements Formatter {
5
6
  constructor(shellService: ShellService) {
6
- super({ cmd: "oxfmt", shellService });
7
+ super({ bin: "oxfmt", shellService });
7
8
  }
8
9
 
9
10
  getBinDir() {
10
11
  return require.resolve("oxfmt/bin/oxfmt");
11
12
  }
13
+
14
+ async format(options: FormatOptions) {
15
+ const commonOptions = "--no-error-on-unmatched-pattern";
16
+
17
+ if (options.fix) {
18
+ await this.exec(`${commonOptions} --fix`);
19
+ } else if (options.check) {
20
+ await this.exec(`${commonOptions} --check`);
21
+ }
22
+ }
12
23
  }
@@ -1,12 +1,23 @@
1
1
  import type { ShellService } from "@vlandoss/clibuddy";
2
+ import type { Linter, LintOptions } from "#/types/tool";
2
3
  import { ToolService } from "./tool";
3
4
 
4
- export class OxlintService extends ToolService {
5
+ export class OxlintService extends ToolService implements Linter {
5
6
  constructor(shellService: ShellService) {
6
- super({ cmd: "oxlint", shellService });
7
+ super({ bin: "oxlint", shellService });
7
8
  }
8
9
 
9
10
  getBinDir() {
10
11
  return require.resolve("oxlint/bin/oxlint");
11
12
  }
13
+
14
+ async lint(options: LintOptions) {
15
+ const commonOptions = "--report-unused-disable-directives";
16
+
17
+ if (options.fix) {
18
+ await this.exec(`${commonOptions} --fix`);
19
+ } else if (options.check) {
20
+ await this.exec(`${commonOptions} --check`);
21
+ }
22
+ }
12
23
  }
@@ -1,29 +1,40 @@
1
- import type { ShellService } from "@vlandoss/clibuddy";
1
+ import type { Shell, ShellService } from "@vlandoss/clibuddy";
2
+ import memoize from "memoize";
2
3
  import { gracefullBinDir } from "#/utils/gracefullBinDir";
3
4
 
4
5
  type CreateOptions = {
5
- cmd: string;
6
+ bin: string;
6
7
  shellService: ShellService;
7
8
  };
8
9
 
9
10
  export abstract class ToolService {
10
11
  #shellService: ShellService;
11
- #cmd: string;
12
+ #bin: string;
12
13
 
13
- constructor({ cmd, shellService }: CreateOptions) {
14
- this.#cmd = cmd;
14
+ constructor({ bin: cmd, shellService }: CreateOptions) {
15
+ this.#bin = cmd;
15
16
  this.#shellService = shellService;
16
17
  }
17
18
 
18
19
  abstract getBinDir(): string;
19
20
 
20
- get $() {
21
+ exec(args: string | string[]) {
22
+ const $ = this.#shell();
23
+ return this.#run($, args);
24
+ }
25
+
26
+ #shell = memoize((cwd?: string) => {
21
27
  return this.#shellService.child({
28
+ cwd,
22
29
  preferLocal: gracefullBinDir(() => this.getBinDir()),
23
30
  }).$;
31
+ });
32
+
33
+ #run(shell: Shell, args: string | string[]) {
34
+ return shell`${this.#bin} ${typeof args === "string" ? args : args.join(" ")}`;
24
35
  }
25
36
 
26
- async execute(args: string[]): Promise<void> {
27
- this.$`${this.#cmd} ${args.join(" ")}`;
37
+ get bin() {
38
+ return this.#bin;
28
39
  }
29
40
  }
@@ -3,3 +3,11 @@ export type UserConfig = {
3
3
  oxc?: boolean;
4
4
  };
5
5
  };
6
+
7
+ export type ExportedConfig = {
8
+ config: UserConfig;
9
+ meta: {
10
+ isDefault: boolean;
11
+ filepath?: string;
12
+ };
13
+ };
@@ -0,0 +1,19 @@
1
+ export type FormatOptions = {
2
+ check?: boolean;
3
+ fix?: boolean;
4
+ };
5
+
6
+ export type LintOptions = {
7
+ check?: boolean;
8
+ fix?: boolean;
9
+ };
10
+
11
+ export type Formatter = {
12
+ bin: string;
13
+ format(options: FormatOptions): Promise<void>;
14
+ };
15
+
16
+ export type Linter = {
17
+ bin: string;
18
+ lint(options: LintOptions): Promise<void>;
19
+ };
package/tools/oxfmt CHANGED
File without changes
package/tools/oxlint CHANGED
File without changes
@@ -1,22 +0,0 @@
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
- });
package/src/utils/get.ts DELETED
@@ -1,30 +0,0 @@
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
- }