@sasjs/cli 6.13.3 → 6.15.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.
@@ -391,6 +391,37 @@ var EXECUTABLE_TYPES = [
391
391
  'text/javascript',
392
392
  'application/javascript'
393
393
  ];
394
+ /**
395
+ * Tells the interface that this page is answered by the browser host.
396
+ *
397
+ * A mock site is the server's own interface running against a service worker,
398
+ * and nothing in the document said so: the API answers the same routes, the same
399
+ * mode and the same run times a desktop server does, so an interface could not
400
+ * tell a visitor what they were looking at. sasjs/server reads the tag and, where
401
+ * it is absent, knows the server answered the page.
402
+ *
403
+ * The insertion is reported when the page has no `<head>`, the way the other two
404
+ * rewrites report markup they could not find - silently leaving the tag off a
405
+ * page the browser host cannot mark is what the reader of the absence rule would
406
+ * misread as "a server answered this". The check for an existing tag is scoped
407
+ * to the head, because a page may mention the tag in a script or a template
408
+ * without carrying it, and a document-wide test would then insert nothing.
409
+ */
410
+ var HOST_TAG = '<meta name="sasjs-host" content="browser" />';
411
+ var HOST_TAG_PATTERN = /<meta\s+name=["']sasjs-host["']/i;
412
+ var markBrowserHost = function (html, warnings) {
413
+ var head = /<head(\s[^>]*)?>/i.exec(html);
414
+ if (!head) {
415
+ warnings.push('no <head> element found - the sasjs-host tag was not added, so the page will read as server-answered');
416
+ return html;
417
+ }
418
+ var start = head.index + head[0].length;
419
+ var close = html.toLowerCase().indexOf('</head>', start);
420
+ var inHead = close < 0 ? html.slice(start) : html.slice(start, close);
421
+ if (HOST_TAG_PATTERN.test(inHead))
422
+ return html;
423
+ return html.slice(0, start) + HOST_TAG + html.slice(start);
424
+ };
394
425
  /**
395
426
  * Points the app at its own origin and replaces its script tags with the boot
396
427
  * loader. Both changes are reported when the expected markup is absent, rather
@@ -399,6 +430,7 @@ var EXECUTABLE_TYPES = [
399
430
  var patchIndexHtml = function (html, version) {
400
431
  var warnings = [];
401
432
  var patched = html;
433
+ patched = markBrowserHost(patched, warnings);
402
434
  if (/<sasjs[^>]*>/i.test(patched)) {
403
435
  patched = patched.replace(/(<sasjs[^>]*?)\sserverurl="[^"]*"/i, '$1 serverurl=""');
404
436
  patched = patched.replace(/(<sasjs[^>]*?)\sloginmechanism="[^"]*"/i, '$1 loginmechanism="Default"');
@@ -367,6 +367,96 @@ describe('browser-hosted build', function () {
367
367
  }
368
368
  });
369
369
  }); });
370
+ it('states in the page that the API is answered here', function () { return __awaiter(void 0, void 0, void 0, function () {
371
+ var html, tag;
372
+ return __generator(this, function (_a) {
373
+ switch (_a.label) {
374
+ case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
375
+ case 1:
376
+ _a.sent();
377
+ html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
378
+ tag = '<meta name="sasjs-host" content="browser" />';
379
+ expect(html).toContain(tag);
380
+ expect(html.indexOf(tag)).toBeLessThan(html.indexOf('</head>'));
381
+ // once: the tag it inserts is the one the guard then finds
382
+ expect(html.split('sasjs-host').length - 1).toBe(1);
383
+ return [2 /*return*/];
384
+ }
385
+ });
386
+ }); });
387
+ it('leaves a page that already carries the host tag alone', function () { return __awaiter(void 0, void 0, void 0, function () {
388
+ var html;
389
+ return __generator(this, function (_a) {
390
+ switch (_a.label) {
391
+ case 0:
392
+ write(path_1.default.join(root, 'client', 'dist', 'index.html'), [
393
+ '<!doctype html><html><head>',
394
+ '<meta name="sasjs-host" content="browser">',
395
+ '</head><body><script src="main.abc123.js"></script></body></html>'
396
+ ].join('\n'));
397
+ return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
398
+ case 1:
399
+ _a.sent();
400
+ html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
401
+ expect(html.split('sasjs-host').length - 1).toBe(1);
402
+ return [2 /*return*/];
403
+ }
404
+ });
405
+ }); });
406
+ it('inserts the tag when the page only mentions it outside the head', function () { return __awaiter(void 0, void 0, void 0, function () {
407
+ var html;
408
+ return __generator(this, function (_a) {
409
+ switch (_a.label) {
410
+ case 0:
411
+ write(path_1.default.join(root, 'client', 'dist', 'index.html'), [
412
+ '<!doctype html><html><head><title>App</title></head><body>',
413
+ '<script src="main.abc123.js"></script>',
414
+ '<template><meta name="sasjs-host" content="browser"></template>',
415
+ '</body></html>'
416
+ ].join('\n'));
417
+ return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
418
+ case 1:
419
+ _a.sent();
420
+ html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
421
+ // the template's copy is not the page's tag: the head gets its own
422
+ expect(html.split('sasjs-host').length - 1).toBe(2);
423
+ expect(html.indexOf('content="browser" />')).toBeLessThan(html.indexOf('</head>'));
424
+ return [2 /*return*/];
425
+ }
426
+ });
427
+ }); });
428
+ it('marks a page whose head is never closed', function () { return __awaiter(void 0, void 0, void 0, function () {
429
+ var html;
430
+ return __generator(this, function (_a) {
431
+ switch (_a.label) {
432
+ case 0:
433
+ write(path_1.default.join(root, 'client', 'dist', 'index.html'), '<!doctype html><html><head><title>App</title><body><script src="main.abc123.js"></script></body></html>');
434
+ return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
435
+ case 1:
436
+ _a.sent();
437
+ html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
438
+ expect(html).toContain('<meta name="sasjs-host" content="browser" />');
439
+ return [2 /*return*/];
440
+ }
441
+ });
442
+ }); });
443
+ it('warns when a page has no head to mark', function () { return __awaiter(void 0, void 0, void 0, function () {
444
+ var html;
445
+ var _a;
446
+ return __generator(this, function (_b) {
447
+ switch (_b.label) {
448
+ case 0:
449
+ write(path_1.default.join(root, 'client', 'dist', 'index.html'), '<!doctype html><html><body><script src="main.abc123.js"></script></body></html>');
450
+ return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
451
+ case 1:
452
+ _b.sent();
453
+ expect((_a = process.logger) === null || _a === void 0 ? void 0 : _a.warn).toHaveBeenCalledWith(expect.stringContaining('no <head> element found'));
454
+ html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
455
+ expect(html).not.toContain('sasjs-host');
456
+ return [2 /*return*/];
457
+ }
458
+ });
459
+ }); });
370
460
  it('replays every script tag a page loads, not just a bundled entry point', function () { return __awaiter(void 0, void 0, void 0, function () {
371
461
  var html, tag, specs;
372
462
  return __generator(this, function (_a) {