@positronic/cli 0.0.2 → 0.0.4
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/dist/src/cli.js +16 -1
- package/dist/src/commands/helpers.js +11 -25
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/commands/helpers.d.ts.map +1 -1
- package/package.json +11 -4
- package/dist/src/commands/brain.test.js +0 -2936
- package/dist/src/commands/helpers.test.js +0 -832
- package/dist/src/commands/project.test.js +0 -1201
- package/dist/src/commands/resources.test.js +0 -2511
- package/dist/src/commands/schedule.test.js +0 -1235
- package/dist/src/commands/secret.test.d.js +0 -1
- package/dist/src/commands/secret.test.js +0 -761
- package/dist/src/commands/server.test.js +0 -1237
- package/dist/src/commands/test-utils.js +0 -737
- package/dist/src/components/secret-sync.js +0 -303
- package/dist/src/test/mock-api-client.js +0 -371
- package/dist/src/test/test-dev-server.js +0 -1376
- package/dist/types/commands/test-utils.d.ts +0 -45
- package/dist/types/commands/test-utils.d.ts.map +0 -1
- package/dist/types/components/secret-sync.d.ts +0 -9
- package/dist/types/components/secret-sync.d.ts.map +0 -1
- package/dist/types/test/mock-api-client.d.ts +0 -25
- package/dist/types/test/mock-api-client.d.ts.map +0 -1
- package/dist/types/test/test-dev-server.d.ts +0 -129
- package/dist/types/test/test-dev-server.d.ts.map +0 -1
- package/src/cli.ts +0 -981
- package/src/commands/backend.ts +0 -63
- package/src/commands/brain.test.ts +0 -1004
- package/src/commands/brain.ts +0 -215
- package/src/commands/helpers.test.ts +0 -487
- package/src/commands/helpers.ts +0 -870
- package/src/commands/project-config-manager.ts +0 -152
- package/src/commands/project.test.ts +0 -502
- package/src/commands/project.ts +0 -109
- package/src/commands/resources.test.ts +0 -1052
- package/src/commands/resources.ts +0 -97
- package/src/commands/schedule.test.ts +0 -481
- package/src/commands/schedule.ts +0 -65
- package/src/commands/secret.test.ts +0 -210
- package/src/commands/secret.ts +0 -50
- package/src/commands/server.test.ts +0 -493
- package/src/commands/server.ts +0 -353
- package/src/commands/test-utils.ts +0 -324
- package/src/components/brain-history.tsx +0 -198
- package/src/components/brain-list.tsx +0 -105
- package/src/components/brain-rerun.tsx +0 -111
- package/src/components/brain-show.tsx +0 -92
- package/src/components/error.tsx +0 -24
- package/src/components/project-add.tsx +0 -59
- package/src/components/project-create.tsx +0 -83
- package/src/components/project-list.tsx +0 -83
- package/src/components/project-remove.tsx +0 -55
- package/src/components/project-select.tsx +0 -200
- package/src/components/project-show.tsx +0 -58
- package/src/components/resource-clear.tsx +0 -127
- package/src/components/resource-delete.tsx +0 -160
- package/src/components/resource-list.tsx +0 -177
- package/src/components/resource-sync.tsx +0 -170
- package/src/components/resource-types.tsx +0 -55
- package/src/components/resource-upload.tsx +0 -182
- package/src/components/schedule-create.tsx +0 -90
- package/src/components/schedule-delete.tsx +0 -116
- package/src/components/schedule-list.tsx +0 -186
- package/src/components/schedule-runs.tsx +0 -151
- package/src/components/secret-bulk.tsx +0 -79
- package/src/components/secret-create.tsx +0 -49
- package/src/components/secret-delete.tsx +0 -41
- package/src/components/secret-list.tsx +0 -41
- package/src/components/watch.tsx +0 -155
- package/src/hooks/useApi.ts +0 -183
- package/src/positronic.ts +0 -40
- package/src/test/data/resources/config.json +0 -1
- package/src/test/data/resources/data/config.json +0 -1
- package/src/test/data/resources/data/logo.png +0 -2
- package/src/test/data/resources/docs/api.md +0 -3
- package/src/test/data/resources/docs/readme.md +0 -3
- package/src/test/data/resources/example.md +0 -3
- package/src/test/data/resources/file with spaces.txt +0 -1
- package/src/test/data/resources/readme.md +0 -3
- package/src/test/data/resources/test.txt +0 -1
- package/src/test/mock-api-client.ts +0 -145
- package/src/test/test-dev-server.ts +0 -1003
- package/tsconfig.json +0 -11
|
@@ -1,832 +0,0 @@
|
|
|
1
|
-
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
2
|
-
try {
|
|
3
|
-
var info = gen[key](arg);
|
|
4
|
-
var value = info.value;
|
|
5
|
-
} catch (error) {
|
|
6
|
-
reject(error);
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
if (info.done) {
|
|
10
|
-
resolve(value);
|
|
11
|
-
} else {
|
|
12
|
-
Promise.resolve(value).then(_next, _throw);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function _async_to_generator(fn) {
|
|
16
|
-
return function() {
|
|
17
|
-
var self = this, args = arguments;
|
|
18
|
-
return new Promise(function(resolve, reject) {
|
|
19
|
-
var gen = fn.apply(self, args);
|
|
20
|
-
function _next(value) {
|
|
21
|
-
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
22
|
-
}
|
|
23
|
-
function _throw(err) {
|
|
24
|
-
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
25
|
-
}
|
|
26
|
-
_next(undefined);
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function _instanceof(left, right) {
|
|
31
|
-
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
|
|
32
|
-
return !!right[Symbol.hasInstance](left);
|
|
33
|
-
} else {
|
|
34
|
-
return left instanceof right;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function _ts_generator(thisArg, body) {
|
|
38
|
-
var f, y, t, _ = {
|
|
39
|
-
label: 0,
|
|
40
|
-
sent: function() {
|
|
41
|
-
if (t[0] & 1) throw t[1];
|
|
42
|
-
return t[1];
|
|
43
|
-
},
|
|
44
|
-
trys: [],
|
|
45
|
-
ops: []
|
|
46
|
-
}, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
47
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() {
|
|
48
|
-
return this;
|
|
49
|
-
}), g;
|
|
50
|
-
function verb(n) {
|
|
51
|
-
return function(v) {
|
|
52
|
-
return step([
|
|
53
|
-
n,
|
|
54
|
-
v
|
|
55
|
-
]);
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function step(op) {
|
|
59
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
60
|
-
while(g && (g = 0, op[0] && (_ = 0)), _)try {
|
|
61
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
62
|
-
if (y = 0, t) op = [
|
|
63
|
-
op[0] & 2,
|
|
64
|
-
t.value
|
|
65
|
-
];
|
|
66
|
-
switch(op[0]){
|
|
67
|
-
case 0:
|
|
68
|
-
case 1:
|
|
69
|
-
t = op;
|
|
70
|
-
break;
|
|
71
|
-
case 4:
|
|
72
|
-
_.label++;
|
|
73
|
-
return {
|
|
74
|
-
value: op[1],
|
|
75
|
-
done: false
|
|
76
|
-
};
|
|
77
|
-
case 5:
|
|
78
|
-
_.label++;
|
|
79
|
-
y = op[1];
|
|
80
|
-
op = [
|
|
81
|
-
0
|
|
82
|
-
];
|
|
83
|
-
continue;
|
|
84
|
-
case 7:
|
|
85
|
-
op = _.ops.pop();
|
|
86
|
-
_.trys.pop();
|
|
87
|
-
continue;
|
|
88
|
-
default:
|
|
89
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
90
|
-
_ = 0;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
94
|
-
_.label = op[1];
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
if (op[0] === 6 && _.label < t[1]) {
|
|
98
|
-
_.label = t[1];
|
|
99
|
-
t = op;
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
if (t && _.label < t[2]) {
|
|
103
|
-
_.label = t[2];
|
|
104
|
-
_.ops.push(op);
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
if (t[2]) _.ops.pop();
|
|
108
|
-
_.trys.pop();
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
op = body.call(thisArg, _);
|
|
112
|
-
} catch (e) {
|
|
113
|
-
op = [
|
|
114
|
-
6,
|
|
115
|
-
e
|
|
116
|
-
];
|
|
117
|
-
y = 0;
|
|
118
|
-
} finally{
|
|
119
|
-
f = t = 0;
|
|
120
|
-
}
|
|
121
|
-
if (op[0] & 5) throw op[1];
|
|
122
|
-
return {
|
|
123
|
-
value: op[0] ? op[1] : void 0,
|
|
124
|
-
done: true
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
import * as fs from 'fs';
|
|
129
|
-
import * as path from 'path';
|
|
130
|
-
import * as os from 'os';
|
|
131
|
-
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
132
|
-
import { Response } from 'node-fetch';
|
|
133
|
-
import { syncResources, generateTypes } from './helpers.js';
|
|
134
|
-
import { createMockApiClient } from '../test/mock-api-client.js';
|
|
135
|
-
describe('Helper Functions Unit Tests', function() {
|
|
136
|
-
var tempDir;
|
|
137
|
-
var projectPath;
|
|
138
|
-
var mockClient;
|
|
139
|
-
beforeEach(function() {
|
|
140
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'positronic-unit-test-'));
|
|
141
|
-
projectPath = path.join(tempDir, 'test-project');
|
|
142
|
-
fs.mkdirSync(projectPath, {
|
|
143
|
-
recursive: true
|
|
144
|
-
});
|
|
145
|
-
mockClient = createMockApiClient();
|
|
146
|
-
});
|
|
147
|
-
afterEach(function() {
|
|
148
|
-
fs.rmSync(tempDir, {
|
|
149
|
-
recursive: true,
|
|
150
|
-
force: true
|
|
151
|
-
});
|
|
152
|
-
mockClient.reset();
|
|
153
|
-
});
|
|
154
|
-
describe('syncResources', function() {
|
|
155
|
-
it('should create resources directory if it does not exist', function() {
|
|
156
|
-
return _async_to_generator(function() {
|
|
157
|
-
var resourcesDir;
|
|
158
|
-
return _ts_generator(this, function(_state) {
|
|
159
|
-
switch(_state.label){
|
|
160
|
-
case 0:
|
|
161
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
162
|
-
expect(fs.existsSync(resourcesDir)).toBe(false);
|
|
163
|
-
return [
|
|
164
|
-
4,
|
|
165
|
-
syncResources(projectPath, mockClient)
|
|
166
|
-
];
|
|
167
|
-
case 1:
|
|
168
|
-
_state.sent();
|
|
169
|
-
expect(fs.existsSync(resourcesDir)).toBe(true);
|
|
170
|
-
return [
|
|
171
|
-
2
|
|
172
|
-
];
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
})();
|
|
176
|
-
});
|
|
177
|
-
it('should return zero counts for empty resources directory', function() {
|
|
178
|
-
return _async_to_generator(function() {
|
|
179
|
-
var resourcesDir, result;
|
|
180
|
-
return _ts_generator(this, function(_state) {
|
|
181
|
-
switch(_state.label){
|
|
182
|
-
case 0:
|
|
183
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
184
|
-
fs.mkdirSync(resourcesDir, {
|
|
185
|
-
recursive: true
|
|
186
|
-
});
|
|
187
|
-
return [
|
|
188
|
-
4,
|
|
189
|
-
syncResources(projectPath, mockClient)
|
|
190
|
-
];
|
|
191
|
-
case 1:
|
|
192
|
-
result = _state.sent();
|
|
193
|
-
expect(result).toEqual({
|
|
194
|
-
uploadCount: 0,
|
|
195
|
-
skipCount: 0,
|
|
196
|
-
errorCount: 0,
|
|
197
|
-
totalCount: 0,
|
|
198
|
-
deleteCount: 0,
|
|
199
|
-
errors: []
|
|
200
|
-
});
|
|
201
|
-
// Should not make any API calls for empty directory
|
|
202
|
-
expect(mockClient.calls.length).toBe(1); // Only GET /resources call
|
|
203
|
-
return [
|
|
204
|
-
2
|
|
205
|
-
];
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
})();
|
|
209
|
-
});
|
|
210
|
-
it('should upload new resources', function() {
|
|
211
|
-
return _async_to_generator(function() {
|
|
212
|
-
var _mockClient_calls__options, resourcesDir, result, resources;
|
|
213
|
-
return _ts_generator(this, function(_state) {
|
|
214
|
-
switch(_state.label){
|
|
215
|
-
case 0:
|
|
216
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
217
|
-
fs.mkdirSync(resourcesDir, {
|
|
218
|
-
recursive: true
|
|
219
|
-
});
|
|
220
|
-
// Create test files
|
|
221
|
-
fs.writeFileSync(path.join(resourcesDir, 'test.txt'), 'Hello World');
|
|
222
|
-
fs.writeFileSync(path.join(resourcesDir, 'data.json'), '{"key": "value"}');
|
|
223
|
-
return [
|
|
224
|
-
4,
|
|
225
|
-
syncResources(projectPath, mockClient)
|
|
226
|
-
];
|
|
227
|
-
case 1:
|
|
228
|
-
result = _state.sent();
|
|
229
|
-
expect(result.uploadCount).toBe(2);
|
|
230
|
-
expect(result.skipCount).toBe(0);
|
|
231
|
-
expect(result.errorCount).toBe(0);
|
|
232
|
-
expect(result.totalCount).toBe(2);
|
|
233
|
-
// Verify API calls
|
|
234
|
-
expect(mockClient.calls).toHaveLength(3); // 1 GET + 2 POSTs
|
|
235
|
-
expect(mockClient.calls[0].path).toBe('/resources');
|
|
236
|
-
expect(mockClient.calls[1].path).toBe('/resources');
|
|
237
|
-
expect((_mockClient_calls__options = mockClient.calls[1].options) === null || _mockClient_calls__options === void 0 ? void 0 : _mockClient_calls__options.method).toBe('POST');
|
|
238
|
-
// Verify uploaded resources
|
|
239
|
-
resources = mockClient.getResources();
|
|
240
|
-
expect(resources).toHaveLength(2);
|
|
241
|
-
expect(resources.find(function(r) {
|
|
242
|
-
return r.key === 'test.txt';
|
|
243
|
-
})).toBeDefined();
|
|
244
|
-
expect(resources.find(function(r) {
|
|
245
|
-
return r.key === 'data.json';
|
|
246
|
-
})).toBeDefined();
|
|
247
|
-
return [
|
|
248
|
-
2
|
|
249
|
-
];
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
})();
|
|
253
|
-
});
|
|
254
|
-
it('should skip unchanged resources', function() {
|
|
255
|
-
return _async_to_generator(function() {
|
|
256
|
-
var resourcesDir, result;
|
|
257
|
-
return _ts_generator(this, function(_state) {
|
|
258
|
-
switch(_state.label){
|
|
259
|
-
case 0:
|
|
260
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
261
|
-
fs.mkdirSync(resourcesDir, {
|
|
262
|
-
recursive: true
|
|
263
|
-
});
|
|
264
|
-
// Add existing resource to mock with future lastModified to ensure it's newer than the file
|
|
265
|
-
mockClient.addResource({
|
|
266
|
-
key: 'existing.txt',
|
|
267
|
-
type: 'text',
|
|
268
|
-
size: 8,
|
|
269
|
-
lastModified: new Date(Date.now() + 10000).toISOString(),
|
|
270
|
-
local: false
|
|
271
|
-
});
|
|
272
|
-
// Create matching file
|
|
273
|
-
fs.writeFileSync(path.join(resourcesDir, 'existing.txt'), '12345678'); // 8 bytes
|
|
274
|
-
return [
|
|
275
|
-
4,
|
|
276
|
-
syncResources(projectPath, mockClient)
|
|
277
|
-
];
|
|
278
|
-
case 1:
|
|
279
|
-
result = _state.sent();
|
|
280
|
-
expect(result.uploadCount).toBe(0);
|
|
281
|
-
expect(result.skipCount).toBe(1);
|
|
282
|
-
expect(result.errorCount).toBe(0);
|
|
283
|
-
// Should only make GET call
|
|
284
|
-
expect(mockClient.calls).toHaveLength(1);
|
|
285
|
-
expect(mockClient.calls[0].path).toBe('/resources');
|
|
286
|
-
return [
|
|
287
|
-
2
|
|
288
|
-
];
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
})();
|
|
292
|
-
});
|
|
293
|
-
it('should upload modified resources based on size', function() {
|
|
294
|
-
return _async_to_generator(function() {
|
|
295
|
-
var resourcesDir, result;
|
|
296
|
-
return _ts_generator(this, function(_state) {
|
|
297
|
-
switch(_state.label){
|
|
298
|
-
case 0:
|
|
299
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
300
|
-
fs.mkdirSync(resourcesDir, {
|
|
301
|
-
recursive: true
|
|
302
|
-
});
|
|
303
|
-
// Add existing resource to mock with different size
|
|
304
|
-
mockClient.addResource({
|
|
305
|
-
key: 'modified.txt',
|
|
306
|
-
type: 'text',
|
|
307
|
-
size: 5,
|
|
308
|
-
lastModified: new Date(Date.now() - 10000).toISOString(),
|
|
309
|
-
local: false
|
|
310
|
-
});
|
|
311
|
-
// Create file with different content
|
|
312
|
-
fs.writeFileSync(path.join(resourcesDir, 'modified.txt'), 'New content');
|
|
313
|
-
return [
|
|
314
|
-
4,
|
|
315
|
-
syncResources(projectPath, mockClient)
|
|
316
|
-
];
|
|
317
|
-
case 1:
|
|
318
|
-
result = _state.sent();
|
|
319
|
-
expect(result.uploadCount).toBe(1);
|
|
320
|
-
expect(result.skipCount).toBe(0);
|
|
321
|
-
return [
|
|
322
|
-
2
|
|
323
|
-
];
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
})();
|
|
327
|
-
});
|
|
328
|
-
it('should upload resources based on modification time when size matches', function() {
|
|
329
|
-
return _async_to_generator(function() {
|
|
330
|
-
var resourcesDir, result;
|
|
331
|
-
return _ts_generator(this, function(_state) {
|
|
332
|
-
switch(_state.label){
|
|
333
|
-
case 0:
|
|
334
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
335
|
-
fs.mkdirSync(resourcesDir, {
|
|
336
|
-
recursive: true
|
|
337
|
-
});
|
|
338
|
-
// Add existing resource to mock with same size but older timestamp
|
|
339
|
-
mockClient.addResource({
|
|
340
|
-
key: 'updated.txt',
|
|
341
|
-
type: 'text',
|
|
342
|
-
size: 11,
|
|
343
|
-
lastModified: new Date(Date.now() - 10000).toISOString(),
|
|
344
|
-
local: false
|
|
345
|
-
});
|
|
346
|
-
// Create file with same size but it will have a newer modification time
|
|
347
|
-
fs.writeFileSync(path.join(resourcesDir, 'updated.txt'), 'Same content'); // 11 bytes
|
|
348
|
-
return [
|
|
349
|
-
4,
|
|
350
|
-
syncResources(projectPath, mockClient)
|
|
351
|
-
];
|
|
352
|
-
case 1:
|
|
353
|
-
result = _state.sent();
|
|
354
|
-
expect(result.uploadCount).toBe(1);
|
|
355
|
-
expect(result.skipCount).toBe(0);
|
|
356
|
-
return [
|
|
357
|
-
2
|
|
358
|
-
];
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
})();
|
|
362
|
-
});
|
|
363
|
-
it('should handle nested directories', function() {
|
|
364
|
-
return _async_to_generator(function() {
|
|
365
|
-
var resourcesDir, docsDir, result, resources;
|
|
366
|
-
return _ts_generator(this, function(_state) {
|
|
367
|
-
switch(_state.label){
|
|
368
|
-
case 0:
|
|
369
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
370
|
-
docsDir = path.join(resourcesDir, 'docs');
|
|
371
|
-
fs.mkdirSync(docsDir, {
|
|
372
|
-
recursive: true
|
|
373
|
-
});
|
|
374
|
-
fs.writeFileSync(path.join(docsDir, 'readme.md'), '# README');
|
|
375
|
-
return [
|
|
376
|
-
4,
|
|
377
|
-
syncResources(projectPath, mockClient)
|
|
378
|
-
];
|
|
379
|
-
case 1:
|
|
380
|
-
result = _state.sent();
|
|
381
|
-
expect(result.uploadCount).toBe(1);
|
|
382
|
-
resources = mockClient.getResources();
|
|
383
|
-
expect(resources[0].key).toBe('docs/readme.md');
|
|
384
|
-
return [
|
|
385
|
-
2
|
|
386
|
-
];
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
})();
|
|
390
|
-
});
|
|
391
|
-
it('should handle upload errors gracefully', function() {
|
|
392
|
-
return _async_to_generator(function() {
|
|
393
|
-
var resourcesDir, badClient, originalFetch, result;
|
|
394
|
-
return _ts_generator(this, function(_state) {
|
|
395
|
-
switch(_state.label){
|
|
396
|
-
case 0:
|
|
397
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
398
|
-
fs.mkdirSync(resourcesDir, {
|
|
399
|
-
recursive: true
|
|
400
|
-
});
|
|
401
|
-
// Create a file that will trigger an error in our mock
|
|
402
|
-
// We'll modify the mock to fail on specific file names
|
|
403
|
-
badClient = createMockApiClient();
|
|
404
|
-
originalFetch = badClient.fetch;
|
|
405
|
-
badClient.fetch = function(path, options) {
|
|
406
|
-
return _async_to_generator(function() {
|
|
407
|
-
return _ts_generator(this, function(_state) {
|
|
408
|
-
if ((options === null || options === void 0 ? void 0 : options.method) === 'POST' && _instanceof(options.body, FormData) && options.body.get('key') === 'error.txt') {
|
|
409
|
-
throw new Error('Upload failed');
|
|
410
|
-
}
|
|
411
|
-
return [
|
|
412
|
-
2,
|
|
413
|
-
originalFetch.call(badClient, path, options)
|
|
414
|
-
];
|
|
415
|
-
});
|
|
416
|
-
})();
|
|
417
|
-
};
|
|
418
|
-
fs.writeFileSync(path.join(resourcesDir, 'error.txt'), 'This will fail');
|
|
419
|
-
fs.writeFileSync(path.join(resourcesDir, 'good.txt'), 'This will succeed');
|
|
420
|
-
return [
|
|
421
|
-
4,
|
|
422
|
-
syncResources(projectPath, badClient)
|
|
423
|
-
];
|
|
424
|
-
case 1:
|
|
425
|
-
result = _state.sent();
|
|
426
|
-
expect(result.uploadCount).toBe(1);
|
|
427
|
-
expect(result.errorCount).toBe(1);
|
|
428
|
-
expect(result.errors).toHaveLength(1);
|
|
429
|
-
expect(result.errors[0]).toEqual({
|
|
430
|
-
file: 'error.txt',
|
|
431
|
-
message: 'Upload failed'
|
|
432
|
-
});
|
|
433
|
-
return [
|
|
434
|
-
2
|
|
435
|
-
];
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
})();
|
|
439
|
-
});
|
|
440
|
-
it('should delete server resources with local=true when files are removed', function() {
|
|
441
|
-
return _async_to_generator(function() {
|
|
442
|
-
var resourcesDir, result, remainingResources, deleteCalls;
|
|
443
|
-
return _ts_generator(this, function(_state) {
|
|
444
|
-
switch(_state.label){
|
|
445
|
-
case 0:
|
|
446
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
447
|
-
fs.mkdirSync(resourcesDir, {
|
|
448
|
-
recursive: true
|
|
449
|
-
});
|
|
450
|
-
// Create the file that should still exist first
|
|
451
|
-
fs.writeFileSync(path.join(resourcesDir, 'still-exists.txt'), 'I still exist');
|
|
452
|
-
// Add existing resources to mock - some synced (local=true), some manual (local=false)
|
|
453
|
-
mockClient.addResource({
|
|
454
|
-
key: 'deleted-file.txt',
|
|
455
|
-
type: 'text',
|
|
456
|
-
size: 100,
|
|
457
|
-
lastModified: new Date().toISOString(),
|
|
458
|
-
local: true
|
|
459
|
-
});
|
|
460
|
-
mockClient.addResource({
|
|
461
|
-
key: 'still-exists.txt',
|
|
462
|
-
type: 'text',
|
|
463
|
-
size: 13,
|
|
464
|
-
lastModified: new Date(Date.now() + 10000).toISOString(),
|
|
465
|
-
local: true
|
|
466
|
-
});
|
|
467
|
-
mockClient.addResource({
|
|
468
|
-
key: 'docs/removed-doc.md',
|
|
469
|
-
type: 'text',
|
|
470
|
-
size: 200,
|
|
471
|
-
lastModified: new Date().toISOString(),
|
|
472
|
-
local: true
|
|
473
|
-
});
|
|
474
|
-
return [
|
|
475
|
-
4,
|
|
476
|
-
syncResources(projectPath, mockClient)
|
|
477
|
-
];
|
|
478
|
-
case 1:
|
|
479
|
-
result = _state.sent();
|
|
480
|
-
expect(result.deleteCount).toBe(2);
|
|
481
|
-
expect(result.uploadCount).toBe(0); // No new uploads
|
|
482
|
-
expect(result.skipCount).toBe(1); // still-exists.txt is up to date
|
|
483
|
-
// Verify the correct resources were deleted
|
|
484
|
-
remainingResources = mockClient.getResources();
|
|
485
|
-
expect(remainingResources).toHaveLength(1);
|
|
486
|
-
expect(remainingResources[0].key).toBe('still-exists.txt');
|
|
487
|
-
// Verify DELETE API calls were made
|
|
488
|
-
deleteCalls = mockClient.calls.filter(function(call) {
|
|
489
|
-
var _call_options;
|
|
490
|
-
return ((_call_options = call.options) === null || _call_options === void 0 ? void 0 : _call_options.method) === 'DELETE';
|
|
491
|
-
});
|
|
492
|
-
expect(deleteCalls).toHaveLength(2);
|
|
493
|
-
expect(deleteCalls[0].path).toBe('/resources/deleted-file.txt');
|
|
494
|
-
expect(deleteCalls[1].path).toBe('/resources/docs%2Fremoved-doc.md'); // URL encoded
|
|
495
|
-
return [
|
|
496
|
-
2
|
|
497
|
-
];
|
|
498
|
-
}
|
|
499
|
-
});
|
|
500
|
-
})();
|
|
501
|
-
});
|
|
502
|
-
it('should preserve manually uploaded resources (local=false)', function() {
|
|
503
|
-
return _async_to_generator(function() {
|
|
504
|
-
var resourcesDir, result, remainingResources, deleteCalls;
|
|
505
|
-
return _ts_generator(this, function(_state) {
|
|
506
|
-
switch(_state.label){
|
|
507
|
-
case 0:
|
|
508
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
509
|
-
fs.mkdirSync(resourcesDir, {
|
|
510
|
-
recursive: true
|
|
511
|
-
});
|
|
512
|
-
// Add existing resources to mock
|
|
513
|
-
mockClient.addResource({
|
|
514
|
-
key: 'manual-upload.txt',
|
|
515
|
-
type: 'text',
|
|
516
|
-
size: 100,
|
|
517
|
-
lastModified: new Date().toISOString(),
|
|
518
|
-
local: false
|
|
519
|
-
});
|
|
520
|
-
mockClient.addResource({
|
|
521
|
-
key: 'another-manual.png',
|
|
522
|
-
type: 'binary',
|
|
523
|
-
size: 5000,
|
|
524
|
-
lastModified: new Date().toISOString(),
|
|
525
|
-
local: false
|
|
526
|
-
});
|
|
527
|
-
mockClient.addResource({
|
|
528
|
-
key: 'synced-file.txt',
|
|
529
|
-
type: 'text',
|
|
530
|
-
size: 75,
|
|
531
|
-
lastModified: new Date().toISOString(),
|
|
532
|
-
local: true
|
|
533
|
-
});
|
|
534
|
-
return [
|
|
535
|
-
4,
|
|
536
|
-
syncResources(projectPath, mockClient)
|
|
537
|
-
];
|
|
538
|
-
case 1:
|
|
539
|
-
result = _state.sent();
|
|
540
|
-
expect(result.deleteCount).toBe(1); // Only the synced file
|
|
541
|
-
expect(result.uploadCount).toBe(0);
|
|
542
|
-
expect(result.skipCount).toBe(0);
|
|
543
|
-
// Verify manual uploads are still there
|
|
544
|
-
remainingResources = mockClient.getResources();
|
|
545
|
-
expect(remainingResources).toHaveLength(2);
|
|
546
|
-
expect(remainingResources.find(function(r) {
|
|
547
|
-
return r.key === 'manual-upload.txt';
|
|
548
|
-
})).toBeDefined();
|
|
549
|
-
expect(remainingResources.find(function(r) {
|
|
550
|
-
return r.key === 'another-manual.png';
|
|
551
|
-
})).toBeDefined();
|
|
552
|
-
expect(remainingResources.find(function(r) {
|
|
553
|
-
return r.key === 'synced-file.txt';
|
|
554
|
-
})).toBeUndefined();
|
|
555
|
-
// Verify only one DELETE call was made
|
|
556
|
-
deleteCalls = mockClient.calls.filter(function(call) {
|
|
557
|
-
var _call_options;
|
|
558
|
-
return ((_call_options = call.options) === null || _call_options === void 0 ? void 0 : _call_options.method) === 'DELETE';
|
|
559
|
-
});
|
|
560
|
-
expect(deleteCalls).toHaveLength(1);
|
|
561
|
-
expect(deleteCalls[0].path).toBe('/resources/synced-file.txt');
|
|
562
|
-
return [
|
|
563
|
-
2
|
|
564
|
-
];
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
})();
|
|
568
|
-
});
|
|
569
|
-
it('should handle delete errors gracefully', function() {
|
|
570
|
-
return _async_to_generator(function() {
|
|
571
|
-
var resourcesDir, badClient, originalFetch, result;
|
|
572
|
-
return _ts_generator(this, function(_state) {
|
|
573
|
-
switch(_state.label){
|
|
574
|
-
case 0:
|
|
575
|
-
resourcesDir = path.join(projectPath, 'resources');
|
|
576
|
-
fs.mkdirSync(resourcesDir, {
|
|
577
|
-
recursive: true
|
|
578
|
-
});
|
|
579
|
-
// Create a client that fails on DELETE requests
|
|
580
|
-
badClient = createMockApiClient();
|
|
581
|
-
badClient.addResource({
|
|
582
|
-
key: 'will-fail-delete.txt',
|
|
583
|
-
type: 'text',
|
|
584
|
-
size: 100,
|
|
585
|
-
lastModified: new Date().toISOString(),
|
|
586
|
-
local: true
|
|
587
|
-
});
|
|
588
|
-
originalFetch = badClient.fetch;
|
|
589
|
-
badClient.fetch = function(path, options) {
|
|
590
|
-
return _async_to_generator(function() {
|
|
591
|
-
return _ts_generator(this, function(_state) {
|
|
592
|
-
if ((options === null || options === void 0 ? void 0 : options.method) === 'DELETE') {
|
|
593
|
-
return [
|
|
594
|
-
2,
|
|
595
|
-
new Response('Internal Server Error', {
|
|
596
|
-
status: 500
|
|
597
|
-
})
|
|
598
|
-
];
|
|
599
|
-
}
|
|
600
|
-
return [
|
|
601
|
-
2,
|
|
602
|
-
originalFetch.call(badClient, path, options)
|
|
603
|
-
];
|
|
604
|
-
});
|
|
605
|
-
})();
|
|
606
|
-
};
|
|
607
|
-
return [
|
|
608
|
-
4,
|
|
609
|
-
syncResources(projectPath, badClient)
|
|
610
|
-
];
|
|
611
|
-
case 1:
|
|
612
|
-
result = _state.sent();
|
|
613
|
-
expect(result.deleteCount).toBe(0); // Failed to delete
|
|
614
|
-
expect(result.errorCount).toBe(1);
|
|
615
|
-
expect(result.errors).toHaveLength(1);
|
|
616
|
-
expect(result.errors[0]).toEqual({
|
|
617
|
-
file: 'will-fail-delete.txt',
|
|
618
|
-
message: 'Failed to delete: Delete failed: 500 Internal Server Error'
|
|
619
|
-
});
|
|
620
|
-
return [
|
|
621
|
-
2
|
|
622
|
-
];
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
})();
|
|
626
|
-
});
|
|
627
|
-
});
|
|
628
|
-
describe('generateTypes', function() {
|
|
629
|
-
it('should generate types file for empty resources', function() {
|
|
630
|
-
return _async_to_generator(function() {
|
|
631
|
-
var result, typesPath, content;
|
|
632
|
-
return _ts_generator(this, function(_state) {
|
|
633
|
-
switch(_state.label){
|
|
634
|
-
case 0:
|
|
635
|
-
return [
|
|
636
|
-
4,
|
|
637
|
-
generateTypes(projectPath, mockClient)
|
|
638
|
-
];
|
|
639
|
-
case 1:
|
|
640
|
-
result = _state.sent();
|
|
641
|
-
typesPath = path.join(projectPath, 'resources.d.ts');
|
|
642
|
-
expect(fs.existsSync(typesPath)).toBe(true);
|
|
643
|
-
content = fs.readFileSync(typesPath, 'utf-8');
|
|
644
|
-
expect(content).toContain("declare module '@positronic/core'");
|
|
645
|
-
expect(content).toContain('interface Resources');
|
|
646
|
-
expect(content).toContain('loadText(path: string): Promise<string>');
|
|
647
|
-
expect(content).toContain('loadBinary(path: string): Promise<Buffer>');
|
|
648
|
-
return [
|
|
649
|
-
2
|
|
650
|
-
];
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
})();
|
|
654
|
-
});
|
|
655
|
-
it('should generate types for text and binary resources', function() {
|
|
656
|
-
return _async_to_generator(function() {
|
|
657
|
-
var typesPath, content;
|
|
658
|
-
return _ts_generator(this, function(_state) {
|
|
659
|
-
switch(_state.label){
|
|
660
|
-
case 0:
|
|
661
|
-
// Add mock resources
|
|
662
|
-
mockClient.addResource({
|
|
663
|
-
key: 'readme.md',
|
|
664
|
-
type: 'text',
|
|
665
|
-
size: 100,
|
|
666
|
-
lastModified: new Date().toISOString(),
|
|
667
|
-
local: false
|
|
668
|
-
});
|
|
669
|
-
mockClient.addResource({
|
|
670
|
-
key: 'logo.png',
|
|
671
|
-
type: 'binary',
|
|
672
|
-
size: 1000,
|
|
673
|
-
lastModified: new Date().toISOString(),
|
|
674
|
-
local: false
|
|
675
|
-
});
|
|
676
|
-
mockClient.addResource({
|
|
677
|
-
key: 'data.json',
|
|
678
|
-
type: 'text',
|
|
679
|
-
size: 50,
|
|
680
|
-
lastModified: new Date().toISOString(),
|
|
681
|
-
local: false
|
|
682
|
-
});
|
|
683
|
-
return [
|
|
684
|
-
4,
|
|
685
|
-
generateTypes(projectPath, mockClient)
|
|
686
|
-
];
|
|
687
|
-
case 1:
|
|
688
|
-
_state.sent();
|
|
689
|
-
typesPath = path.join(projectPath, 'resources.d.ts');
|
|
690
|
-
content = fs.readFileSync(typesPath, 'utf-8');
|
|
691
|
-
// Check resource declarations
|
|
692
|
-
expect(content).toContain('readme: TextResource;');
|
|
693
|
-
expect(content).toContain('logo: BinaryResource;');
|
|
694
|
-
expect(content).toContain('data: TextResource;');
|
|
695
|
-
return [
|
|
696
|
-
2
|
|
697
|
-
];
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
|
-
})();
|
|
701
|
-
});
|
|
702
|
-
it('should handle nested resources', function() {
|
|
703
|
-
return _async_to_generator(function() {
|
|
704
|
-
var typesPath, content;
|
|
705
|
-
return _ts_generator(this, function(_state) {
|
|
706
|
-
switch(_state.label){
|
|
707
|
-
case 0:
|
|
708
|
-
mockClient.addResource({
|
|
709
|
-
key: 'docs/api.md',
|
|
710
|
-
type: 'text',
|
|
711
|
-
size: 100,
|
|
712
|
-
lastModified: new Date().toISOString(),
|
|
713
|
-
local: false
|
|
714
|
-
});
|
|
715
|
-
mockClient.addResource({
|
|
716
|
-
key: 'docs/images/diagram.png',
|
|
717
|
-
type: 'binary',
|
|
718
|
-
size: 500,
|
|
719
|
-
lastModified: new Date().toISOString(),
|
|
720
|
-
local: false
|
|
721
|
-
});
|
|
722
|
-
mockClient.addResource({
|
|
723
|
-
key: 'config/settings.json',
|
|
724
|
-
type: 'text',
|
|
725
|
-
size: 200,
|
|
726
|
-
lastModified: new Date().toISOString(),
|
|
727
|
-
local: false
|
|
728
|
-
});
|
|
729
|
-
return [
|
|
730
|
-
4,
|
|
731
|
-
generateTypes(projectPath, mockClient)
|
|
732
|
-
];
|
|
733
|
-
case 1:
|
|
734
|
-
_state.sent();
|
|
735
|
-
typesPath = path.join(projectPath, 'resources.d.ts');
|
|
736
|
-
content = fs.readFileSync(typesPath, 'utf-8');
|
|
737
|
-
// Check nested structure
|
|
738
|
-
expect(content).toContain('docs: {');
|
|
739
|
-
expect(content).toContain('api: TextResource;');
|
|
740
|
-
expect(content).toContain('images: {');
|
|
741
|
-
expect(content).toContain('diagram: BinaryResource;');
|
|
742
|
-
expect(content).toContain('config: {');
|
|
743
|
-
expect(content).toContain('settings: TextResource;');
|
|
744
|
-
return [
|
|
745
|
-
2
|
|
746
|
-
];
|
|
747
|
-
}
|
|
748
|
-
});
|
|
749
|
-
})();
|
|
750
|
-
});
|
|
751
|
-
it('should exclude invalid JavaScript identifiers', function() {
|
|
752
|
-
return _async_to_generator(function() {
|
|
753
|
-
var typesPath, content;
|
|
754
|
-
return _ts_generator(this, function(_state) {
|
|
755
|
-
switch(_state.label){
|
|
756
|
-
case 0:
|
|
757
|
-
mockClient.addResource({
|
|
758
|
-
key: '123invalid.txt',
|
|
759
|
-
type: 'text',
|
|
760
|
-
size: 100,
|
|
761
|
-
lastModified: new Date().toISOString(),
|
|
762
|
-
local: false
|
|
763
|
-
});
|
|
764
|
-
mockClient.addResource({
|
|
765
|
-
key: 'file-with-dash.txt',
|
|
766
|
-
type: 'text',
|
|
767
|
-
size: 100,
|
|
768
|
-
lastModified: new Date().toISOString(),
|
|
769
|
-
local: false
|
|
770
|
-
});
|
|
771
|
-
mockClient.addResource({
|
|
772
|
-
key: 'valid_file.txt',
|
|
773
|
-
type: 'text',
|
|
774
|
-
size: 100,
|
|
775
|
-
lastModified: new Date().toISOString(),
|
|
776
|
-
local: false
|
|
777
|
-
});
|
|
778
|
-
return [
|
|
779
|
-
4,
|
|
780
|
-
generateTypes(projectPath, mockClient)
|
|
781
|
-
];
|
|
782
|
-
case 1:
|
|
783
|
-
_state.sent();
|
|
784
|
-
typesPath = path.join(projectPath, 'resources.d.ts');
|
|
785
|
-
content = fs.readFileSync(typesPath, 'utf-8');
|
|
786
|
-
// Should include valid identifier
|
|
787
|
-
expect(content).toContain('valid_file: TextResource;');
|
|
788
|
-
// Should not include invalid identifiers
|
|
789
|
-
expect(content).not.toContain('123invalid');
|
|
790
|
-
expect(content).not.toContain('file-with-dash');
|
|
791
|
-
return [
|
|
792
|
-
2
|
|
793
|
-
];
|
|
794
|
-
}
|
|
795
|
-
});
|
|
796
|
-
})();
|
|
797
|
-
});
|
|
798
|
-
it('should handle API errors gracefully', function() {
|
|
799
|
-
return _async_to_generator(function() {
|
|
800
|
-
var errorClient;
|
|
801
|
-
return _ts_generator(this, function(_state) {
|
|
802
|
-
switch(_state.label){
|
|
803
|
-
case 0:
|
|
804
|
-
// Create a client that returns an error
|
|
805
|
-
errorClient = createMockApiClient();
|
|
806
|
-
errorClient.fetch = function(path, options) {
|
|
807
|
-
return _async_to_generator(function() {
|
|
808
|
-
return _ts_generator(this, function(_state) {
|
|
809
|
-
return [
|
|
810
|
-
2,
|
|
811
|
-
new Response('Internal Server Error', {
|
|
812
|
-
status: 500
|
|
813
|
-
})
|
|
814
|
-
];
|
|
815
|
-
});
|
|
816
|
-
})();
|
|
817
|
-
};
|
|
818
|
-
return [
|
|
819
|
-
4,
|
|
820
|
-
expect(generateTypes(projectPath, errorClient)).rejects.toThrow('Failed to fetch resources: 500 Internal Server Error')
|
|
821
|
-
];
|
|
822
|
-
case 1:
|
|
823
|
-
_state.sent();
|
|
824
|
-
return [
|
|
825
|
-
2
|
|
826
|
-
];
|
|
827
|
-
}
|
|
828
|
-
});
|
|
829
|
-
})();
|
|
830
|
-
});
|
|
831
|
-
});
|
|
832
|
-
});
|