@sasjs/cli 6.23.0 → 6.23.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.
|
@@ -594,13 +594,55 @@ var markBrowserHost = function (html, warnings) {
|
|
|
594
594
|
return html;
|
|
595
595
|
return html.slice(0, start) + HOST_TAG + html.slice(start);
|
|
596
596
|
};
|
|
597
|
+
/**
|
|
598
|
+
* Each app script's URL carries a hash of the script's own contents.
|
|
599
|
+
*
|
|
600
|
+
* A static host caches an asset for as long as it says to, and GitHub Pages says
|
|
601
|
+
* two hours - while the app's bundle name never changes from one build to the
|
|
602
|
+
* next. So a publish that changes only the app reuses the previous URL, and a
|
|
603
|
+
* visitor inside that window runs the previous build against the new page: the
|
|
604
|
+
* screens the new build adds simply never appear, and the ones it removed keep
|
|
605
|
+
* working. Only the host's own files were versioned before (`?v=` on boot.js and
|
|
606
|
+
* the worker's imports), and those change with the CLI, not with the app.
|
|
607
|
+
*
|
|
608
|
+
* A src that names no file in the output is left alone: a page may load a
|
|
609
|
+
* script from somewhere this build does not own, and guessing a version for it
|
|
610
|
+
* would break it.
|
|
611
|
+
*
|
|
612
|
+
* The tags in the page are all this covers. A chunk the entry point loads later
|
|
613
|
+
* - `import('./chunk.js')`, a lazy route - keeps its own URL and is cached for
|
|
614
|
+
* the same window, because the request for it is made by the bundle's runtime
|
|
615
|
+
* and never passes through this file. Versioning those would mean rewriting the
|
|
616
|
+
* chunk map inside the bundle, which is the bundler's business, not the host's;
|
|
617
|
+
* a page whose chunks matter more than its entry should name them in the page,
|
|
618
|
+
* where this does reach them.
|
|
619
|
+
*/
|
|
620
|
+
var versionScripts = function (scripts, outputFolder) {
|
|
621
|
+
return scripts.map(function (script) {
|
|
622
|
+
var src = script.src;
|
|
623
|
+
if (!src)
|
|
624
|
+
return script;
|
|
625
|
+
var _a = __read(src.split('?'), 2), pathname = _a[0], query = _a[1];
|
|
626
|
+
var file = path_1.default.join(outputFolder, pathname.replace(/^(\.\/|\/)/, ''));
|
|
627
|
+
if (!fs_1.default.existsSync(file))
|
|
628
|
+
return script;
|
|
629
|
+
var hash = (0, crypto_1.createHash)('md5')
|
|
630
|
+
.update(fs_1.default.readFileSync(file))
|
|
631
|
+
.digest('hex')
|
|
632
|
+
.slice(0, 10);
|
|
633
|
+
return Object.assign({}, script, {
|
|
634
|
+
src: "".concat(pathname, "?v=").concat(hash).concat(query ? "&".concat(query) : '')
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
};
|
|
597
638
|
/**
|
|
598
639
|
* Points the app at its own origin and replaces its script tags with the boot
|
|
599
640
|
* loader. Both changes are reported when the expected markup is absent, rather
|
|
600
641
|
* than producing a site that silently talks to a real server.
|
|
601
642
|
*/
|
|
602
|
-
var patchIndexHtml = function (html, version, basePath) {
|
|
643
|
+
var patchIndexHtml = function (html, version, basePath, outputFolder) {
|
|
603
644
|
if (basePath === void 0) { basePath = ''; }
|
|
645
|
+
if (outputFolder === void 0) { outputFolder = ''; }
|
|
604
646
|
var warnings = [];
|
|
605
647
|
var patched = html;
|
|
606
648
|
patched = markBrowserHost(patched, warnings);
|
|
@@ -657,7 +699,10 @@ var patchIndexHtml = function (html, version, basePath) {
|
|
|
657
699
|
return '';
|
|
658
700
|
});
|
|
659
701
|
if (scripts.length) {
|
|
660
|
-
var
|
|
702
|
+
var versioned = outputFolder
|
|
703
|
+
? versionScripts(scripts, outputFolder)
|
|
704
|
+
: scripts;
|
|
705
|
+
var tag = bootTag(versioned, version, basePath);
|
|
661
706
|
patched = /<\/body>/i.test(patched)
|
|
662
707
|
? patched.replace(/<\/body>/i, "".concat(tag, "\n</body>"))
|
|
663
708
|
: patched + tag;
|
|
@@ -872,7 +917,7 @@ function buildMockSite(target) {
|
|
|
872
917
|
builtAt: new Date().toISOString()
|
|
873
918
|
}, null, 2));
|
|
874
919
|
indexPath = path_1.default.join(outputFolder, 'index.html');
|
|
875
|
-
_d = patchIndexHtml(fs_1.default.readFileSync(indexPath, 'utf8'), hostVersion, basePath), patched = _d.patched, warnings = _d.warnings;
|
|
920
|
+
_d = patchIndexHtml(fs_1.default.readFileSync(indexPath, 'utf8'), hostVersion, basePath, outputFolder), patched = _d.patched, warnings = _d.warnings;
|
|
876
921
|
fs_1.default.writeFileSync(indexPath, patched);
|
|
877
922
|
warnings.forEach(function (warning) { var _a; return (_a = process.logger) === null || _a === void 0 ? void 0 : _a.warn(warning); });
|
|
878
923
|
return [2 /*return*/, {
|
|
@@ -468,7 +468,7 @@ describe('browser-hosted build', function () {
|
|
|
468
468
|
});
|
|
469
469
|
}); });
|
|
470
470
|
it('points the app at itself and installs the boot loader', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
471
|
-
var html;
|
|
471
|
+
var html, specs;
|
|
472
472
|
return __generator(this, function (_a) {
|
|
473
473
|
switch (_a.label) {
|
|
474
474
|
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
@@ -486,7 +486,60 @@ describe('browser-hosted build', function () {
|
|
|
486
486
|
// the app's own tags are replayed by the loader, in the order the page has
|
|
487
487
|
// them, and are gone from the page itself
|
|
488
488
|
expect(html).not.toContain('<script src="main.abc123.js"');
|
|
489
|
-
|
|
489
|
+
specs = JSON.parse(html
|
|
490
|
+
.match(/data-scripts='([^']*)'/)[1]
|
|
491
|
+
.replace(/'/g, "'")
|
|
492
|
+
.replace(/&/g, '&'));
|
|
493
|
+
expect(specs).toHaveLength(1);
|
|
494
|
+
expect(specs[0].type).toBe('module');
|
|
495
|
+
expect(specs[0].src).toMatch(/^main\.abc123\.js\?v=[0-9a-f]{10}$/);
|
|
496
|
+
return [2 /*return*/];
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}); });
|
|
500
|
+
/**
|
|
501
|
+
* A publish that changes only the app has to reach a reader whose browser
|
|
502
|
+
* still holds the previous copy. The host caches an asset for two hours and the
|
|
503
|
+
* bundle's name never changes, so the URL is what has to change.
|
|
504
|
+
*/
|
|
505
|
+
it('gives each app script a URL that follows its contents', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
506
|
+
var specs, first;
|
|
507
|
+
return __generator(this, function (_a) {
|
|
508
|
+
switch (_a.label) {
|
|
509
|
+
case 0:
|
|
510
|
+
specs = function () {
|
|
511
|
+
var html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
512
|
+
var tag = html.match(/data-scripts='([^']*)'/);
|
|
513
|
+
return JSON.parse(tag[1].replace(/'/g, "'").replace(/&/g, '&'));
|
|
514
|
+
};
|
|
515
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
516
|
+
case 1:
|
|
517
|
+
_a.sent();
|
|
518
|
+
first = specs()[0].src;
|
|
519
|
+
expect(first).toMatch(/^main\.abc123\.js\?v=[0-9a-f]{10}$/);
|
|
520
|
+
// the same file twice: nothing about the URL changes
|
|
521
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
522
|
+
case 2:
|
|
523
|
+
// the same file twice: nothing about the URL changes
|
|
524
|
+
_a.sent();
|
|
525
|
+
expect(specs()[0].src).toBe(first);
|
|
526
|
+
// a different bundle: a different URL, which is the caching bust
|
|
527
|
+
write(path_1.default.join(root, 'client', 'dist', 'main.abc123.js'), 'console.log(2)\n');
|
|
528
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
529
|
+
case 3:
|
|
530
|
+
_a.sent();
|
|
531
|
+
expect(specs()[0].src).not.toBe(first);
|
|
532
|
+
// and a script this build does not own is left exactly as the page asked
|
|
533
|
+
write(path_1.default.join(root, 'client', 'dist', 'index.html'), [
|
|
534
|
+
'<!doctype html><html><head><title>App</title></head><body>',
|
|
535
|
+
"<sasjs serverurl=\"https://sas.example.com\" apploc=\"".concat(appLoc, "\"></sasjs>"),
|
|
536
|
+
'<script src="https://cdn.example.com/lib.js"></script>',
|
|
537
|
+
'</body></html>'
|
|
538
|
+
].join('\n'));
|
|
539
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
540
|
+
case 4:
|
|
541
|
+
_a.sent();
|
|
542
|
+
expect(specs()[0].src).toBe('https://cdn.example.com/lib.js');
|
|
490
543
|
return [2 /*return*/];
|
|
491
544
|
}
|
|
492
545
|
});
|
package/package.json
CHANGED