@keplog/cli 0.2.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +495 -0
  3. package/bin/keplog +2 -0
  4. package/dist/commands/delete.d.ts +3 -0
  5. package/dist/commands/delete.d.ts.map +1 -0
  6. package/dist/commands/delete.js +158 -0
  7. package/dist/commands/delete.js.map +1 -0
  8. package/dist/commands/init.d.ts +3 -0
  9. package/dist/commands/init.d.ts.map +1 -0
  10. package/dist/commands/init.js +131 -0
  11. package/dist/commands/init.js.map +1 -0
  12. package/dist/commands/issues.d.ts +3 -0
  13. package/dist/commands/issues.d.ts.map +1 -0
  14. package/dist/commands/issues.js +543 -0
  15. package/dist/commands/issues.js.map +1 -0
  16. package/dist/commands/list.d.ts +3 -0
  17. package/dist/commands/list.d.ts.map +1 -0
  18. package/dist/commands/list.js +104 -0
  19. package/dist/commands/list.js.map +1 -0
  20. package/dist/commands/releases.d.ts +3 -0
  21. package/dist/commands/releases.d.ts.map +1 -0
  22. package/dist/commands/releases.js +100 -0
  23. package/dist/commands/releases.js.map +1 -0
  24. package/dist/commands/upload.d.ts +3 -0
  25. package/dist/commands/upload.d.ts.map +1 -0
  26. package/dist/commands/upload.js +76 -0
  27. package/dist/commands/upload.js.map +1 -0
  28. package/dist/index.d.ts +3 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +28 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/lib/config.d.ts +57 -0
  33. package/dist/lib/config.d.ts.map +1 -0
  34. package/dist/lib/config.js +155 -0
  35. package/dist/lib/config.js.map +1 -0
  36. package/dist/lib/uploader.d.ts +11 -0
  37. package/dist/lib/uploader.d.ts.map +1 -0
  38. package/dist/lib/uploader.js +171 -0
  39. package/dist/lib/uploader.js.map +1 -0
  40. package/jest.config.js +16 -0
  41. package/package.json +58 -0
  42. package/src/commands/delete.ts +186 -0
  43. package/src/commands/init.ts +137 -0
  44. package/src/commands/issues.ts +695 -0
  45. package/src/commands/list.ts +124 -0
  46. package/src/commands/releases.ts +122 -0
  47. package/src/commands/upload.ts +76 -0
  48. package/src/index.ts +31 -0
  49. package/src/lib/config.ts +138 -0
  50. package/src/lib/uploader.ts +168 -0
  51. package/tests/README.md +380 -0
  52. package/tests/config.test.ts +397 -0
  53. package/tests/uploader.test.ts +524 -0
  54. package/tsconfig.json +20 -0
