@revstackhq/cli 0.0.0-dev-20260226054033

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.
@@ -0,0 +1,216 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { Command } from "commander";
3
+
4
+ // ── Mock auth ────────────────────────────────────────────────
5
+ vi.mock("../../src/utils/auth", () => ({
6
+ getApiKey: vi.fn(),
7
+ }));
8
+
9
+ // ── Mock prompts ─────────────────────────────────────────────
10
+ vi.mock("prompts", () => ({
11
+ default: vi.fn(),
12
+ }));
13
+
14
+ // ── Mock ora ─────────────────────────────────────────────────
15
+ const mockSpinner = {
16
+ start: vi.fn().mockReturnThis(),
17
+ succeed: vi.fn().mockReturnThis(),
18
+ fail: vi.fn().mockReturnThis(),
19
+ };
20
+
21
+ vi.mock("ora", () => ({
22
+ default: vi.fn(() => mockSpinner),
23
+ }));
24
+
25
+ // ── Mock chalk (no-op proxy) ─────────────────────────────────
26
+ vi.mock("chalk", () => {
27
+ const passthrough = (s: string) => s;
28
+ const handler: ProxyHandler<typeof passthrough> = {
29
+ get: () => new Proxy(passthrough, handler),
30
+ apply: (_t, _ctx, args: string[]) => args[0],
31
+ };
32
+ return { default: new Proxy(passthrough, handler) };
33
+ });
34
+
35
+ // ── Mock node:fs ─────────────────────────────────────────────
36
+ vi.mock("node:fs", () => ({
37
+ default: {
38
+ existsSync: vi.fn(),
39
+ writeFileSync: vi.fn(),
40
+ },
41
+ existsSync: vi.fn(),
42
+ writeFileSync: vi.fn(),
43
+ }));
44
+
45
+ // ── Imports (after mocks) ────────────────────────────────────
46
+ import { getApiKey } from "../../src/utils/auth";
47
+ import prompts from "prompts";
48
+ import fs from "node:fs";
49
+ import { pullCommand } from "../../src/commands/pull";
50
+
51
+ const mockGetApiKey = vi.mocked(getApiKey);
52
+ const mockPrompts = vi.mocked(prompts);
53
+ const mockFs = vi.mocked(fs);
54
+
55
+ // ── Helpers ──────────────────────────────────────────────────
56
+
57
+ const REMOTE_CONFIG = {
58
+ features: {
59
+ seats: { name: "Seats", type: "static", unit_type: "count" },
60
+ },
61
+ plans: {
62
+ default: {
63
+ name: "Default",
64
+ is_default: true,
65
+ is_public: false,
66
+ type: "free",
67
+ features: {},
68
+ },
69
+ },
70
+ };
71
+
72
+ function createFetchResponse(body: unknown, ok = true, status = 200): Response {
73
+ return {
74
+ ok,
75
+ status,
76
+ statusText: ok ? "OK" : "Bad Request",
77
+ json: () => Promise.resolve(body),
78
+ headers: new Headers(),
79
+ redirected: false,
80
+ type: "basic" as ResponseType,
81
+ url: "",
82
+ clone: () => createFetchResponse(body, ok, status),
83
+ body: null,
84
+ bodyUsed: false,
85
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
86
+ blob: () => Promise.resolve(new Blob()),
87
+ formData: () => Promise.resolve(new FormData()),
88
+ text: () => Promise.resolve(JSON.stringify(body)),
89
+ bytes: () => Promise.resolve(new Uint8Array()),
90
+ };
91
+ }
92
+
93
+ class ProcessExitError extends Error {
94
+ code: number;
95
+ constructor(code: number) {
96
+ super(`process.exit(${code})`);
97
+ this.code = code;
98
+ }
99
+ }
100
+
101
+ function createTestProgram(): Command {
102
+ const program = new Command();
103
+ program.exitOverride();
104
+ program.addCommand(pullCommand);
105
+ return program;
106
+ }
107
+
108
+ let exitSpy: ReturnType<typeof vi.spyOn>;
109
+
110
+ beforeEach(() => {
111
+ vi.clearAllMocks();
112
+ exitSpy = vi
113
+ .spyOn(process, "exit")
114
+ .mockImplementation((code?: string | number | null | undefined) => {
115
+ throw new ProcessExitError(Number(code ?? 0));
116
+ });
117
+ });
118
+
119
+ afterEach(() => {
120
+ exitSpy.mockRestore();
121
+ vi.unstubAllGlobals();
122
+ });
123
+
124
+ // ── Tests ────────────────────────────────────────────────────
125
+
126
+ describe("pull command", () => {
127
+ it("exits if the user is not authenticated", async () => {
128
+ mockGetApiKey.mockReturnValue(null);
129
+
130
+ const program = createTestProgram();
131
+
132
+ await expect(
133
+ program.parseAsync(["node", "revstack", "pull"], { from: "node" })
134
+ ).rejects.toThrow(ProcessExitError);
135
+
136
+ expect(exitSpy).toHaveBeenCalledWith(1);
137
+ });
138
+
139
+ it("exits when the API returns an error", async () => {
140
+ mockGetApiKey.mockReturnValue("sk_test_valid123");
141
+
142
+ const mockFetch = vi
143
+ .fn()
144
+ .mockResolvedValueOnce(createFetchResponse({}, false, 500));
145
+
146
+ vi.stubGlobal("fetch", mockFetch);
147
+
148
+ const program = createTestProgram();
149
+
150
+ await expect(
151
+ program.parseAsync(["node", "revstack", "pull"], { from: "node" })
152
+ ).rejects.toThrow(ProcessExitError);
153
+
154
+ expect(exitSpy).toHaveBeenCalledWith(1);
155
+ expect(mockSpinner.fail).toHaveBeenCalled();
156
+ });
157
+
158
+ it("pulls config and writes file after user confirmation", async () => {
159
+ mockGetApiKey.mockReturnValue("sk_test_valid123");
160
+ mockFs.existsSync.mockReturnValue(true); // config file exists
161
+
162
+ const mockFetch = vi
163
+ .fn()
164
+ .mockResolvedValueOnce(createFetchResponse(REMOTE_CONFIG));
165
+
166
+ vi.stubGlobal("fetch", mockFetch);
167
+ mockPrompts.mockResolvedValue({ confirm: true });
168
+
169
+ const program = createTestProgram();
170
+ await program.parseAsync(["node", "revstack", "pull"], { from: "node" });
171
+
172
+ expect(mockFetch).toHaveBeenCalledTimes(1);
173
+ expect(mockFetch.mock.calls[0][0]).toContain("/api/v1/cli/pull");
174
+ expect(mockFs.writeFileSync).toHaveBeenCalledWith(
175
+ expect.stringContaining("revstack.config.ts"),
176
+ expect.stringContaining("defineConfig"),
177
+ "utf-8"
178
+ );
179
+ });
180
+
181
+ it("cancels without writing when user declines", async () => {
182
+ mockGetApiKey.mockReturnValue("sk_test_valid123");
183
+ mockFs.existsSync.mockReturnValue(true);
184
+
185
+ const mockFetch = vi
186
+ .fn()
187
+ .mockResolvedValueOnce(createFetchResponse(REMOTE_CONFIG));
188
+
189
+ vi.stubGlobal("fetch", mockFetch);
190
+ mockPrompts.mockResolvedValue({ confirm: false });
191
+
192
+ const program = createTestProgram();
193
+ await program.parseAsync(["node", "revstack", "pull"], { from: "node" });
194
+
195
+ expect(mockFs.writeFileSync).not.toHaveBeenCalled();
196
+ });
197
+
198
+ it("skips confirmation when config file does not exist", async () => {
199
+ mockGetApiKey.mockReturnValue("sk_test_valid123");
200
+ mockFs.existsSync.mockReturnValue(false); // no existing config
201
+
202
+ const mockFetch = vi
203
+ .fn()
204
+ .mockResolvedValueOnce(createFetchResponse(REMOTE_CONFIG));
205
+
206
+ vi.stubGlobal("fetch", mockFetch);
207
+
208
+ const program = createTestProgram();
209
+ await program.parseAsync(["node", "revstack", "pull"], { from: "node" });
210
+
211
+ // Prompts never called since file doesn't exist
212
+ expect(mockPrompts).not.toHaveBeenCalled();
213
+ // File was written directly
214
+ expect(mockFs.writeFileSync).toHaveBeenCalled();
215
+ });
216
+ });
@@ -0,0 +1,204 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { Command } from "commander";
3
+
4
+ // ── Mock auth (using @/ alias to match push.ts imports) ──────
5
+ vi.mock("../../src/utils/auth", () => ({
6
+ getApiKey: vi.fn(),
7
+ }));
8
+
9
+ // ── Mock config-loader ───────────────────────────────────────
10
+ vi.mock("../../src/utils/config-loader", () => ({
11
+ loadLocalConfig: vi.fn(),
12
+ }));
13
+
14
+ // ── Mock prompts ─────────────────────────────────────────────
15
+ vi.mock("prompts", () => ({
16
+ default: vi.fn(),
17
+ }));
18
+
19
+ // ── Mock ora ─────────────────────────────────────────────────
20
+ const mockSpinner = {
21
+ start: vi.fn().mockReturnThis(),
22
+ succeed: vi.fn().mockReturnThis(),
23
+ fail: vi.fn().mockReturnThis(),
24
+ };
25
+
26
+ vi.mock("ora", () => ({
27
+ default: vi.fn(() => mockSpinner),
28
+ }));
29
+
30
+ // ── Mock chalk (no-op proxy) ─────────────────────────────────
31
+ vi.mock("chalk", () => {
32
+ const passthrough = (s: string) => s;
33
+ const handler: ProxyHandler<typeof passthrough> = {
34
+ get: () => new Proxy(passthrough, handler),
35
+ apply: (_t, _ctx, args: string[]) => args[0],
36
+ };
37
+ return { default: new Proxy(passthrough, handler) };
38
+ });
39
+
40
+ // ── Imports (after mocks) ────────────────────────────────────
41
+ import { getApiKey } from "../../src/utils/auth";
42
+ import { loadLocalConfig } from "../../src/utils/config-loader";
43
+ import { pushCommand } from "../../src/commands/push";
44
+ import prompts from "prompts";
45
+
46
+ const mockGetApiKey = vi.mocked(getApiKey);
47
+ const mockLoadLocalConfig = vi.mocked(loadLocalConfig);
48
+ const mockPrompts = vi.mocked(prompts);
49
+
50
+ // ── Helpers ──────────────────────────────────────────────────
51
+
52
+ const SAMPLE_CONFIG = {
53
+ features: { seats: { name: "Seats", type: "static", unit_type: "count" } },
54
+ plans: {
55
+ default: {
56
+ name: "Default",
57
+ is_default: true,
58
+ is_public: false,
59
+ type: "free",
60
+ features: {},
61
+ },
62
+ },
63
+ };
64
+
65
+ function createFetchResponse(body: unknown, ok = true, status = 200): Response {
66
+ return {
67
+ ok,
68
+ status,
69
+ statusText: ok ? "OK" : "Bad Request",
70
+ json: () => Promise.resolve(body),
71
+ headers: new Headers(),
72
+ redirected: false,
73
+ type: "basic" as ResponseType,
74
+ url: "",
75
+ clone: () => createFetchResponse(body, ok, status),
76
+ body: null,
77
+ bodyUsed: false,
78
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
79
+ blob: () => Promise.resolve(new Blob()),
80
+ formData: () => Promise.resolve(new FormData()),
81
+ text: () => Promise.resolve(JSON.stringify(body)),
82
+ bytes: () => Promise.resolve(new Uint8Array()),
83
+ };
84
+ }
85
+
86
+ // ── Capture process.exit via a thrown sentinel ────────────────
87
+
88
+ class ProcessExitError extends Error {
89
+ code: number;
90
+ constructor(code: number) {
91
+ super(`process.exit(${code})`);
92
+ this.code = code;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Creates a fresh commander wrapper around pushCommand's action
98
+ * to avoid state leakage between tests.
99
+ */
100
+ function createTestProgram(): Command {
101
+ const program = new Command();
102
+ program.exitOverride(); // Throw instead of calling process.exit
103
+ program.addCommand(pushCommand);
104
+ return program;
105
+ }
106
+
107
+ let exitSpy: ReturnType<typeof vi.spyOn>;
108
+
109
+ beforeEach(() => {
110
+ vi.clearAllMocks();
111
+ exitSpy = vi
112
+ .spyOn(process, "exit")
113
+ .mockImplementation((code?: string | number | null | undefined) => {
114
+ throw new ProcessExitError(Number(code ?? 0));
115
+ });
116
+ });
117
+
118
+ afterEach(() => {
119
+ exitSpy.mockRestore();
120
+ vi.unstubAllGlobals();
121
+ });
122
+
123
+ // ── Tests ────────────────────────────────────────────────────
124
+
125
+ describe("push command", () => {
126
+ it("exits if the user is not authenticated", async () => {
127
+ mockGetApiKey.mockReturnValue(null);
128
+
129
+ const program = createTestProgram();
130
+
131
+ await expect(
132
+ program.parseAsync(["node", "revstack", "push"], { from: "node" })
133
+ ).rejects.toThrow(ProcessExitError);
134
+
135
+ expect(exitSpy).toHaveBeenCalledWith(1);
136
+ expect(mockLoadLocalConfig).not.toHaveBeenCalled();
137
+ });
138
+
139
+ it("diffs, confirms, and pushes successfully", async () => {
140
+ mockGetApiKey.mockReturnValue("sk_test_valid123");
141
+ mockLoadLocalConfig.mockResolvedValue(
142
+ SAMPLE_CONFIG as Record<string, unknown>
143
+ );
144
+
145
+ const diffResponse = {
146
+ diff: [
147
+ { action: "added", entity: "plan", id: "pro", message: "New plan" },
148
+ ],
149
+ canPush: true,
150
+ };
151
+
152
+ const mockFetch = vi
153
+ .fn()
154
+ .mockResolvedValueOnce(createFetchResponse(diffResponse))
155
+ .mockResolvedValueOnce(createFetchResponse({ success: true }));
156
+
157
+ vi.stubGlobal("fetch", mockFetch);
158
+ mockPrompts.mockResolvedValue({ confirm: true });
159
+
160
+ const program = createTestProgram();
161
+ await program.parseAsync(["node", "revstack", "push"], { from: "node" });
162
+
163
+ // Verify both diff and push endpoints were called
164
+ expect(mockFetch).toHaveBeenCalledTimes(2);
165
+ expect(mockFetch.mock.calls[0][0]).toContain("/api/v1/cli/diff");
166
+ expect(mockFetch.mock.calls[1][0]).toContain("/api/v1/cli/push");
167
+ });
168
+
169
+ it("exits early when push is blocked (canPush: false)", async () => {
170
+ mockGetApiKey.mockReturnValue("sk_test_valid123");
171
+ mockLoadLocalConfig.mockResolvedValue(
172
+ SAMPLE_CONFIG as Record<string, unknown>
173
+ );
174
+
175
+ const diffResponse = {
176
+ diff: [
177
+ {
178
+ action: "removed",
179
+ entity: "plan",
180
+ id: "default",
181
+ message: "Cannot remove default plan",
182
+ },
183
+ ],
184
+ canPush: false,
185
+ blockedReason: "Cannot delete the default plan.",
186
+ };
187
+
188
+ const mockFetch = vi
189
+ .fn()
190
+ .mockResolvedValueOnce(createFetchResponse(diffResponse));
191
+
192
+ vi.stubGlobal("fetch", mockFetch);
193
+
194
+ const program = createTestProgram();
195
+
196
+ await expect(
197
+ program.parseAsync(["node", "revstack", "push"], { from: "node" })
198
+ ).rejects.toThrow(ProcessExitError);
199
+
200
+ // Only diff called, push never reached
201
+ expect(mockFetch).toHaveBeenCalledTimes(1);
202
+ expect(exitSpy).toHaveBeenCalledWith(1);
203
+ });
204
+ });
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ // ── Mock node:fs before importing auth ────────────────────────
4
+ vi.mock("node:fs", () => ({
5
+ default: {
6
+ existsSync: vi.fn(),
7
+ mkdirSync: vi.fn(),
8
+ writeFileSync: vi.fn(),
9
+ readFileSync: vi.fn(),
10
+ unlinkSync: vi.fn(),
11
+ },
12
+ existsSync: vi.fn(),
13
+ mkdirSync: vi.fn(),
14
+ writeFileSync: vi.fn(),
15
+ readFileSync: vi.fn(),
16
+ unlinkSync: vi.fn(),
17
+ }));
18
+
19
+ import fs from "node:fs";
20
+ import { setApiKey, getApiKey, clearApiKey } from "../../src/utils/auth.js";
21
+
22
+ const mockFs = vi.mocked(fs);
23
+
24
+ beforeEach(() => {
25
+ vi.clearAllMocks();
26
+ });
27
+
28
+ describe("auth", () => {
29
+ describe("setApiKey", () => {
30
+ it("creates the .revstack directory if it does not exist", () => {
31
+ mockFs.existsSync.mockReturnValue(false);
32
+
33
+ setApiKey("sk_test_abc123");
34
+
35
+ expect(mockFs.mkdirSync).toHaveBeenCalledWith(
36
+ expect.stringContaining(".revstack"),
37
+ { recursive: true }
38
+ );
39
+ });
40
+
41
+ it("writes credentials.json with the API key", () => {
42
+ mockFs.existsSync.mockReturnValue(true);
43
+
44
+ setApiKey("sk_test_abc123");
45
+
46
+ expect(mockFs.writeFileSync).toHaveBeenCalledWith(
47
+ expect.stringContaining("credentials.json"),
48
+ JSON.stringify({ apiKey: "sk_test_abc123" }, null, 2),
49
+ "utf-8"
50
+ );
51
+ });
52
+
53
+ it("skips mkdir if directory already exists", () => {
54
+ mockFs.existsSync.mockReturnValue(true);
55
+
56
+ setApiKey("sk_live_xyz");
57
+
58
+ expect(mockFs.mkdirSync).not.toHaveBeenCalled();
59
+ });
60
+ });
61
+
62
+ describe("getApiKey", () => {
63
+ it("returns the stored API key", () => {
64
+ mockFs.existsSync.mockReturnValue(true);
65
+ mockFs.readFileSync.mockReturnValue(
66
+ JSON.stringify({ apiKey: "sk_test_stored" })
67
+ );
68
+
69
+ const key = getApiKey();
70
+
71
+ expect(key).toBe("sk_test_stored");
72
+ });
73
+
74
+ it("returns null if credentials file does not exist", () => {
75
+ mockFs.existsSync.mockReturnValue(false);
76
+
77
+ const key = getApiKey();
78
+
79
+ expect(key).toBeNull();
80
+ expect(mockFs.readFileSync).not.toHaveBeenCalled();
81
+ });
82
+
83
+ it("returns null if the file contains invalid JSON", () => {
84
+ mockFs.existsSync.mockReturnValue(true);
85
+ mockFs.readFileSync.mockReturnValue("not valid json");
86
+
87
+ const key = getApiKey();
88
+
89
+ expect(key).toBeNull();
90
+ });
91
+ });
92
+
93
+ describe("clearApiKey", () => {
94
+ it("deletes the credentials file if it exists", () => {
95
+ mockFs.existsSync.mockReturnValue(true);
96
+
97
+ clearApiKey();
98
+
99
+ expect(mockFs.unlinkSync).toHaveBeenCalledWith(
100
+ expect.stringContaining("credentials.json")
101
+ );
102
+ });
103
+
104
+ it("does nothing if credentials file does not exist", () => {
105
+ mockFs.existsSync.mockReturnValue(false);
106
+
107
+ clearApiKey();
108
+
109
+ expect(mockFs.unlinkSync).not.toHaveBeenCalled();
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ // ── Mock jiti ─────────────────────────────────────────────────
4
+ const mockImport = vi.fn();
5
+
6
+ vi.mock("jiti", () => ({
7
+ createJiti: vi.fn(() => ({
8
+ import: mockImport,
9
+ })),
10
+ }));
11
+
12
+ // ── Mock chalk to avoid ANSI in test output ───────────────────
13
+ vi.mock("chalk", () => ({
14
+ default: {
15
+ red: (s: string) => s,
16
+ dim: (s: string) => s,
17
+ bold: (s: string) => s,
18
+ },
19
+ }));
20
+
21
+ // ── Mock process.exit ────────────────────────────────────────
22
+ const mockExit = vi
23
+ .spyOn(process, "exit")
24
+ .mockImplementation(() => undefined as never);
25
+
26
+ import { loadLocalConfig } from "../../src/utils/config-loader.js";
27
+
28
+ beforeEach(() => {
29
+ vi.clearAllMocks();
30
+ });
31
+
32
+ describe("config-loader", () => {
33
+ describe("loadLocalConfig", () => {
34
+ it("extracts the default export from the module", async () => {
35
+ const sampleConfig = {
36
+ features: {
37
+ seats: { name: "Seats", type: "static", unit_type: "count" },
38
+ },
39
+ plans: {},
40
+ };
41
+
42
+ mockImport.mockResolvedValue({ default: sampleConfig });
43
+
44
+ const result = await loadLocalConfig("/fake/project");
45
+
46
+ expect(result).toEqual(sampleConfig);
47
+ });
48
+
49
+ it("falls back to the module itself if no default export", async () => {
50
+ const sampleConfig = { features: {}, plans: {} };
51
+
52
+ mockImport.mockResolvedValue(sampleConfig);
53
+
54
+ const result = await loadLocalConfig("/fake/project");
55
+
56
+ expect(result).toEqual(sampleConfig);
57
+ });
58
+
59
+ it("sanitizes non-serializable values (functions stripped)", async () => {
60
+ const configWithFn = {
61
+ default: {
62
+ features: {},
63
+ plans: {},
64
+ helper: () => "should be stripped",
65
+ },
66
+ };
67
+
68
+ mockImport.mockResolvedValue(configWithFn);
69
+
70
+ const result = await loadLocalConfig("/fake/project");
71
+
72
+ expect(result).not.toHaveProperty("helper");
73
+ expect(result).toEqual({ features: {}, plans: {} });
74
+ });
75
+
76
+ it("calls process.exit(1) if the config file is not found", async () => {
77
+ const error: NodeJS.ErrnoException = new Error("Cannot find module");
78
+ error.code = "MODULE_NOT_FOUND";
79
+ mockImport.mockRejectedValue(error);
80
+
81
+ await loadLocalConfig("/fake/project");
82
+
83
+ expect(mockExit).toHaveBeenCalledWith(1);
84
+ });
85
+
86
+ it("calls process.exit(1) on syntax errors", async () => {
87
+ mockImport.mockRejectedValue(new SyntaxError("Unexpected token"));
88
+
89
+ await loadLocalConfig("/fake/project");
90
+
91
+ expect(mockExit).toHaveBeenCalledWith(1);
92
+ });
93
+ });
94
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "outDir": "./dist",
12
+ "rootDir": "./src",
13
+ "baseUrl": ".",
14
+ "paths": {
15
+ "@/*": ["./src/*"]
16
+ }
17
+ },
18
+
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist"]
21
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@/*": ["./src/*"]
7
+ },
8
+ "noEmit": true,
9
+ "types": ["node", "vitest/globals"]
10
+ },
11
+ "include": ["src", "tests"]
12
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/cli.ts"],
5
+ format: ["esm"],
6
+ dts: false,
7
+ splitting: false,
8
+ sourcemap: true,
9
+ clean: true,
10
+ target: "node18",
11
+ outDir: "dist",
12
+ skipNodeModulesBundle: true,
13
+ banner: {
14
+ js: "#!/usr/bin/env node",
15
+ },
16
+ });
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import path from "node:path";
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ alias: {
7
+ "@": path.resolve(__dirname, "src"),
8
+ },
9
+ },
10
+ test: {
11
+ globals: true,
12
+ environment: "node",
13
+ include: ["tests/**/*.test.ts"],
14
+ },
15
+ });