@sasjs/cli 6.30.1 → 6.32.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.
@@ -0,0 +1,83 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>API console - browser demo</title>
7
+ <meta name="sasjs-mock-version" content="__MOCK_VERSION__" />
8
+ <!--
9
+ The API console for a browser-hosted build.
10
+
11
+ Static, and deliberately so: no bundler, no framework, no network of its
12
+ own beyond the API it is documenting. It is served from <base>/api-console/ and
13
+ every request it makes is the same request an application makes, so the
14
+ service worker answers it and the answer is the mock's real answer.
15
+
16
+ Nothing is inline: a project that sets `default-src 'self'` without
17
+ 'unsafe-inline' would refuse an inline script or style, and this page
18
+ cannot add a hash to a policy it does not own.
19
+ -->
20
+ <link rel="stylesheet" href="console.css" />
21
+ </head>
22
+ <body>
23
+ <header class="bar">
24
+ <h1>API console</h1>
25
+ <span class="badge">browser demo</span>
26
+ <span class="spacer"></span>
27
+ <span id="who" class="who" title="The account this host answers as"
28
+ >connected</span
29
+ >
30
+ </header>
31
+
32
+ <main class="layout">
33
+ <aside class="list">
34
+ <input
35
+ id="filter"
36
+ type="search"
37
+ placeholder="Filter routes (user, group, drive...)"
38
+ autocomplete="off"
39
+ />
40
+ <div id="routes" class="routes"></div>
41
+ </aside>
42
+
43
+ <section class="detail">
44
+ <div id="empty" class="empty">
45
+ <p>
46
+ This is a <strong>browser-hosted</strong> SASjs server: the API you
47
+ are about to call runs in a service worker in this tab, and its
48
+ drive lives in this browser.
49
+ </p>
50
+ <p>
51
+ Pick a route on the left. Writes are allowed on purpose, and they
52
+ survive a refresh - add <code>?reset=1</code> to this page's URL to
53
+ clear the drive and start again from the shipped state.
54
+ </p>
55
+ </div>
56
+
57
+ <!-- outside #empty: the accounts are most needed while a route is open,
58
+ and #empty is hidden as soon as one is selected -->
59
+ <div id="accounts"></div>
60
+
61
+ <div id="route" hidden>
62
+ <h2 id="title"></h2>
63
+ <p id="summary" class="summary"></p>
64
+ <form id="form" class="form"></form>
65
+ <div class="actions">
66
+ <button id="send" type="button">Send</button>
67
+ <span id="timing" class="timing"></span>
68
+ </div>
69
+ <div id="response" class="response" hidden>
70
+ <div class="responsebar">
71
+ <span id="status" class="status"></span>
72
+ <span class="spacer"></span>
73
+ <button id="copy" type="button" class="link">Copy</button>
74
+ </div>
75
+ <pre id="body"></pre>
76
+ </div>
77
+ </div>
78
+ </section>
79
+ </main>
80
+
81
+ <script src="console.js"></script>
82
+ </body>
83
+ </html>
@@ -1764,10 +1764,11 @@ self.addEventListener('fetch', (event) => {
1764
1764
  // listing is the server's web route, and an interface that lists apps asks for
1765
1765
  // it on every visit.
1766
1766
  //
1767
- // `/api` and `/api/` on their own are the API's documentation page - a file
1768
- // the build copies into the site - so the worker stays out of the way there
1769
- // and the static host answers it. Every other `/api/...` is an API call on the
1770
- // short spelling, which routeRequest canonicalises to `/SASjsApi`.
1767
+ // `/api` and `/api/` on their own are left to the static host: a site's own
1768
+ // application can answer that route (this framework's server interface does),
1769
+ // and the worker has no business putting an API answer in front of it. Every
1770
+ // other `/api/...` is an API call on the short spelling, which routeRequest
1771
+ // canonicalises to `/SASjsApi`.
1771
1772
  if (/^\/api\/?$/i.test(path)) return
1772
1773
 
1773
1774
  if (!/^\/(SASjsApi|SASLogon|AppStream|api|app)(\/|$)/i.test(path)) return
@@ -14,6 +14,8 @@ export interface MockSiteResult {
14
14
  * large folder or file. Zero when the site ships no snapshot.
15
15
  */
16
16
  snapshotShards: number;
17
+ /** True when the site carries the API console at <base>/api-console/. */
18
+ apiConsole: boolean;
17
19
  /** True when a recorded SAS session was shipped, for .sas runs to answer. */
18
20
  sasTranscript: boolean;
19
21
  /**
@@ -1008,6 +1008,75 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
1008
1008
  }
1009
1009
  return { patched: patched, warnings: warnings };
1010
1010
  };
1011
+ /**
1012
+ * The folder the console lives in, under the site root.
1013
+ *
1014
+ * Not `api`: an application can answer `/api` itself - this framework's own server
1015
+ * interface does - and a page there would collide with its route, so a site
1016
+ * refresh on that screen would land on the console instead of the application.
1017
+ */
1018
+ var API_CONSOLE_DIR = 'api-console';
1019
+ /** The files the console consists of, under `<output>/api-console`. */
1020
+ var API_CONSOLE_FILES = [
1021
+ 'index.html',
1022
+ 'console.js',
1023
+ 'console.css',
1024
+ 'operations.json'
1025
+ ];
1026
+ /**
1027
+ * The API console: a page at `<base>/api-console/` that lists every operation the API
1028
+ * document declares and sends them against this host.
1029
+ *
1030
+ * The route table is the same one the worker answers from (`ROUTES` from
1031
+ * @sasjs/api), so the console cannot drift from the API: a route the package
1032
+ * adds appears here on the next build, with the parameters it declares.
1033
+ *
1034
+ * Three files and the data, all static. It is a developer surface rather than a
1035
+ * screen of the app, so nothing in the page links to it and nothing preloads it -
1036
+ * a visit costs the demo nothing until someone types the URL.
1037
+ */
1038
+ var writeApiConsole = function (outputFolder, version) {
1039
+ var folder = path_1.default.join(outputFolder, API_CONSOLE_DIR);
1040
+ // `<base>/api-console/` is this build's, not the project's: a site that ships its own
1041
+ // files there would have them replaced without a word, so it keeps them and
1042
+ // the console is left out
1043
+ var taken = API_CONSOLE_FILES.filter(function (file) {
1044
+ return fs_1.default.existsSync(path_1.default.join(folder, file));
1045
+ });
1046
+ if (taken.length)
1047
+ return {
1048
+ written: false,
1049
+ operations: 0,
1050
+ warning: "The site already carries ".concat(taken
1051
+ .map(function (file) { return API_CONSOLE_DIR + '/' + file; })
1052
+ .join(', '), ", so the API console was not written. ") +
1053
+ 'Move those files, or set streamConfig.mockApi to false to silence this.'
1054
+ };
1055
+ fs_1.default.mkdirSync(folder, { recursive: true });
1056
+ // The console starts the host itself, so it has to ask for the same worker the
1057
+ // app's loader asks for: a registration is identified by its script URL, and a
1058
+ // bare `sw.js` here would replace the versioned one on every visit to either
1059
+ // page - an install nobody asked for, which is what the version exists to avoid.
1060
+ fs_1.default.writeFileSync(path_1.default.join(folder, 'index.html'), fs_1.default
1061
+ .readFileSync(path_1.default.join(ASSETS_FOLDER, 'api-index.html'), 'utf8')
1062
+ .replace('__MOCK_VERSION__', version));
1063
+ fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'api-console.js'), path_1.default.join(folder, 'console.js'));
1064
+ fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'api-console.css'), path_1.default.join(folder, 'console.css'));
1065
+ var operations = api_1.ROUTES.map(function (route) { return ({
1066
+ id: route.id,
1067
+ method: route.method,
1068
+ path: route.path,
1069
+ tag: route.tag,
1070
+ summary: route.summary,
1071
+ params: route.params,
1072
+ requestBodies: route.requestBodies,
1073
+ responses: route.responses,
1074
+ implemented: route.implemented,
1075
+ reason: route.reason
1076
+ }); });
1077
+ fs_1.default.writeFileSync(path_1.default.join(folder, 'operations.json'), JSON.stringify(operations, null, 2));
1078
+ return { written: true, operations: operations.length };
1079
+ };
1011
1080
  /**
1012
1081
  * Builds a static site that runs the project's JS services (its mocks) in the
1013
1082
  * browser, so a frontend can be tested with no server and no SAS.
@@ -1024,11 +1093,11 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
1024
1093
  */
