@sasjs/cli 6.29.0 → 6.30.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.
- package/build/commands/build/internal/mockSite/assets/boot.js +23 -1
- package/build/commands/build/internal/mockSite/assets/persist.js +76 -5
- package/build/commands/build/internal/mockSite/assets/runtime-core.js +413 -50
- package/build/commands/build/internal/mockSite/assets/sw.js +182 -12
- package/build/commands/build/internal/mockSite/mockSite.d.ts +23 -0
- package/build/commands/build/internal/mockSite/mockSite.js +245 -29
- package/build/commands/build/internal/mockSite/spec/boot.spec.js +50 -3
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +132 -3
- package/build/commands/build/internal/mockSite/spec/persist.spec.js +179 -1
- package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +546 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +587 -4
- package/build/commands/create/spec/create.spec.js +22 -18
- package/build/commands/deploy/spec/cbd.spec.js +149 -75
- package/build/utils/test.d.ts +12 -0
- package/build/utils/test.js +66 -1
- package/build/utils/utils.d.ts +8 -0
- package/build/utils/utils.js +16 -4
- package/package.json +2 -2
- package/build/commands/create/spec/angularAppFiles.d.ts +0 -2
- package/build/commands/create/spec/angularAppFiles.js +0 -117
- package/build/commands/create/spec/minimalAppFiles.d.ts +0 -2
- package/build/commands/create/spec/minimalAppFiles.js +0 -44
- package/build/commands/create/spec/reactAppFiles.d.ts +0 -2
- package/build/commands/create/spec/reactAppFiles.js +0 -93
|
@@ -79,7 +79,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
79
79
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
80
80
|
};
|
|
81
81
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
82
|
-
exports.DRIVE_FORMAT = exports.MAX_SEED_BYTES = exports.MAX_BINARY_SEED_BYTES = exports.buildLintBundle = exports.WEBOUT_SENTINELS = void 0;
|
|
82
|
+
exports.SHARD_GROUP_BYTES = exports.SHARD_FILE_BYTES = exports.DRIVE_FORMAT = exports.MAX_SEED_BYTES = exports.MAX_BINARY_SEED_BYTES = exports.buildLintBundle = exports.WEBOUT_SENTINELS = void 0;
|
|
83
83
|
exports.buildMockSite = buildMockSite;
|
|
84
84
|
var crypto_1 = require("crypto");
|
|
85
85
|
var fs_1 = __importDefault(require("fs"));
|
|
@@ -130,10 +130,11 @@ var toPosix = function (value) { return value.split(path_1.default.sep).join('/'
|
|
|
130
130
|
* configuration by walking a filesystem - but the linter's core (`lintText`,
|
|
131
131
|
* `formatText`) reads only the text it is given and the configuration it is
|
|
132
132
|
* passed, so the two can run anywhere the module graph can be built. This
|
|
133
|
-
* bundle is that graph: the package's own code service, the
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
133
|
+
* bundle is that graph: the package's own code service, the configuration class
|
|
134
|
+
* a host builds from its own rules file, the linter behind both, and shims for
|
|
135
|
+
* the four node modules reached at import time (`path` and `os` for resolution,
|
|
136
|
+
* `fs` and `@sasjs/utils/file` for the linter's own filesystem lookup, which
|
|
137
|
+
* this host never makes - it hands the routes the rules it read from the drive).
|
|
137
138
|
*
|
|
138
139
|
* It is built from the installed @sasjs/api and @sasjs/lint on every mock
|
|
139
140
|
* build, so the rules a client sees are the version the CLI resolved - a
|
|
@@ -142,8 +143,8 @@ var toPosix = function (value) { return value.split(path_1.default.sep).join('/'
|
|
|
142
143
|
var LINT_BUNDLE_FILES = {
|
|
143
144
|
'entry.js': [
|
|
144
145
|
"import './process.js'",
|
|
145
|
-
"import { createCodeService } from '@sasjs/api/code'",
|
|
146
|
-
'globalThis.SASjsCode = { createCodeService }'
|
|
146
|
+
"import { createCodeService, LintConfig } from '@sasjs/api/code'",
|
|
147
|
+
'globalThis.SASjsCode = { createCodeService, LintConfig }'
|
|
147
148
|
].join('\n'),
|
|
148
149
|
'process.js': [
|
|
149
150
|
'// the linter reads process.env for a default and process.cwd() for a root;',
|
|
@@ -511,6 +512,137 @@ var countPrograms = function (files, extension) {
|
|
|
511
512
|
return inProgramFolder && key.toLowerCase().endsWith(extension);
|
|
512
513
|
}).length;
|
|
513
514
|
};
|
|
515
|
+
/**
|
|
516
|
+
* The size at which a captured file is fetched on its own rather than with its
|
|
517
|
+
* folder.
|
|
518
|
+
*
|
|
519
|
+
* A shard is a download the visitor waits for, and an app's folder holds both
|
|
520
|
+
* its bundles and the small files a listing shows - its logo, a config. Splitting
|
|
521
|
+
* on size keeps one from dragging the other: the tile asks for the folder's small
|
|
522
|
+
* files, the app's own page asks for the bundle.
|
|
523
|
+
*/
|
|
524
|
+
exports.SHARD_FILE_BYTES = 256 * 1024;
|
|
525
|
+
/**
|
|
526
|
+
* The size below which a folder's files ride in the boot shard.
|
|
527
|
+
*
|
|
528
|
+
* The boot shard is fetched before the first page renders, so it is paid by every
|
|
529
|
+
* visit. Folders under this size are cheaper there than as a request of their
|
|
530
|
+
* own; folders over it are only fetched when something reads them.
|
|
531
|
+
*/
|
|
532
|
+
exports.SHARD_GROUP_BYTES = 64 * 1024;
|
|
533
|
+
/** The folder a captured path is grouped under: its first three segments. */
|
|
534
|
+
var shardFolderFor = function (filePath) {
|
|
535
|
+
var prefix = '/sasjs/drive/files/';
|
|
536
|
+
if (!filePath.startsWith(prefix))
|
|
537
|
+
return path_1.default.dirname(filePath);
|
|
538
|
+
var parts = filePath.slice(prefix.length).split('/');
|
|
539
|
+
// a file at the drive root groups with the other root files, which is where
|
|
540
|
+
// the index, the linter rules and a project's configs live
|
|
541
|
+
return (prefix +
|
|
542
|
+
parts
|
|
543
|
+
.slice(0, Math.max(0, parts.length - 1))
|
|
544
|
+
.slice(0, 3)
|
|
545
|
+
.join('/'));
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* Splits a captured drive into a boot shard and the pieces fetched on demand.
|
|
549
|
+
*
|
|
550
|
+
* A site that ships its whole drive as one file makes every visitor download all
|
|
551
|
+
* of it before the first page renders - on a demonstration site that is every
|
|
552
|
+
* app's bundle, behind a list of tiles that names them. The split is by what a
|
|
553
|
+
* page reads, not by what the drive holds:
|
|
554
|
+
*
|
|
555
|
+
* - a file at or over `SHARD_FILE_BYTES` gets a shard of its own;
|
|
556
|
+
* - the rest are grouped by folder, and a folder under `SHARD_GROUP_BYTES` joins
|
|
557
|
+
* the boot shard (id 0), so no visit pays a round trip for a logo;
|
|
558
|
+
* - everything over it becomes a shard, fetched when a path in it is read.
|
|
559
|
+
*
|
|
560
|
+
* The index carries every path with its shard and byte count, so a listing, a
|
|
561
|
+
* stat and an existence check answer for the whole drive with nothing fetched.
|
|
562
|
+
*/
|
|
563
|
+
var shardSnapshot = function (files) {
|
|
564
|
+
var e_7, _a, e_8, _b, _c;
|
|
565
|
+
var paths = Object.keys(files).sort();
|
|
566
|
+
var bytesOf = function (filePath) {
|
|
567
|
+
return Buffer.byteLength(String(files[filePath]));
|
|
568
|
+
};
|
|
569
|
+
var groups = new Map();
|
|
570
|
+
try {
|
|
571
|
+
for (var paths_1 = __values(paths), paths_1_1 = paths_1.next(); !paths_1_1.done; paths_1_1 = paths_1.next()) {
|
|
572
|
+
var filePath = paths_1_1.value;
|
|
573
|
+
var big = bytesOf(filePath) >= exports.SHARD_FILE_BYTES;
|
|
574
|
+
var key = big ? "file:".concat(filePath) : "dir:".concat(shardFolderFor(filePath));
|
|
575
|
+
groups.set(key, (groups.get(key) || []).concat(filePath));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
579
|
+
finally {
|
|
580
|
+
try {
|
|
581
|
+
if (paths_1_1 && !paths_1_1.done && (_a = paths_1.return)) _a.call(paths_1);
|
|
582
|
+
}
|
|
583
|
+
finally { if (e_7) throw e_7.error; }
|
|
584
|
+
}
|
|
585
|
+
var shards = [{ paths: [], bytes: 0 }];
|
|
586
|
+
try {
|
|
587
|
+
for (var _d = __values(__spreadArray([], __read(groups.keys()), false).sort()), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
588
|
+
var key = _e.value;
|
|
589
|
+
var members = groups.get(key);
|
|
590
|
+
var bytes = members.reduce(function (total, filePath) { return total + bytesOf(filePath); }, 0);
|
|
591
|
+
var ridesInBoot = key.startsWith('dir:') && bytes <= exports.SHARD_GROUP_BYTES;
|
|
592
|
+
if (ridesInBoot) {
|
|
593
|
+
(_c = shards[0].paths).push.apply(_c, __spreadArray([], __read(members), false));
|
|
594
|
+
shards[0].bytes += bytes;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
shards.push({ paths: members, bytes: bytes });
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
601
|
+
finally {
|
|
602
|
+
try {
|
|
603
|
+
if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
|
|
604
|
+
}
|
|
605
|
+
finally { if (e_8) throw e_8.error; }
|
|
606
|
+
}
|
|
607
|
+
var entries = {};
|
|
608
|
+
shards.forEach(function (shard, id) {
|
|
609
|
+
var e_9, _a;
|
|
610
|
+
try {
|
|
611
|
+
for (var _b = __values(shard.paths), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
612
|
+
var filePath = _c.value;
|
|
613
|
+
entries[filePath] = { shard: id, bytes: bytesOf(filePath) };
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch (e_9_1) { e_9 = { error: e_9_1 }; }
|
|
617
|
+
finally {
|
|
618
|
+
try {
|
|
619
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
620
|
+
}
|
|
621
|
+
finally { if (e_9) throw e_9.error; }
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
return {
|
|
625
|
+
index: {
|
|
626
|
+
format: exports.DRIVE_FORMAT,
|
|
627
|
+
sharded: true,
|
|
628
|
+
boot: 0,
|
|
629
|
+
files: paths.length,
|
|
630
|
+
entries: entries,
|
|
631
|
+
shards: shards.map(function (shard, id) { return ({
|
|
632
|
+
id: id,
|
|
633
|
+
file: "host/vfs-snapshot-".concat(id, ".json"),
|
|
634
|
+
bytes: shard.bytes,
|
|
635
|
+
files: shard.paths.length
|
|
636
|
+
}); })
|
|
637
|
+
},
|
|
638
|
+
shards: shards.map(function (shard, id) { return ({
|
|
639
|
+
id: id,
|
|
640
|
+
file: "vfs-snapshot-".concat(id, ".json"),
|
|
641
|
+
bytes: shard.bytes,
|
|
642
|
+
files: Object.fromEntries(shard.paths.map(function (filePath) { return [filePath, files[filePath]]; }))
|
|
643
|
+
}); })
|
|
644
|
+
};
|
|
645
|
+
};
|
|
514
646
|
/** The script-tag attributes that change how an app's script runs. */
|
|
515
647
|
var REPLAYED_ATTRIBUTES = {
|
|
516
648
|
type: 'type',
|
|
@@ -519,6 +651,20 @@ var REPLAYED_ATTRIBUTES = {
|
|
|
519
651
|
integrity: 'integrity',
|
|
520
652
|
nomodule: 'noModule'
|
|
521
653
|
};
|
|
654
|
+
/**
|
|
655
|
+
* Script types a preload may name.
|
|
656
|
+
*
|
|
657
|
+
* The loader only re-runs a tag once the worker controls the page, so without a
|
|
658
|
+
* hint the app's bundle is not requested until the install has finished - the
|
|
659
|
+
* largest asset on the site behind the largest prerequisite. A preload link
|
|
660
|
+
* executes nothing, so the download can start while the worker is still
|
|
661
|
+
* installing.
|
|
662
|
+
*
|
|
663
|
+
* `module` is excluded on purpose: a module is fetched with different
|
|
664
|
+
* credentials semantics from a classic script, so `as="script"` does not match
|
|
665
|
+
* the later tag and the file is fetched twice - slower than no hint at all.
|
|
666
|
+
*/
|
|
667
|
+
var PRELOADABLE_TYPES = ['', 'text/javascript', 'application/javascript'];
|
|
522
668
|
var parseAttributes = function (source) {
|
|
523
669
|
var _a, _b, _c;
|
|
524
670
|
var attributes = {};
|
|
@@ -537,6 +683,45 @@ var escapeAttribute = function (value) {
|
|
|
537
683
|
.replace(/</g, '<')
|
|
538
684
|
.replace(/>/g, '>');
|
|
539
685
|
};
|
|
686
|
+
/** Puts markup at the end of the head, or leaves the page alone when it has none. */
|
|
687
|
+
var insertIntoHead = function (html, markup) {
|
|
688
|
+
var head = /<head(\s[^>]*)?>/i.exec(html);
|
|
689
|
+
if (!head)
|
|
690
|
+
return html;
|
|
691
|
+
var start = head.index + head[0].length;
|
|
692
|
+
var close = html.toLowerCase().indexOf('</head>', start);
|
|
693
|
+
var at = close < 0 ? start : close;
|
|
694
|
+
return html.slice(0, at) + markup + html.slice(at);
|
|
695
|
+
};
|
|
696
|
+
/**
|
|
697
|
+
* Hints the app scripts the loader will replay, so their download starts while
|
|
698
|
+
* the worker installs rather than after it.
|
|
699
|
+
*
|
|
700
|
+
* Only scripts this build owns are hinted - the file has to be in the output for
|
|
701
|
+
* the version query to be meaningful - and only classic ones. A `type="module"`
|
|
702
|
+
* tag is fetched with different credentials semantics, so an `as="script"`
|
|
703
|
+
* preload does not match it; and a cross-origin src would need `crossorigin` on
|
|
704
|
+
* the hint to match its tag. Either mismatch fetches the file twice, which is
|
|
705
|
+
* worse than no hint.
|
|
706
|
+
*/
|
|
707
|
+
var preloadHints = function (scripts, outputFolder) {
|
|
708
|
+
return scripts
|
|
709
|
+
.filter(function (script) {
|
|
710
|
+
if (!script.src)
|
|
711
|
+
return false;
|
|
712
|
+
if (!PRELOADABLE_TYPES.includes((script.type || '').trim().toLowerCase()))
|
|
713
|
+
return false;
|
|
714
|
+
if (script.crossOrigin)
|
|
715
|
+
return false;
|
|
716
|
+
if (/^([a-z]+:)?\/\//i.test(script.src))
|
|
717
|
+
return false;
|
|
718
|
+
if (!outputFolder)
|
|
719
|
+
return true;
|
|
720
|
+
var pathname = script.src.split('?')[0];
|
|
721
|
+
return fs_1.default.existsSync(path_1.default.join(outputFolder, pathname.replace(/^(\.\/|\/)/, '')));
|
|
722
|
+
})
|
|
723
|
+
.map(function (script) { return "<link rel=\"preload\" as=\"script\" href=\"".concat(script.src, "\" />"); });
|
|
724
|
+
};
|
|
540
725
|
/**
|
|
541
726
|
* The boot loader is a FILE rather than an inline script: an app with a strict
|
|
542
727
|
* Content-Security-Policy (`default-src 'self'`, no `unsafe-inline`) refuses an
|
|
@@ -713,7 +898,7 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
|
|
|
713
898
|
var early = [];
|
|
714
899
|
var tagPattern = /<script\b([^>]*)>([\s\S]*?)<\/script>[ \t]*\r?\n?/gi;
|
|
715
900
|
patched = patched.replace(tagPattern, function (tag, source, body) {
|
|
716
|
-
var
|
|
901
|
+
var e_10, _a;
|
|
717
902
|
var attributes = parseAttributes(source);
|
|
718
903
|
var type = (attributes.type || '').trim().toLowerCase();
|
|
719
904
|
var inlined = !attributes.src;
|
|
@@ -752,12 +937,12 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
|
|
|
752
937
|
spec[name_2] = attribute === 'nomodule' ? 'true' : attributes[attribute];
|
|
753
938
|
}
|
|
754
939
|
}
|
|
755
|
-
catch (
|
|
940
|
+
catch (e_10_1) { e_10 = { error: e_10_1 }; }
|
|
756
941
|
finally {
|
|
757
942
|
try {
|
|
758
943
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
759
944
|
}
|
|
760
|
-
finally { if (
|
|
945
|
+
finally { if (e_10) throw e_10.error; }
|
|
761
946
|
}
|
|
762
947
|
scripts.push(spec);
|
|
763
948
|
return '';
|
|
@@ -766,6 +951,9 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
|
|
|
766
951
|
var versioned = outputFolder
|
|
767
952
|
? versionScripts(scripts, outputFolder)
|
|
768
953
|
: scripts;
|
|
954
|
+
var hints = preloadHints(versioned, outputFolder);
|
|
955
|
+
if (hints.length)
|
|
956
|
+
patched = insertIntoHead(patched, hints.join(''));
|
|
769
957
|
var tag_1 = bootTag(versioned, version, basePath);
|
|
770
958
|
// a function, not a replacement string: the tag carries the page's own srcs
|
|
771
959
|
// in `data-scripts`, and a `$&` in one would expand to the matched markup
|
|
@@ -836,11 +1024,11 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
|
|
|
836
1024
|
*/
|
|
837
1025
|
function buildMockSite(target) {
|
|
838
1026
|
return __awaiter(this, void 0, void 0, function () {
|
|
839
|
-
var projectDir, appLocRaw, appLoc, buildFolder, basePath, webSourceRaw, webFolder, outputFolder, streamConfig, excluded, _a, seed, skipped, jsPrograms, sasPrograms, driveEntries, identity, webFileCount, hostFolder, _b, _c, asset, hostVersion, buildStamp, swPath, snapshotSource, hasSnapshot, transcriptSource, hasTranscript, indexPath,
|
|
840
|
-
var
|
|
841
|
-
var
|
|
842
|
-
return __generator(this, function (
|
|
843
|
-
switch (
|
|
1027
|
+
var projectDir, appLocRaw, appLoc, buildFolder, basePath, webSourceRaw, webFolder, outputFolder, streamConfig, excluded, _a, seed, skipped, jsPrograms, sasPrograms, driveEntries, identity, webFileCount, hostFolder, _b, _c, asset, hostVersion, buildStamp, swPath, snapshotSource, hasSnapshot, snapshotShards, capture, files, split, _d, _e, shard, transcriptSource, hasTranscript, indexPath, _f, patched, warnings;
|
|
1028
|
+
var e_11, _g, e_12, _h;
|
|
1029
|
+
var _j, _k, _l, _m, _o, _p;
|
|
1030
|
+
return __generator(this, function (_q) {
|
|
1031
|
+
switch (_q.label) {
|
|
844
1032
|
case 0:
|
|
845
1033
|
projectDir = process.projectDir || process.cwd();
|
|
846
1034
|
appLocRaw = target.appLoc;
|
|
@@ -852,24 +1040,24 @@ function buildMockSite(target) {
|
|
|
852
1040
|
path_1.default.join(projectDir, 'sasjsbuild');
|
|
853
1041
|
return [4 /*yield*/, (0, utils_1.folderExists)(buildFolder)];
|
|
854
1042
|
case 1:
|
|
855
|
-
if (!(
|
|
1043
|
+
if (!(_q.sent())) {
|
|
856
1044
|
throw new Error("Build folder not found: ".concat(buildFolder, ". Run `sasjs compile` first."));
|
|
857
1045
|
}
|
|
858
|
-
basePath = normaliseBasePath((
|
|
859
|
-
webSourceRaw = (
|
|
1046
|
+
basePath = normaliseBasePath((_j = target.streamConfig) === null || _j === void 0 ? void 0 : _j.mockWebBasePath);
|
|
1047
|
+
webSourceRaw = (_k = target.streamConfig) === null || _k === void 0 ? void 0 : _k.webSourcePath;
|
|
860
1048
|
if (!webSourceRaw) {
|
|
861
1049
|
throw new Error('No built web app. Set streamConfig.webSourcePath on the target (for example ./client/dist), and build the frontend first.');
|
|
862
1050
|
}
|
|
863
1051
|
webFolder = path_1.default.resolve(projectDir, webSourceRaw);
|
|
864
1052
|
return [4 /*yield*/, (0, utils_1.folderExists)(webFolder)];
|
|
865
1053
|
case 2:
|
|
866
|
-
if (!(
|
|
1054
|
+
if (!(_q.sent())) {
|
|
867
1055
|
throw new Error("Web source folder not found: ".concat(webFolder, ". Check streamConfig.webSourcePath - build the frontend first."));
|
|
868
1056
|
}
|
|
869
1057
|
if (!fs_1.default.existsSync(path_1.default.join(webFolder, 'index.html'))) {
|
|
870
1058
|
throw new Error("".concat(webFolder, " does not contain an index.html - is streamConfig.webSourcePath pointing at the built app?"));
|
|
871
1059
|
}
|
|
872
|
-
outputFolder = ((
|
|
1060
|
+
outputFolder = ((_l = target.streamConfig) === null || _l === void 0 ? void 0 : _l.mockWebFolder)
|
|
873
1061
|
? path_1.default.resolve(projectDir, target.streamConfig.mockWebFolder)
|
|
874
1062
|
: path_1.default.join(buildFolder, 'mock');
|
|
875
1063
|
assertSafeOutputFolder(outputFolder, projectDir, buildFolder, webFolder);
|
|
@@ -890,14 +1078,14 @@ function buildMockSite(target) {
|
|
|
890
1078
|
// without a SAS licence and without a login. What they cannot do is run here,
|
|
891
1079
|
// so the build says so rather than letting a Run button be the surprise.
|
|
892
1080
|
if (jsPrograms === 0 && sasPrograms > 0) {
|
|
893
|
-
(
|
|
1081
|
+
(_m = process.logger) === null || _m === void 0 ? void 0 : _m.warn("".concat(sasPrograms, " SAS program(s) and no JavaScript in ").concat(buildFolder, ": they are placed on the drive to read, and cannot run in a browser."));
|
|
894
1082
|
}
|
|
895
1083
|
// No PROGRAMS is a different thing again: a compile that produced nothing, or
|
|
896
1084
|
// a stale sasjsbuild from an unrelated command - `sasjs doc` leaves a folder
|
|
897
1085
|
// behind that is not a program folder but does reach the drive, so the count
|
|
898
1086
|
// that decides this is of programs, not of files.
|
|
899
1087
|
if (jsPrograms + sasPrograms === 0) {
|
|
900
|
-
(
|
|
1088
|
+
(_o = process.logger) === null || _o === void 0 ? void 0 : _o.warn("No compiled programs in ".concat(buildFolder, ": this site ships with an empty drive. ") +
|
|
901
1089
|
'That is expected for an interface with no services; for anything else, check that compile wrote the target.');
|
|
902
1090
|
}
|
|
903
1091
|
// The package supplies the drive rules the worker answers with, so an install
|
|
@@ -910,7 +1098,7 @@ function buildMockSite(target) {
|
|
|
910
1098
|
'or check that @sasjs/api still publishes build/browser.js.');
|
|
911
1099
|
}
|
|
912
1100
|
if (skipped.length) {
|
|
913
|
-
(
|
|
1101
|
+
(_p = process.logger) === null || _p === void 0 ? void 0 : _p.warn("Left ".concat(skipped.length, " file(s) off the mock drive, over the ").concat(megabytes(exports.MAX_BINARY_SEED_BYTES), " per-file or ").concat(megabytes(exports.MAX_SEED_BYTES), " total the browser build seeds: ") +
|
|
914
1102
|
"".concat(skipped
|
|
915
1103
|
.slice(0, 3)
|
|
916
1104
|
.map(function (file) {
|
|
@@ -941,12 +1129,12 @@ function buildMockSite(target) {
|
|
|
941
1129
|
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, asset), path_1.default.join(hostFolder, asset));
|
|
942
1130
|
}
|
|
943
1131
|
}
|
|
944
|
-
catch (
|
|
1132
|
+
catch (e_11_1) { e_11 = { error: e_11_1 }; }
|
|
945
1133
|
finally {
|
|
946
1134
|
try {
|
|
947
|
-
if (_c && !_c.done && (
|
|
1135
|
+
if (_c && !_c.done && (_g = _b.return)) _g.call(_b);
|
|
948
1136
|
}
|
|
949
|
-
finally { if (
|
|
1137
|
+
finally { if (e_11) throw e_11.error; }
|
|
950
1138
|
}
|
|
951
1139
|
fs_1.default.copyFileSync(API_BROWSER_BUILD, path_1.default.join(hostFolder, 'api-browser.js'));
|
|
952
1140
|
// 2. the linter, bundled from the installed packages so the code routes answer
|
|
@@ -958,7 +1146,7 @@ function buildMockSite(target) {
|
|
|
958
1146
|
case 3:
|
|
959
1147
|
// 2. the linter, bundled from the installed packages so the code routes answer
|
|
960
1148
|
// with the rules this CLI resolved rather than a second implementation
|
|
961
|
-
|
|
1149
|
+
_q.sent();
|
|
962
1150
|
// sw.js sits at the site root: a worker can only control the folder it is
|
|
963
1151
|
// served from, and the app has to be controlled from /
|
|
964
1152
|
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'sw.js'), path_1.default.join(outputFolder, 'sw.js'));
|
|
@@ -1014,8 +1202,34 @@ function buildMockSite(target) {
|
|
|
1014
1202
|
.replace("importScripts('host/lint-browser.js')", "importScripts('host/lint-browser.js?v=".concat(hostVersion, "')")));
|
|
1015
1203
|
snapshotSource = path_1.default.join(buildFolder, 'mock-snapshot.json');
|
|
1016
1204
|
hasSnapshot = fs_1.default.existsSync(snapshotSource);
|
|
1205
|
+
snapshotShards = 0;
|
|
1017
1206
|
if (hasSnapshot) {
|
|
1018
|
-
fs_1.default.
|
|
1207
|
+
capture = JSON.parse(fs_1.default.readFileSync(snapshotSource, 'utf8'));
|
|
1208
|
+
if (capture &&
|
|
1209
|
+
capture.format !== undefined &&
|
|
1210
|
+
capture.format !== exports.DRIVE_FORMAT)
|
|
1211
|
+
throw new Error("The captured drive at ".concat(snapshotSource, " is format ") +
|
|
1212
|
+
"".concat(JSON.stringify(capture.format), ", and this build writes format ") +
|
|
1213
|
+
"".concat(exports.DRIVE_FORMAT, ". Seed the drive again with window.SASjsMock.download()."));
|
|
1214
|
+
files = capture && capture.files ? capture.files : capture;
|
|
1215
|
+
if (!files || typeof files !== 'object')
|
|
1216
|
+
throw new Error("The captured drive at ".concat(snapshotSource, " carries no files - the export is a JSON object of path to contents."));
|
|
1217
|
+
split = shardSnapshot(files);
|
|
1218
|
+
try {
|
|
1219
|
+
for (_d = __values(split.shards), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
1220
|
+
shard = _e.value;
|
|
1221
|
+
fs_1.default.writeFileSync(path_1.default.join(hostFolder, shard.file), JSON.stringify({ format: exports.DRIVE_FORMAT, files: shard.files }));
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
catch (e_12_1) { e_12 = { error: e_12_1 }; }
|
|
1225
|
+
finally {
|
|
1226
|
+
try {
|
|
1227
|
+
if (_e && !_e.done && (_h = _d.return)) _h.call(_d);
|
|
1228
|
+
}
|
|
1229
|
+
finally { if (e_12) throw e_12.error; }
|
|
1230
|
+
}
|
|
1231
|
+
fs_1.default.writeFileSync(path_1.default.join(hostFolder, 'vfs-snapshot.json'), JSON.stringify(split.index, null, 2));
|
|
1232
|
+
snapshotShards = split.shards.length;
|
|
1019
1233
|
}
|
|
1020
1234
|
transcriptSource = path_1.default.join(buildFolder, 'sas-transcript.json');
|
|
1021
1235
|
hasTranscript = fs_1.default.existsSync(transcriptSource);
|
|
@@ -1030,13 +1244,14 @@ function buildMockSite(target) {
|
|
|
1030
1244
|
jsPrograms: jsPrograms,
|
|
1031
1245
|
programFolders: PROGRAM_FOLDERS,
|
|
1032
1246
|
snapshot: hasSnapshot,
|
|
1247
|
+
snapshotShards: snapshotShards,
|
|
1033
1248
|
sasTranscript: hasTranscript,
|
|
1034
1249
|
identity: identity.source,
|
|
1035
1250
|
buildStamp: buildStamp,
|
|
1036
1251
|
builtAt: new Date().toISOString()
|
|
1037
1252
|
}, null, 2));
|
|
1038
1253
|
indexPath = path_1.default.join(outputFolder, 'index.html');
|
|
1039
|
-
|
|
1254
|
+
_f = patchIndexHtml(fs_1.default.readFileSync(indexPath, 'utf8'), hostVersion, basePath, outputFolder), patched = _f.patched, warnings = _f.warnings;
|
|
1040
1255
|
fs_1.default.writeFileSync(indexPath, patched);
|
|
1041
1256
|
warnings.forEach(function (warning) { var _a; return (_a = process.logger) === null || _a === void 0 ? void 0 : _a.warn(warning); });
|
|
1042
1257
|
return [2 /*return*/, {
|
|
@@ -1046,6 +1261,7 @@ function buildMockSite(target) {
|
|
|
1046
1261
|
jsPrograms: jsPrograms,
|
|
1047
1262
|
webFileCount: webFileCount,
|
|
1048
1263
|
snapshot: hasSnapshot,
|
|
1264
|
+
snapshotShards: snapshotShards,
|
|
1049
1265
|
sasTranscript: hasTranscript,
|
|
1050
1266
|
identitySource: identity.source
|
|
1051
1267
|
}];
|
|
@@ -100,9 +100,11 @@ var loadBoot = function (specs, options) {
|
|
|
100
100
|
};
|
|
101
101
|
var listeners = {};
|
|
102
102
|
var controller = options.controller || 'present';
|
|
103
|
+
var timelineAtRegister = [];
|
|
103
104
|
var navigator = {
|
|
104
105
|
serviceWorker: {
|
|
105
106
|
register: function (url, init) {
|
|
107
|
+
timelineAtRegister = timeline.slice();
|
|
106
108
|
registered.push(url + ' ' + JSON.stringify(init));
|
|
107
109
|
return options.registerFails
|
|
108
110
|
? Promise.reject(new Error('nope'))
|
|
@@ -127,7 +129,11 @@ var loadBoot = function (specs, options) {
|
|
|
127
129
|
global.navigator = navigator;
|
|
128
130
|
global.window = options.bootError
|
|
129
131
|
? { __sasjsMockReady: Promise.resolve({ error: options.bootError }) }
|
|
130
|
-
:
|
|
132
|
+
: options.driveError
|
|
133
|
+
? {
|
|
134
|
+
__sasjsMockReady: Promise.resolve({ driveError: options.driveError })
|
|
135
|
+
}
|
|
136
|
+
: {};
|
|
131
137
|
global.sessionStorage = {
|
|
132
138
|
getItem: function (key) { return (key in stored ? stored[key] : null); },
|
|
133
139
|
setItem: function (key, value) {
|
|
@@ -146,7 +152,7 @@ var loadBoot = function (specs, options) {
|
|
|
146
152
|
late = true;
|
|
147
153
|
(listeners['controllerchange'] || []).forEach(function (fn) { return fn(); });
|
|
148
154
|
};
|
|
149
|
-
return { appended: appended, timeline: timeline, registered: registered, stored: stored, claim: claim };
|
|
155
|
+
return { appended: appended, timeline: timeline, registered: registered, stored: stored, claim: claim, timelineAtRegister: timelineAtRegister };
|
|
150
156
|
};
|
|
151
157
|
var settle = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
152
158
|
var i;
|
|
@@ -336,6 +342,29 @@ describe('the boot loader', function () {
|
|
|
336
342
|
}
|
|
337
343
|
});
|
|
338
344
|
}); });
|
|
345
|
+
it('starts the drive restore before the worker is registered', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
346
|
+
var timelineAtRegister;
|
|
347
|
+
return __generator(this, function (_a) {
|
|
348
|
+
switch (_a.label) {
|
|
349
|
+
case 0:
|
|
350
|
+
timelineAtRegister = loadBoot([{ src: 'sasjs.js' }]).timelineAtRegister;
|
|
351
|
+
return [4 /*yield*/, settle()
|
|
352
|
+
// persist.js reads the page's own IndexedDB and hands the drive to the
|
|
353
|
+
// worker as soon as there is one, so nothing about it needs the worker to be
|
|
354
|
+
// ready: sequenced after the registration it is another download plus a
|
|
355
|
+
// store read on top of the install, which is what made a first visit slow
|
|
356
|
+
];
|
|
357
|
+
case 1:
|
|
358
|
+
_a.sent();
|
|
359
|
+
// persist.js reads the page's own IndexedDB and hands the drive to the
|
|
360
|
+
// worker as soon as there is one, so nothing about it needs the worker to be
|
|
361
|
+
// ready: sequenced after the registration it is another download plus a
|
|
362
|
+
// store read on top of the install, which is what made a first visit slow
|
|
363
|
+
expect(timelineAtRegister).toEqual(['host/persist.js']);
|
|
364
|
+
return [2 /*return*/];
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}); });
|
|
339
368
|
it('keeps the attributes an app relies on', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
340
369
|
var appended, app;
|
|
341
370
|
return __generator(this, function (_a) {
|
|
@@ -430,6 +459,21 @@ describe('the boot loader', function () {
|
|
|
430
459
|
}
|
|
431
460
|
});
|
|
432
461
|
}); });
|
|
462
|
+
it('reports a drive that was restored from less than the whole capture', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
463
|
+
return __generator(this, function (_a) {
|
|
464
|
+
switch (_a.label) {
|
|
465
|
+
case 0:
|
|
466
|
+
// the page still runs - a missing piece only fails the file that needed it -
|
|
467
|
+
// so the error is the only thing that says the drive is incomplete
|
|
468
|
+
loadBoot([], { driveError: 'the drive index answered 500' });
|
|
469
|
+
return [4 /*yield*/, settle()];
|
|
470
|
+
case 1:
|
|
471
|
+
_a.sent();
|
|
472
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('the drive index answered 500'));
|
|
473
|
+
return [2 /*return*/];
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}); });
|
|
433
477
|
it('refuses a boot config it cannot read instead of guessing', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
434
478
|
var timeline;
|
|
435
479
|
return __generator(this, function (_a) {
|
|
@@ -527,7 +571,10 @@ describe('the boot loader', function () {
|
|
|
527
571
|
// into the next test's globals
|
|
528
572
|
_b.sent();
|
|
529
573
|
expect(stored['mock-sw-reloaded']).toEqual('1');
|
|
530
|
-
|
|
574
|
+
// the drive restore is requested before the registration (it overlaps the
|
|
575
|
+
// install), so it is already in flight when the page decides to reload; the
|
|
576
|
+
// reload discards it, which costs nothing the cache does not already hold
|
|
577
|
+
expect(timeline).toEqual(['host/persist.js', 'reload']);
|
|
531
578
|
return [2 /*return*/];
|
|
532
579
|
}
|
|
533
580
|
});
|