spck 0.3.1

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 (155) hide show
  1. package/.oxlintrc.json +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +631 -0
  4. package/bin/cli.js +20 -0
  5. package/bin/validate-cwd.js +41 -0
  6. package/dist/config/__tests__/config.test.d.ts +2 -0
  7. package/dist/config/__tests__/config.test.js +262 -0
  8. package/dist/config/__tests__/credentials.test.d.ts +2 -0
  9. package/dist/config/__tests__/credentials.test.js +360 -0
  10. package/dist/config/config.d.ts +33 -0
  11. package/dist/config/config.js +185 -0
  12. package/dist/config/credentials.d.ts +75 -0
  13. package/dist/config/credentials.js +259 -0
  14. package/dist/config/server-selection.d.ts +40 -0
  15. package/dist/config/server-selection.js +130 -0
  16. package/dist/connection/__tests__/firebase-auth.test.d.ts +2 -0
  17. package/dist/connection/__tests__/firebase-auth.test.js +96 -0
  18. package/dist/connection/__tests__/hmac.test.d.ts +2 -0
  19. package/dist/connection/__tests__/hmac.test.js +372 -0
  20. package/dist/connection/auth.d.ts +13 -0
  21. package/dist/connection/auth.js +91 -0
  22. package/dist/connection/firebase-auth.d.ts +40 -0
  23. package/dist/connection/firebase-auth.js +429 -0
  24. package/dist/connection/hmac.d.ts +24 -0
  25. package/dist/connection/hmac.js +109 -0
  26. package/dist/i18n/index.d.ts +25 -0
  27. package/dist/i18n/index.js +101 -0
  28. package/dist/i18n/locales/en.json +313 -0
  29. package/dist/i18n/locales/es.json +302 -0
  30. package/dist/i18n/locales/fr.json +302 -0
  31. package/dist/i18n/locales/id.json +302 -0
  32. package/dist/i18n/locales/ja.json +302 -0
  33. package/dist/i18n/locales/ko.json +302 -0
  34. package/dist/i18n/locales/locales/en.json +309 -0
  35. package/dist/i18n/locales/locales/es.json +302 -0
  36. package/dist/i18n/locales/locales/fr.json +302 -0
  37. package/dist/i18n/locales/locales/id.json +302 -0
  38. package/dist/i18n/locales/locales/ja.json +302 -0
  39. package/dist/i18n/locales/locales/ko.json +302 -0
  40. package/dist/i18n/locales/locales/pt.json +302 -0
  41. package/dist/i18n/locales/locales/zh-Hans.json +302 -0
  42. package/dist/i18n/locales/pt.json +302 -0
  43. package/dist/i18n/locales/zh-Hans.json +302 -0
  44. package/dist/index.d.ts +25 -0
  45. package/dist/index.js +493 -0
  46. package/dist/proxy/ProxyClient.d.ts +125 -0
  47. package/dist/proxy/ProxyClient.js +781 -0
  48. package/dist/proxy/ProxySocketWrapper.d.ts +43 -0
  49. package/dist/proxy/ProxySocketWrapper.js +98 -0
  50. package/dist/proxy/__tests__/ProxyClient.test.d.ts +2 -0
  51. package/dist/proxy/__tests__/ProxyClient.test.js +445 -0
  52. package/dist/proxy/__tests__/ProxySocketWrapper.test.d.ts +2 -0
  53. package/dist/proxy/__tests__/ProxySocketWrapper.test.js +190 -0
  54. package/dist/proxy/__tests__/handshake-validation.test.d.ts +2 -0
  55. package/dist/proxy/__tests__/handshake-validation.test.js +282 -0
  56. package/dist/proxy/__tests__/token-refresh-race.test.d.ts +14 -0
  57. package/dist/proxy/__tests__/token-refresh-race.test.js +173 -0
  58. package/dist/proxy/chunking.d.ts +53 -0
  59. package/dist/proxy/chunking.js +127 -0
  60. package/dist/proxy/handshake-validation.d.ts +21 -0
  61. package/dist/proxy/handshake-validation.js +49 -0
  62. package/dist/rpc/__tests__/router.test.d.ts +2 -0
  63. package/dist/rpc/__tests__/router.test.js +262 -0
  64. package/dist/rpc/router.d.ts +37 -0
  65. package/dist/rpc/router.js +132 -0
  66. package/dist/services/BrowserProxyService.d.ts +13 -0
  67. package/dist/services/BrowserProxyService.js +139 -0
  68. package/dist/services/FilesystemService.d.ts +99 -0
  69. package/dist/services/FilesystemService.js +742 -0
  70. package/dist/services/GitService.d.ts +243 -0
  71. package/dist/services/GitService.js +1439 -0
  72. package/dist/services/SearchService.d.ts +93 -0
  73. package/dist/services/SearchService.js +670 -0
  74. package/dist/services/TerminalService.d.ts +62 -0
  75. package/dist/services/TerminalService.js +337 -0
  76. package/dist/services/__tests__/BrowserProxyService.test.d.ts +2 -0
  77. package/dist/services/__tests__/BrowserProxyService.test.js +145 -0
  78. package/dist/services/__tests__/FilesystemService.test.d.ts +2 -0
  79. package/dist/services/__tests__/FilesystemService.test.js +609 -0
  80. package/dist/services/__tests__/GitService.test.d.ts +2 -0
  81. package/dist/services/__tests__/GitService.test.js +953 -0
  82. package/dist/services/__tests__/SearchService.test.d.ts +2 -0
  83. package/dist/services/__tests__/SearchService.test.js +384 -0
  84. package/dist/services/__tests__/TerminalService.test.d.ts +2 -0
  85. package/dist/services/__tests__/TerminalService.test.js +513 -0
  86. package/dist/setup/wizard.d.ts +10 -0
  87. package/dist/setup/wizard.js +172 -0
  88. package/dist/types.d.ts +196 -0
  89. package/dist/types.js +44 -0
  90. package/dist/utils/__tests__/gitignore.test.d.ts +2 -0
  91. package/dist/utils/__tests__/gitignore.test.js +127 -0
  92. package/dist/utils/gitignore.d.ts +24 -0
  93. package/dist/utils/gitignore.js +77 -0
  94. package/dist/utils/logger.d.ts +96 -0
  95. package/dist/utils/logger.js +456 -0
  96. package/dist/utils/project-dir.d.ts +51 -0
  97. package/dist/utils/project-dir.js +191 -0
  98. package/dist/utils/ripgrep.d.ts +34 -0
  99. package/dist/utils/ripgrep.js +148 -0
  100. package/dist/utils/tool-detection.d.ts +17 -0
  101. package/dist/utils/tool-detection.js +126 -0
  102. package/dist/watcher/FileWatcher.d.ts +10 -0
  103. package/dist/watcher/FileWatcher.js +42 -0
  104. package/package.json +70 -0
  105. package/src/config/__tests__/config.test.ts +318 -0
  106. package/src/config/__tests__/credentials.test.ts +494 -0
  107. package/src/config/config.ts +206 -0
  108. package/src/config/credentials.ts +302 -0
  109. package/src/config/server-selection.ts +150 -0
  110. package/src/connection/__tests__/firebase-auth.test.ts +121 -0
  111. package/src/connection/__tests__/hmac.test.ts +509 -0
  112. package/src/connection/auth.ts +140 -0
  113. package/src/connection/firebase-auth.ts +504 -0
  114. package/src/connection/hmac.ts +139 -0
  115. package/src/i18n/index.ts +119 -0
  116. package/src/i18n/locales/en.json +313 -0
  117. package/src/i18n/locales/es.json +302 -0
  118. package/src/i18n/locales/fr.json +302 -0
  119. package/src/i18n/locales/id.json +302 -0
  120. package/src/i18n/locales/ja.json +302 -0
  121. package/src/i18n/locales/ko.json +302 -0
  122. package/src/i18n/locales/pt.json +302 -0
  123. package/src/i18n/locales/zh-Hans.json +302 -0
  124. package/src/index.ts +542 -0
  125. package/src/proxy/ProxyClient.ts +968 -0
  126. package/src/proxy/ProxySocketWrapper.ts +113 -0
  127. package/src/proxy/__tests__/ProxyClient.test.ts +575 -0
  128. package/src/proxy/__tests__/ProxySocketWrapper.test.ts +251 -0
  129. package/src/proxy/__tests__/handshake-validation.test.ts +367 -0
  130. package/src/proxy/chunking.ts +162 -0
  131. package/src/proxy/handshake-validation.ts +64 -0
  132. package/src/rpc/__tests__/router.test.ts +400 -0
  133. package/src/rpc/router.ts +183 -0
  134. package/src/services/BrowserProxyService.ts +179 -0
  135. package/src/services/FilesystemService.ts +841 -0
  136. package/src/services/GitService.ts +1639 -0
  137. package/src/services/SearchService.ts +809 -0
  138. package/src/services/TerminalService.ts +413 -0
  139. package/src/services/__tests__/BrowserProxyService.test.ts +155 -0
  140. package/src/services/__tests__/FilesystemService.test.ts +1002 -0
  141. package/src/services/__tests__/GitService.test.ts +1552 -0
  142. package/src/services/__tests__/SearchService.test.ts +484 -0
  143. package/src/services/__tests__/TerminalService.test.ts +702 -0
  144. package/src/setup/wizard.ts +242 -0
  145. package/src/types/fossil-delta.d.ts +4 -0
  146. package/src/types.ts +287 -0
  147. package/src/utils/__tests__/gitignore.test.ts +174 -0
  148. package/src/utils/gitignore.ts +91 -0
  149. package/src/utils/logger.ts +578 -0
  150. package/src/utils/project-dir.ts +218 -0
  151. package/src/utils/ripgrep.ts +180 -0
  152. package/src/utils/tool-detection.ts +141 -0
  153. package/src/watcher/FileWatcher.ts +53 -0
  154. package/tsconfig.json +24 -0
  155. package/vitest.config.ts +19 -0
