@sasjs/cli 6.14.0 → 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,14 +430,7 @@ var EXECUTABLE_TYPES = [
399
430
  var patchIndexHtml = function (html, version) {
400
431
  var warnings = [];
401
432
  var patched = html;
402
- // States where this page's API comes from, for anything in the page that
403
- // wants to say so: the server's own interface reads it to describe the host
404
- // it is running on, and a browser host is a different thing to explain than a
405
- // server process. Absence means the server answered the page.
406
- if (!/<meta\s+name=["']sasjs-host["']/i.test(patched))
407
- patched = patched.replace(/<head(\s[^>]*)?>/i, function (head) {
408
- return "".concat(head, "\n <meta name=\"sasjs-host\" content=\"browser\" />");
409
- });
433
+ patched = markBrowserHost(patched, warnings);
410
434
  if (/<sasjs[^>]*>/i.test(patched)) {
411
435
  patched = patched.replace(/(<sasjs[^>]*?)\sserverurl="[^"]*"/i, '$1 serverurl=""');
412
436
  patched = patched.replace(/(<sasjs[^>]*?)\sloginmechanism="[^"]*"/i, '$1 loginmechanism="Default"');
@@ -368,18 +368,91 @@ describe('browser-hosted build', function () {
368
368
  });
369
369
  }); });
370
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;
371
+ var html, tag;
372
372
  return __generator(this, function (_a) {
373
373
  switch (_a.label) {
374
374
  case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
375
375
  case 1:
376
376
  _a.sent();
377
377
  html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
378
- // The interface reads this to tell a browser host from a server: the API
379
- // cannot, because a browser host answers the same routes and the same
380
- // /SASjsApi/info shape. Only the host knows, so the host says.
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');
381
438
  expect(html).toContain('<meta name="sasjs-host" content="browser" />');
382
- expect(html.indexOf('sasjs-host')).toBeLessThan(html.indexOf('</head>'));
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');
383
456
  return [2 /*return*/];
384
457
  }
385
458
  });