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.
- package/bin/ck-darwin-arm64 +0 -0
- package/bin/ck-darwin-x64 +0 -0
- package/bin/ck-linux-x64 +0 -0
- package/bin/ck-win32-x64.exe +0 -0
- package/package.json +8 -2
- package/scripts/postinstall.js +74 -0
- package/.github/workflows/ci.yml +0 -45
- package/.github/workflows/claude-code-review.yml +0 -57
- package/.github/workflows/claude.yml +0 -50
- package/.github/workflows/release.yml +0 -102
- package/.releaserc.json +0 -17
- package/.repomixignore +0 -15
- package/AGENTS.md +0 -217
- package/CHANGELOG.md +0 -95
- package/CLAUDE.md +0 -34
- package/biome.json +0 -28
- package/bun.lock +0 -863
- package/dist/index.js +0 -22511
- package/src/commands/new.ts +0 -185
- package/src/commands/update.ts +0 -174
- package/src/commands/version.ts +0 -135
- package/src/index.ts +0 -102
- package/src/lib/auth.ts +0 -157
- package/src/lib/download.ts +0 -689
- package/src/lib/github.ts +0 -230
- package/src/lib/merge.ts +0 -119
- package/src/lib/prompts.ts +0 -114
- package/src/types.ts +0 -178
- package/src/utils/config.ts +0 -87
- package/src/utils/file-scanner.ts +0 -134
- package/src/utils/logger.ts +0 -124
- package/src/utils/safe-prompts.ts +0 -44
- package/src/utils/safe-spinner.ts +0 -38
- package/src/version.json +0 -3
- package/tests/commands/version.test.ts +0 -297
- package/tests/integration/cli.test.ts +0 -252
- package/tests/lib/auth.test.ts +0 -116
- package/tests/lib/download.test.ts +0 -292
- package/tests/lib/github-download-priority.test.ts +0 -432
- package/tests/lib/github.test.ts +0 -52
- package/tests/lib/merge.test.ts +0 -267
- package/tests/lib/prompts.test.ts +0 -66
- package/tests/types.test.ts +0 -337
- package/tests/utils/config.test.ts +0 -263
- package/tests/utils/file-scanner.test.ts +0 -202
- package/tests/utils/logger.test.ts +0 -239
- package/tsconfig.json +0 -30
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
import type { GitHubRelease } from "../../src/types.js";
|
|
3
|
-
import { AVAILABLE_KITS, VersionCommandOptionsSchema } from "../../src/types.js";
|
|
4
|
-
|
|
5
|
-
describe("Version Command", () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
// Set environment variable to avoid auth prompts during tests
|
|
8
|
-
process.env.GITHUB_TOKEN = "ghp_test_token_for_testing";
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
describe("VersionCommandOptionsSchema", () => {
|
|
12
|
-
test("should accept valid options with kit filter", () => {
|
|
13
|
-
const options = { kit: "engineer" as const };
|
|
14
|
-
const result = VersionCommandOptionsSchema.parse(options);
|
|
15
|
-
expect(result.kit).toBe("engineer");
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("should accept valid options with limit", () => {
|
|
19
|
-
const options = { limit: 10 };
|
|
20
|
-
const result = VersionCommandOptionsSchema.parse(options);
|
|
21
|
-
expect(result.limit).toBe(10);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("should accept valid options with all flag", () => {
|
|
25
|
-
const options = { all: true };
|
|
26
|
-
const result = VersionCommandOptionsSchema.parse(options);
|
|
27
|
-
expect(result.all).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("should accept all options combined", () => {
|
|
31
|
-
const options = { kit: "marketing" as const, limit: 20, all: true };
|
|
32
|
-
const result = VersionCommandOptionsSchema.parse(options);
|
|
33
|
-
expect(result.kit).toBe("marketing");
|
|
34
|
-
expect(result.limit).toBe(20);
|
|
35
|
-
expect(result.all).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("should accept empty options", () => {
|
|
39
|
-
const options = {};
|
|
40
|
-
const result = VersionCommandOptionsSchema.parse(options);
|
|
41
|
-
expect(result.kit).toBeUndefined();
|
|
42
|
-
expect(result.limit).toBeUndefined();
|
|
43
|
-
expect(result.all).toBeUndefined();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("should reject invalid kit type", () => {
|
|
47
|
-
const options = { kit: "invalid" };
|
|
48
|
-
expect(() => VersionCommandOptionsSchema.parse(options)).toThrow();
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe("Kit Configuration", () => {
|
|
53
|
-
test("should have engineer kit configured", () => {
|
|
54
|
-
const engineerKit = AVAILABLE_KITS.engineer;
|
|
55
|
-
expect(engineerKit.name).toBe("ClaudeKit Engineer");
|
|
56
|
-
expect(engineerKit.repo).toBe("claudekit-engineer");
|
|
57
|
-
expect(engineerKit.owner).toBe("claudekit");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("should have marketing kit configured", () => {
|
|
61
|
-
const marketingKit = AVAILABLE_KITS.marketing;
|
|
62
|
-
expect(marketingKit.name).toBe("ClaudeKit Marketing");
|
|
63
|
-
expect(marketingKit.repo).toBe("claudekit-marketing");
|
|
64
|
-
expect(marketingKit.owner).toBe("claudekit");
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe("Release Data Handling", () => {
|
|
69
|
-
test("should handle release with all fields", () => {
|
|
70
|
-
const release: GitHubRelease = {
|
|
71
|
-
id: 1,
|
|
72
|
-
tag_name: "v1.0.0",
|
|
73
|
-
name: "Release 1.0.0",
|
|
74
|
-
draft: false,
|
|
75
|
-
prerelease: false,
|
|
76
|
-
assets: [],
|
|
77
|
-
published_at: "2024-01-01T00:00:00Z",
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
expect(release.tag_name).toBe("v1.0.0");
|
|
81
|
-
expect(release.name).toBe("Release 1.0.0");
|
|
82
|
-
expect(release.draft).toBe(false);
|
|
83
|
-
expect(release.prerelease).toBe(false);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("should handle release without published_at", () => {
|
|
87
|
-
const release: GitHubRelease = {
|
|
88
|
-
id: 1,
|
|
89
|
-
tag_name: "v1.0.0",
|
|
90
|
-
name: "Release 1.0.0",
|
|
91
|
-
draft: false,
|
|
92
|
-
prerelease: false,
|
|
93
|
-
assets: [],
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
expect(release.published_at).toBeUndefined();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("should handle draft release", () => {
|
|
100
|
-
const release: GitHubRelease = {
|
|
101
|
-
id: 1,
|
|
102
|
-
tag_name: "v1.0.0-draft",
|
|
103
|
-
name: "Draft Release",
|
|
104
|
-
draft: true,
|
|
105
|
-
prerelease: false,
|
|
106
|
-
assets: [],
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
expect(release.draft).toBe(true);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("should handle prerelease", () => {
|
|
113
|
-
const release: GitHubRelease = {
|
|
114
|
-
id: 1,
|
|
115
|
-
tag_name: "v1.0.0-beta.1",
|
|
116
|
-
name: "Beta Release",
|
|
117
|
-
draft: false,
|
|
118
|
-
prerelease: true,
|
|
119
|
-
assets: [],
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
expect(release.prerelease).toBe(true);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe("Date Formatting", () => {
|
|
127
|
-
test("should format recent dates correctly", () => {
|
|
128
|
-
const now = new Date();
|
|
129
|
-
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
130
|
-
const dateString = yesterday.toISOString();
|
|
131
|
-
|
|
132
|
-
// The actual formatting logic is in the command file
|
|
133
|
-
// We just verify the date string is valid
|
|
134
|
-
expect(dateString).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("should handle undefined date", () => {
|
|
138
|
-
const dateString = undefined;
|
|
139
|
-
expect(dateString).toBeUndefined();
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
describe("Release Filtering", () => {
|
|
144
|
-
const releases: GitHubRelease[] = [
|
|
145
|
-
{
|
|
146
|
-
id: 1,
|
|
147
|
-
tag_name: "v1.0.0",
|
|
148
|
-
name: "Stable Release",
|
|
149
|
-
draft: false,
|
|
150
|
-
prerelease: false,
|
|
151
|
-
assets: [],
|
|
152
|
-
published_at: "2024-01-01T00:00:00Z",
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: 2,
|
|
156
|
-
tag_name: "v1.1.0-beta.1",
|
|
157
|
-
name: "Beta Release",
|
|
158
|
-
draft: false,
|
|
159
|
-
prerelease: true,
|
|
160
|
-
assets: [],
|
|
161
|
-
published_at: "2024-01-02T00:00:00Z",
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
id: 3,
|
|
165
|
-
tag_name: "v1.2.0-draft",
|
|
166
|
-
name: "Draft Release",
|
|
167
|
-
draft: true,
|
|
168
|
-
prerelease: false,
|
|
169
|
-
assets: [],
|
|
170
|
-
published_at: "2024-01-03T00:00:00Z",
|
|
171
|
-
},
|
|
172
|
-
];
|
|
173
|
-
|
|
174
|
-
test("should filter out drafts by default", () => {
|
|
175
|
-
const stable = releases.filter((r) => !r.draft && !r.prerelease);
|
|
176
|
-
expect(stable).toHaveLength(1);
|
|
177
|
-
expect(stable[0].tag_name).toBe("v1.0.0");
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("should filter out prereleases by default", () => {
|
|
181
|
-
const stable = releases.filter((r) => !r.draft && !r.prerelease);
|
|
182
|
-
expect(stable.every((r) => !r.prerelease)).toBe(true);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("should include all when --all flag is used", () => {
|
|
186
|
-
const all = releases; // No filtering when --all is true
|
|
187
|
-
expect(all).toHaveLength(3);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("should handle empty release list", () => {
|
|
191
|
-
const empty: GitHubRelease[] = [];
|
|
192
|
-
expect(empty).toHaveLength(0);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
describe("Command Options Validation", () => {
|
|
197
|
-
test("should validate limit as number", () => {
|
|
198
|
-
const validLimit = { limit: 50 };
|
|
199
|
-
const result = VersionCommandOptionsSchema.parse(validLimit);
|
|
200
|
-
expect(result.limit).toBe(50);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("should validate all as boolean", () => {
|
|
204
|
-
const validAll = { all: false };
|
|
205
|
-
const result = VersionCommandOptionsSchema.parse(validAll);
|
|
206
|
-
expect(result.all).toBe(false);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test("should handle optional fields", () => {
|
|
210
|
-
const minimal = {};
|
|
211
|
-
const result = VersionCommandOptionsSchema.parse(minimal);
|
|
212
|
-
expect(result).toBeDefined();
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe("Error Scenarios", () => {
|
|
217
|
-
test("should handle invalid option types", () => {
|
|
218
|
-
const invalidLimit = { limit: "not-a-number" };
|
|
219
|
-
expect(() => VersionCommandOptionsSchema.parse(invalidLimit)).toThrow();
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test("should handle invalid all flag type", () => {
|
|
223
|
-
const invalidAll = { all: "not-a-boolean" };
|
|
224
|
-
expect(() => VersionCommandOptionsSchema.parse(invalidAll)).toThrow();
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
describe("Assets Handling", () => {
|
|
229
|
-
test("should handle release with multiple assets", () => {
|
|
230
|
-
const release: GitHubRelease = {
|
|
231
|
-
id: 1,
|
|
232
|
-
tag_name: "v1.0.0",
|
|
233
|
-
name: "Release",
|
|
234
|
-
draft: false,
|
|
235
|
-
prerelease: false,
|
|
236
|
-
assets: [
|
|
237
|
-
{
|
|
238
|
-
id: 1,
|
|
239
|
-
name: "package.tar.gz",
|
|
240
|
-
browser_download_url: "https://example.com/package.tar.gz",
|
|
241
|
-
size: 1024,
|
|
242
|
-
content_type: "application/gzip",
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
id: 2,
|
|
246
|
-
name: "package.zip",
|
|
247
|
-
browser_download_url: "https://example.com/package.zip",
|
|
248
|
-
size: 2048,
|
|
249
|
-
content_type: "application/zip",
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
expect(release.assets).toHaveLength(2);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test("should handle release with no assets", () => {
|
|
258
|
-
const release: GitHubRelease = {
|
|
259
|
-
id: 1,
|
|
260
|
-
tag_name: "v1.0.0",
|
|
261
|
-
name: "Release",
|
|
262
|
-
draft: false,
|
|
263
|
-
prerelease: false,
|
|
264
|
-
assets: [],
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
expect(release.assets).toHaveLength(0);
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
describe("Integration Scenarios", () => {
|
|
272
|
-
test("should handle both kits in parallel", () => {
|
|
273
|
-
const kits = Object.keys(AVAILABLE_KITS);
|
|
274
|
-
expect(kits).toContain("engineer");
|
|
275
|
-
expect(kits).toContain("marketing");
|
|
276
|
-
expect(kits).toHaveLength(2);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test("should support filtering by engineer kit", () => {
|
|
280
|
-
const options = { kit: "engineer" as const };
|
|
281
|
-
const result = VersionCommandOptionsSchema.parse(options);
|
|
282
|
-
expect(result.kit).toBe("engineer");
|
|
283
|
-
|
|
284
|
-
const kitConfig = AVAILABLE_KITS[result.kit];
|
|
285
|
-
expect(kitConfig.repo).toBe("claudekit-engineer");
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("should support filtering by marketing kit", () => {
|
|
289
|
-
const options = { kit: "marketing" as const };
|
|
290
|
-
const result = VersionCommandOptionsSchema.parse(options);
|
|
291
|
-
expect(result.kit).toBe("marketing");
|
|
292
|
-
|
|
293
|
-
const kitConfig = AVAILABLE_KITS[result.kit];
|
|
294
|
-
expect(kitConfig.repo).toBe("claudekit-marketing");
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
});
|
|
@@ -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
|
-
});
|
package/tests/lib/auth.test.ts
DELETED
|
@@ -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
|
-
});
|