claudekit-cli 1.4.1 → 1.5.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 (47) hide show
  1. package/bin/ck-darwin-arm64 +0 -0
  2. package/bin/ck-darwin-x64 +0 -0
  3. package/bin/ck-linux-x64 +0 -0
  4. package/bin/ck-win32-x64.exe +0 -0
  5. package/package.json +8 -2
  6. package/scripts/postinstall.js +74 -0
  7. package/.github/workflows/ci.yml +0 -45
  8. package/.github/workflows/claude-code-review.yml +0 -57
  9. package/.github/workflows/claude.yml +0 -50
  10. package/.github/workflows/release.yml +0 -102
  11. package/.releaserc.json +0 -17
  12. package/.repomixignore +0 -15
  13. package/AGENTS.md +0 -217
  14. package/CHANGELOG.md +0 -95
  15. package/CLAUDE.md +0 -34
  16. package/biome.json +0 -28
  17. package/bun.lock +0 -863
  18. package/dist/index.js +0 -22511
  19. package/src/commands/new.ts +0 -185
  20. package/src/commands/update.ts +0 -174
  21. package/src/commands/version.ts +0 -135
  22. package/src/index.ts +0 -102
  23. package/src/lib/auth.ts +0 -157
  24. package/src/lib/download.ts +0 -689
  25. package/src/lib/github.ts +0 -230
  26. package/src/lib/merge.ts +0 -119
  27. package/src/lib/prompts.ts +0 -114
  28. package/src/types.ts +0 -178
  29. package/src/utils/config.ts +0 -87
  30. package/src/utils/file-scanner.ts +0 -134
  31. package/src/utils/logger.ts +0 -124
  32. package/src/utils/safe-prompts.ts +0 -44
  33. package/src/utils/safe-spinner.ts +0 -38
  34. package/src/version.json +0 -3
  35. package/tests/commands/version.test.ts +0 -297
  36. package/tests/integration/cli.test.ts +0 -252
  37. package/tests/lib/auth.test.ts +0 -116
  38. package/tests/lib/download.test.ts +0 -292
  39. package/tests/lib/github-download-priority.test.ts +0 -432
  40. package/tests/lib/github.test.ts +0 -52
  41. package/tests/lib/merge.test.ts +0 -267
  42. package/tests/lib/prompts.test.ts +0 -66
  43. package/tests/types.test.ts +0 -337
  44. package/tests/utils/config.test.ts +0 -263
  45. package/tests/utils/file-scanner.test.ts +0 -202
  46. package/tests/utils/logger.test.ts +0 -239
  47. package/tsconfig.json +0 -30