@@ -0,0 +1,397 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { ConfigManager, KeplogConfig } from '../src/lib/config';
5
+
6
+ describe('ConfigManager', () => {
7
+ let testDir: string;
8
+ let originalCwd: string;
9
+ let originalEnv: NodeJS.ProcessEnv;
10
+
11
+ beforeEach(() => {
12
+ // Create a temporary test directory
13
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'keplog-test-'));
14
+ originalCwd = process.cwd();
15
+ originalEnv = { ...process.env };
16
+
17
+ // Change to test directory
18
+ process.chdir(testDir);
19
+
20
+ // Clear environment variables
21
+ delete process.env.KEPLOG_PROJECT_ID;
22
+ delete process.env.KEPLOG_API_KEY;
23
+ delete process.env.KEPLOG_API_URL;
24
+ });
25
+
26
+ afterEach(() => {
27
+ // Restore original state
28
+ process.chdir(originalCwd);
29
+ process.env = originalEnv;
30
+
31
+ // Clean up test directory
32
+ if (fs.existsSync(testDir)) {
33
+ fs.rmSync(testDir, { recursive: true, force: true });
34
+ }
35
+
36
+ // Clean up global config if created during tests
37
+ const globalConfig = path.join(os.homedir(), '.keplogrc');
38
+ if (fs.existsSync(globalConfig)) {
39
+ fs.unlinkSync(globalConfig);
40
+ }
41
+ });
42
+
43
+ describe('writeLocalConfig', () => {
44
+ it('should write config to local .keplog.json file', () => {
45
+ const config: KeplogConfig = {
46
+ projectId: 'test-project-id',
47
+ apiKey: 'test-api-key',
48
+ apiUrl: 'https://api.keplog.com',
49
+ projectName: 'Test Project',
50
+ };
51
+
52
+ ConfigManager.writeLocalConfig(config, testDir);
53
+
54
+ const configPath = path.join(testDir, '.keplog.json');
55
+ expect(fs.existsSync(configPath)).toBe(true);
56
+
57
+ const savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
58
+ expect(savedConfig).toEqual(config);
59
+ });
60
+
61
+ it('should format config file with proper indentation', () => {
62
+ const config: KeplogConfig = {
63
+ projectId: 'test-project-id',
64
+ apiKey: 'test-api-key',
65
+ };
66
+
67
+ ConfigManager.writeLocalConfig(config, testDir);
68
+
69
+ const content = fs.readFileSync(path.join(testDir, '.keplog.json'), 'utf-8');
70
+ expect(content).toContain(' '); // Should have 2-space indentation
71
+ expect(content.split('\n').length).toBeGreaterThan(1); // Should be multi-line
72
+ });
73
+
74
+ it('should overwrite existing config', () => {
75
+ const firstConfig: KeplogConfig = { projectId: 'first', apiKey: 'key1' };
76
+ const secondConfig: KeplogConfig = { projectId: 'second', apiKey: 'key2' };
77
+
78
+ ConfigManager.writeLocalConfig(firstConfig, testDir);
79
+ ConfigManager.writeLocalConfig(secondConfig, testDir);
80
+
81
+ const savedConfig = JSON.parse(
82
+ fs.readFileSync(path.join(testDir, '.keplog.json'), 'utf-8')
83
+ );
84
+ expect(savedConfig).toEqual(secondConfig);
85
+ });
86
+ });
87
+
88
+ describe('writeGlobalConfig', () => {
89
+ it('should write config to global ~/.keplogrc file', () => {
90
+ const config: KeplogConfig = {
91
+ projectId: 'global-project-id',
92
+ apiKey: 'global-api-key',
93
+ };
94
+
95
+ ConfigManager.writeGlobalConfig(config);
96
+
97
+ const globalConfigPath = path.join(os.homedir(), '.keplogrc');
98
+ expect(fs.existsSync(globalConfigPath)).toBe(true);
99
+
100
+ const savedConfig = JSON.parse(fs.readFileSync(globalConfigPath, 'utf-8'));
101
+ expect(savedConfig).toEqual(config);
102
+ });
103
+ });
104
+
105
+ describe('readConfig', () => {
106
+ it('should read local config when it exists', () => {
107
+ const config: KeplogConfig = {
108
+ projectId: 'local-project',
109
+ apiKey: 'local-key',
110
+ };
111
+
112
+ ConfigManager.writeLocalConfig(config, testDir);
113
+ const readConfig = ConfigManager.readConfig();
114
+
115
+ expect(readConfig).toEqual(config);
116
+ });
117
+
118
+ it('should read global config when local does not exist', () => {
119
+ const config: KeplogConfig = {
120
+ projectId: 'global-project',
121
+ apiKey: 'global-key',
122
+ };
123
+
124
+ ConfigManager.writeGlobalConfig(config);
125
+ const readConfig = ConfigManager.readConfig();
126
+
127
+ expect(readConfig).toEqual(config);
128
+ });
129
+
130
+ it('should prefer local config over global config', () => {
131
+ const localConfig: KeplogConfig = {
132
+ projectId: 'local-project',
133
+ apiKey: 'local-key',
134
+ };
135
+ const globalConfig: KeplogConfig = {
136
+ projectId: 'global-project',
137
+ apiKey: 'global-key',
138
+ };
139
+
140
+ ConfigManager.writeGlobalConfig(globalConfig);
141
+ ConfigManager.writeLocalConfig(localConfig, testDir);
142
+
143
+ const readConfig = ConfigManager.readConfig();
144
+ expect(readConfig).toEqual(localConfig);
145
+ });
146
+
147
+ it('should return empty object when no config exists', () => {
148
+ const readConfig = ConfigManager.readConfig();
149
+ expect(readConfig).toEqual({});
150
+ });
151
+
152
+ it('should handle malformed JSON gracefully', () => {
153
+ const configPath = path.join(testDir, '.keplog.json');
154
+ fs.writeFileSync(configPath, 'invalid json{', 'utf-8');
155
+
156
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
157
+ const readConfig = ConfigManager.readConfig();
158
+
159
+ expect(readConfig).toEqual({});
160
+ expect(consoleSpy).toHaveBeenCalled();
161
+ consoleSpy.mockRestore();
162
+ });
163
+
164
+ it('should walk up directory tree to find local config', () => {
165
+ // Create nested directory structure
166
+ const nestedDir = path.join(testDir, 'a', 'b', 'c');
167
+ fs.mkdirSync(nestedDir, { recursive: true });
168
+
169
+ // Write config in root test dir
170
+ const config: KeplogConfig = { projectId: 'root-project', apiKey: 'root-key' };
171
+ ConfigManager.writeLocalConfig(config, testDir);
172
+
173
+ // Change to nested directory
174
+ process.chdir(nestedDir);
175
+
176
+ // Should find config from parent directory
177
+ const readConfig = ConfigManager.readConfig();
178
+ expect(readConfig).toEqual(config);
179
+ });
180
+ });
181
+
182
+ describe('getConfig', () => {
183
+ it('should merge file config with environment variables', () => {
184
+ const fileConfig: KeplogConfig = {
185
+ projectId: 'file-project',
186
+ projectName: 'File Project',
187
+ };
188
+
189
+ ConfigManager.writeLocalConfig(fileConfig, testDir);
190
+
191
+ process.env.KEPLOG_API_KEY = 'env-api-key';
192
+ process.env.KEPLOG_API_URL = 'https://env-api.keplog.com';
193
+
194
+ const config = ConfigManager.getConfig();
195
+
196
+ expect(config.projectId).toBe('file-project');
197
+ expect(config.apiKey).toBe('env-api-key');
198
+ expect(config.apiUrl).toBe('https://env-api.keplog.com');
199
+ expect(config.projectName).toBe('File Project');
200
+ });
201
+
202
+ it('should prioritize file config over environment variables', () => {
203
+ const fileConfig: KeplogConfig = {
204
+ projectId: 'file-project',
205
+ apiKey: 'file-api-key',
206
+ };
207
+
208
+ ConfigManager.writeLocalConfig(fileConfig, testDir);
209
+
210
+ process.env.KEPLOG_PROJECT_ID = 'env-project';
211
+ process.env.KEPLOG_API_KEY = 'env-api-key';
212
+
213
+ const config = ConfigManager.getConfig();
214
+
215
+ expect(config.projectId).toBe('file-project');
216
+ expect(config.apiKey).toBe('file-api-key');
217
+ });
218
+
219
+ it('should use environment variables when file config is empty', () => {
220
+ process.env.KEPLOG_PROJECT_ID = 'env-project';
221
+ process.env.KEPLOG_API_KEY = 'env-api-key';
222
+ process.env.KEPLOG_API_URL = 'https://env-api.keplog.com';
223
+
224
+ const config = ConfigManager.getConfig();
225
+
226
+ expect(config.projectId).toBe('env-project');
227
+ expect(config.apiKey).toBe('env-api-key');
228
+ expect(config.apiUrl).toBe('https://env-api.keplog.com');
229
+ });
230
+
231
+ it('should use default API URL when not specified', () => {
232
+ const config = ConfigManager.getConfig();
233
+ expect(config.apiUrl).toBe('https://api.keplog.com');
234
+ });
235
+
236
+ it('should override default API URL with environment variable', () => {
237
+ process.env.KEPLOG_API_URL = 'https://custom.keplog.com';
238
+ const config = ConfigManager.getConfig();
239
+ expect(config.apiUrl).toBe('https://custom.keplog.com');
240
+ });
241
+
242
+ it('should override environment API URL with file config', () => {
243
+ process.env.KEPLOG_API_URL = 'https://env.keplog.com';
244
+ const fileConfig: KeplogConfig = {
245
+ apiUrl: 'https://file.keplog.com',
246
+ };
247
+ ConfigManager.writeLocalConfig(fileConfig, testDir);
248
+
249
+ const config = ConfigManager.getConfig();
250
+ expect(config.apiUrl).toBe('https://file.keplog.com');
251
+ });
252
+ });
253
+
254
+ describe('hasLocalConfig', () => {
255
+ it('should return true when local config exists', () => {
256
+ const config: KeplogConfig = { projectId: 'test', apiKey: 'key' };
257
+ ConfigManager.writeLocalConfig(config, testDir);
258
+
259
+ expect(ConfigManager.hasLocalConfig()).toBe(true);
260
+ });
261
+
262
+ it('should return false when local config does not exist', () => {
263
+ expect(ConfigManager.hasLocalConfig()).toBe(false);
264
+ });
265
+
266
+ it('should return true when config exists in parent directory', () => {
267
+ const nestedDir = path.join(testDir, 'nested');
268
+ fs.mkdirSync(nestedDir);
269
+
270
+ const config: KeplogConfig = { projectId: 'test', apiKey: 'key' };
271
+ ConfigManager.writeLocalConfig(config, testDir);
272
+
273
+ process.chdir(nestedDir);
274
+ expect(ConfigManager.hasLocalConfig()).toBe(true);
275
+ });
276
+ });
277
+
278
+ describe('hasGlobalConfig', () => {
279
+ it('should return true when global config exists', () => {
280
+ const config: KeplogConfig = { projectId: 'global', apiKey: 'key' };
281
+ ConfigManager.writeGlobalConfig(config);
282
+
283
+ expect(ConfigManager.hasGlobalConfig()).toBe(true);
284
+ });
285
+
286
+ it('should return false when global config does not exist', () => {
287
+ expect(ConfigManager.hasGlobalConfig()).toBe(false);
288
+ });
289
+ });
290
+
291
+ describe('getLocalConfigPath', () => {
292
+ it('should return config path when it exists', () => {
293
+ const config: KeplogConfig = { projectId: 'test', apiKey: 'key' };
294
+ ConfigManager.writeLocalConfig(config, testDir);
295
+
296
+ const configPath = ConfigManager.getLocalConfigPath();
297
+ const expectedPath = fs.realpathSync(path.join(testDir, '.keplog.json'));
298
+ expect(configPath).toBe(expectedPath);
299
+ });
300
+
301
+ it('should return null when config does not exist', () => {
302
+ const configPath = ConfigManager.getLocalConfigPath();
303
+ expect(configPath).toBeNull();
304
+ });
305
+
306
+ it('should return parent directory config path', () => {
307
+ const nestedDir = path.join(testDir, 'a', 'b');
308
+ fs.mkdirSync(nestedDir, { recursive: true });
309
+
310
+ const config: KeplogConfig = { projectId: 'test', apiKey: 'key' };
311
+ ConfigManager.writeLocalConfig(config, testDir);
312
+
313
+ process.chdir(nestedDir);
314
+ const configPath = ConfigManager.getLocalConfigPath();
315
+ const expectedPath = fs.realpathSync(path.join(testDir, '.keplog.json'));
316
+ expect(configPath).toBe(expectedPath);
317
+ });
318
+ });
319
+
320
+ describe('deleteLocalConfig', () => {
321
+ it('should delete local config file', () => {
322
+ const config: KeplogConfig = { projectId: 'test', apiKey: 'key' };
323
+ ConfigManager.writeLocalConfig(config, testDir);
324
+
325
+ const configPath = path.join(testDir, '.keplog.json');
326
+ expect(fs.existsSync(configPath)).toBe(true);
327
+
328
+ ConfigManager.deleteLocalConfig(testDir);
329
+ expect(fs.existsSync(configPath)).toBe(false);
330
+ });
331
+
332
+ it('should not throw when config does not exist', () => {
333
+ expect(() => ConfigManager.deleteLocalConfig(testDir)).not.toThrow();
334
+ });
335
+ });
336
+
337
+ describe('deleteGlobalConfig', () => {
338
+ it('should delete global config file', () => {
339
+ const config: KeplogConfig = { projectId: 'test', apiKey: 'key' };
340
+ ConfigManager.writeGlobalConfig(config);
341
+
342
+ const globalPath = path.join(os.homedir(), '.keplogrc');
343
+ expect(fs.existsSync(globalPath)).toBe(true);
344
+
345
+ ConfigManager.deleteGlobalConfig();
346
+ expect(fs.existsSync(globalPath)).toBe(false);
347
+ });
348
+
349
+ it('should not throw when global config does not exist', () => {
350
+ expect(() => ConfigManager.deleteGlobalConfig()).not.toThrow();
351
+ });
352
+ });
353
+
354
+ describe('edge cases', () => {
355
+ it('should handle partial config objects', () => {
356
+ const partialConfig: KeplogConfig = {
357
+ projectId: 'test-id',
358
+ // apiKey is missing
359
+ };
360
+
361
+ ConfigManager.writeLocalConfig(partialConfig, testDir);
362
+ const readConfig = ConfigManager.readConfig();
363
+
364
+ expect(readConfig.projectId).toBe('test-id');
365
+ expect(readConfig.apiKey).toBeUndefined();
366
+ });
367
+
368
+ it('should handle config with only optional fields', () => {
369
+ const config: KeplogConfig = {
370
+ projectName: 'Optional Name',
371
+ };
372
+
373
+ ConfigManager.writeLocalConfig(config, testDir);
374
+ const fullConfig = ConfigManager.getConfig();
375
+
376
+ expect(fullConfig.projectName).toBe('Optional Name');
377
+ expect(fullConfig.apiUrl).toBe('https://api.keplog.com'); // Default
378
+ });
379
+
380
+ it('should handle empty string values', () => {
381
+ const config: KeplogConfig = {
382
+ projectId: '',
383
+ apiKey: '',
384
+ };
385
+
386
+ ConfigManager.writeLocalConfig(config, testDir);
387
+ process.env.KEPLOG_PROJECT_ID = 'env-id';
388
+ process.env.KEPLOG_API_KEY = 'env-key';
389
+
390
+ const fullConfig = ConfigManager.getConfig();
391
+
392
+ // Empty strings are falsy, so env vars should be used
393
+ expect(fullConfig.projectId).toBe('env-id');
394
+ expect(fullConfig.apiKey).toBe('env-key');
395
+ });
396
+ });
397
+ });