@sasjs/cli 6.30.1 → 6.32.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/api-console.css +365 -0
- package/build/commands/build/internal/mockSite/assets/api-console.js +797 -0
- package/build/commands/build/internal/mockSite/assets/api-index.html +83 -0
- package/build/commands/build/internal/mockSite/assets/sw.js +5 -4
- package/build/commands/build/internal/mockSite/mockSite.d.ts +2 -0
- package/build/commands/build/internal/mockSite/mockSite.js +84 -7
- package/build/commands/build/internal/mockSite/spec/apiConsole.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/apiConsole.spec.js +1437 -0
- package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.js +415 -0
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +84 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +28 -0
- package/build/commands/version/spec/version.spec.js +11 -1
- package/build/commands/version/version.d.ts +5 -3
- package/build/commands/version/version.js +17 -7
- package/build/types/mockWebBasePath.d.ts +9 -0
- package/package.json +13 -32
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
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;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
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 __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
55
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
56
|
+
if (ar || !(i in from)) {
|
|
57
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
58
|
+
ar[i] = from[i];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
62
|
+
};
|
|
63
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
64
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
65
|
+
};
|
|
66
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
67
|
+
/**
|
|
68
|
+
* @jest-environment jsdom
|
|
69
|
+
* @jest-environment-options {"url": "http://localhost/demo/api-console/"}
|
|
70
|
+
*/
|
|
71
|
+
var fs_1 = __importDefault(require("fs"));
|
|
72
|
+
var path_1 = __importDefault(require("path"));
|
|
73
|
+
/**
|
|
74
|
+
* The console under a base path.
|
|
75
|
+
*
|
|
76
|
+
* `mockWebBasePath` is a supported option (a site published at `/demo`), and the
|
|
77
|
+
* console works out its root from the worker it registered - `<base>/sw.js` - with
|
|
78
|
+
* its own URL as the fallback. This file runs the page at `/demo/api-console/`,
|
|
79
|
+
* where a wrong derivation is silent: the page still renders, and every call goes
|
|
80
|
+
* to a path the host does not answer.
|
|
81
|
+
*/
|
|
82
|
+
var assets = path_1.default.join(__dirname, '..', 'assets');
|
|
83
|
+
var page = function () { return fs_1.default.readFileSync(path_1.default.join(assets, 'api-index.html'), 'utf8'); };
|
|
84
|
+
var bodyOf = function (html) {
|
|
85
|
+
return html.slice(html.indexOf('<body>') + 6, html.lastIndexOf('</body>'));
|
|
86
|
+
};
|
|
87
|
+
var jsonResponse = function (body) { return ({
|
|
88
|
+
ok: true,
|
|
89
|
+
status: 200,
|
|
90
|
+
statusText: 'OK',
|
|
91
|
+
headers: { get: function () { return 'application/json'; } },
|
|
92
|
+
json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
93
|
+
return [2 /*return*/, body];
|
|
94
|
+
}); }); },
|
|
95
|
+
text: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
96
|
+
return [2 /*return*/, JSON.stringify(body)];
|
|
97
|
+
}); }); }
|
|
98
|
+
}); };
|
|
99
|
+
var operations = [
|
|
100
|
+
{
|
|
101
|
+
id: 'GET /SASjsApi/user',
|
|
102
|
+
method: 'GET',
|
|
103
|
+
path: '/SASjsApi/user',
|
|
104
|
+
tag: 'User',
|
|
105
|
+
summary: 'List all users',
|
|
106
|
+
params: [],
|
|
107
|
+
requestBodies: [],
|
|
108
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
109
|
+
implemented: true
|
|
110
|
+
}
|
|
111
|
+
];
|
|
112
|
+
var settle = function (check) { return __awaiter(void 0, void 0, void 0, function () {
|
|
113
|
+
var i;
|
|
114
|
+
return __generator(this, function (_a) {
|
|
115
|
+
switch (_a.label) {
|
|
116
|
+
case 0:
|
|
117
|
+
i = 0;
|
|
118
|
+
_a.label = 1;
|
|
119
|
+
case 1:
|
|
120
|
+
if (!(i < 400)) return [3 /*break*/, 4];
|
|
121
|
+
if (check())
|
|
122
|
+
return [2 /*return*/];
|
|
123
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
124
|
+
case 2:
|
|
125
|
+
_a.sent();
|
|
126
|
+
_a.label = 3;
|
|
127
|
+
case 3:
|
|
128
|
+
i++;
|
|
129
|
+
return [3 /*break*/, 1];
|
|
130
|
+
case 4: throw new Error('the page did not reach the state the case expected');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}); };
|
|
134
|
+
describe('the API console under a base path', function () {
|
|
135
|
+
afterEach(function () {
|
|
136
|
+
delete navigator.serviceWorker;
|
|
137
|
+
});
|
|
138
|
+
it('takes the root from the worker that covers it, not from its own folder', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
139
|
+
var realCreate, sw, asked;
|
|
140
|
+
return __generator(this, function (_a) {
|
|
141
|
+
switch (_a.label) {
|
|
142
|
+
case 0:
|
|
143
|
+
// the shapes where the two disagree are the only ones that prove the worker
|
|
144
|
+
// is read at all: the site's worker at `/demo/sw.js` controlling a console
|
|
145
|
+
// two folders down still names `/demo/`, which the folder alone cannot
|
|
146
|
+
document.body.innerHTML = bodyOf(page());
|
|
147
|
+
document.head.innerHTML =
|
|
148
|
+
'<meta name="sasjs-mock-version" content="3.0.0" />';
|
|
149
|
+
global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
|
|
150
|
+
return __generator(this, function (_a) {
|
|
151
|
+
return [2 /*return*/, String(url).includes('operations.json')
|
|
152
|
+
? jsonResponse(operations)
|
|
153
|
+
: jsonResponse({ format: 1, users: [] })];
|
|
154
|
+
});
|
|
155
|
+
}); });
|
|
156
|
+
realCreate = document.createElement.bind(document);
|
|
157
|
+
jest.spyOn(document, 'createElement').mockImplementation(function (tag) {
|
|
158
|
+
var node = realCreate(tag);
|
|
159
|
+
if (String(tag).toLowerCase() === 'script')
|
|
160
|
+
setTimeout(function () { return node.dispatchEvent(new Event('load')); }, 0);
|
|
161
|
+
return node;
|
|
162
|
+
});
|
|
163
|
+
sw = {
|
|
164
|
+
controller: { scriptURL: 'http://localhost/demo/sw.js' },
|
|
165
|
+
register: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
166
|
+
return [2 /*return*/, ({})];
|
|
167
|
+
}); }); }),
|
|
168
|
+
addEventListener: jest.fn()
|
|
169
|
+
};
|
|
170
|
+
Object.defineProperty(navigator, 'serviceWorker', {
|
|
171
|
+
value: sw,
|
|
172
|
+
configurable: true
|
|
173
|
+
});
|
|
174
|
+
history.replaceState({}, '', '/demo/tools/api-console/');
|
|
175
|
+
jest.resetModules();
|
|
176
|
+
require('../assets/api-console.js');
|
|
177
|
+
return [4 /*yield*/, settle(function () {
|
|
178
|
+
return global.fetch.mock.calls.some(function (call) {
|
|
179
|
+
return String(call[0]).includes('identity.json');
|
|
180
|
+
});
|
|
181
|
+
})];
|
|
182
|
+
case 1:
|
|
183
|
+
_a.sent();
|
|
184
|
+
asked = global.fetch.mock.calls.map(function (call) {
|
|
185
|
+
return String(call[0]);
|
|
186
|
+
});
|
|
187
|
+
// the site's root, not the folder two levels up from the page
|
|
188
|
+
expect(asked).toContain('/demo/host/identity.json');
|
|
189
|
+
expect(asked.some(function (url) { return url.startsWith('/demo/tools/'); })).toBe(false);
|
|
190
|
+
history.replaceState({}, '', '/demo/tools/api-console/');
|
|
191
|
+
return [2 /*return*/];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}); });
|
|
195
|
+
it('ignores a worker that belongs to another site', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
196
|
+
var realCreate, sw, asked;
|
|
197
|
+
return __generator(this, function (_a) {
|
|
198
|
+
switch (_a.label) {
|
|
199
|
+
case 0:
|
|
200
|
+
// a page can be controlled by some other site's worker; its `sw.js` names a
|
|
201
|
+
// root that has nothing to do with this console, so the page's own folder has
|
|
202
|
+
// to win
|
|
203
|
+
document.body.innerHTML = bodyOf(page());
|
|
204
|
+
document.head.innerHTML =
|
|
205
|
+
'<meta name="sasjs-mock-version" content="3.0.0" />';
|
|
206
|
+
global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
|
|
207
|
+
return __generator(this, function (_a) {
|
|
208
|
+
return [2 /*return*/, String(url).includes('operations.json')
|
|
209
|
+
? jsonResponse(operations)
|
|
210
|
+
: jsonResponse({ format: 1, users: [] })];
|
|
211
|
+
});
|
|
212
|
+
}); });
|
|
213
|
+
realCreate = document.createElement.bind(document);
|
|
214
|
+
jest.spyOn(document, 'createElement').mockImplementation(function (tag) {
|
|
215
|
+
var node = realCreate(tag);
|
|
216
|
+
if (String(tag).toLowerCase() === 'script')
|
|
217
|
+
setTimeout(function () { return node.dispatchEvent(new Event('load')); }, 0);
|
|
218
|
+
return node;
|
|
219
|
+
});
|
|
220
|
+
sw = {
|
|
221
|
+
controller: { scriptURL: 'http://localhost/other/sw.js' },
|
|
222
|
+
register: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
223
|
+
return [2 /*return*/, ({})];
|
|
224
|
+
}); }); }),
|
|
225
|
+
addEventListener: jest.fn()
|
|
226
|
+
};
|
|
227
|
+
Object.defineProperty(navigator, 'serviceWorker', {
|
|
228
|
+
value: sw,
|
|
229
|
+
configurable: true
|
|
230
|
+
});
|
|
231
|
+
history.replaceState({}, '', '/demo/api-console/');
|
|
232
|
+
jest.resetModules();
|
|
233
|
+
require('../assets/api-console.js');
|
|
234
|
+
return [4 /*yield*/, settle(function () {
|
|
235
|
+
return global.fetch.mock.calls.some(function (call) {
|
|
236
|
+
return String(call[0]).includes('identity.json');
|
|
237
|
+
});
|
|
238
|
+
})];
|
|
239
|
+
case 1:
|
|
240
|
+
_a.sent();
|
|
241
|
+
asked = global.fetch.mock.calls.map(function (call) {
|
|
242
|
+
return String(call[0]);
|
|
243
|
+
});
|
|
244
|
+
expect(asked).toContain('/demo/host/identity.json');
|
|
245
|
+
expect(asked.some(function (url) { return url.startsWith('/other/'); })).toBe(false);
|
|
246
|
+
history.replaceState({}, '', '/demo/api-console/');
|
|
247
|
+
return [2 /*return*/];
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}); });
|
|
251
|
+
it('finds the folder from a URL without its trailing slash', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
252
|
+
var realCreate, sw, asked;
|
|
253
|
+
return __generator(this, function (_a) {
|
|
254
|
+
switch (_a.label) {
|
|
255
|
+
case 0:
|
|
256
|
+
// `/demo/api` is the shape a person types, and the page used to read that as
|
|
257
|
+
// the origin root: every call went to /SASjsApi/... and the worker to /sw.js
|
|
258
|
+
history.replaceState({}, '', '/demo/api-console');
|
|
259
|
+
expect(location.pathname).toBe('/demo/api-console');
|
|
260
|
+
document.body.innerHTML = bodyOf(page());
|
|
261
|
+
document.head.innerHTML =
|
|
262
|
+
'<meta name="sasjs-mock-version" content="2.0.0" />';
|
|
263
|
+
global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
|
|
264
|
+
return __generator(this, function (_a) {
|
|
265
|
+
return [2 /*return*/, String(url).includes('operations.json')
|
|
266
|
+
? jsonResponse(operations)
|
|
267
|
+
: jsonResponse({ format: 1, users: [] })];
|
|
268
|
+
});
|
|
269
|
+
}); });
|
|
270
|
+
realCreate = document.createElement.bind(document);
|
|
271
|
+
jest.spyOn(document, 'createElement').mockImplementation(function (tag) {
|
|
272
|
+
var node = realCreate(tag);
|
|
273
|
+
if (String(tag).toLowerCase() === 'script')
|
|
274
|
+
setTimeout(function () { return node.dispatchEvent(new Event('load')); }, 0);
|
|
275
|
+
return node;
|
|
276
|
+
});
|
|
277
|
+
sw = {
|
|
278
|
+
controller: {},
|
|
279
|
+
register: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
280
|
+
return [2 /*return*/, ({})];
|
|
281
|
+
}); }); }),
|
|
282
|
+
addEventListener: jest.fn()
|
|
283
|
+
};
|
|
284
|
+
Object.defineProperty(navigator, 'serviceWorker', {
|
|
285
|
+
value: sw,
|
|
286
|
+
configurable: true
|
|
287
|
+
});
|
|
288
|
+
jest.resetModules();
|
|
289
|
+
require('../assets/api-console.js');
|
|
290
|
+
return [4 /*yield*/, settle(function () { return sw.register.mock.calls.length === 1; })];
|
|
291
|
+
case 1:
|
|
292
|
+
_a.sent();
|
|
293
|
+
expect(sw.register.mock.calls[0][0]).toContain('/demo/sw.js?v=2.0.0');
|
|
294
|
+
expect(sw.register.mock.calls[0][1]).toEqual(expect.objectContaining({ scope: '/demo/' }));
|
|
295
|
+
return [4 /*yield*/, settle(function () { return document.querySelectorAll('#routes .route').length > 0; })];
|
|
296
|
+
case 2:
|
|
297
|
+
_a.sent();
|
|
298
|
+
asked = global.fetch.mock.calls.map(function (call) {
|
|
299
|
+
return String(call[0]);
|
|
300
|
+
});
|
|
301
|
+
expect(asked).toContain('/demo/host/identity.json');
|
|
302
|
+
history.replaceState({}, '', '/demo/api-console/');
|
|
303
|
+
return [2 /*return*/];
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}); });
|
|
307
|
+
it('registers the worker and calls the API under the folder the site lives in', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
308
|
+
var calls, realCreate, sw, errors, errorSpy, warnSpy, asked;
|
|
309
|
+
return __generator(this, function (_a) {
|
|
310
|
+
switch (_a.label) {
|
|
311
|
+
case 0:
|
|
312
|
+
document.body.innerHTML = bodyOf(page());
|
|
313
|
+
document.head.innerHTML =
|
|
314
|
+
'<meta name="sasjs-mock-version" content="2.0.0" />';
|
|
315
|
+
calls = [];
|
|
316
|
+
global.fetch = jest.fn(function (url_1) {
|
|
317
|
+
var args_1 = [];
|
|
318
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
319
|
+
args_1[_i - 1] = arguments[_i];
|
|
320
|
+
}
|
|
321
|
+
return __awaiter(void 0, __spreadArray([url_1], __read(args_1), false), void 0, function (url, init) {
|
|
322
|
+
if (init === void 0) { init = {}; }
|
|
323
|
+
return __generator(this, function (_a) {
|
|
324
|
+
if (String(url).includes('operations.json'))
|
|
325
|
+
return [2 /*return*/, jsonResponse(operations)];
|
|
326
|
+
if (String(url).includes('identity.json'))
|
|
327
|
+
return [2 /*return*/, jsonResponse({ format: 1, users: [] })];
|
|
328
|
+
calls.push({ url: String(url), init: init });
|
|
329
|
+
return [2 /*return*/, jsonResponse([])];
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
realCreate = document.createElement.bind(document);
|
|
334
|
+
jest.spyOn(document, 'createElement').mockImplementation(function (tag) {
|
|
335
|
+
var node = realCreate(tag);
|
|
336
|
+
if (String(tag).toLowerCase() === 'script')
|
|
337
|
+
setTimeout(function () { return node.dispatchEvent(new Event('load')); }, 0);
|
|
338
|
+
return node;
|
|
339
|
+
});
|
|
340
|
+
sw = {
|
|
341
|
+
controller: {},
|
|
342
|
+
register: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
343
|
+
return [2 /*return*/, ({})];
|
|
344
|
+
}); }); }),
|
|
345
|
+
addEventListener: jest.fn()
|
|
346
|
+
};
|
|
347
|
+
Object.defineProperty(navigator, 'serviceWorker', {
|
|
348
|
+
value: sw,
|
|
349
|
+
configurable: true
|
|
350
|
+
});
|
|
351
|
+
errors = [];
|
|
352
|
+
errorSpy = jest
|
|
353
|
+
.spyOn(console, 'error')
|
|
354
|
+
.mockImplementation(function () {
|
|
355
|
+
var args = [];
|
|
356
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
357
|
+
args[_i] = arguments[_i];
|
|
358
|
+
}
|
|
359
|
+
return errors.push(args);
|
|
360
|
+
});
|
|
361
|
+
warnSpy = jest
|
|
362
|
+
.spyOn(console, 'warn')
|
|
363
|
+
.mockImplementation(function () {
|
|
364
|
+
var args = [];
|
|
365
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
366
|
+
args[_i] = arguments[_i];
|
|
367
|
+
}
|
|
368
|
+
return errors.push(args);
|
|
369
|
+
});
|
|
370
|
+
jest.resetModules();
|
|
371
|
+
require('../assets/api-console.js');
|
|
372
|
+
return [4 /*yield*/, settle(function () { return sw.register.mock.calls.length === 1; }).catch(function (e) {
|
|
373
|
+
throw new Error(e.message +
|
|
374
|
+
' | errors: ' +
|
|
375
|
+
JSON.stringify(errors) +
|
|
376
|
+
' | routes: ' +
|
|
377
|
+
(document.getElementById('routes') || {}).textContent);
|
|
378
|
+
})];
|
|
379
|
+
case 1:
|
|
380
|
+
_a.sent();
|
|
381
|
+
errorSpy.mockRestore();
|
|
382
|
+
warnSpy.mockRestore();
|
|
383
|
+
expect(sw.register.mock.calls[0][0]).toContain('/demo/sw.js?v=2.0.0');
|
|
384
|
+
expect(sw.register.mock.calls[0][1]).toEqual(expect.objectContaining({ scope: '/demo/' }));
|
|
385
|
+
// the page's data comes from the same folder, not from the origin root
|
|
386
|
+
return [4 /*yield*/, settle(function () { return document.querySelectorAll('#routes .route').length > 0; }).catch(function (e) {
|
|
387
|
+
throw new Error(e.message +
|
|
388
|
+
' | routes: ' +
|
|
389
|
+
JSON.stringify((document.getElementById('routes') || {}).textContent) +
|
|
390
|
+
' | fetched: ' +
|
|
391
|
+
JSON.stringify(global.fetch.mock.calls.map(function (c) { return String(c[0]); })) +
|
|
392
|
+
' | errors: ' +
|
|
393
|
+
JSON.stringify(errors));
|
|
394
|
+
})];
|
|
395
|
+
case 2:
|
|
396
|
+
// the page's data comes from the same folder, not from the origin root
|
|
397
|
+
_a.sent();
|
|
398
|
+
asked = global.fetch.mock.calls.map(function (call) {
|
|
399
|
+
return String(call[0]);
|
|
400
|
+
});
|
|
401
|
+
// the page's own table is fetched relative to the page, so it follows the
|
|
402
|
+
// folder without any substitution; the host files are addressed absolutely
|
|
403
|
+
expect(asked).toContain('operations.json');
|
|
404
|
+
expect(asked).toContain('/demo/host/identity.json');
|
|
405
|
+
__spreadArray([], __read(document.querySelectorAll('#routes .route')), false)[0].click();
|
|
406
|
+
document.getElementById('send').click();
|
|
407
|
+
return [4 /*yield*/, settle(function () { return calls.length > 0; })];
|
|
408
|
+
case 3:
|
|
409
|
+
_a.sent();
|
|
410
|
+
expect(calls[0].url).toBe('/demo/SASjsApi/user');
|
|
411
|
+
return [2 /*return*/];
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}); });
|
|
415
|
+
});
|
|
@@ -1042,6 +1042,90 @@ describe('browser-hosted build', function () {
|
|
|
1042
1042
|
}
|
|
1043
1043
|
});
|
|
1044
1044
|
}); });
|
|
1045
|
+
/**
|
|
1046
|
+
* The API console is generated, not written.
|
|
1047
|
+
*
|
|
1048
|
+
* It is the page that lets a visitor try the API a mock site is a mock of, so
|
|
1049
|
+
* it has to list the operations the package actually answers - and be
|
|
1050
|
+
* generated from the same route table the worker dispatches, or the page and
|
|
1051
|
+
* the host drift.
|
|
1052
|
+
*/
|
|
1053
|
+
it('ships an API console built from the route table', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1054
|
+
var result, folder, shipped, sw, operations, createUser, html, version, appHtml, loader;
|
|
1055
|
+
return __generator(this, function (_a) {
|
|
1056
|
+
switch (_a.label) {
|
|
1057
|
+
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
1058
|
+
case 1:
|
|
1059
|
+
result = _a.sent();
|
|
1060
|
+
expect(result.apiConsole).toBe(true);
|
|
1061
|
+
folder = path_1.default.join(out(), 'api-console');
|
|
1062
|
+
shipped = fs_1.default.readdirSync(folder).sort();
|
|
1063
|
+
// every file the folder holds, and the worker's passthrough list is that same
|
|
1064
|
+
// list: a second copy in sw.js would let a new file be answered as an API path
|
|
1065
|
+
expect(shipped).toEqual([
|
|
1066
|
+
'console.css',
|
|
1067
|
+
'console.js',
|
|
1068
|
+
'index.html',
|
|
1069
|
+
'operations.json'
|
|
1070
|
+
]);
|
|
1071
|
+
sw = fs_1.default.readFileSync(path_1.default.join(out(), 'sw.js'), 'utf8');
|
|
1072
|
+
expect(sw).not.toContain('API_CONSOLE_FILES');
|
|
1073
|
+
operations = JSON.parse(fs_1.default.readFileSync(path_1.default.join(folder, 'operations.json'), 'utf8'));
|
|
1074
|
+
expect(operations.length).toBeGreaterThan(20);
|
|
1075
|
+
createUser = operations.find(function (operation) { return operation.id === 'POST /SASjsApi/user'; });
|
|
1076
|
+
expect(createUser).toBeDefined();
|
|
1077
|
+
expect(createUser.params.map(function (parameter) { return parameter.name; })).toEqual(expect.arrayContaining(['username', 'password']));
|
|
1078
|
+
expect(createUser.method).toBe('POST');
|
|
1079
|
+
html = fs_1.default.readFileSync(path_1.default.join(folder, 'index.html'), 'utf8');
|
|
1080
|
+
expect(html).toContain('<script src="console.js"></script>');
|
|
1081
|
+
expect(html).not.toMatch(/<script(?![^>]*\ssrc=)[^>]*>[\s\S]*?<\/script>/);
|
|
1082
|
+
version = /<meta name="sasjs-mock-version" content="([^"]+)" \/>/.exec(html);
|
|
1083
|
+
expect(version).not.toBeNull();
|
|
1084
|
+
appHtml = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
1085
|
+
loader = /host\/boot\.js\?v=([^"']+)/.exec(appHtml);
|
|
1086
|
+
expect(loader).not.toBeNull();
|
|
1087
|
+
expect(version[1]).toBe(loader[1]);
|
|
1088
|
+
return [2 /*return*/];
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
}); });
|
|
1092
|
+
it('leaves the console out when the site ships its own console folder, and says so', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1093
|
+
var own, result;
|
|
1094
|
+
return __generator(this, function (_a) {
|
|
1095
|
+
switch (_a.label) {
|
|
1096
|
+
case 0:
|
|
1097
|
+
own = path_1.default.join(root, 'client', 'dist', 'api-console');
|
|
1098
|
+
fs_1.default.mkdirSync(own, { recursive: true });
|
|
1099
|
+
fs_1.default.writeFileSync(path_1.default.join(own, 'index.html'), '<p>ours</p>');
|
|
1100
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())
|
|
1101
|
+
// the project's file is not replaced, and the loss is reported rather than
|
|
1102
|
+
// silent: the build has no way to know which of the two the site wants
|
|
1103
|
+
];
|
|
1104
|
+
case 1:
|
|
1105
|
+
result = _a.sent();
|
|
1106
|
+
// the project's file is not replaced, and the loss is reported rather than
|
|
1107
|
+
// silent: the build has no way to know which of the two the site wants
|
|
1108
|
+
expect(result.apiConsole).toBe(false);
|
|
1109
|
+
expect(fs_1.default.readFileSync(path_1.default.join(out(), 'api-console', 'index.html'), 'utf8')).toBe('<p>ours</p>');
|
|
1110
|
+
expect(fs_1.default.existsSync(path_1.default.join(out(), 'api-console', 'console.js'))).toBe(false);
|
|
1111
|
+
expect(process.logger.warn).toHaveBeenCalledWith(expect.stringContaining('api-console/index.html'));
|
|
1112
|
+
return [2 /*return*/];
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
}); });
|
|
1116
|
+
it('leaves the console out when the target asks for the app alone', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1117
|
+
var result;
|
|
1118
|
+
return __generator(this, function (_a) {
|
|
1119
|
+
switch (_a.label) {
|
|
1120
|
+
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target({ mockApi: false }))];
|
|
1121
|
+
case 1:
|
|
1122
|
+
result = _a.sent();
|
|
1123
|
+
expect(result.apiConsole).toBe(false);
|
|
1124
|
+
expect(fs_1.default.existsSync(path_1.default.join(out(), 'api-console'))).toBe(false);
|
|
1125
|
+
return [2 /*return*/];
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
}); });
|
|
1045
1129
|
it('ships a recorded SAS session when the project has one', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1046
1130
|
var recording, result;
|
|
1047
1131
|
var _a;
|
|
@@ -2561,6 +2561,34 @@ describe('service worker host', function () {
|
|
|
2561
2561
|
}
|
|
2562
2562
|
});
|
|
2563
2563
|
}); });
|
|
2564
|
+
/**
|
|
2565
|
+
* The console's own files are the page, not an API call.
|
|
2566
|
+
*
|
|
2567
|
+
* `/api/...` is the API's short spelling, so the worker answers it - except for
|
|
2568
|
+
* `/api` itself, which a site's own application may answer.
|
|
2569
|
+
*/
|
|
2570
|
+
it('leaves /api to the site, and answers the short spelling below it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2571
|
+
var worker, response;
|
|
2572
|
+
return __generator(this, function (_a) {
|
|
2573
|
+
switch (_a.label) {
|
|
2574
|
+
case 0:
|
|
2575
|
+
worker = seeded();
|
|
2576
|
+
// a site's own application can answer /api (the server interface does), so the
|
|
2577
|
+
// worker hands it on; `/api/<something>` is an API call on the short spelling
|
|
2578
|
+
expect(worker.dispatch('/api')).toBeUndefined();
|
|
2579
|
+
expect(worker.dispatch('/api/')).toBeUndefined();
|
|
2580
|
+
// the console has a folder of its own, which is not an API prefix: the static
|
|
2581
|
+
// host serves it and the worker never sees it
|
|
2582
|
+
expect(worker.dispatch('/api-console/console.js')).toBeUndefined();
|
|
2583
|
+
expect(worker.dispatch('/api-console/operations.json')).toBeUndefined();
|
|
2584
|
+
return [4 /*yield*/, worker.dispatch('/api/user')];
|
|
2585
|
+
case 1:
|
|
2586
|
+
response = _a.sent();
|
|
2587
|
+
expect(response.status).toBe(200);
|
|
2588
|
+
return [2 /*return*/];
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
}); });
|
|
2564
2592
|
it('answers 404 for a drive file the drive does not hold', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2565
2593
|
var dispatch, response, _a;
|
|
2566
2594
|
return __generator(this, function (_b) {
|
|
@@ -75,6 +75,11 @@ describe('versionMessage', function () {
|
|
|
75
75
|
writePackage(root, '6.4.7-alpha.1');
|
|
76
76
|
expect((0, version_1.versionMessage)(root)).toEqual("You are using a linked version of SASjs CLI (v6.4.7-alpha.1) running from sources at ".concat(root));
|
|
77
77
|
});
|
|
78
|
+
it('leaves the placeholder version out of a source checkout', function () {
|
|
79
|
+
var root = path_1.default.join(tempRoot(), 'cli');
|
|
80
|
+
writePackage(root, '0.0.0');
|
|
81
|
+
expect((0, version_1.versionMessage)(root)).toEqual("You are using a linked version of SASjs CLI running from sources at ".concat(root));
|
|
82
|
+
});
|
|
78
83
|
it('leaves the version out of a source checkout that has none', function () {
|
|
79
84
|
var root = path_1.default.join(tempRoot(), 'cli');
|
|
80
85
|
writePackage(root, undefined);
|
|
@@ -92,6 +97,11 @@ describe('versionMessage', function () {
|
|
|
92
97
|
it('throws when an installed package carries no version', function () {
|
|
93
98
|
var root = packageRoot();
|
|
94
99
|
writePackage(root, undefined);
|
|
95
|
-
expect(function () { return (0, version_1.versionMessage)(root); }).toThrow(/has no version in its package.json
|
|
100
|
+
expect(function () { return (0, version_1.versionMessage)(root); }).toThrow(/has no version in its package.json\./);
|
|
101
|
+
});
|
|
102
|
+
it('names the placeholder when an installed package was never stamped', function () {
|
|
103
|
+
var root = packageRoot();
|
|
104
|
+
writePackage(root, '0.0.0');
|
|
105
|
+
expect(function () { return (0, version_1.versionMessage)(root); }).toThrow(/carries the placeholder version 0\.0\.0, so it was never stamped from a tag/);
|
|
96
106
|
});
|
|
97
107
|
});
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* Reads the version from the package.json of the package that is running, so
|
|
5
5
|
* it works for every install shape - global, project-local, `npx`, a temp
|
|
6
6
|
* prefix, or embedded as a dependency. A package root outside a
|
|
7
|
-
* `node_modules` folder is a source checkout (an `npm link`, or a local
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* `node_modules` folder is a source checkout (an `npm link`, or a local build),
|
|
8
|
+
* and is reported as such. Its package.json carries the placeholder version
|
|
9
|
+
* (`0.0.0`): the version is the git tag, stamped in when the package is
|
|
10
|
+
* published, so from sources there is nothing to report and the version is left
|
|
11
|
+
* out.
|
|
10
12
|
*
|
|
11
13
|
* @param packageRoot - the folder holding the package.json of the running CLI.
|
|
12
14
|
*/
|
|
@@ -49,9 +49,11 @@ var path_1 = __importDefault(require("path"));
|
|
|
49
49
|
* Reads the version from the package.json of the package that is running, so
|
|
50
50
|
* it works for every install shape - global, project-local, `npx`, a temp
|
|
51
51
|
* prefix, or embedded as a dependency. A package root outside a
|
|
52
|
-
* `node_modules` folder is a source checkout (an `npm link`, or a local
|
|
53
|
-
*
|
|
54
|
-
*
|
|
52
|
+
* `node_modules` folder is a source checkout (an `npm link`, or a local build),
|
|
53
|
+
* and is reported as such. Its package.json carries the placeholder version
|
|
54
|
+
* (`0.0.0`): the version is the git tag, stamped in when the package is
|
|
55
|
+
* published, so from sources there is nothing to report and the version is left
|
|
56
|
+
* out.
|
|
55
57
|
*
|
|
56
58
|
* @param packageRoot - the folder holding the package.json of the running CLI.
|
|
57
59
|
*/
|
|
@@ -65,13 +67,21 @@ var versionMessage = function (packageRoot) {
|
|
|
65
67
|
"Reinstall the CLI - 'npm i -g @sasjs/cli@latest'.");
|
|
66
68
|
}
|
|
67
69
|
var fromSources = !packageRoot.split(path_1.default.sep).includes('node_modules');
|
|
70
|
+
// the placeholder means "not stamped from a tag" - the same thing as absent
|
|
71
|
+
var stated = version && !/^0\.0\.0(-|$)/.test(version) ? version : undefined;
|
|
68
72
|
if (fromSources) {
|
|
69
|
-
return
|
|
70
|
-
? "You are using a linked version of SASjs CLI (v".concat(
|
|
73
|
+
return stated
|
|
74
|
+
? "You are using a linked version of SASjs CLI (v".concat(stated, ") running from sources at ").concat(packageRoot)
|
|
71
75
|
: "You are using a linked version of SASjs CLI running from sources at ".concat(packageRoot);
|
|
72
76
|
}
|
|
73
|
-
if (!
|
|
74
|
-
|
|
77
|
+
if (!stated) {
|
|
78
|
+
// two ways to get here, and they mean different things to whoever is reading
|
|
79
|
+
// it: no version field at all, or the placeholder that was never stamped from
|
|
80
|
+
// a tag
|
|
81
|
+
var reason = version
|
|
82
|
+
? "carries the placeholder version ".concat(version, ", so it was never stamped from a tag")
|
|
83
|
+
: 'has no version in its package.json';
|
|
84
|
+
throw new Error("The installed CLI at ".concat(packageRoot, " ").concat(reason, ". ") +
|
|
75
85
|
"Reinstall it - 'npm i -g @sasjs/cli@latest'.");
|
|
76
86
|
}
|
|
77
87
|
return "You are using SASjs CLI v".concat(version);
|
|
@@ -20,5 +20,14 @@ declare module '@sasjs/utils/types/config' {
|
|
|
20
20
|
* slash (`/demo`). Unset means the origin root.
|
|
21
21
|
*/
|
|
22
22
|
mockWebBasePath?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the mock site carries the API console at `<base>/api-console/`.
|
|
25
|
+
*
|
|
26
|
+
* On unless it is set to false: the console is how a demonstration site lets
|
|
27
|
+
* a visitor try the API it is a mock of, and a page nothing links to costs
|
|
28
|
+
* the app nothing. A site whose output must be the app and nothing else
|
|
29
|
+
* turns it off.
|
|
30
|
+
*/
|
|
31
|
+
mockApi?: boolean;
|
|
23
32
|
}
|
|
24
33
|
}
|