@sasjs/cli 6.11.0 → 6.12.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.
@@ -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 that would produce a blocked extension', function () { return __awaiter(void 0, void 0, void 0, function () {
1093
- var dispatch, form, toBlocked, fromBlocked;
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
- // DELETE and PATCH run the same check on the path they are given, so a file
1112
- // renamed into a blocked name could not be removed or replaced afterwards
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
- // DELETE and PATCH run the same check on the path they are given, so a file
1117
- // renamed into a blocked name could not be removed or replaced afterwards
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
- return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/rename', {
1120
- method: 'POST',
1121
- headers: { 'content-type': 'application/json' },
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
- fromBlocked = _a.sent();
1129
- expect(fromBlocked.status).toBe(400);
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
- expect(under.status).toBe(400);
1184
- expect(under.body).toBe('An ancestor of the path is a file: /data/a.csv');
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": ">=22"
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.11.0"
127
+ "version": "6.12.1"
127
128
  }