@sasjs/cli 6.30.0 → 6.31.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.
Files changed (23) hide show
  1. package/build/commands/build/internal/mockSite/assets/api-console.css +365 -0
  2. package/build/commands/build/internal/mockSite/assets/api-console.js +762 -0
  3. package/build/commands/build/internal/mockSite/assets/api-index.html +83 -0
  4. package/build/commands/build/internal/mockSite/assets/boot.js +23 -1
  5. package/build/commands/build/internal/mockSite/assets/persist.js +76 -5
  6. package/build/commands/build/internal/mockSite/assets/runtime-core.js +413 -50
  7. package/build/commands/build/internal/mockSite/assets/sw.js +156 -15
  8. package/build/commands/build/internal/mockSite/mockSite.d.ts +25 -0
  9. package/build/commands/build/internal/mockSite/mockSite.js +311 -24
  10. package/build/commands/build/internal/mockSite/spec/apiConsole.spec.d.ts +1 -0
  11. package/build/commands/build/internal/mockSite/spec/apiConsole.spec.js +1371 -0
  12. package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.d.ts +1 -0
  13. package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.js +303 -0
  14. package/build/commands/build/internal/mockSite/spec/boot.spec.js +50 -3
  15. package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +217 -3
  16. package/build/commands/build/internal/mockSite/spec/persist.spec.js +179 -1
  17. package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +546 -0
  18. package/build/commands/build/internal/mockSite/spec/sw.spec.js +407 -53
  19. package/build/commands/version/spec/version.spec.js +11 -1
  20. package/build/commands/version/version.d.ts +5 -3
  21. package/build/commands/version/version.js +17 -7
  22. package/build/types/mockWebBasePath.d.ts +9 -0
  23. package/package.json +13 -32
@@ -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"));
@@ -512,6 +512,137 @@ var countPrograms = function (files, extension) {
512
512
  return inProgramFolder && key.toLowerCase().endsWith(extension);
513
513
  }).length;
514
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
+ };
515
646
  /** The script-tag attributes that change how an app's script runs. */
