@sasjs/cli 6.30.1 → 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.
- package/build/commands/build/internal/mockSite/assets/api-console.css +365 -0
- package/build/commands/build/internal/mockSite/assets/api-console.js +762 -0
- package/build/commands/build/internal/mockSite/assets/api-index.html +83 -0
- package/build/commands/build/internal/mockSite/assets/sw.js +28 -4
- package/build/commands/build/internal/mockSite/mockSite.d.ts +2 -0
- package/build/commands/build/internal/mockSite/mockSite.js +80 -8
- package/build/commands/build/internal/mockSite/spec/apiConsole.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/apiConsole.spec.js +1371 -0
- package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.js +303 -0
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +86 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +99 -49
- package/build/commands/version/spec/version.spec.js +11 -1
- package/build/commands/version/version.d.ts +5 -3
- package/build/commands/version/version.js +17 -7
- package/build/types/mockWebBasePath.d.ts +9 -0
- package/package.json +13 -32
|
@@ -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/ 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>
|
|
@@ -1732,6 +1732,22 @@ const handle = async (request, url) => {
|
|
|
1732
1732
|
return response
|
|
1733
1733
|
}
|
|
1734
1734
|
|
|
1735
|
+
// The console's own files under `/api/`, replaced at build time from the list the
|
|
1736
|
+
// builder writes the folder from, so the two cannot drift apart.
|
|
1737
|
+
const API_CONSOLE_FILES = [
|
|
1738
|
+
'index.html',
|
|
1739
|
+
'console.js',
|
|
1740
|
+
'console.css',
|
|
1741
|
+
'operations.json'
|
|
1742
|
+
]
|
|
1743
|
+
|
|
1744
|
+
const CONSOLE_FILE = new RegExp(
|
|
1745
|
+
'^/api/(' +
|
|
1746
|
+
API_CONSOLE_FILES.map((name) => name.replace(/\./g, '\\.')).join('|') +
|
|
1747
|
+
')$',
|
|
1748
|
+
'i'
|
|
1749
|
+
)
|
|
1750
|
+
|
|
1735
1751
|
self.addEventListener('fetch', (event) => {
|
|
1736
1752
|
const url = new URL(event.request.url)
|
|
1737
1753
|
if (url.origin !== self.location.origin) return
|
|
@@ -1764,12 +1780,20 @@ self.addEventListener('fetch', (event) => {
|
|
|
1764
1780
|
// listing is the server's web route, and an interface that lists apps asks for
|
|
1765
1781
|
// it on every visit.
|
|
1766
1782
|
//
|
|
1767
|
-
// `/api` and `/api/` on their own are the
|
|
1768
|
-
//
|
|
1769
|
-
//
|
|
1770
|
-
//
|
|
1783
|
+
// `/api` and `/api/` on their own are the console page - `api/index.html`,
|
|
1784
|
+
// which this build writes - so the worker stays out of the way there and the
|
|
1785
|
+
// static host answers it. Every other `/api/...` is an API call on the short
|
|
1786
|
+
// spelling, which routeRequest canonicalises to `/SASjsApi`.
|
|
1771
1787
|
if (/^\/api\/?$/i.test(path)) return
|
|
1772
1788
|
|
|
1789
|
+
// And the console's own files, which live under that prefix: a path naming one
|
|
1790
|
+
// of them is the page's, not an API call, so the static host answers it. Named
|
|
1791
|
+
// one by one on purpose - an extension test would also pass through
|
|
1792
|
+
// `/api/admin/logs/foo.log`, which is a real call on the short spelling. The
|
|
1793
|
+
// list is replaced at build time with the one the builder writes the folder
|
|
1794
|
+
// from, so a file added there cannot arrive here as an API path.
|
|
1795
|
+
if (CONSOLE_FILE.test(path)) return
|
|
1796
|
+
|
|
1773
1797
|
if (!/^\/(SASjsApi|SASLogon|AppStream|api|app)(\/|$)/i.test(path)) return
|
|
1774
1798
|
event.respondWith(handle(event.request, url))
|
|
1775
1799
|
})
|
|
@@ -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/. */
|
|
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,67 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
|
|
|
1008
1008
|
}
|
|
1009
1009
|
return { patched: patched, warnings: warnings };
|
|
1010
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
|
+
};
|
|
1011
1072
|
/**
|
|
1012
1073
|
* Builds a static site that runs the project's JS services (its mocks) in the
|
|
1013
1074
|
* browser, so a frontend can be tested with no server and no SAS.
|
|
@@ -1024,11 +1085,11 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
|
|
|
1024
1085
|
*/
|
|
1025
1086
|
function buildMockSite(target) {
|
|
1026
1087
|
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;
|
|
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;
|
|
1028
1089
|
var e_11, _g, e_12, _h;
|
|
1029
|
-
var _j, _k, _l, _m, _o, _p;
|
|
1030
|
-
return __generator(this, function (
|
|
1031
|
-
switch (
|
|
1090
|
+
var _j, _k, _l, _m, _o, _p, _q, _r;
|
|
1091
|
+
return __generator(this, function (_s) {
|
|
1092
|
+
switch (_s.label) {
|
|
1032
1093
|
case 0:
|
|
1033
1094
|
projectDir = process.projectDir || process.cwd();
|
|
1034
1095
|
appLocRaw = target.appLoc;
|
|
@@ -1040,7 +1101,7 @@ function buildMockSite(target) {
|
|
|
1040
1101
|
path_1.default.join(projectDir, 'sasjsbuild');
|
|
1041
1102
|
return [4 /*yield*/, (0, utils_1.folderExists)(buildFolder)];
|
|
1042
1103
|
case 1:
|
|
1043
|
-
if (!(
|
|
1104
|
+
if (!(_s.sent())) {
|
|
1044
1105
|
throw new Error("Build folder not found: ".concat(buildFolder, ". Run `sasjs compile` first."));
|
|
1045
1106
|
}
|
|
1046
1107
|
basePath = normaliseBasePath((_j = target.streamConfig) === null || _j === void 0 ? void 0 : _j.mockWebBasePath);
|
|
@@ -1051,7 +1112,7 @@ function buildMockSite(target) {
|
|
|
1051
1112
|
webFolder = path_1.default.resolve(projectDir, webSourceRaw);
|
|
1052
1113
|
return [4 /*yield*/, (0, utils_1.folderExists)(webFolder)];
|
|
1053
1114
|
case 2:
|
|
1054
|
-
if (!(
|
|
1115
|
+
if (!(_s.sent())) {
|
|
1055
1116
|
throw new Error("Web source folder not found: ".concat(webFolder, ". Check streamConfig.webSourcePath - build the frontend first."));
|
|
1056
1117
|
}
|
|
1057
1118
|
if (!fs_1.default.existsSync(path_1.default.join(webFolder, 'index.html'))) {
|
|
@@ -1146,7 +1207,7 @@ function buildMockSite(target) {
|
|
|
1146
1207
|
case 3:
|
|
1147
1208
|
// 2. the linter, bundled from the installed packages so the code routes answer
|
|
1148
1209
|
// with the rules this CLI resolved rather than a second implementation
|
|
1149
|
-
|
|
1210
|
+
_s.sent();
|
|
1150
1211
|
// sw.js sits at the site root: a worker can only control the folder it is
|
|
1151
1212
|
// served from, and the app has to be controlled from /
|
|
1152
1213
|
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'sw.js'), path_1.default.join(outputFolder, 'sw.js'));
|
|
@@ -1199,7 +1260,10 @@ function buildMockSite(target) {
|
|
|
1199
1260
|
.readFileSync(swPath, 'utf8')
|
|
1200
1261
|
.replace("importScripts('host/runtime-core.js')", "importScripts('host/runtime-core.js?v=".concat(hostVersion, "')"))
|
|
1201
1262
|
.replace("importScripts('host/api-browser.js')", "importScripts('host/api-browser.js?v=".concat(hostVersion, "')"))
|
|
1202
|
-
.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))));
|
|
1203
1267
|
snapshotSource = path_1.default.join(buildFolder, 'mock-snapshot.json');
|
|
1204
1268
|
hasSnapshot = fs_1.default.existsSync(snapshotSource);
|
|
1205
1269
|
snapshotShards = 0;
|
|
@@ -1250,6 +1314,13 @@ function buildMockSite(target) {
|
|
|
1250
1314
|
buildStamp: buildStamp,
|
|
1251
1315
|
builtAt: new Date().toISOString()
|
|
1252
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
|
+
}
|
|
1253
1324
|
indexPath = path_1.default.join(outputFolder, 'index.html');
|
|
1254
1325
|
_f = patchIndexHtml(fs_1.default.readFileSync(indexPath, 'utf8'), hostVersion, basePath, outputFolder), patched = _f.patched, warnings = _f.warnings;
|
|
1255
1326
|
fs_1.default.writeFileSync(indexPath, patched);
|
|
@@ -1262,6 +1333,7 @@ function buildMockSite(target) {
|
|
|
1262
1333
|
webFileCount: webFileCount,
|
|
1263
1334
|
snapshot: hasSnapshot,
|
|
1264
1335
|
snapshotShards: snapshotShards,
|
|
1336
|
+
apiConsole: apiConsole,
|
|
1265
1337
|
sasTranscript: hasTranscript,
|
|
1266
1338
|
identitySource: identity.source
|
|
1267
1339
|
}];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|