@@ -1,267 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync } from "node:fs";
3
- import { mkdir, rm, writeFile } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import { FileMerger } from "../../src/lib/merge.js";
7
-
8
- describe("FileMerger", () => {
9
- let merger: FileMerger;
10
- let testSourceDir: string;
11
- let testDestDir: string;
12
-
13
- beforeEach(async () => {
14
- merger = new FileMerger();
15
-
16
- // Create temporary test directories
17
- const timestamp = Date.now();
18
- testSourceDir = join(tmpdir(), `test-source-${timestamp}`);
19
- testDestDir = join(tmpdir(), `test-dest-${timestamp}`);
20
-
21
- await mkdir(testSourceDir, { recursive: true });
22
- await mkdir(testDestDir, { recursive: true });
23
- });
24
-
25
- afterEach(async () => {
26
- // Cleanup test directories
27
- if (existsSync(testSourceDir)) {
28
- await rm(testSourceDir, { recursive: true, force: true });
29
- }
30
- if (existsSync(testDestDir)) {
31
- await rm(testDestDir, { recursive: true, force: true });
32
- }
33
- });
34
-
35
- describe("constructor", () => {
36
- test("should create FileMerger instance", () => {
37
- expect(merger).toBeInstanceOf(FileMerger);
38
- });
39
- });
40
-
41
- describe("addIgnorePatterns", () => {
42
- test("should add custom ignore patterns", () => {
43
- const patterns = ["*.log", "temp/**"];
44
- expect(() => merger.addIgnorePatterns(patterns)).not.toThrow();
45
- });
46
-
47
- test("should accept empty array", () => {
48
- expect(() => merger.addIgnorePatterns([])).not.toThrow();
49
- });
50
- });
51
-
52
- describe("merge with skipConfirmation", () => {
53
- test("should copy files from source to destination", async () => {
54
- // Create test files
55
- await writeFile(join(testSourceDir, "test.txt"), "test content");
56
- await writeFile(join(testSourceDir, "readme.md"), "# README");
57
-
58
- await merger.merge(testSourceDir, testDestDir, true);
59
-
60
- // Verify files were copied
61
- expect(existsSync(join(testDestDir, "test.txt"))).toBe(true);
62
- expect(existsSync(join(testDestDir, "readme.md"))).toBe(true);
63
- });
64
-
65
- test("should skip protected files like .env if they exist in destination", async () => {
66
- // Create test files in source
67
- await writeFile(join(testSourceDir, "normal.txt"), "normal");
68
- await writeFile(join(testSourceDir, ".env"), "NEW_SECRET=new_value");
69
-
70
- // Create existing .env in destination
71
- await writeFile(join(testDestDir, ".env"), "OLD_SECRET=old_value");
72
-
73
- await merger.merge(testSourceDir, testDestDir, true);
74
-
75
- // Verify normal file was copied
76
- expect(existsSync(join(testDestDir, "normal.txt"))).toBe(true);
77
-
78
- // Verify .env was NOT overwritten (still has old value)
79
- const envContent = await Bun.file(join(testDestDir, ".env")).text();
80
- expect(envContent).toBe("OLD_SECRET=old_value");
81
- });
82
-
83
- test("should copy protected files like .env if they don't exist in destination", async () => {
84
- // Create test files in source
85
- await writeFile(join(testSourceDir, "normal.txt"), "normal");
86
- await writeFile(join(testSourceDir, ".env"), "SECRET=value");
87
-
88
- await merger.merge(testSourceDir, testDestDir, true);
89
-
90
- // Verify both files were copied (no existing .env to protect)
91
- expect(existsSync(join(testDestDir, "normal.txt"))).toBe(true);
92
- expect(existsSync(join(testDestDir, ".env"))).toBe(true);
93
- });
94
-
95
- test("should skip protected patterns like *.key if they exist in destination", async () => {
96
- await writeFile(join(testSourceDir, "normal.txt"), "normal");
97
- await writeFile(join(testSourceDir, "private.key"), "new key data");
98
-
99
- // Create existing key file in destination
100
- await writeFile(join(testDestDir, "private.key"), "old key data");
101
-
102
- await merger.merge(testSourceDir, testDestDir, true);
103
-
104
- expect(existsSync(join(testDestDir, "normal.txt"))).toBe(true);
105
-
106
- // Verify key file was NOT overwritten
107
- const keyContent = await Bun.file(join(testDestDir, "private.key")).text();
108
- expect(keyContent).toBe("old key data");
109
- });
110
-
111
- test("should copy protected patterns like *.key if they don't exist in destination", async () => {
112
- await writeFile(join(testSourceDir, "normal.txt"), "normal");
113
- await writeFile(join(testSourceDir, "private.key"), "key data");
114
-
115
- await merger.merge(testSourceDir, testDestDir, true);
116
-
117
- expect(existsSync(join(testDestDir, "normal.txt"))).toBe(true);
118
- expect(existsSync(join(testDestDir, "private.key"))).toBe(true);
119
- });
120
-
121
- test("should handle nested directories", async () => {
122
- const nestedDir = join(testSourceDir, "nested", "deep");
123
- await mkdir(nestedDir, { recursive: true });
124
- await writeFile(join(nestedDir, "file.txt"), "nested content");
125
-
126
- await merger.merge(testSourceDir, testDestDir, true);
127
-
128
- expect(existsSync(join(testDestDir, "nested", "deep", "file.txt"))).toBe(true);
129
- });
130
-
131
- test("should overwrite existing files", async () => {
132
- // Create files in both directories
133
- await writeFile(join(testSourceDir, "file.txt"), "new content");
134
- await writeFile(join(testDestDir, "file.txt"), "old content");
135
-
136
- await merger.merge(testSourceDir, testDestDir, true);
137
-
138
- const content = await Bun.file(join(testDestDir, "file.txt")).text();
139
- expect(content).toBe("new content");
140
- });
141
-
142
- test("should handle empty source directory", async () => {
143
- // Empty directory should complete without errors
144
- await merger.merge(testSourceDir, testDestDir, true);
145
- // If we get here, the test passed
146
- expect(true).toBe(true);
147
- });
148
- });
149
-
150
- describe("edge cases", () => {
151
- test("should handle files with special characters in names", async () => {
152
- const specialFileName = "file with spaces.txt";
153
- await writeFile(join(testSourceDir, specialFileName), "content");
154
-
155
- await merger.merge(testSourceDir, testDestDir, true);
156
-
157
- expect(existsSync(join(testDestDir, specialFileName))).toBe(true);
158
- });
159
-
160
- test("should skip custom ignore patterns if they exist in destination", async () => {
161
- merger.addIgnorePatterns(["custom-*"]);
162
-
163
- await writeFile(join(testSourceDir, "normal.txt"), "normal");
164
- await writeFile(join(testSourceDir, "custom-ignore.txt"), "new content");
165
-
166
- // Create existing file in destination
167
- await writeFile(join(testDestDir, "custom-ignore.txt"), "old content");
168
-
169
- await merger.merge(testSourceDir, testDestDir, true);
170
-
171
- expect(existsSync(join(testDestDir, "normal.txt"))).toBe(true);
172
-
173
- // Verify custom file was NOT overwritten
174
- const customContent = await Bun.file(join(testDestDir, "custom-ignore.txt")).text();
175
- expect(customContent).toBe("old content");
176
- });
177
-
178
- test("should copy custom ignore patterns if they don't exist in destination", async () => {
179
- merger.addIgnorePatterns(["custom-*"]);
180
-
181
- await writeFile(join(testSourceDir, "normal.txt"), "normal");
182
- await writeFile(join(testSourceDir, "custom-ignore.txt"), "ignore me");
183
-
184
- await merger.merge(testSourceDir, testDestDir, true);
185
-
186
- expect(existsSync(join(testDestDir, "normal.txt"))).toBe(true);
187
- expect(existsSync(join(testDestDir, "custom-ignore.txt"))).toBe(true);
188
- });
189
- });
190
-
191
- describe("custom .claude file preservation", () => {
192
- test("should preserve custom .claude files when patterns are added", async () => {
193
- // Create .claude directories
194
- const sourceClaudeDir = join(testSourceDir, ".claude");
195
- const destClaudeDir = join(testDestDir, ".claude");
196
- await mkdir(sourceClaudeDir, { recursive: true });
197
- await mkdir(destClaudeDir, { recursive: true });
198
-
199
- // Create files in source (from release package)
200
- await writeFile(join(sourceClaudeDir, "standard.md"), "standard content");
201
-
202
- // Create files in destination (existing + custom)
203
- await writeFile(join(destClaudeDir, "standard.md"), "old standard content");
204
- await writeFile(join(destClaudeDir, "custom.md"), "custom content");
205
-
206
- // Add custom file to ignore patterns (this would be done by update.ts)
207
- merger.addIgnorePatterns([".claude/custom.md"]);
208
-
209
- await merger.merge(testSourceDir, testDestDir, true);
210
-
211
- // Standard file should be overwritten
212
- const standardContent = await Bun.file(join(destClaudeDir, "standard.md")).text();
213
- expect(standardContent).toBe("standard content");
214
-
215
- // Custom file should be preserved
216
- expect(existsSync(join(destClaudeDir, "custom.md"))).toBe(true);
217
- const customContent = await Bun.file(join(destClaudeDir, "custom.md")).text();
218
- expect(customContent).toBe("custom content");
219
- });
220
-
221
- test("should preserve nested custom .claude files", async () => {
222
- // Create nested .claude structure
223
- const sourceCommandsDir = join(testSourceDir, ".claude", "commands");
224
- const destCommandsDir = join(testDestDir, ".claude", "commands");
225
- await mkdir(sourceCommandsDir, { recursive: true });
226
- await mkdir(destCommandsDir, { recursive: true });
227
-
228
- // Create standard file in source
229
- await writeFile(join(sourceCommandsDir, "standard-cmd.md"), "standard command");
230
-
231
- // Create custom file in destination
232
- await writeFile(join(destCommandsDir, "custom-cmd.md"), "custom command");
233
-
234
- // Add custom file to ignore patterns
235
- merger.addIgnorePatterns([".claude/commands/custom-cmd.md"]);
236
-
237
- await merger.merge(testSourceDir, testDestDir, true);
238
-
239
- // Custom file should be preserved
240
- expect(existsSync(join(destCommandsDir, "custom-cmd.md"))).toBe(true);
241
- const customContent = await Bun.file(join(destCommandsDir, "custom-cmd.md")).text();
242
- expect(customContent).toBe("custom command");
243
- });
244
-
245
- test("should preserve multiple custom .claude files", async () => {
246
- const sourceClaudeDir = join(testSourceDir, ".claude");
247
- const destClaudeDir = join(testDestDir, ".claude");
248
- await mkdir(sourceClaudeDir, { recursive: true });
249
- await mkdir(destClaudeDir, { recursive: true });
250
-
251
- // Create multiple custom files in destination
252
- await writeFile(join(destClaudeDir, "custom1.md"), "custom1");
253
- await writeFile(join(destClaudeDir, "custom2.md"), "custom2");
254
- await writeFile(join(destClaudeDir, "custom3.md"), "custom3");
255
-
256
- // Add all custom files to ignore patterns
257
- merger.addIgnorePatterns([".claude/custom1.md", ".claude/custom2.md", ".claude/custom3.md"]);
258
-
259
- await merger.merge(testSourceDir, testDestDir, true);
260
-
261
- // All custom files should be preserved
262
- expect(existsSync(join(destClaudeDir, "custom1.md"))).toBe(true);
263
- expect(existsSync(join(destClaudeDir, "custom2.md"))).toBe(true);
264
- expect(existsSync(join(destClaudeDir, "custom3.md"))).toBe(true);
265
- });
266
- });
267
- });
@@ -1,66 +0,0 @@
1
- import { beforeEach, describe, expect, test } from "bun:test";
2
- import { PromptsManager } from "../../src/lib/prompts.js";
3
- import { AVAILABLE_KITS } from "../../src/types.js";
4
-
5
- describe("PromptsManager", () => {
6
- let manager: PromptsManager;
7
-
8
- beforeEach(() => {
9
- manager = new PromptsManager();
10
- });
11
-
12
- describe("constructor", () => {
13
- test("should create PromptsManager instance", () => {
14
- expect(manager).toBeInstanceOf(PromptsManager);
15
- });
16
- });
17
-
18
- describe("utility methods", () => {
19
- test("intro should not throw", () => {
20
- expect(() => manager.intro("Test intro")).not.toThrow();
21
- });
22
-
23
- test("outro should not throw", () => {
24
- expect(() => manager.outro("Test outro")).not.toThrow();
25
- });
26
-
27
- test("note should not throw", () => {
28
- expect(() => manager.note("Test note", "Title")).not.toThrow();
29
- });
30
-
31
- test("note should work without title", () => {
32
- expect(() => manager.note("Test note")).not.toThrow();
33
- });
34
- });
35
-
36
- // Note: Interactive prompt tests (selectKit, selectVersion, getDirectory, confirm)
37
- // would require mocking the @clack/prompts library or using integration tests
38
- // with simulated user input. These are better suited for e2e testing.
39
-
40
- describe("validation logic", () => {
41
- test("selectVersion should handle empty versions array", async () => {
42
- await expect(manager.selectVersion([], undefined)).rejects.toThrow("No versions available");
43
- });
44
-
45
- test("selectVersion should return first version when only one exists", async () => {
46
- const versions = ["v1.0.0"];
47
- const result = await manager.selectVersion(versions);
48
- expect(result).toBe("v1.0.0");
49
- });
50
-
51
- test("selectVersion should return first version when no default is provided", async () => {
52
- const versions = ["v1.0.0", "v2.0.0"];
53
- const result = await manager.selectVersion(versions);
54
- expect(result).toBe("v1.0.0");
55
- });
56
- });
57
-
58
- describe("kit configuration", () => {
59
- test("AVAILABLE_KITS should be properly structured", () => {
60
- expect(AVAILABLE_KITS.engineer).toBeDefined();
61
- expect(AVAILABLE_KITS.marketing).toBeDefined();
62
- expect(AVAILABLE_KITS.engineer.name).toBe("ClaudeKit Engineer");
63
- expect(AVAILABLE_KITS.marketing.name).toBe("ClaudeKit Marketing");
64
- });
65
- });
66
- });
@@ -1,337 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import {
3
- AVAILABLE_KITS,
4
- AuthenticationError,
5
- ClaudeKitError,
6
- ConfigSchema,
7
- DownloadError,
8
- ExcludePatternSchema,
9
- ExtractionError,
10
- GitHubError,
11
- GitHubReleaseAssetSchema,
12
- GitHubReleaseSchema,
13
- KitConfigSchema,
14
- KitType,
15
- NewCommandOptionsSchema,
16
- UpdateCommandOptionsSchema,
17
- } from "../src/types.js";
18
-
19
- describe("Types and Schemas", () => {
20
- describe("KitType", () => {
21
- test("should validate correct kit types", () => {
22
- expect(KitType.parse("engineer")).toBe("engineer");
23
- expect(KitType.parse("marketing")).toBe("marketing");
24
- });
25
-
26
- test("should reject invalid kit types", () => {
27
- expect(() => KitType.parse("invalid")).toThrow();
28
- expect(() => KitType.parse("")).toThrow();
29
- expect(() => KitType.parse(123)).toThrow();
30
- });
31
- });
32
-
33
- describe("ExcludePatternSchema", () => {
34
- test("should accept valid glob patterns", () => {
35
- const validPatterns = ["*.log", "**/*.tmp", "temp/**", "logs/*.txt", "cache/**/*"];
36
- validPatterns.forEach((pattern) => {
37
- expect(() => ExcludePatternSchema.parse(pattern)).not.toThrow();
38
- });
39
- });
40
-
41
- test("should reject absolute paths", () => {
42
- expect(() => ExcludePatternSchema.parse("/etc/passwd")).toThrow("Absolute paths not allowed");
43
- expect(() => ExcludePatternSchema.parse("/var/log/**")).toThrow("Absolute paths not allowed");
44
- });
45
-
46
- test("should reject path traversal", () => {
47
- expect(() => ExcludePatternSchema.parse("../../etc/passwd")).toThrow(
48
- "Path traversal not allowed",
49
- );
50
- expect(() => ExcludePatternSchema.parse("../../../secret")).toThrow(
51
- "Path traversal not allowed",
52
- );
53
- });
54
-
55
- test("should reject empty patterns", () => {
56
- expect(() => ExcludePatternSchema.parse("")).toThrow("Exclude pattern cannot be empty");
57
- expect(() => ExcludePatternSchema.parse(" ")).toThrow("Exclude pattern cannot be empty");
58
- });
59
-
60
- test("should reject overly long patterns", () => {
61
- const longPattern = "a".repeat(501);
62
- expect(() => ExcludePatternSchema.parse(longPattern)).toThrow("Exclude pattern too long");
63
- });
64
-
65
- test("should trim whitespace", () => {
66
- const result = ExcludePatternSchema.parse(" *.log ");
67
- expect(result).toBe("*.log");
68
- });
69
- });
70
-
71
- describe("NewCommandOptionsSchema", () => {
72
- test("should validate correct options", () => {
73
- const result = NewCommandOptionsSchema.parse({
74
- dir: "./test",
75
- kit: "engineer",
76
- version: "v1.0.0",
77
- });
78
- expect(result.dir).toBe("./test");
79
- expect(result.kit).toBe("engineer");
80
- expect(result.version).toBe("v1.0.0");
81
- });
82
-
83
- test("should use default values", () => {
84
- const result = NewCommandOptionsSchema.parse({});
85
- expect(result.dir).toBe(".");
86
- expect(result.kit).toBeUndefined();
87
- expect(result.version).toBeUndefined();
88
- expect(result.exclude).toEqual([]);
89
- });
90
-
91
- test("should accept optional fields", () => {
92
- const result = NewCommandOptionsSchema.parse({ dir: "./custom" });
93
- expect(result.dir).toBe("./custom");
94
- expect(result.kit).toBeUndefined();
95
- });
96
-
97
- test("should validate exclude patterns", () => {
98
- const result = NewCommandOptionsSchema.parse({
99
- dir: "./test",
100
- exclude: ["*.log", "temp/**"],
101
- });
102
- expect(result.exclude).toEqual(["*.log", "temp/**"]);
103
- });
104
-
105
- test("should reject invalid exclude patterns", () => {
106
- expect(() =>
107
- NewCommandOptionsSchema.parse({
108
- dir: "./test",
109
- exclude: ["/etc/passwd"],
110
- }),
111
- ).toThrow();
112
- });
113
- });
114
-
115
- describe("UpdateCommandOptionsSchema", () => {
116
- test("should validate correct options", () => {
117
- const result = UpdateCommandOptionsSchema.parse({
118
- dir: "./test",
119
- kit: "engineer",
120
- version: "v2.0.0",
121
- });
122
- expect(result.dir).toBe("./test");
123
- expect(result.kit).toBe("engineer");
124
- expect(result.version).toBe("v2.0.0");
125
- });
126
-
127
- test("should use default values", () => {
128
- const result = UpdateCommandOptionsSchema.parse({});
129
- expect(result.dir).toBe(".");
130
- expect(result.exclude).toEqual([]);
131
- });
132
-
133
- test("should validate exclude patterns", () => {
134
- const result = UpdateCommandOptionsSchema.parse({
135
- dir: "./test",
136
- exclude: ["*.log", "**/*.tmp"],
137
- });
138
- expect(result.exclude).toEqual(["*.log", "**/*.tmp"]);
139
- });
140
-
141
- test("should reject invalid exclude patterns", () => {
142
- expect(() =>
143
- UpdateCommandOptionsSchema.parse({
144
- dir: "./test",
145
- exclude: ["../../../etc"],
146
- }),
147
- ).toThrow();
148
- });
149
- });
150
-
151
- describe("ConfigSchema", () => {
152
- test("should validate complete config", () => {
153
- const config = {
154
- github: {
155
- token: "ghp_test123456789",
156
- },
157
- defaults: {
158
- kit: "engineer",
159
- dir: "./projects",
160
- },
161
- };
162
- const result = ConfigSchema.parse(config);
163
- expect(result.github?.token).toBe("ghp_test123456789");
164
- expect(result.defaults?.kit).toBe("engineer");
165
- expect(result.defaults?.dir).toBe("./projects");
166
- });
167
-
168
- test("should validate empty config", () => {
169
- const result = ConfigSchema.parse({});
170
- expect(result.github).toBeUndefined();
171
- expect(result.defaults).toBeUndefined();
172
- });
173
-
174
- test("should validate partial config", () => {
175
- const result = ConfigSchema.parse({ github: {} });
176
- expect(result.github).toEqual({});
177
- expect(result.defaults).toBeUndefined();
178
- });
179
- });
180
-
181
- describe("GitHubReleaseAssetSchema", () => {
182
- test("should validate correct asset", () => {
183
- const asset = {
184
- id: 123,
185
- name: "release.tar.gz",
186
- url: "https://api.github.com/repos/test/repo/releases/assets/123",
187
- browser_download_url: "https://github.com/test/release.tar.gz",
188
- size: 1024,
189
- content_type: "application/gzip",
190
- };
191
- const result = GitHubReleaseAssetSchema.parse(asset);
192
- expect(result.id).toBe(123);
193
- expect(result.name).toBe("release.tar.gz");
194
- expect(result.size).toBe(1024);
195
- });
196
-
197
- test("should reject invalid URL", () => {
198
- const asset = {
199
- id: 123,
200
- name: "release.tar.gz",
201
- url: "not-a-url",
202
- browser_download_url: "not-a-url",
203
- size: 1024,
204
- content_type: "application/gzip",
205
- };
206
- expect(() => GitHubReleaseAssetSchema.parse(asset)).toThrow();
207
- });
208
-
209
- test("should reject missing required fields", () => {
210
- const asset = {
211
- id: 123,
212
- name: "release.tar.gz",
213
- };
214
- expect(() => GitHubReleaseAssetSchema.parse(asset)).toThrow();
215
- });
216
- });
217
-
218
- describe("GitHubReleaseSchema", () => {
219
- test("should validate complete release", () => {
220
- const release = {
221
- id: 1,
222
- tag_name: "v1.0.0",
223
- name: "Version 1.0.0",
224
- draft: false,
225
- prerelease: false,
226
- assets: [
227
- {
228
- id: 123,
229
- name: "release.tar.gz",
230
- url: "https://api.github.com/repos/test/repo/releases/assets/123",
231
- browser_download_url: "https://github.com/test/release.tar.gz",
232
- size: 1024,
233
- content_type: "application/gzip",
234
- },
235
- ],
236
- published_at: "2024-01-01T00:00:00Z",
237
- tarball_url: "https://api.github.com/repos/test/test-repo/tarball/v1.0.0",
238
- zipball_url: "https://api.github.com/repos/test/test-repo/zipball/v1.0.0",
239
- };
240
- const result = GitHubReleaseSchema.parse(release);
241
- expect(result.id).toBe(1);
242
- expect(result.tag_name).toBe("v1.0.0");
243
- expect(result.assets).toHaveLength(1);
244
- });
245
-
246
- test("should validate release without published_at", () => {
247
- const release = {
248
- id: 1,
249
- tag_name: "v1.0.0",
250
- name: "Version 1.0.0",
251
- draft: false,
252
- prerelease: false,
253
- assets: [],
254
- tarball_url: "https://api.github.com/repos/test/test-repo/tarball/v1.0.0",
255
- zipball_url: "https://api.github.com/repos/test/test-repo/zipball/v1.0.0",
256
- };
257
- const result = GitHubReleaseSchema.parse(release);
258
- expect(result.published_at).toBeUndefined();
259
- });
260
- });
261
-
262
- describe("KitConfigSchema", () => {
263
- test("should validate correct kit config", () => {
264
- const config = {
265
- name: "Test Kit",
266
- repo: "test-repo",
267
- owner: "test-owner",
268
- description: "Test description",
269
- };
270
- const result = KitConfigSchema.parse(config);
271
- expect(result.name).toBe("Test Kit");
272
- expect(result.repo).toBe("test-repo");
273
- });
274
-
275
- test("should reject missing fields", () => {
276
- const config = {
277
- name: "Test Kit",
278
- repo: "test-repo",
279
- };
280
- expect(() => KitConfigSchema.parse(config)).toThrow();
281
- });
282
- });
283
-
284
- describe("AVAILABLE_KITS", () => {
285
- test("should have engineer kit", () => {
286
- expect(AVAILABLE_KITS.engineer).toBeDefined();
287
- expect(AVAILABLE_KITS.engineer.name).toBe("ClaudeKit Engineer");
288
- expect(AVAILABLE_KITS.engineer.repo).toBe("claudekit-engineer");
289
- });
290
-
291
- test("should have marketing kit", () => {
292
- expect(AVAILABLE_KITS.marketing).toBeDefined();
293
- expect(AVAILABLE_KITS.marketing.name).toBe("ClaudeKit Marketing");
294
- expect(AVAILABLE_KITS.marketing.repo).toBe("claudekit-marketing");
295
- });
296
- });
297
-
298
- describe("Custom Error Classes", () => {
299
- test("ClaudeKitError should store code and statusCode", () => {
300
- const error = new ClaudeKitError("Test error", "TEST_CODE", 500);
301
- expect(error.message).toBe("Test error");
302
- expect(error.code).toBe("TEST_CODE");
303
- expect(error.statusCode).toBe(500);
304
- expect(error.name).toBe("ClaudeKitError");
305
- });
306
-
307
- test("AuthenticationError should set correct defaults", () => {
308
- const error = new AuthenticationError("Auth failed");
309
- expect(error.message).toBe("Auth failed");
310
- expect(error.code).toBe("AUTH_ERROR");
311
- expect(error.statusCode).toBe(401);
312
- expect(error.name).toBe("AuthenticationError");
313
- });
314
-
315
- test("GitHubError should store statusCode", () => {
316
- const error = new GitHubError("GitHub failed", 404);
317
- expect(error.message).toBe("GitHub failed");
318
- expect(error.code).toBe("GITHUB_ERROR");
319
- expect(error.statusCode).toBe(404);
320
- expect(error.name).toBe("GitHubError");
321
- });
322
-
323
- test("DownloadError should have correct code", () => {
324
- const error = new DownloadError("Download failed");
325
- expect(error.message).toBe("Download failed");
326
- expect(error.code).toBe("DOWNLOAD_ERROR");
327
- expect(error.name).toBe("DownloadError");
328
- });
329
-
330
- test("ExtractionError should have correct code", () => {
331
- const error = new ExtractionError("Extract failed");
332
- expect(error.message).toBe("Extract failed");
333
- expect(error.code).toBe("EXTRACTION_ERROR");
334
- expect(error.name).toBe("ExtractionError");
335
- });
336
- });
337
- });