516
647
  var REPLAYED_ATTRIBUTES = {
517
648
  type: 'type',
@@ -520,6 +651,20 @@ var REPLAYED_ATTRIBUTES = {
520
651
  integrity: 'integrity',
521
652
  nomodule: 'noModule'
522
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'];
523
668
  var parseAttributes = function (source) {
524
669
  var _a, _b, _c;
525
670
  var attributes = {};
@@ -538,6 +683,45 @@ var escapeAttribute = function (value) {
538
683
  .replace(/</g, '&lt;')
539
684
  .replace(/>/g, '&gt;');
540
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
+ };
541
725
  /**
542
726
  * The boot loader is a FILE rather than an inline script: an app with a strict
543
727
  * Content-Security-Policy (`default-src 'self'`, no `unsafe-inline`) refuses an
@@ -714,7 +898,7 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
714
898
  var early = [];
715
899
  var tagPattern = /<script\b([^>]*)>([\s\S]*?)<\/script>[ \t]*\r?\n?/gi;
716
900
  patched = patched.replace(tagPattern, function (tag, source, body) {
717
- var e_7, _a;
901
+ var e_10, _a;
718
902
  var attributes = parseAttributes(source);
719
903
  var type = (attributes.type || '').trim().toLowerCase();
720
904
  var inlined = !attributes.src;
@@ -753,12 +937,12 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
753
937
  spec[name_2] = attribute === 'nomodule' ? 'true' : attributes[attribute];
754
938
  }
755
939
  }
756
- catch (e_7_1) { e_7 = { error: e_7_1 }; }
940
+ catch (e_10_1) { e_10 = { error: e_10_1 }; }
757
941
  finally {
758
942
  try {
759
943
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
760
944
  }
761
- finally { if (e_7) throw e_7.error; }
945
+ finally { if (e_10) throw e_10.error; }
762
946
  }
763
947
  scripts.push(spec);
764
948
  return '';
@@ -767,6 +951,9 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
767
951
  var versioned = outputFolder
768
952
  ? versionScripts(scripts, outputFolder)
769
953
  : scripts;
954
+ var hints = preloadHints(versioned, outputFolder);
955
+ if (hints.length)
956
+ patched = insertIntoHead(patched, hints.join(''));
770
957
  var tag_1 = bootTag(versioned, version, basePath);
771
958
  // a function, not a replacement string: the tag carries the page's own srcs
772
959
  // in `data-scripts`, and a `$&` in one would expand to the matched markup
@@ -821,6 +1008,67 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
821
1008
  }
822
1009
  return { patched: patched, warnings: warnings };
823
1010
  };
1011
+ /** The files the console consists of, under `<output>/api`. */
1012
+ var API_CONSOLE_FILES = [
1013
+ 'index.html',
1014
+ 'console.js',
1015
+ 'console.css',
1016
+ 'operations.json'
1017
+ ];
1018
+ /**
1019
+ * The API console: a page at `<base>/api/` that lists every operation the API
1020
+ * document declares and sends them against this host.
1021
+ *
1022
+ * The route table is the same one the worker answers from (`ROUTES` from
1023
+ * @sasjs/api), so the console cannot drift from the API: a route the package
1024
+ * adds appears here on the next build, with the parameters it declares.
1025
+ *
1026
+ * Three files and the data, all static. It is a developer surface rather than a
1027
+ * screen of the app, so nothing in the page links to it and nothing preloads it -
1028
+ * a visit costs the demo nothing until someone types the URL.
1029
+ */
1030
+ var writeApiConsole = function (outputFolder, version) {
1031
+ var folder = path_1.default.join(outputFolder, 'api');
1032
+ // `<base>/api/` is this build's, not the project's: a site that ships its own
1033
+ // files there would have them replaced without a word, so it keeps them and
1034
+ // the console is left out
1035
+ var taken = API_CONSOLE_FILES.filter(function (file) {
1036
+ return fs_1.default.existsSync(path_1.default.join(folder, file));
1037
+ });
1038
+ if (taken.length)
1039
+ return {
1040
+ written: false,
1041
+ operations: 0,
1042
+ warning: "The site already carries ".concat(taken
1043
+ .map(function (file) { return 'api/' + file; })
1044
+ .join(', '), ", so the API console was not written. ") +
1045
+ 'Move those files, or set streamConfig.mockApi to false to silence this.'
1046
+ };
1047
+ fs_1.default.mkdirSync(folder, { recursive: true });
1048
+ // The console starts the host itself, so it has to ask for the same worker the
1049
+ // app's loader asks for: a registration is identified by its script URL, and a
1050
+ // bare `sw.js` here would replace the versioned one on every visit to either
1051
+ // page - an install nobody asked for, which is what the version exists to avoid.
1052
+ fs_1.default.writeFileSync(path_1.default.join(folder, 'index.html'), fs_1.default
1053
+ .readFileSync(path_1.default.join(ASSETS_FOLDER, 'api-index.html'), 'utf8')
1054
+ .replace('__MOCK_VERSION__', version));
1055
+ fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'api-console.js'), path_1.default.join(folder, 'console.js'));
1056
+ fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'api-console.css'), path_1.default.join(folder, 'console.css'));
1057
+ var operations = api_1.ROUTES.map(function (route) { return ({
1058
+ id: route.id,
1059
+ method: route.method,
1060
+ path: route.path,
1061
+ tag: route.tag,
1062
+ summary: route.summary,
1063
+ params: route.params,
1064
+ requestBodies: route.requestBodies,
1065
+ responses: route.responses,
1066
+ implemented: route.implemented,
1067
+ reason: route.reason
1068
+ }); });
1069
+ fs_1.default.writeFileSync(path_1.default.join(folder, 'operations.json'), JSON.stringify(operations, null, 2));
1070
+ return { written: true, operations: operations.length };
1071
+ };
824
1072
  /**
825
1073
  * Builds a static site that runs the project's JS services (its mocks) in the
826
1074
  * browser, so a frontend can be tested with no server and no SAS.
@@ -837,11 +1085,11 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
837
1085
  */
