@sasjs/cli 6.11.0 → 6.12.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/build/commands/build/internal/mockSite/assets/runtime-core.js +163 -241
- package/build/commands/build/internal/mockSite/assets/sw.js +62 -107
- package/build/commands/build/internal/mockSite/mockSite.js +34 -2
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +120 -1
- package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +380 -159
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +240 -20
- package/package.json +3 -2
|
@@ -35,7 +35,39 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
35
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
39
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
40
|
+
if (!m) return o;
|
|
41
|
+
var i = m.call(o), r, ar = [], e;
|
|
42
|
+
try {
|
|
43
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
44
|
+
}
|
|
45
|
+
catch (error) { e = { error: error }; }
|
|
46
|
+
finally {
|
|
47
|
+
try {
|
|
48
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
49
|
+
}
|
|
50
|
+
finally { if (e) throw e.error; }
|
|
51
|
+
}
|
|
52
|
+
return ar;
|
|
53
|
+
};
|
|
54
|
+
var __values = (this && this.__values) || function(o) {
|
|
55
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
56
|
+
if (m) return m.call(o);
|
|
57
|
+
if (o && typeof o.length === "number") return {
|
|
58
|
+
next: function () {
|
|
59
|
+
if (o && i >= o.length) o = void 0;
|
|
60
|
+
return { value: o && o[i++], done: !o };
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
64
|
+
};
|
|
65
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
66
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
67
|
+
};
|
|
38
68
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
69
|
+
var path_1 = __importDefault(require("path"));
|
|
70
|
+
var api_1 = require("@sasjs/api");
|
|
39
71
|
/**
|
|
40
72
|
* The other half of the parity claim: the service worker is what turns an
|
|
41
73
|
* /SASjsApi/ request into a response, so its framing (webout body, the _debug
|
|
@@ -47,6 +79,13 @@ var LOGS_SEPARATOR = 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784';
|
|
|
47
79
|
var drivePath = function (relative) {
|
|
48
80
|
return "/sasjs/drive/files".concat(appLoc, "/").concat(relative);
|
|
49
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* The browser build of @sasjs/api that mockSite.ts copies into the site, from the
|
|
84
|
+
* installed package - the worker under test must be the worker the build ships.
|
|
85
|
+
*/
|
|
86
|
+
var apiBrowserBuild = function () {
|
|
87
|
+
return path_1.default.join(path_1.default.dirname(require.resolve('@sasjs/api')), '..', '..', 'build', 'browser.js');
|
|
88
|
+
};
|
|
50
89
|
var loadWorker = function () {
|
|
51
90
|
jest.resetModules();
|
|
52
91
|
var handlers = {};
|
|
@@ -65,6 +104,8 @@ var loadWorker = function () {
|
|
|
65
104
|
global.importScripts = function (path) {
|
|
66
105
|
if (path.includes('md5'))
|
|
67
106
|
return;
|
|
107
|
+
if (path.includes('api-browser'))
|
|
108
|
+
return require(apiBrowserBuild());
|
|
68
109
|
require('../assets/runtime-core.js');
|
|
69
110
|
};
|
|
70
111
|
require('../assets/sw.js');
|
|
@@ -1089,8 +1130,8 @@ describe('service worker host', function () {
|
|
|
1089
1130
|
}
|
|
1090
1131
|
});
|
|
1091
1132
|
}); });
|
|
1092
|
-
it('refuses a rename
|
|
1093
|
-
var dispatch, form, toBlocked,
|
|
1133
|
+
it('refuses a rename into a blocked extension, as the server does', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1134
|
+
var dispatch, form, toBlocked, read;
|
|
1094
1135
|
return __generator(this, function (_a) {
|
|
1095
1136
|
switch (_a.label) {
|
|
1096
1137
|
case 0:
|
|
@@ -1108,25 +1149,29 @@ describe('service worker host', function () {
|
|
|
1108
1149
|
newPath: '/data/x.sh'
|
|
1109
1150
|
})
|
|
1110
1151
|
}))
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
1152
|
+
// The extension rule is the drive's file-path rule, and a rename is a
|
|
1153
|
+
// file operation: sasjs/api (since 0.2.1, sasjs/api#12) applies the rule
|
|
1154
|
+
// to a rename whose target is a file, so both hosts refuse the move and
|
|
1155
|
+
// no entry can be created that the API itself cannot read back. A folder
|
|
1156
|
+
// is not subject to the file-path schema, so renaming a folder to a
|
|
1157
|
+
// dotted name stays allowed.
|
|
1113
1158
|
];
|
|
1114
1159
|
case 2:
|
|
1115
1160
|
toBlocked = _a.sent();
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1161
|
+
// The extension rule is the drive's file-path rule, and a rename is a
|
|
1162
|
+
// file operation: sasjs/api (since 0.2.1, sasjs/api#12) applies the rule
|
|
1163
|
+
// to a rename whose target is a file, so both hosts refuse the move and
|
|
1164
|
+
// no entry can be created that the API itself cannot read back. A folder
|
|
1165
|
+
// is not subject to the file-path schema, so renaming a folder to a
|
|
1166
|
+
// dotted name stays allowed.
|
|
1118
1167
|
expect(toBlocked.status).toBe(400);
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
body: JSON.stringify({
|
|
1123
|
-
oldPath: '/data/x.sh',
|
|
1124
|
-
newPath: '/data/new.csv'
|
|
1125
|
-
})
|
|
1126
|
-
}))];
|
|
1168
|
+
expect(toBlocked.body).toBe('Invalid file extension');
|
|
1169
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1170
|
+
encodeURIComponent('/data/old.csv')))];
|
|
1127
1171
|
case 3:
|
|
1128
|
-
|
|
1129
|
-
expect(
|
|
1172
|
+
read = _a.sent();
|
|
1173
|
+
expect(read.status).toBe(200);
|
|
1174
|
+
expect(read.body).toBe('r');
|
|
1130
1175
|
return [2 /*return*/];
|
|
1131
1176
|
}
|
|
1132
1177
|
});
|
|
@@ -1175,13 +1220,21 @@ describe('service worker host', function () {
|
|
|
1175
1220
|
child.set('file', new Blob(['b']), 'b.csv');
|
|
1176
1221
|
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1177
1222
|
encodeURIComponent('/data/a.csv/child.csv'), { method: 'POST', body: child }))
|
|
1178
|
-
// mkdirp would otherwise turn the file into a folder to hold the child
|
|
1223
|
+
// mkdirp would otherwise turn the file into a folder to hold the child.
|
|
1224
|
+
// The server loses this at the filesystem (ENOTDIR) and answers a 500, so
|
|
1225
|
+
// this answers a 500 too: the refusal is the point, the status is the
|
|
1226
|
+
// server's class, and the message says which path was in the way.
|
|
1179
1227
|
];
|
|
1180
1228
|
case 2:
|
|
1181
1229
|
under = _b.sent();
|
|
1182
|
-
// mkdirp would otherwise turn the file into a folder to hold the child
|
|
1183
|
-
|
|
1184
|
-
|
|
1230
|
+
// mkdirp would otherwise turn the file into a folder to hold the child.
|
|
1231
|
+
// The server loses this at the filesystem (ENOTDIR) and answers a 500, so
|
|
1232
|
+
// this answers a 500 too: the refusal is the point, the status is the
|
|
1233
|
+
// server's class, and the message says which path was in the way.
|
|
1234
|
+
expect(under.status).toBe(500);
|
|
1235
|
+
expect(under.body).toEqual({
|
|
1236
|
+
message: 'ENOTDIR: an ancestor of the path is a file: /data/a.csv'
|
|
1237
|
+
});
|
|
1185
1238
|
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/a.csv'))];
|
|
1186
1239
|
case 3:
|
|
1187
1240
|
stillAFile = _b.sent();
|
|
@@ -1285,4 +1338,171 @@ describe('service worker host', function () {
|
|
|
1285
1338
|
});
|
|
1286
1339
|
}); });
|
|
1287
1340
|
});
|
|
1341
|
+
describe('conformance corpus', function () {
|
|
1342
|
+
var e_1, _a;
|
|
1343
|
+
/**
|
|
1344
|
+
* The corpus is what "the same API" means: the cases @sasjs/api runs against
|
|
1345
|
+
* its own services, and against a live sasjs/server, run here against this
|
|
1346
|
+
* worker's HTTP surface. A case that answers differently is this host
|
|
1347
|
+
* drifting from the server - the failure mode the hand-written drive routes
|
|
1348
|
+
* used to have - and it is a test rather than a review.
|
|
1349
|
+
*/
|
|
1350
|
+
var httpFor = function (testCase) {
|
|
1351
|
+
var e_2, _a;
|
|
1352
|
+
var _b;
|
|
1353
|
+
var _c = __read(testCase.route.split(' '), 2), method = _c[0], routePath = _c[1];
|
|
1354
|
+
var query = new URLSearchParams();
|
|
1355
|
+
try {
|
|
1356
|
+
for (var _d = __values(Object.entries(testCase.request.query || {})), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
1357
|
+
var _f = __read(_e.value, 2), key = _f[0], value = _f[1];
|
|
1358
|
+
if (value !== undefined)
|
|
1359
|
+
query.set(key, String(value));
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
1363
|
+
finally {
|
|
1364
|
+
try {
|
|
1365
|
+
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
|
|
1366
|
+
}
|
|
1367
|
+
finally { if (e_2) throw e_2.error; }
|
|
1368
|
+
}
|
|
1369
|
+
var suffix = query.toString() ? "?".concat(query) : '';
|
|
1370
|
+
var init = { method: method };
|
|
1371
|
+
var file = testCase.request.file;
|
|
1372
|
+
if (file) {
|
|
1373
|
+
var form = new FormData();
|
|
1374
|
+
// the Blob types want a view over an ArrayBuffer; a Uint8Array from the
|
|
1375
|
+
// corpus is typed over ArrayBufferLike, which a Blob does not accept
|
|
1376
|
+
var bytes = typeof file.contents === 'string'
|
|
1377
|
+
? new TextEncoder().encode(file.contents)
|
|
1378
|
+
: new Uint8Array(file.contents);
|
|
1379
|
+
form.set('file', new Blob([bytes]), file.name);
|
|
1380
|
+
var formPath = (_b = testCase.request.body) === null || _b === void 0 ? void 0 : _b.filePath;
|
|
1381
|
+
if (formPath !== undefined)
|
|
1382
|
+
form.set('filePath', String(formPath));
|
|
1383
|
+
init.body = form;
|
|
1384
|
+
}
|
|
1385
|
+
else if (testCase.request.body) {
|
|
1386
|
+
init.body = JSON.stringify(testCase.request.body);
|
|
1387
|
+
init.headers = { 'content-type': 'application/json' };
|
|
1388
|
+
}
|
|
1389
|
+
return { path: routePath + suffix, init: init };
|
|
1390
|
+
};
|
|
1391
|
+
/**
|
|
1392
|
+
* The case's own files, at the paths the drive stores them under. `load`
|
|
1393
|
+
* restores the seed snapshot, which is a map of strings - a byte entry is a
|
|
1394
|
+
* snapshot of an upload, so it goes in through the store the way an upload
|
|
1395
|
+
* does.
|
|
1396
|
+
*/
|
|
1397
|
+
var seedDrive = function (runtime, testCase) { return __awaiter(void 0, void 0, void 0, function () {
|
|
1398
|
+
var text, _a, _b, _c, file, contents, _d, _e, _f, file, contents, e_3_1;
|
|
1399
|
+
var e_4, _g, e_3, _h;
|
|
1400
|
+
return __generator(this, function (_j) {
|
|
1401
|
+
switch (_j.label) {
|
|
1402
|
+
case 0:
|
|
1403
|
+
text = {};
|
|
1404
|
+
try {
|
|
1405
|
+
for (_a = __values(Object.entries(testCase.seed || {})), _b = _a.next(); !_b.done; _b = _a.next()) {
|
|
1406
|
+
_c = __read(_b.value, 2), file = _c[0], contents = _c[1];
|
|
1407
|
+
if (typeof contents === 'string')
|
|
1408
|
+
text["/sasjs/drive/files".concat(file)] = contents;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
1412
|
+
finally {
|
|
1413
|
+
try {
|
|
1414
|
+
if (_b && !_b.done && (_g = _a.return)) _g.call(_a);
|
|
1415
|
+
}
|
|
1416
|
+
finally { if (e_4) throw e_4.error; }
|
|
1417
|
+
}
|
|
1418
|
+
return [4 /*yield*/, runtime.load(text)];
|
|
1419
|
+
case 1:
|
|
1420
|
+
_j.sent();
|
|
1421
|
+
_j.label = 2;
|
|
1422
|
+
case 2:
|
|
1423
|
+
_j.trys.push([2, 7, 8, 9]);
|
|
1424
|
+
_d = __values(Object.entries(testCase.seed || {})), _e = _d.next();
|
|
1425
|
+
_j.label = 3;
|
|
1426
|
+
case 3:
|
|
1427
|
+
if (!!_e.done) return [3 /*break*/, 6];
|
|
1428
|
+
_f = __read(_e.value, 2), file = _f[0], contents = _f[1];
|
|
1429
|
+
if (!(typeof contents !== 'string')) return [3 /*break*/, 5];
|
|
1430
|
+
return [4 /*yield*/, runtime.driveStore.write(file, contents)];
|
|
1431
|
+
case 4:
|
|
1432
|
+
_j.sent();
|
|
1433
|
+
_j.label = 5;
|
|
1434
|
+
case 5:
|
|
1435
|
+
_e = _d.next();
|
|
1436
|
+
return [3 /*break*/, 3];
|
|
1437
|
+
case 6: return [3 /*break*/, 9];
|
|
1438
|
+
case 7:
|
|
1439
|
+
e_3_1 = _j.sent();
|
|
1440
|
+
e_3 = { error: e_3_1 };
|
|
1441
|
+
return [3 /*break*/, 9];
|
|
1442
|
+
case 8:
|
|
1443
|
+
try {
|
|
1444
|
+
if (_e && !_e.done && (_h = _d.return)) _h.call(_d);
|
|
1445
|
+
}
|
|
1446
|
+
finally { if (e_3) throw e_3.error; }
|
|
1447
|
+
return [7 /*endfinally*/];
|
|
1448
|
+
case 9: return [2 /*return*/];
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
}); };
|
|
1452
|
+
var _loop_1 = function (testCase) {
|
|
1453
|
+
it(testCase.id, function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1454
|
+
var worker, _a, requestPath, init, response, _b, want, _c, _d, _e;
|
|
1455
|
+
return __generator(this, function (_f) {
|
|
1456
|
+
switch (_f.label) {
|
|
1457
|
+
case 0:
|
|
1458
|
+
worker = loadWorker();
|
|
1459
|
+
return [4 /*yield*/, seedDrive(worker.runtime, testCase)];
|
|
1460
|
+
case 1:
|
|
1461
|
+
_f.sent();
|
|
1462
|
+
_a = httpFor(testCase), requestPath = _a.path, init = _a.init;
|
|
1463
|
+
return [4 /*yield*/, worker.dispatch(requestPath, init)];
|
|
1464
|
+
case 2:
|
|
1465
|
+
response = _f.sent();
|
|
1466
|
+
expect(response.status).toBe(testCase.expect.status);
|
|
1467
|
+
if (!(testCase.expect.error !== undefined)) return [3 /*break*/, 4];
|
|
1468
|
+
_b = expect;
|
|
1469
|
+
return [4 /*yield*/, response.text()];
|
|
1470
|
+
case 3:
|
|
1471
|
+
_b.apply(void 0, [_f.sent()]).toBe(testCase.expect.error);
|
|
1472
|
+
_f.label = 4;
|
|
1473
|
+
case 4:
|
|
1474
|
+
want = testCase.expect.contents;
|
|
1475
|
+
if (!(typeof want === 'string')) return [3 /*break*/, 6];
|
|
1476
|
+
_c = expect;
|
|
1477
|
+
return [4 /*yield*/, response.text()];
|
|
1478
|
+
case 5:
|
|
1479
|
+
_c.apply(void 0, [_f.sent()]).toBe(want);
|
|
1480
|
+
return [3 /*break*/, 8];
|
|
1481
|
+
case 6:
|
|
1482
|
+
if (!(want !== undefined)) return [3 /*break*/, 8];
|
|
1483
|
+
_d = expect;
|
|
1484
|
+
_e = Uint8Array.bind;
|
|
1485
|
+
return [4 /*yield*/, response.arrayBuffer()];
|
|
1486
|
+
case 7:
|
|
1487
|
+
_d.apply(void 0, [new (_e.apply(Uint8Array, [void 0, _f.sent()]))()]).toEqual(want);
|
|
1488
|
+
_f.label = 8;
|
|
1489
|
+
case 8: return [2 /*return*/];
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
}); });
|
|
1493
|
+
};
|
|
1494
|
+
try {
|
|
1495
|
+
for (var DRIVE_CORPUS_1 = __values(api_1.DRIVE_CORPUS), DRIVE_CORPUS_1_1 = DRIVE_CORPUS_1.next(); !DRIVE_CORPUS_1_1.done; DRIVE_CORPUS_1_1 = DRIVE_CORPUS_1.next()) {
|
|
1496
|
+
var testCase = DRIVE_CORPUS_1_1.value;
|
|
1497
|
+
_loop_1(testCase);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
1501
|
+
finally {
|
|
1502
|
+
try {
|
|
1503
|
+
if (DRIVE_CORPUS_1_1 && !DRIVE_CORPUS_1_1.done && (_a = DRIVE_CORPUS_1.return)) _a.call(DRIVE_CORPUS_1);
|
|
1504
|
+
}
|
|
1505
|
+
finally { if (e_1) throw e_1.error; }
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1288
1508
|
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@sasjs/cli",
|
|
3
3
|
"description": "Command line interface for SASjs",
|
|
4
4
|
"engines": {
|
|
5
|
-
"node": ">=
|
|
5
|
+
"node": "^22.14.0 || >=24.10.0"
|
|
6
6
|
},
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"files": [
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@sasjs/adapter": "^5.1.3",
|
|
74
|
+
"@sasjs/api": "0.2.1",
|
|
74
75
|
"@sasjs/core": "^6.1.1",
|
|
75
76
|
"@sasjs/lint": "5.0.0",
|
|
76
77
|
"@sasjs/utils": "^4.7.0",
|
|
@@ -123,5 +124,5 @@
|
|
|
123
124
|
"ws": "8.21.0",
|
|
124
125
|
"nanoid": "3.3.18"
|
|
125
126
|
},
|
|
126
|
-
"version": "6.
|
|
127
|
+
"version": "6.12.0"
|
|
127
128
|
}
|