@@ -0,0 +1,318 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi, type Mocked } from 'vitest';
2
+ /**
3
+ * Unit tests for config management
4
+ */
5
+
6
+ import * as fs from 'fs';
7
+ import {
8
+ loadConfig,
9
+ saveConfig,
10
+ ConfigNotFoundError,
11
+ createDefaultConfig,
12
+ parseFileSize,
13
+ } from '../config.js';
14
+ import { ServerConfig } from '../../types.js';
15
+
16
+ // Mock fs module
17
+ vi.mock('fs');
18
+
19
+ const mockFs = fs as Mocked<typeof fs>;
20
+
21
+ describe('config', () => {
22
+ const mockCwd = '/mock/project';
23
+
24
+ beforeEach(() => {
25
+ vi.clearAllMocks();
26
+ vi.spyOn(process, 'cwd').mockReturnValue(mockCwd);
27
+ });
28
+
29
+ afterEach(() => {
30
+ vi.restoreAllMocks();
31
+ });
32
+
33
+ describe('loadConfig()', () => {
34
+ const validConfig: ServerConfig = {
35
+ version: 1,
36
+ root: '/mock/root',
37
+ terminal: {
38
+ enabled: true,
39
+ maxBufferedLines: 5000,
40
+ maxTerminals: 10,
41
+ },
42
+ security: {
43
+ userAuthenticationEnabled: false,
44
+ },
45
+ browserProxy: {
46
+ enabled: true
47
+ },
48
+ filesystem: {
49
+ maxFileSize: '100MB',
50
+ watchIgnorePatterns: ['node_modules/**'],
51
+ },
52
+ };
53
+
54
+ beforeEach(() => {
55
+ // Mock root directory exists for validation
56
+ mockFs.existsSync.mockImplementation((path: any) => {
57
+ if (path === '/mock/root') return true;
58
+ return false;
59
+ });
60
+ });
61
+
62
+ it('should throw ConfigNotFoundError if file does not exist', () => {
63
+ mockFs.existsSync.mockReturnValue(false);
64
+
65
+ expect(() => loadConfig()).toThrow(ConfigNotFoundError);
66
+ });
67
+
68
+ it('should load and return valid config', () => {
69
+ mockFs.existsSync.mockImplementation((_path: any) => {
70
+ // Config file and root directory exist
71
+ return true;
72
+ });
73
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(validConfig));
74
+
75
+ const result = loadConfig();
76
+
77
+ expect(result).toEqual(validConfig);
78
+ });
79
+
80
+ it('should throw CORRUPTED error for invalid JSON', () => {
81
+ mockFs.existsSync.mockReturnValue(true);
82
+ mockFs.readFileSync.mockReturnValue('not valid json{');
83
+
84
+ expect(() => {
85
+ try {
86
+ loadConfig();
87
+ } catch (error: any) {
88
+ expect(error.code).toBe('CORRUPTED');
89
+ throw error;
90
+ }
91
+ }).toThrow();
92
+ });
93
+
94
+ it('should throw CORRUPTED error for invalid version', () => {
95
+ const invalidConfig = {
96
+ ...validConfig,
97
+ version: 999,
98
+ };
99
+
100
+ mockFs.existsSync.mockReturnValue(true);
101
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
102
+
103
+ expect(() => {
104
+ try {
105
+ loadConfig();
106
+ } catch (error: any) {
107
+ expect(error.code).toBe('CORRUPTED');
108
+ throw error;
109
+ }
110
+ }).toThrow();
111
+ });
112
+
113
+ it('should throw CORRUPTED error for missing root', () => {
114
+ const invalidConfig = {
115
+ ...validConfig,
116
+ root: undefined,
117
+ };
118
+
119
+ mockFs.existsSync.mockReturnValue(true);
120
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
121
+
122
+ expect(() => {
123
+ try {
124
+ loadConfig();
125
+ } catch (error: any) {
126
+ expect(error.code).toBe('CORRUPTED');
127
+ throw error;
128
+ }
129
+ }).toThrow();
130
+ });
131
+
132
+ it('should throw error if root directory does not exist', () => {
133
+ mockFs.existsSync.mockImplementation((path: any) => {
134
+ // Config exists, but root doesn't
135
+ if (path.includes('.spck-editor')) return true;
136
+ return false;
137
+ });
138
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(validConfig));
139
+
140
+ expect(() => loadConfig()).toThrow('Root directory does not exist');
141
+ });
142
+
143
+ it('should use custom config path when provided', () => {
144
+ const customPath = 'custom/config.json';
145
+ mockFs.existsSync.mockReturnValue(false);
146
+
147
+ expect(() => loadConfig(customPath)).toThrow(ConfigNotFoundError);
148
+ });
149
+ });
150
+
151
+ describe('saveConfig()', () => {
152
+ const validConfig: ServerConfig = {
153
+ version: 1,
154
+ root: mockCwd,
155
+ terminal: {
156
+ enabled: true,
157
+ maxBufferedLines: 5000,
158
+ maxTerminals: 10,
159
+ },
160
+ security: {
161
+ userAuthenticationEnabled: false,
162
+ },
163
+ filesystem: {
164
+ maxFileSize: '100MB',
165
+ watchIgnorePatterns: [],
166
+ },
167
+ };
168
+
169
+ beforeEach(() => {
170
+ // Mock root directory exists for validation
171
+ mockFs.existsSync.mockImplementation((path: any) => {
172
+ if (path === mockCwd) return true;
173
+ return false;
174
+ });
175
+ mockFs.mkdirSync.mockReturnValue(undefined);
176
+ mockFs.writeFileSync.mockReturnValue(undefined);
177
+ });
178
+
179
+ it('should create directory if it does not exist', () => {
180
+ mockFs.existsSync.mockImplementation((path: any) => {
181
+ // Root exists, but .spck-editor doesn't
182
+ if (path === mockCwd) return true;
183
+ return false;
184
+ });
185
+
186
+ saveConfig(validConfig);
187
+
188
+ expect(mockFs.mkdirSync).toHaveBeenCalled();
189
+ });
190
+
191
+ it('should write config file', () => {
192
+ mockFs.existsSync.mockReturnValue(true);
193
+
194
+ saveConfig(validConfig);
195
+
196
+ expect(mockFs.writeFileSync).toHaveBeenCalledWith(
197
+ expect.stringContaining('spck-cli.config.json'),
198
+ JSON.stringify(validConfig, null, 2),
199
+ 'utf8'
200
+ );
201
+ });
202
+
203
+ it('should validate config before saving', () => {
204
+ const invalidConfig = {
205
+ ...validConfig,
206
+ version: 999, // Invalid version
207
+ } as any;
208
+
209
+ expect(() => saveConfig(invalidConfig)).toThrow('Invalid config version');
210
+ });
211
+
212
+ it('should throw error with operation context on write failure', () => {
213
+ const mockError: any = new Error('ENOSPC');
214
+ mockError.code = 'ENOSPC';
215
+ mockFs.writeFileSync.mockImplementation(() => {
216
+ throw mockError;
217
+ });
218
+ mockFs.existsSync.mockReturnValue(true);
219
+
220
+ expect(() => {
221
+ try {
222
+ saveConfig(validConfig);
223
+ } catch (error: any) {
224
+ expect(error.operation).toBe('save config');
225
+ throw error;
226
+ }
227
+ }).toThrow();
228
+ });
229
+ });
230
+
231
+ describe('createDefaultConfig()', () => {
232
+ it('should create config with default values', () => {
233
+ const config = createDefaultConfig();
234
+
235
+ expect(config.version).toBe(1);
236
+ expect(config.root).toBe(mockCwd);
237
+ expect(config.terminal.enabled).toBe(true);
238
+ expect(config.security.userAuthenticationEnabled).toBe(false);
239
+ });
240
+
241
+ it('should merge overrides with defaults', () => {
242
+ const overrides = {
243
+ root: '/custom/root',
244
+ terminal: {
245
+ enabled: false,
246
+ maxBufferedLines: 1000,
247
+ maxTerminals: 5,
248
+ },
249
+ };
250
+
251
+ const config = createDefaultConfig(overrides);
252
+
253
+ expect(config.root).toBe('/custom/root');
254
+ expect(config.terminal.enabled).toBe(false);
255
+ expect(config.terminal.maxBufferedLines).toBe(1000);
256
+ });
257
+ });
258
+
259
+ describe('parseFileSize()', () => {
260
+ it('should parse bytes correctly', () => {
261
+ expect(parseFileSize('100B')).toBe(100);
262
+ expect(parseFileSize('100')).toBe(100);
263
+ });
264
+
265
+ it('should parse kilobytes correctly', () => {
266
+ expect(parseFileSize('1KB')).toBe(1024);
267
+ expect(parseFileSize('10KB')).toBe(10240);
268
+ });
269
+
270
+ it('should parse megabytes correctly', () => {
271
+ expect(parseFileSize('1MB')).toBe(1024 * 1024);
272
+ expect(parseFileSize('100MB')).toBe(100 * 1024 * 1024);
273
+ });
274
+
275
+ it('should parse gigabytes correctly', () => {
276
+ expect(parseFileSize('1GB')).toBe(1024 * 1024 * 1024);
277
+ expect(parseFileSize('2GB')).toBe(2 * 1024 * 1024 * 1024);
278
+ });
279
+
280
+ it('should handle decimal values', () => {
281
+ expect(parseFileSize('1.5MB')).toBe(1.5 * 1024 * 1024);
282
+ expect(parseFileSize('0.5GB')).toBe(0.5 * 1024 * 1024 * 1024);
283
+ });
284
+
285
+ it('should be case insensitive', () => {
286
+ expect(parseFileSize('1mb')).toBe(1024 * 1024);
287
+ expect(parseFileSize('1MB')).toBe(1024 * 1024);
288
+ expect(parseFileSize('1Mb')).toBe(1024 * 1024);
289
+ });
290
+
291
+ it('should handle spaces', () => {
292
+ expect(parseFileSize('100 MB')).toBe(100 * 1024 * 1024);
293
+ });
294
+
295
+ it('should throw error for invalid format', () => {
296
+ expect(() => parseFileSize('invalid')).toThrow('Invalid file size format');
297
+ expect(() => parseFileSize('100XB')).toThrow('Invalid file size format');
298
+ expect(() => parseFileSize('')).toThrow('Invalid file size format');
299
+ });
300
+ });
301
+
302
+ describe('ConfigNotFoundError', () => {
303
+ it('should create error with config path', () => {
304
+ const error = new ConfigNotFoundError('/path/to/config.json');
305
+
306
+ expect(error.name).toBe('ConfigNotFoundError');
307
+ expect(error.message).toContain('/path/to/config.json');
308
+ expect(error.configPath).toBe('/path/to/config.json');
309
+ });
310
+
311
+ it('should be instance of Error', () => {
312
+ const error = new ConfigNotFoundError('/path');
313
+
314
+ expect(error).toBeInstanceOf(Error);
315
+ expect(error).toBeInstanceOf(ConfigNotFoundError);
316
+ });
317
+ });
318
+ });