claudekit-cli 1.4.0 → 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 (50) 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 -88
  15. package/CLAUDE.md +0 -34
  16. package/biome.json +0 -28
  17. package/bun.lock +0 -863
  18. package/dist/index.js +0 -22489
  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 -654
  25. package/src/lib/github.ts +0 -230
  26. package/src/lib/merge.ts +0 -116
  27. package/src/lib/prompts.ts +0 -114
  28. package/src/types.ts +0 -171
  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/test-integration/demo/.mcp.json +0 -13
  36. package/test-integration/demo/.repomixignore +0 -15
  37. package/test-integration/demo/CLAUDE.md +0 -34
  38. package/tests/commands/version.test.ts +0 -297
  39. package/tests/integration/cli.test.ts +0 -252
  40. package/tests/lib/auth.test.ts +0 -116
  41. package/tests/lib/download.test.ts +0 -292
  42. package/tests/lib/github-download-priority.test.ts +0 -432
  43. package/tests/lib/github.test.ts +0 -52
  44. package/tests/lib/merge.test.ts +0 -215
  45. package/tests/lib/prompts.test.ts +0 -66
  46. package/tests/types.test.ts +0 -337
  47. package/tests/utils/config.test.ts +0 -263
  48. package/tests/utils/file-scanner.test.ts +0 -202
  49. package/tests/utils/logger.test.ts +0 -239
  50. package/tsconfig.json +0 -30