838
1086
  function buildMockSite(target) {
839
1087
  return __awaiter(this, void 0, void 0, function () {
840
- 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, _d, patched, warnings;
841
- var e_8, _e;
842
- var _f, _g, _h, _j, _k, _l;
843
- return __generator(this, function (_m) {
844
- switch (_m.label) {
1088
+ 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, apiConsole, written, indexPath, _f, patched, warnings;
1089
+ var e_11, _g, e_12, _h;
1090
+ var _j, _k, _l, _m, _o, _p, _q, _r;
1091
+ return __generator(this, function (_s) {
1092
+ switch (_s.label) {
845
1093
  case 0:
846
1094
  projectDir = process.projectDir || process.cwd();
847
1095
  appLocRaw = target.appLoc;
@@ -853,24 +1101,24 @@ function buildMockSite(target) {
853
1101
  path_1.default.join(projectDir, 'sasjsbuild');
854
1102
  return [4 /*yield*/, (0, utils_1.folderExists)(buildFolder)];
855
1103
  case 1:
856
- if (!(_m.sent())) {
1104
+ if (!(_s.sent())) {
857
1105
  throw new Error("Build folder not found: ".concat(buildFolder, ". Run `sasjs compile` first."));
858
1106
  }
859
- basePath = normaliseBasePath((_f = target.streamConfig) === null || _f === void 0 ? void 0 : _f.mockWebBasePath);
860
- webSourceRaw = (_g = target.streamConfig) === null || _g === void 0 ? void 0 : _g.webSourcePath;
1107
+ basePath = normaliseBasePath((_j = target.streamConfig) === null || _j === void 0 ? void 0 : _j.mockWebBasePath);
1108
+ webSourceRaw = (_k = target.streamConfig) === null || _k === void 0 ? void 0 : _k.webSourcePath;
861
1109
  if (!webSourceRaw) {
862
1110
  throw new Error('No built web app. Set streamConfig.webSourcePath on the target (for example ./client/dist), and build the frontend first.');
863
1111
  }
864
1112
  webFolder = path_1.default.resolve(projectDir, webSourceRaw);
865
1113
  return [4 /*yield*/, (0, utils_1.folderExists)(webFolder)];
866
1114
  case 2:
867
- if (!(_m.sent())) {
1115
+ if (!(_s.sent())) {
868
1116
  throw new Error("Web source folder not found: ".concat(webFolder, ". Check streamConfig.webSourcePath - build the frontend first."));
869
1117
  }
870
1118
  if (!fs_1.default.existsSync(path_1.default.join(webFolder, 'index.html'))) {
871
1119
  throw new Error("".concat(webFolder, " does not contain an index.html - is streamConfig.webSourcePath pointing at the built app?"));
872
1120
  }
873
- outputFolder = ((_h = target.streamConfig) === null || _h === void 0 ? void 0 : _h.mockWebFolder)
1121
+ outputFolder = ((_l = target.streamConfig) === null || _l === void 0 ? void 0 : _l.mockWebFolder)
874
1122
  ? path_1.default.resolve(projectDir, target.streamConfig.mockWebFolder)
875
1123
  : path_1.default.join(buildFolder, 'mock');
876
1124
  assertSafeOutputFolder(outputFolder, projectDir, buildFolder, webFolder);
@@ -891,14 +1139,14 @@ function buildMockSite(target) {
891
1139
  // without a SAS licence and without a login. What they cannot do is run here,
892
1140
  // so the build says so rather than letting a Run button be the surprise.
893
1141
  if (jsPrograms === 0 && sasPrograms > 0) {
894
- (_j = process.logger) === null || _j === void 0 ? void 0 : _j.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."));
1142
+ (_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."));
895
1143
  }
896
1144
  // No PROGRAMS is a different thing again: a compile that produced nothing, or
897
1145
  // a stale sasjsbuild from an unrelated command - `sasjs doc` leaves a folder
898
1146
  // behind that is not a program folder but does reach the drive, so the count
899
1147
  // that decides this is of programs, not of files.
900
1148
  if (jsPrograms + sasPrograms === 0) {
901
- (_k = process.logger) === null || _k === void 0 ? void 0 : _k.warn("No compiled programs in ".concat(buildFolder, ": this site ships with an empty drive. ") +
1149
+ (_o = process.logger) === null || _o === void 0 ? void 0 : _o.warn("No compiled programs in ".concat(buildFolder, ": this site ships with an empty drive. ") +
902
1150
  'That is expected for an interface with no services; for anything else, check that compile wrote the target.');
903
1151
  }
904
1152
  // The package supplies the drive rules the worker answers with, so an install
@@ -911,7 +1159,7 @@ function buildMockSite(target) {
911
1159
  'or check that @sasjs/api still publishes build/browser.js.');
912
1160
  }
913
1161
  if (skipped.length) {
914
- (_l = process.logger) === null || _l === void 0 ? void 0 : _l.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: ") +
1162
+ (_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: ") +
915
1163
  "".concat(skipped
916
1164
  .slice(0, 3)
917
1165
  .map(function (file) {
@@ -942,12 +1190,12 @@ function buildMockSite(target) {
942
1190
  fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, asset), path_1.default.join(hostFolder, asset));
943
1191
  }
944
1192
  }
945
- catch (e_8_1) { e_8 = { error: e_8_1 }; }
1193
+ catch (e_11_1) { e_11 = { error: e_11_1 }; }
946
1194
  finally {
947
1195
  try {
948
- if (_c && !_c.done && (_e = _b.return)) _e.call(_b);
1196
+ if (_c && !_c.done && (_g = _b.return)) _g.call(_b);
949
1197
  }
950
- finally { if (e_8) throw e_8.error; }
1198
+ finally { if (e_11) throw e_11.error; }
951
1199
  }
952
1200
  fs_1.default.copyFileSync(API_BROWSER_BUILD, path_1.default.join(hostFolder, 'api-browser.js'));
953
1201
  // 2. the linter, bundled from the installed packages so the code routes answer
@@ -959,7 +1207,7 @@ function buildMockSite(target) {
959
1207
  case 3:
960
1208
  // 2. the linter, bundled from the installed packages so the code routes answer
961
1209
  // with the rules this CLI resolved rather than a second implementation
962
- _m.sent();
1210
+ _s.sent();
963
1211
  // sw.js sits at the site root: a worker can only control the folder it is
964
1212
  // served from, and the app has to be controlled from /
965
1213
  fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'sw.js'), path_1.default.join(outputFolder, 'sw.js'));
@@ -1012,11 +1260,40 @@ function buildMockSite(target) {
1012
1260
  .readFileSync(swPath, 'utf8')
1013
1261
  .replace("importScripts('host/runtime-core.js')", "importScripts('host/runtime-core.js?v=".concat(hostVersion, "')"))
1014
1262
  .replace("importScripts('host/api-browser.js')", "importScripts('host/api-browser.js?v=".concat(hostVersion, "')"))
1015
- .replace("importScripts('host/lint-browser.js')", "importScripts('host/lint-browser.js?v=".concat(hostVersion, "')")));
1263
+ .replace("importScripts('host/lint-browser.js')", "importScripts('host/lint-browser.js?v=".concat(hostVersion, "')"))
1264
+ // the console's file list, from the one place that writes the folder: a
1265
+ // second copy here would let a new file be served as an API path
1266
+ .replace(/const API_CONSOLE_FILES = \[[^\]]*\]/, "const API_CONSOLE_FILES = ".concat(JSON.stringify(API_CONSOLE_FILES))));
1016
1267
  snapshotSource = path_1.default.join(buildFolder, 'mock-snapshot.json');
1017
1268
  hasSnapshot = fs_1.default.existsSync(snapshotSource);
1269
+ snapshotShards = 0;
1018
1270
  if (hasSnapshot) {
1019
- fs_1.default.copyFileSync(snapshotSource, path_1.default.join(hostFolder, 'vfs-snapshot.json'));
1271
+ capture = JSON.parse(fs_1.default.readFileSync(snapshotSource, 'utf8'));
1272
+ if (capture &&
1273
+ capture.format !== undefined &&
1274
+ capture.format !== exports.DRIVE_FORMAT)
1275
+ throw new Error("The captured drive at ".concat(snapshotSource, " is format ") +
1276
+ "".concat(JSON.stringify(capture.format), ", and this build writes format ") +
1277
+ "".concat(exports.DRIVE_FORMAT, ". Seed the drive again with window.SASjsMock.download()."));
1278
+ files = capture && capture.files ? capture.files : capture;
1279
+ if (!files || typeof files !== 'object')
1280
+ throw new Error("The captured drive at ".concat(snapshotSource, " carries no files - the export is a JSON object of path to contents."));
1281
+ split = shardSnapshot(files);
1282
+ try {
1283
+ for (_d = __values(split.shards), _e = _d.next(); !_e.done; _e = _d.next()) {
1284
+ shard = _e.value;
1285
+ fs_1.default.writeFileSync(path_1.default.join(hostFolder, shard.file), JSON.stringify({ format: exports.DRIVE_FORMAT, files: shard.files }));
1286
+ }
1287
+ }
1288
+ catch (e_12_1) { e_12 = { error: e_12_1 }; }
1289
+ finally {
1290
+ try {
1291
+ if (_e && !_e.done && (_h = _d.return)) _h.call(_d);
1292
+ }
1293
+ finally { if (e_12) throw e_12.error; }
1294
+ }
1295
+ fs_1.default.writeFileSync(path_1.default.join(hostFolder, 'vfs-snapshot.json'), JSON.stringify(split.index, null, 2));
1296
+ snapshotShards = split.shards.length;
1020
1297
  }
1021
1298
  transcriptSource = path_1.default.join(buildFolder, 'sas-transcript.json');
1022
1299
  hasTranscript = fs_1.default.existsSync(transcriptSource);
@@ -1031,13 +1308,21 @@ function buildMockSite(target) {
1031
1308
  jsPrograms: jsPrograms,
1032
1309
  programFolders: PROGRAM_FOLDERS,
1033
1310
  snapshot: hasSnapshot,
1311
+ snapshotShards: snapshotShards,
1034
1312
  sasTranscript: hasTranscript,
1035
1313
  identity: identity.source,
1036
1314
  buildStamp: buildStamp,
1037
1315
  builtAt: new Date().toISOString()
1038
1316
  }, null, 2));
1317
+ apiConsole = false;
1318
+ if (((_q = target.streamConfig) === null || _q === void 0 ? void 0 : _q.mockApi) !== false) {
1319
+ written = writeApiConsole(outputFolder, hostVersion);
1320
+ apiConsole = written.written;
1321
+ if (written.warning)
1322
+ (_r = process.logger) === null || _r === void 0 ? void 0 : _r.warn(written.warning);
1323
+ }
1039
1324
  indexPath = path_1.default.join(outputFolder, 'index.html');
1040
- _d = patchIndexHtml(fs_1.default.readFileSync(indexPath, 'utf8'), hostVersion, basePath, outputFolder), patched = _d.patched, warnings = _d.warnings;
1325
+ _f = patchIndexHtml(fs_1.default.readFileSync(indexPath, 'utf8'), hostVersion, basePath, outputFolder), patched = _f.patched, warnings = _f.warnings;
1041
1326
  fs_1.default.writeFileSync(indexPath, patched);
1042
1327
  warnings.forEach(function (warning) { var _a; return (_a = process.logger) === null || _a === void 0 ? void 0 : _a.warn(warning); });
1043
1328
  return [2 /*return*/, {
@@ -1047,6 +1332,8 @@ function buildMockSite(target) {
1047
1332
  jsPrograms: jsPrograms,
1048
1333
  webFileCount: webFileCount,
1049
1334
  snapshot: hasSnapshot,
1335
+ snapshotShards: snapshotShards,
1336
+ apiConsole: apiConsole,
1050
1337
  sasTranscript: hasTranscript,
1051
1338
  identitySource: identity.source
1052
1339
  }];