@getrouter/getrouter-cli 0.1.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.
Files changed (120) hide show
  1. package/.github/workflows/ci.yml +19 -0
  2. package/AGENTS.md +78 -0
  3. package/README.ja.md +116 -0
  4. package/README.md +116 -0
  5. package/README.zh-cn.md +116 -0
  6. package/biome.json +10 -0
  7. package/bun.lock +397 -0
  8. package/dist/bin.mjs +1422 -0
  9. package/docs/plans/2026-01-01-getrouter-cli-config-command-plan.md +231 -0
  10. package/docs/plans/2026-01-01-getrouter-cli-config-core-plan.md +307 -0
  11. package/docs/plans/2026-01-01-getrouter-cli-design.md +106 -0
  12. package/docs/plans/2026-01-01-getrouter-cli-scaffold-plan.md +327 -0
  13. package/docs/plans/2026-01-02-getrouter-cli-auth-design.md +68 -0
  14. package/docs/plans/2026-01-02-getrouter-cli-auth-device-design.md +73 -0
  15. package/docs/plans/2026-01-02-getrouter-cli-auth-device-plan.md +411 -0
  16. package/docs/plans/2026-01-02-getrouter-cli-auth-plan.md +435 -0
  17. package/docs/plans/2026-01-02-getrouter-cli-http-client-plan.md +235 -0
  18. package/docs/plans/2026-01-02-getrouter-cli-keys-create-update-output-design.md +24 -0
  19. package/docs/plans/2026-01-02-getrouter-cli-keys-create-update-output-plan.md +141 -0
  20. package/docs/plans/2026-01-02-getrouter-cli-keys-delete-output-design.md +22 -0
  21. package/docs/plans/2026-01-02-getrouter-cli-keys-delete-output-plan.md +122 -0
  22. package/docs/plans/2026-01-02-getrouter-cli-keys-get-output-design.md +23 -0
  23. package/docs/plans/2026-01-02-getrouter-cli-keys-get-output-plan.md +141 -0
  24. package/docs/plans/2026-01-02-getrouter-cli-keys-interactive-design.md +28 -0
  25. package/docs/plans/2026-01-02-getrouter-cli-keys-interactive-plan.md +247 -0
  26. package/docs/plans/2026-01-02-getrouter-cli-keys-output-design.md +31 -0
  27. package/docs/plans/2026-01-02-getrouter-cli-keys-output-plan.md +187 -0
  28. package/docs/plans/2026-01-02-getrouter-cli-keys-subscription-design.md +52 -0
  29. package/docs/plans/2026-01-02-getrouter-cli-keys-subscription-plan.md +306 -0
  30. package/docs/plans/2026-01-02-getrouter-cli-setup-env-design.md +67 -0
  31. package/docs/plans/2026-01-02-getrouter-cli-setup-env-plan.md +441 -0
  32. package/docs/plans/2026-01-02-getrouter-cli-subscription-output-design.md +34 -0
  33. package/docs/plans/2026-01-02-getrouter-cli-subscription-output-plan.md +157 -0
  34. package/docs/plans/2026-01-03-bun-migration-plan.md +103 -0
  35. package/docs/plans/2026-01-03-cli-emoji-output.md +45 -0
  36. package/docs/plans/2026-01-03-cli-english-output.md +123 -0
  37. package/docs/plans/2026-01-03-cli-simplify-design.md +62 -0
  38. package/docs/plans/2026-01-03-cli-simplify-implementation.md +468 -0
  39. package/docs/plans/2026-01-03-readme-command-descriptions.md +116 -0
  40. package/docs/plans/2026-01-03-tsdown-migration-plan.md +75 -0
  41. package/docs/plans/2026-01-04-cli-docs-cleanup-design.md +49 -0
  42. package/docs/plans/2026-01-04-cli-docs-cleanup-plan.md +126 -0
  43. package/docs/plans/2026-01-04-codex-multistep-design.md +76 -0
  44. package/docs/plans/2026-01-04-codex-multistep-plan.md +240 -0
  45. package/docs/plans/2026-01-04-env-hook-design.md +48 -0
  46. package/docs/plans/2026-01-04-env-hook-plan.md +173 -0
  47. package/docs/plans/2026-01-04-models-keys-fuzzy-design.md +75 -0
  48. package/docs/plans/2026-01-04-models-keys-fuzzy-implementation.md +704 -0
  49. package/package.json +37 -0
  50. package/src/.gitkeep +0 -0
  51. package/src/bin.ts +4 -0
  52. package/src/cli.ts +12 -0
  53. package/src/cmd/auth.ts +44 -0
  54. package/src/cmd/claude.ts +10 -0
  55. package/src/cmd/codex.ts +119 -0
  56. package/src/cmd/config-helpers.ts +16 -0
  57. package/src/cmd/config.ts +31 -0
  58. package/src/cmd/env.ts +103 -0
  59. package/src/cmd/index.ts +20 -0
  60. package/src/cmd/keys.ts +207 -0
  61. package/src/cmd/models.ts +48 -0
  62. package/src/cmd/status.ts +106 -0
  63. package/src/cmd/usages.ts +29 -0
  64. package/src/core/api/client.ts +79 -0
  65. package/src/core/auth/device.ts +105 -0
  66. package/src/core/auth/index.ts +37 -0
  67. package/src/core/config/fs.ts +13 -0
  68. package/src/core/config/index.ts +37 -0
  69. package/src/core/config/paths.ts +5 -0
  70. package/src/core/config/redact.ts +18 -0
  71. package/src/core/config/types.ts +23 -0
  72. package/src/core/http/errors.ts +32 -0
  73. package/src/core/http/request.ts +41 -0
  74. package/src/core/http/url.ts +12 -0
  75. package/src/core/interactive/clipboard.ts +61 -0
  76. package/src/core/interactive/codex.ts +75 -0
  77. package/src/core/interactive/fuzzy.ts +64 -0
  78. package/src/core/interactive/keys.ts +164 -0
  79. package/src/core/output/table.ts +34 -0
  80. package/src/core/output/usages.ts +75 -0
  81. package/src/core/paths.ts +4 -0
  82. package/src/core/setup/codex.ts +129 -0
  83. package/src/core/setup/env.ts +220 -0
  84. package/src/core/usages/aggregate.ts +69 -0
  85. package/src/generated/router/dashboard/v1/index.ts +1104 -0
  86. package/src/index.ts +1 -0
  87. package/tests/.gitkeep +0 -0
  88. package/tests/auth/device.test.ts +75 -0
  89. package/tests/auth/status.test.ts +64 -0
  90. package/tests/cli.test.ts +31 -0
  91. package/tests/cmd/auth.test.ts +90 -0
  92. package/tests/cmd/claude.test.ts +132 -0
  93. package/tests/cmd/codex.test.ts +147 -0
  94. package/tests/cmd/config-helpers.test.ts +18 -0
  95. package/tests/cmd/config.test.ts +56 -0
  96. package/tests/cmd/keys.test.ts +163 -0
  97. package/tests/cmd/models.test.ts +63 -0
  98. package/tests/cmd/status.test.ts +82 -0
  99. package/tests/cmd/usages.test.ts +42 -0
  100. package/tests/config/fs.test.ts +14 -0
  101. package/tests/config/index.test.ts +63 -0
  102. package/tests/config/paths.test.ts +10 -0
  103. package/tests/config/redact.test.ts +17 -0
  104. package/tests/config/types.test.ts +10 -0
  105. package/tests/core/api/client.test.ts +92 -0
  106. package/tests/core/interactive/clipboard.test.ts +44 -0
  107. package/tests/core/interactive/codex.test.ts +17 -0
  108. package/tests/core/interactive/fuzzy.test.ts +30 -0
  109. package/tests/core/setup/codex.test.ts +38 -0
  110. package/tests/core/setup/env.test.ts +84 -0
  111. package/tests/core/usages/aggregate.test.ts +55 -0
  112. package/tests/http/errors.test.ts +15 -0
  113. package/tests/http/request.test.ts +82 -0
  114. package/tests/http/url.test.ts +17 -0
  115. package/tests/output/table.test.ts +29 -0
  116. package/tests/output/usages.test.ts +71 -0
  117. package/tests/paths.test.ts +9 -0
  118. package/tsconfig.json +13 -0
  119. package/tsdown.config.ts +5 -0
  120. package/vitest.config.ts +7 -0
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mergeAuthJson, mergeCodexToml } from "../../../src/core/setup/codex";
3
+
4
+ describe("codex setup helpers", () => {
5
+ it("merges codex toml at root and provider table", () => {
6
+ const input = [
7
+ 'other = "keep"',
8
+ 'model = "old-model"',
9
+ "",
10
+ "[model_providers.other]",
11
+ 'name = "x"',
12
+ "",
13
+ "[model_providers.getrouter]",
14
+ 'name = "old"',
15
+ 'extra = "keep"',
16
+ "",
17
+ ].join("\n");
18
+ const output = mergeCodexToml(input, {
19
+ model: "gpt-5.2-codex",
20
+ reasoning: "xhigh",
21
+ });
22
+ expect(output).toContain('model = "gpt-5.2-codex"');
23
+ expect(output).toContain('model_reasoning_effort = "xhigh"');
24
+ expect(output).toContain('model_provider = "getrouter"');
25
+ expect(output).toContain("[model_providers.getrouter]");
26
+ expect(output).toContain('base_url = "https://api.getrouter.dev/codex"');
27
+ expect(output).toContain('wire_api = "responses"');
28
+ expect(output).toContain("requires_openai_auth = true");
29
+ expect(output).toContain('other = "keep"');
30
+ expect(output).toContain('extra = "keep"');
31
+ });
32
+
33
+ it("merges auth json", () => {
34
+ const output = mergeAuthJson({ existing: "keep" }, "key-123");
35
+ expect(output.OPENAI_API_KEY).toBe("key-123");
36
+ expect(output.existing).toBe("keep");
37
+ });
38
+ });
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import {
6
+ appendRcIfMissing,
7
+ getEnvFilePath,
8
+ getHookFilePath,
9
+ renderEnv,
10
+ renderHook,
11
+ resolveShellRcPath,
12
+ writeEnvFile,
13
+ } from "../../../src/core/setup/env";
14
+
15
+ const vars = {
16
+ openaiBaseUrl: "https://api.getrouter.dev/codex",
17
+ openaiApiKey: "key-123",
18
+ anthropicBaseUrl: "https://api.getrouter.dev/claude",
19
+ anthropicApiKey: "key-123",
20
+ };
21
+
22
+ describe("setup env helpers", () => {
23
+ it("renders sh env", () => {
24
+ const output = renderEnv("sh", vars);
25
+ expect(output).toContain(
26
+ "export OPENAI_BASE_URL=https://api.getrouter.dev/codex",
27
+ );
28
+ expect(output).toContain("export ANTHROPIC_API_KEY=key-123");
29
+ });
30
+
31
+ it("renders ps1 env", () => {
32
+ const output = renderEnv("ps1", vars);
33
+ expect(output).toContain(
34
+ '$env:OPENAI_BASE_URL="https://api.getrouter.dev/codex"',
35
+ );
36
+ expect(output).toContain('$env:ANTHROPIC_API_KEY="key-123"');
37
+ });
38
+
39
+ it("writes env file", () => {
40
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "getrouter-env-"));
41
+ const filePath = getEnvFilePath("sh", dir);
42
+ writeEnvFile(filePath, "hello");
43
+ expect(fs.readFileSync(filePath, "utf8")).toBe("hello");
44
+ });
45
+
46
+ it("renders sh hook", () => {
47
+ const output = renderHook("bash");
48
+ expect(output).toContain("getrouter() {");
49
+ expect(output).toContain("command getrouter");
50
+ expect(output).toContain("source");
51
+ });
52
+
53
+ it("renders pwsh hook", () => {
54
+ const output = renderHook("pwsh");
55
+ expect(output).toContain("function getrouter");
56
+ expect(output).toContain("$LASTEXITCODE");
57
+ });
58
+
59
+ it("resolves shell rc paths", () => {
60
+ expect(resolveShellRcPath("zsh", "/tmp")).toBe("/tmp/.zshrc");
61
+ expect(resolveShellRcPath("bash", "/tmp")).toBe("/tmp/.bashrc");
62
+ expect(resolveShellRcPath("fish", "/tmp")).toBe(
63
+ "/tmp/.config/fish/config.fish",
64
+ );
65
+ });
66
+
67
+ it("resolves hook file paths", () => {
68
+ expect(getHookFilePath("bash", "/tmp")).toBe("/tmp/hook.sh");
69
+ expect(getHookFilePath("zsh", "/tmp")).toBe("/tmp/hook.sh");
70
+ expect(getHookFilePath("fish", "/tmp")).toBe("/tmp/hook.fish");
71
+ expect(getHookFilePath("pwsh", "/tmp")).toBe("/tmp/hook.ps1");
72
+ });
73
+
74
+ it("appends rc line once", () => {
75
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "getrouter-rc-"));
76
+ const rcPath = path.join(dir, "rc");
77
+ const line = "source ~/.getrouter/env.sh";
78
+ fs.writeFileSync(rcPath, `${line}\n`);
79
+ expect(appendRcIfMissing(rcPath, line)).toBe(false);
80
+ expect(appendRcIfMissing(rcPath, line)).toBe(false);
81
+ const content = fs.readFileSync(rcPath, "utf8");
82
+ expect(content.split(line).length - 1).toBe(1);
83
+ });
84
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { aggregateUsages } from "../../../src/core/usages/aggregate";
3
+
4
+ describe("aggregateUsages", () => {
5
+ it("groups by local day and totals tokens", () => {
6
+ const rows = [
7
+ {
8
+ createdAt: "2026-01-03T10:00:00",
9
+ inputTokens: 5,
10
+ outputTokens: 7,
11
+ totalTokens: 12,
12
+ },
13
+ {
14
+ createdAt: "2026-01-03T18:00:00",
15
+ inputTokens: 3,
16
+ outputTokens: 2,
17
+ totalTokens: 5,
18
+ },
19
+ ];
20
+ const result = aggregateUsages(rows, 7);
21
+ expect(result).toHaveLength(1);
22
+ expect(result[0].totalTokens).toBe(17);
23
+ expect(result[0].inputTokens).toBe(8);
24
+ expect(result[0].outputTokens).toBe(9);
25
+ expect(result[0].requests).toBe(2);
26
+ });
27
+
28
+ it("limits results to the most recent days", () => {
29
+ const rows = [
30
+ { createdAt: "2026-01-05T00:00:00Z", totalTokens: 1 },
31
+ { createdAt: "2026-01-04T00:00:00Z", totalTokens: 1 },
32
+ { createdAt: "2026-01-03T00:00:00Z", totalTokens: 1 },
33
+ ];
34
+ const result = aggregateUsages(rows, 2);
35
+ expect(result).toHaveLength(2);
36
+ expect(result[0].day).toBe("2026-01-05");
37
+ expect(result[1].day).toBe("2026-01-04");
38
+ });
39
+
40
+ it("coerces string token values to numbers", () => {
41
+ const rows = [
42
+ {
43
+ createdAt: "2026-01-03T10:00:00Z",
44
+ inputTokens: "0123",
45
+ outputTokens: "045",
46
+ totalTokens: "0168",
47
+ },
48
+ ];
49
+ const result = aggregateUsages(rows, 7);
50
+ expect(result).toHaveLength(1);
51
+ expect(result[0].inputTokens).toBe(123);
52
+ expect(result[0].outputTokens).toBe(45);
53
+ expect(result[0].totalTokens).toBe(168);
54
+ });
55
+ });
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createApiError } from "../../src/core/http/errors";
3
+
4
+ describe("api errors", () => {
5
+ it("normalizes error payload", () => {
6
+ const err = createApiError(
7
+ { code: "BAD", message: "oops" },
8
+ "fallback",
9
+ 400,
10
+ );
11
+ expect(err.message).toBe("oops");
12
+ expect(err.code).toBe("BAD");
13
+ expect(err.status).toBe(400);
14
+ });
15
+ });
@@ -0,0 +1,82 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { requestJson } from "../../src/core/http/request";
6
+
7
+ const originalCookieName = process.env.GETROUTER_AUTH_COOKIE;
8
+ const originalKratosCookie = process.env.KRATOS_AUTH_COOKIE;
9
+
10
+ afterEach(() => {
11
+ if (originalCookieName === undefined) {
12
+ delete process.env.GETROUTER_AUTH_COOKIE;
13
+ } else {
14
+ process.env.GETROUTER_AUTH_COOKIE = originalCookieName;
15
+ }
16
+ if (originalKratosCookie === undefined) {
17
+ delete process.env.KRATOS_AUTH_COOKIE;
18
+ } else {
19
+ process.env.KRATOS_AUTH_COOKIE = originalKratosCookie;
20
+ }
21
+ });
22
+
23
+ describe("requestJson", () => {
24
+ it("adds Authorization when token exists", async () => {
25
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "getrouter-"));
26
+ process.env.GETROUTER_CONFIG_DIR = dir;
27
+ fs.writeFileSync(
28
+ path.join(dir, "auth.json"),
29
+ JSON.stringify({ accessToken: "t" }),
30
+ );
31
+
32
+ const fetchSpy = vi.fn(
33
+ async (_input: RequestInfo | URL, _init?: RequestInit) =>
34
+ ({
35
+ ok: true,
36
+ json: async () => ({ ok: true }),
37
+ }) as Response,
38
+ );
39
+
40
+ const res = await requestJson<{ ok: boolean }>({
41
+ path: "/v1/test",
42
+ method: "GET",
43
+ fetchImpl: fetchSpy as unknown as typeof fetch,
44
+ });
45
+
46
+ expect(res.ok).toBe(true);
47
+ const call = fetchSpy.mock.calls[0] as Parameters<typeof fetch> | undefined;
48
+ const init = call?.[1];
49
+ const headers = (init?.headers ?? {}) as Record<string, string>;
50
+ expect(headers.Authorization).toBe("Bearer t");
51
+ expect(headers.Cookie).toBe("access_token=t");
52
+ });
53
+
54
+ it("uses GETROUTER_AUTH_COOKIE when set", async () => {
55
+ process.env.GETROUTER_AUTH_COOKIE = "router_auth";
56
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "getrouter-"));
57
+ process.env.GETROUTER_CONFIG_DIR = dir;
58
+ fs.writeFileSync(
59
+ path.join(dir, "auth.json"),
60
+ JSON.stringify({ accessToken: "t2" }),
61
+ );
62
+
63
+ const fetchSpy = vi.fn(
64
+ async (_input: RequestInfo | URL, _init?: RequestInit) =>
65
+ ({
66
+ ok: true,
67
+ json: async () => ({ ok: true }),
68
+ }) as Response,
69
+ );
70
+
71
+ await requestJson({
72
+ path: "/v1/test",
73
+ method: "GET",
74
+ fetchImpl: fetchSpy as unknown as typeof fetch,
75
+ });
76
+
77
+ const call = fetchSpy.mock.calls[0] as Parameters<typeof fetch> | undefined;
78
+ const init = call?.[1];
79
+ const headers = (init?.headers ?? {}) as Record<string, string>;
80
+ expect(headers.Cookie).toBe("router_auth=t2");
81
+ });
82
+ });
@@ -0,0 +1,17 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import { buildApiUrl } from "../../src/core/http/url";
6
+
7
+ describe("buildApiUrl", () => {
8
+ it("joins base and path safely", () => {
9
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "getrouter-"));
10
+ process.env.GETROUTER_CONFIG_DIR = dir;
11
+ fs.writeFileSync(
12
+ path.join(dir, "config.json"),
13
+ JSON.stringify({ apiBase: "https://getrouter.dev/" }),
14
+ );
15
+ expect(buildApiUrl("/v1/test")).toBe("https://getrouter.dev/v1/test");
16
+ });
17
+ });
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { renderTable } from "../../src/core/output/table";
3
+
4
+ describe("table renderer", () => {
5
+ it("renders headers and rows with alignment", () => {
6
+ const out = renderTable(
7
+ ["ID", "NAME"],
8
+ [
9
+ ["1", "alpha"],
10
+ ["2", "beta"],
11
+ ],
12
+ );
13
+ expect(out).toContain("ID");
14
+ expect(out).toContain("NAME");
15
+ expect(out).toContain("alpha");
16
+ });
17
+
18
+ it("truncates long cells", () => {
19
+ const out = renderTable(["ID"], [["0123456789ABCDEFGHIJ"]], {
20
+ maxColWidth: 8,
21
+ });
22
+ expect(out).toContain("01234...");
23
+ });
24
+
25
+ it("fills empty with dash", () => {
26
+ const out = renderTable(["ID"], [[""]]);
27
+ expect(out).toContain("-");
28
+ });
29
+ });
@@ -0,0 +1,71 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { renderUsageChart } from "../../src/core/output/usages";
3
+
4
+ describe("renderUsageChart", () => {
5
+ it("renders stacked bars", () => {
6
+ const output = renderUsageChart([
7
+ {
8
+ day: "2026-01-03",
9
+ inputTokens: 10,
10
+ outputTokens: 20,
11
+ totalTokens: 30,
12
+ requests: 2,
13
+ },
14
+ ]);
15
+ expect(output).toContain("2026-01-03");
16
+ expect(output).toMatch(/โ–ˆ/);
17
+ });
18
+
19
+ it("uses an emoji header and token-friendly numbers", () => {
20
+ const output = renderUsageChart([
21
+ {
22
+ day: "2026-01-03",
23
+ inputTokens: 1000,
24
+ outputTokens: 1000,
25
+ totalTokens: 2000,
26
+ requests: 2,
27
+ },
28
+ ]);
29
+ expect(output.startsWith("๐Ÿ“Š Usage (last 7 days)")).toBe(true);
30
+ expect(output).toContain("Tokens");
31
+ expect(output).toContain("โ–ˆ");
32
+ expect(output).toContain("โ–’");
33
+ expect(output).toContain("I:1K");
34
+ expect(output).toContain("O:1K");
35
+ expect(output).toContain("๐Ÿ“Š Usage (last 7 days) ยท Tokens\n\n2026-01-03");
36
+ expect(output).toContain("O:1K\n\nLegend: โ–ˆ input โ–’ output");
37
+ expect(output).toContain("Legend: โ–ˆ input โ–’ output");
38
+ expect(output).not.toContain("O:0179");
39
+ });
40
+
41
+ it("prints input and output totals separately", () => {
42
+ const output = renderUsageChart([
43
+ {
44
+ day: "2026-01-03",
45
+ inputTokens: 1200,
46
+ outputTokens: 3400,
47
+ totalTokens: 4600,
48
+ requests: 2,
49
+ },
50
+ ]);
51
+ expect(output).toContain("I:1.2K");
52
+ expect(output).toContain("O:3.4K");
53
+ expect(output).not.toContain("4.6K");
54
+ });
55
+
56
+ it("handles numeric strings without skewing bars", () => {
57
+ const output = renderUsageChart(
58
+ [
59
+ {
60
+ day: "2026-01-03",
61
+ inputTokens: "1000",
62
+ outputTokens: "1000",
63
+ totalTokens: "2000",
64
+ requests: 1,
65
+ } as unknown as Parameters<typeof renderUsageChart>[0][number],
66
+ ],
67
+ 10,
68
+ );
69
+ expect(output).toContain("โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–’โ–’โ–’โ–’โ–’");
70
+ });
71
+ });
@@ -0,0 +1,9 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getConfigDir } from "../src/core/paths";
3
+
4
+ describe("paths", () => {
5
+ it("returns ~/.getrouter path", () => {
6
+ const dir = getConfigDir();
7
+ expect(dir).toContain(".getrouter");
8
+ });
9
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "rootDir": ".",
6
+ "outDir": "dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "moduleResolution": "node",
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src", "tests"]
13
+ }
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/bin.ts"],
5
+ });
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["tests/**/*.test.ts"],
6
+ },
7
+ });