@@ -1,252 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { execSync } from "node:child_process";
3
- import { existsSync } from "node:fs";
4
- import { mkdir, rm, writeFile } from "node:fs/promises";
5
- import { join } from "node:path";
6
- import { fileURLToPath } from "node:url";
7
-
8
- /**
9
- * Integration tests for CLI commands
10
- * These tests actually run the CLI and verify the results
11
- */
12
- describe("CLI Integration Tests", () => {
13
- let testDir: string;
14
- const __dirname = join(fileURLToPath(import.meta.url), "..", "..", "..");
15
- const cliPath = join(__dirname, "dist", "index.js");
16
-
17
- // Skip integration tests in CI environments for now due to execution issues
18
- const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
19
-
20
- beforeEach(async () => {
21
- // Skip in CI
22
- if (isCI) {
23
- return;
24
- }
25
-
26
- // Create test directory
27
- testDir = join(process.cwd(), "test-integration", `cli-test-${Date.now()}`);
28
- await mkdir(testDir, { recursive: true });
29
-
30
- // Build the CLI first if not exists
31
- if (!existsSync(cliPath)) {
32
- execSync("bun run build", { cwd: process.cwd() });
33
- }
34
- });
35
-
36
- afterEach(async () => {
37
- // Skip in CI
38
- if (isCI) {
39
- return;
40
- }
41
-
42
- // Cleanup test directory
43
- if (existsSync(testDir)) {
44
- await rm(testDir, { recursive: true, force: true });
45
- }
46
- });
47
-
48
- describe("ck new command", () => {
49
- test("should create new project in specified directory", async () => {
50
- if (isCI) {
51
- return;
52
- }
53
-
54
- const projectDir = join(testDir, "test-ck-new");
55
-
56
- try {
57
- // Run ck new command with --kit and --force flags for non-interactive mode
58
- execSync(`node ${cliPath} new --dir ${projectDir} --kit engineer --force`, {
59
- cwd: testDir,
60
- stdio: "pipe",
61
- timeout: 60000, // 60 second timeout
62
- });
63
-
64
- // Verify project structure
65
- expect(existsSync(projectDir)).toBe(true);
66
- expect(existsSync(join(projectDir, ".claude"))).toBe(true);
67
- expect(existsSync(join(projectDir, "CLAUDE.md"))).toBe(true);
68
- } catch (error) {
69
- // Log error for debugging
70
- console.error("Command failed:", error);
71
- throw error;
72
- }
73
- }, 120000); // 2 minute timeout for the test
74
-
75
- test("should create project with correct file contents", async () => {
76
- if (isCI) {
77
- return;
78
- }
79
-
80
- const projectDir = join(testDir, "test-content");
81
-
82
- try {
83
- execSync(`node ${cliPath} new --dir ${projectDir} --kit engineer --force`, {
84
- cwd: testDir,
85
- stdio: "pipe",
86
- timeout: 60000,
87
- });
88
-
89
- // Verify file contents (basic check)
90
- const claudeMd = await Bun.file(join(projectDir, "CLAUDE.md")).text();
91
- expect(claudeMd).toContain("CLAUDE.md");
92
- } catch (error) {
93
- console.error("Command failed:", error);
94
- throw error;
95
- }
96
- }, 120000);
97
-
98
- test("should not overwrite existing project without confirmation", async () => {
99
- if (isCI) {
100
- return;
101
- }
102
-
103
- const projectDir = join(testDir, "test-no-overwrite");
104
-
105
- // Create existing directory with a file
106
- await mkdir(projectDir, { recursive: true });
107
- await writeFile(join(projectDir, "existing.txt"), "existing content");
108
-
109
- try {
110
- // This should fail because --force is not provided
111
- execSync(`node ${cliPath} new --dir ${projectDir} --kit engineer`, {
112
- cwd: testDir,
113
- stdio: "pipe",
114
- timeout: 5000,
115
- });
116
- // Should not reach here
117
- expect(true).toBe(false);
118
- } catch (error: any) {
119
- // Expected to fail without --force flag
120
- expect(error).toBeDefined();
121
- expect(error.message).toContain("not empty");
122
- }
123
-
124
- // Verify existing file is still there
125
- expect(existsSync(join(projectDir, "existing.txt"))).toBe(true);
126
- });
127
- });
128
-
129
- describe("ck update command", () => {
130
- test("should update existing project", async () => {
131
- if (isCI) {
132
- return;
133
- }
134
-
135
- const projectDir = join(testDir, "test-ck-update");
136
-
137
- // First create a project with --kit and --force flags
138
- execSync(`node ${cliPath} new --dir ${projectDir} --kit engineer --force`, {
139
- cwd: testDir,
140
- stdio: "pipe",
141
- timeout: 60000,
142
- });
143
-
144
- // Add a custom file to .claude directory
145
- await writeFile(join(projectDir, ".claude", "custom.md"), "# Custom file");
146
-
147
- // Update the project (will ask for confirmation, so it may timeout/fail)
148
- try {
149
- execSync(`node ${cliPath} update`, {
150
- cwd: projectDir,
151
- stdio: "pipe",
152
- timeout: 60000,
153
- });
154
-
155
- // Note: Update requires confirmation, so this test may need adjustment
156
- // based on how confirmation is handled in tests
157
- } catch (error) {
158
- // May fail due to confirmation prompt
159
- console.log("Update command requires confirmation, which is expected");
160
- }
161
-
162
- // Verify custom file is preserved
163
- expect(existsSync(join(projectDir, ".claude", "custom.md"))).toBe(true);
164
- }, 120000);
165
-
166
- test("should fail when not in a project directory", async () => {
167
- if (isCI) {
168
- return;
169
- }
170
-
171
- const emptyDir = join(testDir, "empty");
172
- await mkdir(emptyDir, { recursive: true });
173
-
174
- try {
175
- execSync(`node ${cliPath} update`, {
176
- cwd: emptyDir,
177
- stdio: "pipe",
178
- timeout: 5000,
179
- });
180
-
181
- // Should not reach here
182
- expect(true).toBe(false);
183
- } catch (error: any) {
184
- // Expected to fail
185
- expect(error).toBeDefined();
186
- }
187
- });
188
- });
189
-
190
- describe("project structure validation", () => {
191
- test("new project should have all required directories", async () => {
192
- if (isCI) {
193
- return;
194
- }
195
-
196
- const projectDir = join(testDir, "test-structure");
197
-
198
- execSync(`node ${cliPath} new --dir ${projectDir} --kit engineer --force`, {
199
- cwd: testDir,
200
- stdio: "pipe",
201
- timeout: 60000,
202
- });
203
-
204
- // Check for required directories
205
- const requiredDirs = [".claude"];
206
-
207
- for (const dir of requiredDirs) {
208
- expect(existsSync(join(projectDir, dir))).toBe(true);
209
- }
210
- }, 120000);
211
-
212
- test("new project should have all required files", async () => {
213
- if (isCI) {
214
- return;
215
- }
216
-
217
- const projectDir = join(testDir, "test-files");
218
-
219
- execSync(`node ${cliPath} new --dir ${projectDir} --kit engineer --force`, {
220
- cwd: testDir,
221
- stdio: "pipe",
222
- timeout: 60000,
223
- });
224
-
225
- // Check for required files
226
- const requiredFiles = ["CLAUDE.md"];
227
-
228
- for (const file of requiredFiles) {
229
- expect(existsSync(join(projectDir, file))).toBe(true);
230
- }
231
- }, 120000);
232
-
233
- test("project should not contain excluded files", async () => {
234
- if (isCI) {
235
- return;
236
- }
237
-
238
- const projectDir = join(testDir, "test-exclusions");
239
-
240
- execSync(`node ${cliPath} new --dir ${projectDir} --kit engineer --force`, {
241
- cwd: testDir,
242
- stdio: "pipe",
243
- timeout: 60000,
244
- });
245
-
246
- // Verify excluded patterns are not present
247
- expect(existsSync(join(projectDir, ".git"))).toBe(false);
248
- expect(existsSync(join(projectDir, "node_modules"))).toBe(false);
249
- expect(existsSync(join(projectDir, ".DS_Store"))).toBe(false);
250
- }, 120000);
251
- });
252
- });
@@ -1,116 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
- import { AuthManager } from "../../src/lib/auth.js";
3
- import { AuthenticationError } from "../../src/types.js";
4
-
5
- describe("AuthManager", () => {
6
- beforeEach(() => {
7
- // Reset AuthManager state
8
- (AuthManager as any).token = null;
9
- (AuthManager as any).authMethod = null;
10
- });
11
-
12
- afterEach(() => {
13
- // Clean up environment variables
14
- process.env.GITHUB_TOKEN = undefined;
15
- process.env.GH_TOKEN = undefined;
16
- });
17
-
18
- describe("isValidTokenFormat", () => {
19
- test("should accept ghp_ tokens", () => {
20
- expect(AuthManager.isValidTokenFormat("ghp_1234567890")).toBe(true);
21
- });
22
-
23
- test("should accept github_pat_ tokens", () => {
24
- expect(AuthManager.isValidTokenFormat("github_pat_1234567890")).toBe(true);
25
- });
26
-
27
- test("should reject invalid token formats", () => {
28
- expect(AuthManager.isValidTokenFormat("invalid_token")).toBe(false);
29
- expect(AuthManager.isValidTokenFormat("gho_1234567890")).toBe(false);
30
- expect(AuthManager.isValidTokenFormat("")).toBe(false);
31
- expect(AuthManager.isValidTokenFormat("token123")).toBe(false);
32
- });
33
-
34
- test("should handle empty and malformed tokens", () => {
35
- expect(AuthManager.isValidTokenFormat("")).toBe(false);
36
- expect(AuthManager.isValidTokenFormat("ghp")).toBe(false);
37
- expect(AuthManager.isValidTokenFormat("github_pat")).toBe(false);
38
- });
39
- });
40
-
41
- describe("getToken - environment variables", () => {
42
- test("should get token from environment (gh-cli, env-var, or cached)", async () => {
43
- // Set environment variable to avoid prompting in CI
44
- process.env.GITHUB_TOKEN = "ghp_test_token_ci_123";
45
-
46
- // This test acknowledges that the token can come from multiple sources
47
- // in the fallback chain: gh-cli > env-var > config > keychain > prompt
48
- const result = await AuthManager.getToken();
49
-
50
- expect(result.token).toBeDefined();
51
- expect(result.token.length).toBeGreaterThan(0);
52
- expect(result.method).toBeDefined();
53
- // Method could be 'gh-cli', 'env-var', 'keychain', or 'prompt'
54
- expect(["gh-cli", "env-var", "keychain", "prompt"]).toContain(result.method);
55
- });
56
-
57
- test("should cache token after first retrieval", async () => {
58
- // Set environment variable to avoid prompting in CI
59
- process.env.GITHUB_TOKEN = "ghp_test_token_cache_456";
60
-
61
- // Clear cache first
62
- (AuthManager as any).token = null;
63
- (AuthManager as any).authMethod = null;
64
-
65
- const result1 = await AuthManager.getToken();
66
- const result2 = await AuthManager.getToken();
67
-
68
- expect(result1.token).toBe(result2.token);
69
- expect(result1.method).toBe(result2.method);
70
- });
71
-
72
- test("should handle GITHUB_TOKEN env var when gh-cli is not available", async () => {
73
- // Note: If gh CLI is installed and authenticated, it will take precedence
74
- // This test documents the expected behavior but may not enforce it
75
- process.env.GITHUB_TOKEN = "ghp_test_token_123";
76
-
77
- // Clear cache
78
- (AuthManager as any).token = null;
79
- (AuthManager as any).authMethod = null;
80
-
81
- const result = await AuthManager.getToken();
82
-
83
- // Token should either be from gh-cli or env-var
84
- expect(result.token).toBeDefined();
85
- expect(["gh-cli", "env-var"]).toContain(result.method);
86
- });
87
-
88
- test("should handle GH_TOKEN env var when GITHUB_TOKEN is not set", async () => {
89
- process.env.GITHUB_TOKEN = undefined;
90
- process.env.GH_TOKEN = "ghp_test_token_456";
91
-
92
- // Clear cache
93
- (AuthManager as any).token = null;
94
- (AuthManager as any).authMethod = null;
95
-
96
- const result = await AuthManager.getToken();
97
-
98
- // Token should either be from gh-cli or env-var
99
- expect(result.token).toBeDefined();
100
- expect(["gh-cli", "env-var"]).toContain(result.method);
101
- });
102
- });
103
-
104
- describe("clearToken", () => {
105
- test("should clear cached token", async () => {
106
- // Set a cached token
107
- (AuthManager as any).token = "test-token";
108
- (AuthManager as any).authMethod = "env-var";
109
-
110
- await AuthManager.clearToken();
111
-
112
- expect((AuthManager as any).token).toBeNull();
113
- expect((AuthManager as any).authMethod).toBeNull();
114
- });
115
- });
116
- });
@@ -1,292 +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 { join } from "node:path";
5
- import { DownloadManager } from "../../src/lib/download.js";
6
- import { DownloadError, ExtractionError } from "../../src/types.js";
7
-
8
- describe("DownloadManager", () => {
9
- let manager: DownloadManager;
10
- let testDir: string;
11
-
12
- beforeEach(async () => {
13
- manager = new DownloadManager();
14
- testDir = join(process.cwd(), "test-temp", `test-${Date.now()}`);
15
- await mkdir(testDir, { recursive: true });
16
- });
17
-
18
- afterEach(async () => {
19
- if (existsSync(testDir)) {
20
- await rm(testDir, { recursive: true, force: true });
21
- }
22
- });
23
-
24
- describe("constructor", () => {
25
- test("should create DownloadManager instance", () => {
26
- expect(manager).toBeInstanceOf(DownloadManager);
27
- });
28
- });
29
-
30
- describe("createTempDir", () => {
31
- test("should create temporary directory", async () => {
32
- const tempDir = await manager.createTempDir();
33
-
34
- expect(tempDir).toBeDefined();
35
- expect(typeof tempDir).toBe("string");
36
- expect(tempDir).toContain("claudekit-");
37
- expect(existsSync(tempDir)).toBe(true);
38
-
39
- // Cleanup
40
- await rm(tempDir, { recursive: true, force: true });
41
- });
42
-
43
- test("should create unique directories", async () => {
44
- const tempDir1 = await manager.createTempDir();
45
-
46
- // Wait 1ms to ensure different timestamps
47
- await new Promise((resolve) => setTimeout(resolve, 1));
48
-
49
- const tempDir2 = await manager.createTempDir();
50
-
51
- expect(tempDir1).not.toBe(tempDir2);
52
-
53
- // Cleanup
54
- await rm(tempDir1, { recursive: true, force: true });
55
- await rm(tempDir2, { recursive: true, force: true });
56
- });
57
- });
58
-
59
- describe("validateExtraction", () => {
60
- test("should throw error for empty directory", async () => {
61
- const emptyDir = join(testDir, "empty");
62
- await mkdir(emptyDir, { recursive: true });
63
-
64
- await expect(manager.validateExtraction(emptyDir)).rejects.toThrow(ExtractionError);
65
- await expect(manager.validateExtraction(emptyDir)).rejects.toThrow(
66
- "Extraction resulted in no files",
67
- );
68
- });
69
-
70
- test("should pass validation for directory with .claude and CLAUDE.md", async () => {
71
- const validDir = join(testDir, "valid");
72
- await mkdir(join(validDir, ".claude"), { recursive: true });
73
- await writeFile(join(validDir, ".claude", "config.json"), "{}");
74
- await writeFile(join(validDir, "CLAUDE.md"), "# Test");
75
-
76
- // Should not throw
77
- await manager.validateExtraction(validDir);
78
- });
79
-
80
- test("should warn but not fail for directory with files but missing critical paths", async () => {
81
- const partialDir = join(testDir, "partial");
82
- await mkdir(partialDir, { recursive: true });
83
- await writeFile(join(partialDir, "README.md"), "# Test");
84
-
85
- // Should not throw but will log warnings
86
- await manager.validateExtraction(partialDir);
87
- });
88
-
89
- test("should throw error for non-existent directory", async () => {
90
- const nonExistentDir = join(testDir, "does-not-exist");
91
-
92
- await expect(manager.validateExtraction(nonExistentDir)).rejects.toThrow();
93
- });
94
- });
95
-
96
- describe("wrapper directory detection", () => {
97
- test("should detect version wrapper with v prefix", () => {
98
- // Access private method via any type casting for testing
99
- const isWrapper = (manager as any).isWrapperDirectory("project-v1.0.0");
100
- expect(isWrapper).toBe(true);
101
- });
102
-
103
- test("should detect version wrapper without v prefix", () => {
104
- const isWrapper = (manager as any).isWrapperDirectory("project-1.0.0");
105
- expect(isWrapper).toBe(true);
106
- });
107
-
108
- test("should detect commit hash wrapper", () => {
109
- const isWrapper = (manager as any).isWrapperDirectory("project-abc1234");
110
- expect(isWrapper).toBe(true);
111
- });
112
-
113
- test("should detect prerelease version wrapper", () => {
114
- const isWrapper = (manager as any).isWrapperDirectory("project-v1.0.0-alpha");
115
- expect(isWrapper).toBe(true);
116
- });
117
-
118
- test("should detect beta version wrapper", () => {
119
- const isWrapper = (manager as any).isWrapperDirectory("project-v2.0.0-beta.1");
120
- expect(isWrapper).toBe(true);
121
- });
122
-
123
- test("should detect rc version wrapper", () => {
124
- const isWrapper = (manager as any).isWrapperDirectory("repo-v3.0.0-rc.5");
125
- expect(isWrapper).toBe(true);
126
- });
127
-
128
- test("should not detect .claude as wrapper", () => {
129
- const isWrapper = (manager as any).isWrapperDirectory(".claude");
130
- expect(isWrapper).toBe(false);
131
- });
132
-
133
- test("should not detect src as wrapper", () => {
134
- const isWrapper = (manager as any).isWrapperDirectory("src");
135
- expect(isWrapper).toBe(false);
136
- });
137
-
138
- test("should not detect docs as wrapper", () => {
139
- const isWrapper = (manager as any).isWrapperDirectory("docs");
140
- expect(isWrapper).toBe(false);
141
- });
142
-
143
- test("should not detect node_modules as wrapper", () => {
144
- const isWrapper = (manager as any).isWrapperDirectory("node_modules");
145
- expect(isWrapper).toBe(false);
146
- });
147
- });
148
-
149
- describe("path safety validation", () => {
150
- test("should allow safe relative paths", () => {
151
- const basePath = join(testDir, "base");
152
- const targetPath = join(testDir, "base", "subdir", "file.txt");
153
- const isSafe = (manager as any).isPathSafe(basePath, targetPath);
154
- expect(isSafe).toBe(true);
155
- });
156
-
157
- test("should block path traversal attempts with ..", () => {
158
- const basePath = join(testDir, "base");
159
- const targetPath = join(testDir, "outside", "file.txt");
160
- const isSafe = (manager as any).isPathSafe(basePath, targetPath);
161
- expect(isSafe).toBe(false);
162
- });
163
-
164
- test("should block absolute path attempts", () => {
165
- const basePath = join(testDir, "base");
166
- const targetPath = "/etc/passwd";
167
- const isSafe = (manager as any).isPathSafe(basePath, targetPath);
168
- expect(isSafe).toBe(false);
169
- });
170
-
171
- test("should allow same directory", () => {
172
- const basePath = join(testDir, "base");
173
- const targetPath = join(testDir, "base");
174
- const isSafe = (manager as any).isPathSafe(basePath, targetPath);
175
- expect(isSafe).toBe(true);
176
- });
177
- });
178
-
179
- describe("archive bomb protection", () => {
180
- test("should track extraction size", () => {
181
- const manager = new DownloadManager();
182
-
183
- // Add some file sizes
184
- (manager as any).checkExtractionSize(100 * 1024 * 1024); // 100MB
185
- expect((manager as any).totalExtractedSize).toBe(100 * 1024 * 1024);
186
-
187
- (manager as any).checkExtractionSize(200 * 1024 * 1024); // 200MB more
188
- expect((manager as any).totalExtractedSize).toBe(300 * 1024 * 1024);
189
- });
190
-
191
- test("should throw error when size exceeds limit", () => {
192
- const manager = new DownloadManager();
193
-
194
- expect(() => {
195
- (manager as any).checkExtractionSize(600 * 1024 * 1024); // 600MB
196
- }).toThrow(ExtractionError);
197
-
198
- expect(() => {
199
- (manager as any).checkExtractionSize(600 * 1024 * 1024); // 600MB
200
- }).toThrow("Archive exceeds maximum extraction size");
201
- });
202
-
203
- test("should allow extraction within limit", () => {
204
- const manager = new DownloadManager();
205
-
206
- expect(() => {
207
- (manager as any).checkExtractionSize(400 * 1024 * 1024); // 400MB
208
- }).not.toThrow();
209
- });
210
-
211
- test("should reset extraction size", () => {
212
- const manager = new DownloadManager();
213
-
214
- (manager as any).checkExtractionSize(300 * 1024 * 1024); // 300MB
215
- expect((manager as any).totalExtractedSize).toBe(300 * 1024 * 1024);
216
-
217
- (manager as any).resetExtractionSize();
218
- expect((manager as any).totalExtractedSize).toBe(0);
219
- });
220
- });
221
-
222
- describe("file exclusion", () => {
223
- test("should exclude .git directory", () => {
224
- const shouldExclude = (manager as any).shouldExclude(".git");
225
- expect(shouldExclude).toBe(true);
226
- });
227
-
228
- test("should exclude .git/** files", () => {
229
- const shouldExclude = (manager as any).shouldExclude(".git/config");
230
- expect(shouldExclude).toBe(true);
231
- });
232
-
233
- test("should exclude node_modules", () => {
234
- const shouldExclude = (manager as any).shouldExclude("node_modules");
235
- expect(shouldExclude).toBe(true);
236
- });
237
-
238
- test("should exclude .DS_Store", () => {
239
- const shouldExclude = (manager as any).shouldExclude(".DS_Store");
240
- expect(shouldExclude).toBe(true);
241
- });
242
-
243
- test("should not exclude normal files", () => {
244
- const shouldExclude = (manager as any).shouldExclude("src/index.ts");
245
- expect(shouldExclude).toBe(false);
246
- });
247
-
248
- test("should not exclude .claude directory", () => {
249
- const shouldExclude = (manager as any).shouldExclude(".claude");
250
- expect(shouldExclude).toBe(false);
251
- });
252
- });
253
-
254
- describe("archive type detection", () => {
255
- test("should detect .tar.gz archive", () => {
256
- const type = (manager as any).detectArchiveType("project-v1.0.0.tar.gz");
257
- expect(type).toBe("tar.gz");
258
- });
259
-
260
- test("should detect .tgz archive", () => {
261
- const type = (manager as any).detectArchiveType("project-v1.0.0.tgz");
262
- expect(type).toBe("tar.gz");
263
- });
264
-
265
- test("should detect .zip archive", () => {
266
- const type = (manager as any).detectArchiveType("project-v1.0.0.zip");
267
- expect(type).toBe("zip");
268
- });
269
-
270
- test("should throw error for unknown archive type", () => {
271
- expect(() => {
272
- (manager as any).detectArchiveType("project-v1.0.0.rar");
273
- }).toThrow(ExtractionError);
274
- });
275
- });
276
-
277
- describe("error classes", () => {
278
- test("DownloadError should store message", () => {
279
- const error = new DownloadError("Download failed");
280
- expect(error.message).toBe("Download failed");
281
- expect(error.code).toBe("DOWNLOAD_ERROR");
282
- expect(error.name).toBe("DownloadError");
283
- });
284
-
285
- test("ExtractionError should store message", () => {
286
- const error = new ExtractionError("Extraction failed");
287
- expect(error.message).toBe("Extraction failed");
288
- expect(error.code).toBe("EXTRACTION_ERROR");
289
- expect(error.name).toBe("ExtractionError");
290
- });
291
- });
292
- });