puter-cli 1.8.6 → 2.0.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/.github/workflows/npm-build.yml +4 -3
- package/CHANGELOG.md +29 -0
- package/README.md +10 -2
- package/bin/index.js +184 -30
- package/package.json +12 -12
- package/src/commands/apps.js +53 -139
- package/src/commands/auth.js +113 -115
- package/src/commands/deploy.js +29 -27
- package/src/commands/files.js +151 -512
- package/src/commands/shell.js +13 -25
- package/src/commands/sites.js +25 -83
- package/src/commands/subdomains.js +46 -55
- package/src/commons.js +2 -2
- package/src/executor.js +26 -26
- package/src/modules/ErrorModule.js +18 -31
- package/src/modules/ProfileModule.js +183 -123
- package/src/modules/PuterModule.js +30 -0
- package/tests/ErrorModule.test.js +42 -0
- package/tests/ProfileModule.test.js +274 -0
- package/tests/PuterModule.test.js +56 -0
- package/tests/apps.test.js +194 -0
- package/tests/commons.test.js +380 -0
- package/tests/deploy.test.js +84 -0
- package/tests/executor.test.js +52 -0
- package/tests/files.test.js +640 -0
- package/tests/login.test.js +69 -51
- package/tests/shell.test.js +184 -0
- package/tests/sites.test.js +67 -0
- package/tests/subdomains.test.js +90 -0
- package/src/modules/SetContextModule.js +0 -5
- package/src/temporary/context_helpers.js +0 -17
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { getAuthToken } from '../src/commands/auth.js';
|
|
3
|
+
import { formatSize } from '../src/utils.js';
|
|
4
|
+
import { readFile } from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
vi.mock('../src/commands/auth.js');
|
|
7
|
+
vi.mock('../src/utils.js');
|
|
8
|
+
vi.mock('fs/promises');
|
|
9
|
+
vi.mock('dotenv', () => ({
|
|
10
|
+
default: {
|
|
11
|
+
config: vi.fn(),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
16
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
17
|
+
|
|
18
|
+
let commons;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
vi.resetModules();
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
|
|
24
|
+
vi.mocked(getAuthToken).mockReturnValue('mock-token');
|
|
25
|
+
vi.mocked(formatSize).mockImplementation((size) => `${size} bytes`);
|
|
26
|
+
|
|
27
|
+
commons = await import('../src/commons.js');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('constants', () => {
|
|
31
|
+
it('should export PROJECT_NAME', () => {
|
|
32
|
+
expect(commons.PROJECT_NAME).toBe('puter-cli');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should export NULL_UUID', () => {
|
|
36
|
+
expect(commons.NULL_UUID).toBe('00000000-0000-0000-0000-000000000000');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should have default API_BASE', () => {
|
|
40
|
+
expect(commons.API_BASE).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should have default BASE_URL', () => {
|
|
44
|
+
expect(commons.BASE_URL).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('reconfigureURLs', () => {
|
|
49
|
+
it('should update API_BASE and BASE_URL', async () => {
|
|
50
|
+
vi.resetModules();
|
|
51
|
+
const freshCommons = await import('../src/commons.js');
|
|
52
|
+
|
|
53
|
+
freshCommons.reconfigureURLs({ api: 'https://new-api.example.com', base: 'https://new.example.com' });
|
|
54
|
+
|
|
55
|
+
expect(freshCommons.API_BASE).toBe('https://new-api.example.com');
|
|
56
|
+
expect(freshCommons.BASE_URL).toBe('https://new.example.com');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getHeaders', () => {
|
|
61
|
+
it('should return headers with default content type', () => {
|
|
62
|
+
const headers = commons.getHeaders();
|
|
63
|
+
|
|
64
|
+
expect(headers['Content-Type']).toBe('application/json');
|
|
65
|
+
expect(headers['Authorization']).toBe('Bearer mock-token');
|
|
66
|
+
expect(headers['Accept']).toBe('*/*');
|
|
67
|
+
expect(headers['Accept-Language']).toBe('en-US,en;q=0.9');
|
|
68
|
+
expect(headers['Connection']).toBe('keep-alive');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should return headers with custom content type', () => {
|
|
72
|
+
const headers = commons.getHeaders('multipart/form-data');
|
|
73
|
+
|
|
74
|
+
expect(headers['Content-Type']).toBe('multipart/form-data');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should include Origin and Referer based on BASE_URL', async () => {
|
|
78
|
+
vi.resetModules();
|
|
79
|
+
const freshCommons = await import('../src/commons.js');
|
|
80
|
+
freshCommons.reconfigureURLs({ api: 'https://api.test.com', base: 'https://test.com' });
|
|
81
|
+
|
|
82
|
+
const headers = freshCommons.getHeaders();
|
|
83
|
+
|
|
84
|
+
expect(headers['Origin']).toBe('https://test.com');
|
|
85
|
+
expect(headers['Referer']).toBe('https://test.com/');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('generateAppName', () => {
|
|
90
|
+
it('should generate a name with default separator', () => {
|
|
91
|
+
const name = commons.generateAppName();
|
|
92
|
+
|
|
93
|
+
expect(name).toMatch(/^[a-z]+-[a-z]+-\d+$/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should generate a name with custom separator', () => {
|
|
97
|
+
const name = commons.generateAppName('_');
|
|
98
|
+
|
|
99
|
+
expect(name).toMatch(/^[a-z]+_[a-z]+_\d+$/);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('displayTable', () => {
|
|
104
|
+
it('should display a table with headers and data', () => {
|
|
105
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
106
|
+
|
|
107
|
+
const data = [
|
|
108
|
+
{ name: 'App1', status: 'running' },
|
|
109
|
+
{ name: 'App2', status: 'stopped' },
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
commons.displayTable(data, {
|
|
113
|
+
headers: ['Name', 'Status'],
|
|
114
|
+
columns: ['name', 'status'],
|
|
115
|
+
columnWidth: 15,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should handle empty data', () => {
|
|
122
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
123
|
+
|
|
124
|
+
commons.displayTable([], {
|
|
125
|
+
headers: ['Name'],
|
|
126
|
+
columns: ['name'],
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(2); // header + separator
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should display N/A for missing values', () => {
|
|
133
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
134
|
+
|
|
135
|
+
const data = [{ name: 'App1' }];
|
|
136
|
+
|
|
137
|
+
commons.displayTable(data, {
|
|
138
|
+
headers: ['Name', 'Status'],
|
|
139
|
+
columns: ['name', 'status'],
|
|
140
|
+
columnWidth: 10,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const calls = consoleLogSpy.mock.calls.flat();
|
|
144
|
+
expect(calls.some(call => call.includes('N/A'))).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('showDiskSpaceUsage', () => {
|
|
149
|
+
it('should display disk usage information', () => {
|
|
150
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
151
|
+
|
|
152
|
+
const data = {
|
|
153
|
+
capacity: '1000000000',
|
|
154
|
+
used: '500000000',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
commons.showDiskSpaceUsage(data);
|
|
158
|
+
|
|
159
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
160
|
+
expect(formatSize).toHaveBeenCalledWith('1000000000'); // capacity
|
|
161
|
+
expect(formatSize).toHaveBeenCalledWith('500000000'); // used
|
|
162
|
+
expect(formatSize).toHaveBeenCalledWith(500000000); // free space
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should calculate usage percentage correctly', () => {
|
|
166
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
167
|
+
|
|
168
|
+
const data = {
|
|
169
|
+
capacity: '100',
|
|
170
|
+
used: '25',
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
commons.showDiskSpaceUsage(data);
|
|
174
|
+
|
|
175
|
+
const calls = consoleLogSpy.mock.calls.flat().join(' ');
|
|
176
|
+
expect(calls).toContain('25.00%');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('resolvePath', () => {
|
|
181
|
+
it('should resolve simple relative path', () => {
|
|
182
|
+
expect(commons.resolvePath('/home/user', 'documents')).toBe('/home/user/documents');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should resolve parent directory', () => {
|
|
186
|
+
expect(commons.resolvePath('/home/user/documents', '..')).toBe('/home/user');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should resolve current directory', () => {
|
|
190
|
+
expect(commons.resolvePath('/home/user', '.')).toBe('/home/user');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should resolve multiple parent directories', () => {
|
|
194
|
+
expect(commons.resolvePath('/home/user/documents/files', '../..')).toBe('/home/user');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should handle trailing slashes', () => {
|
|
198
|
+
expect(commons.resolvePath('/home/user/', 'documents')).toBe('/home/user/documents');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should handle empty relative path', () => {
|
|
202
|
+
expect(commons.resolvePath('/home/user', '')).toBe('/home/user');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should return root when going above root', () => {
|
|
206
|
+
expect(commons.resolvePath('/home', '../../../..')).toBe('/');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should normalize duplicate slashes', () => {
|
|
210
|
+
expect(commons.resolvePath('/home//user', 'documents')).toBe('/home/user/documents');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('resolveRemotePath', () => {
|
|
215
|
+
it('should return absolute path as-is', () => {
|
|
216
|
+
expect(commons.resolveRemotePath('/home/user', '/absolute/path')).toBe('/absolute/path');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should resolve relative path', () => {
|
|
220
|
+
expect(commons.resolveRemotePath('/home/user', 'relative/path')).toBe('/home/user/relative/path');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('isValidAppName', () => {
|
|
225
|
+
it('should return true for valid app name', () => {
|
|
226
|
+
expect(commons.isValidAppName('my-app')).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should return true for app name with spaces', () => {
|
|
230
|
+
expect(commons.isValidAppName('my app')).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should return false for empty string', () => {
|
|
234
|
+
expect(commons.isValidAppName('')).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should return false for whitespace only', () => {
|
|
238
|
+
expect(commons.isValidAppName(' ')).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should return false for reserved name "."', () => {
|
|
242
|
+
expect(commons.isValidAppName('.')).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should return false for reserved name ".."', () => {
|
|
246
|
+
expect(commons.isValidAppName('..')).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should return false for name with forward slash', () => {
|
|
250
|
+
expect(commons.isValidAppName('my/app')).toBe(false);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should return false for name with backslash', () => {
|
|
254
|
+
expect(commons.isValidAppName('my\\app')).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should return false for name with wildcard', () => {
|
|
258
|
+
expect(commons.isValidAppName('my*app')).toBe(false);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should return false for non-string input', () => {
|
|
262
|
+
expect(commons.isValidAppName(123)).toBe(false);
|
|
263
|
+
expect(commons.isValidAppName(null)).toBe(false);
|
|
264
|
+
expect(commons.isValidAppName(undefined)).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('getDefaultHomePage', () => {
|
|
269
|
+
it('should generate HTML with app name', () => {
|
|
270
|
+
const html = commons.getDefaultHomePage('TestApp');
|
|
271
|
+
|
|
272
|
+
expect(html).toContain('<title>TestApp</title>');
|
|
273
|
+
expect(html).toContain('Welcome to TestApp!');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should include CSS files when provided', () => {
|
|
277
|
+
const html = commons.getDefaultHomePage('TestApp', [], ['style.css', 'theme.css']);
|
|
278
|
+
|
|
279
|
+
expect(html).toContain('<link href="style.css" rel="stylesheet">');
|
|
280
|
+
expect(html).toContain('<link href="theme.css" rel="stylesheet">');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should include JS files when provided', () => {
|
|
284
|
+
const html = commons.getDefaultHomePage('TestApp', ['app.js', 'utils.js']);
|
|
285
|
+
|
|
286
|
+
expect(html).toContain('<script type="text/babel" src="app.js"></script>');
|
|
287
|
+
expect(html).toContain('<script src="utils.js"></script>');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should use id="root" when react is included', () => {
|
|
291
|
+
const html = commons.getDefaultHomePage('TestApp', ['react.js']);
|
|
292
|
+
|
|
293
|
+
expect(html).toContain('id="root"');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should use id="app" when no react', () => {
|
|
297
|
+
const html = commons.getDefaultHomePage('TestApp', ['vanilla.js']);
|
|
298
|
+
|
|
299
|
+
expect(html).toContain('id="app"');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('getVersionFromPackage', () => {
|
|
304
|
+
it('should return version from package.json', async () => {
|
|
305
|
+
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.2.3' }));
|
|
306
|
+
|
|
307
|
+
const version = await commons.getVersionFromPackage();
|
|
308
|
+
|
|
309
|
+
expect(version).toBe('1.2.3');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should fallback to production package.json on dev error', async () => {
|
|
313
|
+
vi.mocked(readFile)
|
|
314
|
+
.mockRejectedValueOnce(new Error('File not found'))
|
|
315
|
+
.mockResolvedValueOnce(JSON.stringify({ version: '2.0.0' }));
|
|
316
|
+
|
|
317
|
+
const version = await commons.getVersionFromPackage();
|
|
318
|
+
|
|
319
|
+
expect(version).toBe('2.0.0');
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should return null on error', async () => {
|
|
323
|
+
vi.mocked(readFile).mockRejectedValue(new Error('Read error'));
|
|
324
|
+
|
|
325
|
+
const version = await commons.getVersionFromPackage();
|
|
326
|
+
|
|
327
|
+
expect(version).toBeNull();
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe('getLatestVersion', () => {
|
|
332
|
+
beforeEach(() => {
|
|
333
|
+
global.fetch = vi.fn();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should return up-to-date status when versions match', async () => {
|
|
337
|
+
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' }));
|
|
338
|
+
vi.mocked(global.fetch).mockResolvedValueOnce({
|
|
339
|
+
ok: true,
|
|
340
|
+
json: () => Promise.resolve({ version: '1.0.0' }),
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const result = await commons.getLatestVersion('puter-cli');
|
|
344
|
+
|
|
345
|
+
expect(result).toBe('v1.0.0 (up-to-date)');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should return latest version when different', async () => {
|
|
349
|
+
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' }));
|
|
350
|
+
vi.mocked(global.fetch).mockResolvedValueOnce({
|
|
351
|
+
ok: true,
|
|
352
|
+
json: () => Promise.resolve({ version: '2.0.0' }),
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const result = await commons.getLatestVersion('puter-cli');
|
|
356
|
+
|
|
357
|
+
expect(result).toBe('v1.0.0 (latest: 2.0.0)');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should return offline status when fetch fails', async () => {
|
|
361
|
+
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' }));
|
|
362
|
+
vi.mocked(global.fetch).mockRejectedValueOnce(new Error('Network error'));
|
|
363
|
+
|
|
364
|
+
const result = await commons.getLatestVersion('puter-cli');
|
|
365
|
+
|
|
366
|
+
expect(result).toBe('v1.0.0 (offline)');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should handle unknown current version', async () => {
|
|
370
|
+
vi.mocked(readFile).mockRejectedValue(new Error('Read error'));
|
|
371
|
+
vi.mocked(global.fetch).mockResolvedValueOnce({
|
|
372
|
+
ok: true,
|
|
373
|
+
json: () => Promise.resolve({ version: '2.0.0' }),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const result = await commons.getLatestVersion('puter-cli');
|
|
377
|
+
|
|
378
|
+
expect(result).toBe('vunknown (latest: 2.0.0)');
|
|
379
|
+
});
|
|
380
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, vi, expect, it, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock('conf', () => ({
|
|
4
|
+
default: vi.fn(() => ({
|
|
5
|
+
get: vi.fn(),
|
|
6
|
+
})),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock("../src/commands/files");
|
|
10
|
+
vi.mock("../src/commands/sites");
|
|
11
|
+
vi.mock("../src/modules/PuterModule");
|
|
12
|
+
|
|
13
|
+
vi.spyOn(console, "log").mockImplementation(() => { });
|
|
14
|
+
|
|
15
|
+
let deploy;
|
|
16
|
+
let syncDirectory;
|
|
17
|
+
let createSite;
|
|
18
|
+
let getPuter;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
vi.resetModules();
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
|
|
24
|
+
const deployModule = await import("../src/commands/deploy");
|
|
25
|
+
deploy = deployModule.deploy;
|
|
26
|
+
|
|
27
|
+
const filesModule = await import("../src/commands/files");
|
|
28
|
+
syncDirectory = vi.mocked(filesModule.syncDirectory);
|
|
29
|
+
|
|
30
|
+
const sitesModule = await import("../src/commands/sites");
|
|
31
|
+
createSite = vi.mocked(sitesModule.createSite);
|
|
32
|
+
|
|
33
|
+
const puterModule = await import("../src/modules/PuterModule");
|
|
34
|
+
getPuter = vi.mocked(puterModule.getPuter);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("deploy", () => {
|
|
38
|
+
it("should show usage when no args provided", async () => {
|
|
39
|
+
await deploy([]);
|
|
40
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Usage:"));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should deploy successfully", async () => {
|
|
44
|
+
const mockMkdir = vi.fn().mockResolvedValue({
|
|
45
|
+
path: "~/sites/test-app/deployment"
|
|
46
|
+
});
|
|
47
|
+
getPuter.mockReturnValue({
|
|
48
|
+
fs: {
|
|
49
|
+
mkdir: mockMkdir
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
syncDirectory.mockResolvedValue();
|
|
53
|
+
createSite.mockResolvedValue({
|
|
54
|
+
subdomain: "test-app.puter.site"
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await deploy(["./dist", "--subdomain=test-app"]);
|
|
58
|
+
|
|
59
|
+
expect(mockMkdir).toHaveBeenCalledWith("~/sites/test-app/deployment", {
|
|
60
|
+
dedupeName: true,
|
|
61
|
+
createMissingParents: true
|
|
62
|
+
});
|
|
63
|
+
expect(syncDirectory).toHaveBeenCalled();
|
|
64
|
+
expect(createSite).toHaveBeenCalled();
|
|
65
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Deployment successful!"));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should show updated message when site already exists", async () => {
|
|
69
|
+
const mockMkdir = vi.fn().mockResolvedValue({
|
|
70
|
+
path: "~/sites/test-app/deployment"
|
|
71
|
+
});
|
|
72
|
+
getPuter.mockReturnValue({
|
|
73
|
+
fs: {
|
|
74
|
+
mkdir: mockMkdir
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
syncDirectory.mockResolvedValue();
|
|
78
|
+
createSite.mockResolvedValue(null);
|
|
79
|
+
|
|
80
|
+
await deploy(["./dist", "--subdomain=test-app"]);
|
|
81
|
+
|
|
82
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Deployment successfuly updated!"));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('conf', () => ({
|
|
4
|
+
default: vi.fn(() => ({
|
|
5
|
+
get: vi.fn((key) => {
|
|
6
|
+
if (key === 'cwd') return '/mockuser';
|
|
7
|
+
return null;
|
|
8
|
+
}),
|
|
9
|
+
})),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock('node:child_process');
|
|
13
|
+
|
|
14
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
15
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
16
|
+
|
|
17
|
+
let executor;
|
|
18
|
+
let execMock;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
vi.resetModules();
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
|
|
24
|
+
const childProcess = await import('node:child_process');
|
|
25
|
+
execMock = vi.mocked(childProcess.exec);
|
|
26
|
+
|
|
27
|
+
executor = await import('../src/executor.js');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('getPrompt', () => {
|
|
31
|
+
it('should contain puter@<cwd name>', () => {
|
|
32
|
+
const prompt = executor.getPrompt();
|
|
33
|
+
|
|
34
|
+
expect(prompt).toContain('puter@mockuser');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('showHelp', () => {
|
|
39
|
+
it('should display available commands', async () => {
|
|
40
|
+
await executor.execCommand('help');
|
|
41
|
+
|
|
42
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Available commands'));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('host command (!)', () => {
|
|
47
|
+
it('should call exec with the command after !', async () => {
|
|
48
|
+
await executor.execCommand('!ls -la');
|
|
49
|
+
|
|
50
|
+
expect(execMock).toHaveBeenCalledWith('ls -la', expect.any(Function));
|
|
51
|
+
});
|
|
52
|
+
});
|