1025
1094
  function buildMockSite(target) {
1026
1095
  return __awaiter(this, void 0, void 0, function () {
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;
1096
+ 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;
1028
1097
  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) {
1098
+ var _j, _k, _l, _m, _o, _p, _q, _r;
1099
+ return __generator(this, function (_s) {
1100
+ switch (_s.label) {
1032
1101
  case 0:
1033
1102
  projectDir = process.projectDir || process.cwd();
1034
1103
  appLocRaw = target.appLoc;
@@ -1040,7 +1109,7 @@ function buildMockSite(target) {
1040
1109
  path_1.default.join(projectDir, 'sasjsbuild');
1041
1110
  return [4 /*yield*/, (0, utils_1.folderExists)(buildFolder)];
1042
1111
  case 1:
1043
- if (!(_q.sent())) {
1112
+ if (!(_s.sent())) {
1044
1113
  throw new Error("Build folder not found: ".concat(buildFolder, ". Run `sasjs compile` first."));
1045
1114
  }
1046
1115
  basePath = normaliseBasePath((_j = target.streamConfig) === null || _j === void 0 ? void 0 : _j.mockWebBasePath);
@@ -1051,7 +1120,7 @@ function buildMockSite(target) {
1051
1120
  webFolder = path_1.default.resolve(projectDir, webSourceRaw);
1052
1121
  return [4 /*yield*/, (0, utils_1.folderExists)(webFolder)];
1053
1122
  case 2:
1054
- if (!(_q.sent())) {
1123
+ if (!(_s.sent())) {
1055
1124
  throw new Error("Web source folder not found: ".concat(webFolder, ". Check streamConfig.webSourcePath - build the frontend first."));
1056
1125
  }
1057
1126
  if (!fs_1.default.existsSync(path_1.default.join(webFolder, 'index.html'))) {
@@ -1146,7 +1215,7 @@ function buildMockSite(target) {
1146
1215
  case 3:
1147
1216
  // 2. the linter, bundled from the installed packages so the code routes answer
1148
1217
  // with the rules this CLI resolved rather than a second implementation
1149
- _q.sent();
1218
+ _s.sent();
1150
1219
  // sw.js sits at the site root: a worker can only control the folder it is
1151
1220
  // served from, and the app has to be controlled from /
1152
1221
  fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'sw.js'), path_1.default.join(outputFolder, 'sw.js'));
@@ -1250,6 +1319,13 @@ function buildMockSite(target) {
1250
1319
  buildStamp: buildStamp,
1251
1320
  builtAt: new Date().toISOString()
1252
1321
  }, null, 2));
1322
+ apiConsole = false;
1323
+ if (((_q = target.streamConfig) === null || _q === void 0 ? void 0 : _q.mockApi) !== false) {
1324
+ written = writeApiConsole(outputFolder, hostVersion);
1325
+ apiConsole = written.written;
1326
+ if (written.warning)
1327
+ (_r = process.logger) === null || _r === void 0 ? void 0 : _r.warn(written.warning);
1328
+ }
1253
1329
  indexPath = path_1.default.join(outputFolder, 'index.html');
1254
1330
  _f = patchIndexHtml(fs_1.default.readFileSync(indexPath, 'utf8'), hostVersion, basePath, outputFolder), patched = _f.patched, warnings = _f.warnings;
1255
1331
  fs_1.default.writeFileSync(indexPath, patched);
@@ -1262,6 +1338,7 @@ function buildMockSite(target) {
1262
1338
  webFileCount: webFileCount,
1263
1339
  snapshot: hasSnapshot,
1264
1340
  snapshotShards: snapshotShards,
1341
+ apiConsole: apiConsole,
1265
1342
  sasTranscript: hasTranscript,
1266
1343
  identitySource: identity.source
1267
1344
  }];