@sasjs/cli 6.14.0 → 6.16.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/sw.js +742 -173
- package/build/commands/build/internal/mockSite/mockSite.d.ts +10 -0
- package/build/commands/build/internal/mockSite/mockSite.js +208 -32
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +102 -6
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +661 -34
- package/package.json +2 -1
|
@@ -28,6 +28,16 @@ export declare const WEBOUT_SENTINELS: {
|
|
|
28
28
|
userId: string;
|
|
29
29
|
apiServerUrl: string;
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Builds `lint-browser.js` from the installed packages.
|
|
33
|
+
*
|
|
34
|
+
* The shim sources are written to the system temp folder, never beside this
|
|
35
|
+
* module: an installed package can be read-only (a global install, a pnpm store
|
|
36
|
+
* symlink, a container that builds as another user), and a build that writes
|
|
37
|
+
* inside it fails with EACCES for exactly the users least able to diagnose it.
|
|
38
|
+
* Resolution is kept by naming the CLI's `node_modules` through `nodePaths`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const buildLintBundle: (destination: string) => void;
|
|
31
41
|
/**
|
|
32
42
|
* The bounds on what the browser drive seeds. A byte entry is carried as base64
|
|
33
43
|
* in vfs-seed.json and held in the worker's memory, so a large artefact - an
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
|
|
3
|
+
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
|
|
4
|
+
return cooked;
|
|
5
|
+
};
|
|
2
6
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
7
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
8
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -75,11 +79,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
75
79
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
76
80
|
};
|
|
77
81
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
78
|
-
exports.DRIVE_FORMAT = exports.MAX_SEED_BYTES = exports.MAX_BINARY_SEED_BYTES = exports.WEBOUT_SENTINELS = void 0;
|
|
82
|
+
exports.DRIVE_FORMAT = exports.MAX_SEED_BYTES = exports.MAX_BINARY_SEED_BYTES = exports.buildLintBundle = exports.WEBOUT_SENTINELS = void 0;
|
|
79
83
|
exports.buildMockSite = buildMockSite;
|
|
80
84
|
var crypto_1 = require("crypto");
|
|
81
85
|
var fs_1 = __importDefault(require("fs"));
|
|
86
|
+
var os_1 = __importDefault(require("os"));
|
|
82
87
|
var path_1 = __importDefault(require("path"));
|
|
88
|
+
var esbuild_1 = require("esbuild");
|
|
83
89
|
var utils_1 = require("@sasjs/utils");
|
|
84
90
|
/**
|
|
85
91
|
* The webout() block is the one @sasjs/utils generates for the server, emitted
|
|
@@ -116,8 +122,141 @@ var ASSETS_FOLDER = path_1.default.join(__dirname, 'assets');
|
|
|
116
122
|
*/
|
|
117
123
|
var API_BROWSER_BUILD = path_1.default.join(path_1.default.dirname(require.resolve('@sasjs/api')), '..', '..', 'build', 'browser.js');
|
|
118
124
|
var toPosix = function (value) { return value.split(path_1.default.sep).join('/'); };
|
|
119
|
-
|
|
125
|
+
/**
|
|
126
|
+
* The linter, as a service worker can run it.
|
|
127
|
+
*
|
|
128
|
+
* `@sasjs/api/code` is a node-only entry because `@sasjs/lint` resolves its
|
|
129
|
+
* configuration by walking a filesystem - but the linter's core (`lintText`,
|
|
130
|
+
* `formatText`) reads only the text it is given and the configuration it is
|
|
131
|
+
* passed, so the two can run anywhere the module graph can be built. This
|
|
132
|
+
* bundle is that graph: the package's own code service, the linter it wraps, and
|
|
133
|
+
* shims for the four node modules reached at import time (`path` and `os` for
|
|
134
|
+
* resolution, `fs` and `@sasjs/utils/file` for the config lookup that a mock
|
|
135
|
+
* never makes, because it hands the linter the default configuration).
|
|
136
|
+
*
|
|
137
|
+
* It is built from the installed @sasjs/api and @sasjs/lint on every mock
|
|
138
|
+
* build, so the rules a client sees are the version the CLI resolved - a
|
|
139
|
+
* committed bundle would be a second copy to drift.
|
|
140
|
+
*/
|
|
141
|
+
var LINT_BUNDLE_FILES = {
|
|
142
|
+
'entry.js': [
|
|
143
|
+
"import './process.js'",
|
|
144
|
+
"import { createCodeService } from '@sasjs/api/code'",
|
|
145
|
+
'globalThis.SASjsCode = { createCodeService }'
|
|
146
|
+
].join('\n'),
|
|
147
|
+
'process.js': [
|
|
148
|
+
'// the linter reads process.env for a default and process.cwd() for a root;',
|
|
149
|
+
'// neither is a real lookup in a worker, and both are answered in one place',
|
|
150
|
+
'if (typeof globalThis.process === "undefined")',
|
|
151
|
+
' globalThis.process = {',
|
|
152
|
+
' env: {},',
|
|
153
|
+
' platform: "browser",',
|
|
154
|
+
' cwd: () => "/",',
|
|
155
|
+
' versions: {},',
|
|
156
|
+
' argv: [],',
|
|
157
|
+
' version: "v0"',
|
|
158
|
+
' }'
|
|
159
|
+
].join('\n'),
|
|
160
|
+
'fs.js': [
|
|
161
|
+
'const unavailable = (name) => () => {',
|
|
162
|
+
' throw new Error(name + " is not available in the browser host")',
|
|
163
|
+
'}',
|
|
164
|
+
'module.exports = {',
|
|
165
|
+
' readFileSync: unavailable("readFileSync"),',
|
|
166
|
+
' writeFileSync: unavailable("writeFileSync"),',
|
|
167
|
+
' existsSync: () => false,',
|
|
168
|
+
' promises: {}',
|
|
169
|
+
'}'
|
|
170
|
+
].join('\n'),
|
|
171
|
+
'os.js': [
|
|
172
|
+
'module.exports = {',
|
|
173
|
+
' homedir: () => "/",',
|
|
174
|
+
' platform: () => "browser",',
|
|
175
|
+
' tmpdir: () => "/tmp",',
|
|
176
|
+
' EOL: "\\n"',
|
|
177
|
+
'}'
|
|
178
|
+
].join('\n'),
|
|
179
|
+
'utils-file.js': [
|
|
180
|
+
'const unavailable = (name) => async () => {',
|
|
181
|
+
' throw new Error(name + " is not available in the browser host")',
|
|
182
|
+
'}',
|
|
183
|
+
'module.exports = {',
|
|
184
|
+
' readFile: unavailable("readFile"),',
|
|
185
|
+
' writeFile: unavailable("writeFile"),',
|
|
186
|
+
' fileExists: async () => false,',
|
|
187
|
+
' listFilesInFolder: async () => []',
|
|
188
|
+
'}'
|
|
189
|
+
].join('\n'),
|
|
190
|
+
'path.js': String.raw(templateObject_1 || (templateObject_1 = __makeTemplateObject(["// the posix half of path, which is all the linter uses: the rules name files,\n// and a mock hands the linter no folder to walk\nconst SEP = String.fromCharCode(92)\nconst clean = (value) =>\n String(value).split(SEP).join(\"/\").replace(//+/g, \"/\")\nconst strip = (value) => clean(value).replace(//+$/, \"\")\nconst basename = (value) => strip(value).split(\"/\").pop() || \"\"\nconst extname = (value) => {\n const base = basename(value)\n const dot = base.lastIndexOf(\".\")\n return dot > 0 ? base.slice(dot) : \"\"\n}\nconst dirname = (value) => {\n const parts = clean(value).split(\"/\")\n parts.pop()\n return parts.join(\"/\") || \".\"\n}\nconst join = (...parts) => clean(parts.filter(Boolean).join(\"/\"))\nmodule.exports = {\n sep: \"/\",\n delimiter: \":\",\n basename,\n extname,\n dirname,\n join,\n resolve: (...parts) => clean(parts.filter(Boolean).join(\"/\")),\n relative: (from, to) => clean(to).replace(clean(from) + \"/\", \"\"),\n isAbsolute: (value) => String(value).startsWith(\"/\"),\n normalize: clean,\n parse: (value) => ({\n root: \"\",\n dir: dirname(value),\n base: basename(value),\n ext: extname(value),\n name: basename(value).slice(0, basename(value).length - extname(value).length)\n })\n}"], ["// the posix half of path, which is all the linter uses: the rules name files,\n// and a mock hands the linter no folder to walk\nconst SEP = String.fromCharCode(92)\nconst clean = (value) =>\n String(value).split(SEP).join(\"/\").replace(/\\/+/g, \"/\")\nconst strip = (value) => clean(value).replace(/\\/+$/, \"\")\nconst basename = (value) => strip(value).split(\"/\").pop() || \"\"\nconst extname = (value) => {\n const base = basename(value)\n const dot = base.lastIndexOf(\".\")\n return dot > 0 ? base.slice(dot) : \"\"\n}\nconst dirname = (value) => {\n const parts = clean(value).split(\"/\")\n parts.pop()\n return parts.join(\"/\") || \".\"\n}\nconst join = (...parts) => clean(parts.filter(Boolean).join(\"/\"))\nmodule.exports = {\n sep: \"/\",\n delimiter: \":\",\n basename,\n extname,\n dirname,\n join,\n resolve: (...parts) => clean(parts.filter(Boolean).join(\"/\")),\n relative: (from, to) => clean(to).replace(clean(from) + \"/\", \"\"),\n isAbsolute: (value) => String(value).startsWith(\"/\"),\n normalize: clean,\n parse: (value) => ({\n root: \"\",\n dir: dirname(value),\n base: basename(value),\n ext: extname(value),\n name: basename(value).slice(0, basename(value).length - extname(value).length)\n })\n}"])))
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* The `node_modules` the CLI's own dependency lives in.
|
|
194
|
+
*
|
|
195
|
+
* esbuild resolves a bare import from the entry file upwards, which is wrong for
|
|
196
|
+
* a scratch entry outside the install: the walk would miss the installed
|
|
197
|
+
* packages entirely. `nodePaths` states where to look instead, and it is taken
|
|
198
|
+
* from `require.resolve` so it is the tree the CLI actually loaded from - a
|
|
199
|
+
* hoisted install, a nested one, and a pnpm store symlink all answer with their
|
|
200
|
+
* own location.
|
|
201
|
+
*/
|
|
202
|
+
var NODE_MODULES = (function () {
|
|
203
|
+
var resolved = require.resolve('@sasjs/api');
|
|
204
|
+
var marker = "".concat(path_1.default.sep, "node_modules").concat(path_1.default.sep);
|
|
205
|
+
var index = resolved.lastIndexOf(marker);
|
|
206
|
+
return index < 0 ? undefined : resolved.slice(0, index + marker.length - 1);
|
|
207
|
+
})();
|
|
208
|
+
/**
|
|
209
|
+
* Builds `lint-browser.js` from the installed packages.
|
|
210
|
+
*
|
|
211
|
+
* The shim sources are written to the system temp folder, never beside this
|
|
212
|
+
* module: an installed package can be read-only (a global install, a pnpm store
|
|
213
|
+
* symlink, a container that builds as another user), and a build that writes
|
|
214
|
+
* inside it fails with EACCES for exactly the users least able to diagnose it.
|
|
215
|
+
* Resolution is kept by naming the CLI's `node_modules` through `nodePaths`.
|
|
216
|
+
*/
|
|
217
|
+
var buildLintBundle = function (destination) {
|
|
120
218
|
var e_1, _a;
|
|
219
|
+
var work = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'sasjs-lint-'));
|
|
220
|
+
try {
|
|
221
|
+
try {
|
|
222
|
+
for (var _b = __values(Object.entries(LINT_BUNDLE_FILES)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
223
|
+
var _d = __read(_c.value, 2), name_1 = _d[0], contents = _d[1];
|
|
224
|
+
fs_1.default.writeFileSync(path_1.default.join(work, name_1), contents);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
228
|
+
finally {
|
|
229
|
+
try {
|
|
230
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
231
|
+
}
|
|
232
|
+
finally { if (e_1) throw e_1.error; }
|
|
233
|
+
}
|
|
234
|
+
(0, esbuild_1.buildSync)({
|
|
235
|
+
entryPoints: [path_1.default.join(work, 'entry.js')],
|
|
236
|
+
bundle: true,
|
|
237
|
+
platform: 'browser',
|
|
238
|
+
format: 'iife',
|
|
239
|
+
outfile: destination,
|
|
240
|
+
nodePaths: NODE_MODULES ? [NODE_MODULES] : [],
|
|
241
|
+
alias: {
|
|
242
|
+
fs: path_1.default.join(work, 'fs.js'),
|
|
243
|
+
path: path_1.default.join(work, 'path.js'),
|
|
244
|
+
os: path_1.default.join(work, 'os.js'),
|
|
245
|
+
'@sasjs/utils/file': path_1.default.join(work, 'utils-file.js')
|
|
246
|
+
},
|
|
247
|
+
logLevel: 'silent'
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
throw new Error("The browser lint bundle could not be built from @sasjs/api/code: ".concat((error && error.message) || error));
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
fs_1.default.rmSync(work, { recursive: true, force: true });
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
exports.buildLintBundle = buildLintBundle;
|
|
258
|
+
var listFiles = function (folder, base) {
|
|
259
|
+
var e_2, _a;
|
|
121
260
|
if (base === void 0) { base = folder; }
|
|
122
261
|
var entries = fs_1.default.readdirSync(folder, { withFileTypes: true });
|
|
123
262
|
var files = [];
|
|
@@ -131,17 +270,17 @@ var listFiles = function (folder, base) {
|
|
|
131
270
|
files.push(toPosix(path_1.default.relative(base, absolute)));
|
|
132
271
|
}
|
|
133
272
|
}
|
|
134
|
-
catch (
|
|
273
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
135
274
|
finally {
|
|
136
275
|
try {
|
|
137
276
|
if (entries_1_1 && !entries_1_1.done && (_a = entries_1.return)) _a.call(entries_1);
|
|
138
277
|
}
|
|
139
|
-
finally { if (
|
|
278
|
+
finally { if (e_2) throw e_2.error; }
|
|
140
279
|
}
|
|
141
280
|
return files;
|
|
142
281
|
};
|
|
143
282
|
var copyFolder = function (from, to) {
|
|
144
|
-
var
|
|
283
|
+
var e_3, _a;
|
|
145
284
|
fs_1.default.mkdirSync(to, { recursive: true });
|
|
146
285
|
try {
|
|
147
286
|
for (var _b = __values(fs_1.default.readdirSync(from, { withFileTypes: true })), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
@@ -154,12 +293,12 @@ var copyFolder = function (from, to) {
|
|
|
154
293
|
fs_1.default.copyFileSync(source, target);
|
|
155
294
|
}
|
|
156
295
|
}
|
|
157
|
-
catch (
|
|
296
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
158
297
|
finally {
|
|
159
298
|
try {
|
|
160
299
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
161
300
|
}
|
|
162
|
-
finally { if (
|
|
301
|
+
finally { if (e_3) throw e_3.error; }
|
|
163
302
|
}
|
|
164
303
|
};
|
|
165
304
|
/**
|
|
@@ -221,7 +360,7 @@ var readDriveFile = function (absolute) {
|
|
|
221
360
|
};
|
|
222
361
|
};
|
|
223
362
|
var seedFromBuild = function (buildFolder, appLoc, exclude, excludeFolders) {
|
|
224
|
-
var
|
|
363
|
+
var e_4, _a, e_5, _b, e_6, _c;
|
|
225
364
|
if (exclude === void 0) { exclude = []; }
|
|
226
365
|
if (excludeFolders === void 0) { excludeFolders = []; }
|
|
227
366
|
var files = {};
|
|
@@ -247,26 +386,26 @@ var seedFromBuild = function (buildFolder, appLoc, exclude, excludeFolders) {
|
|
|
247
386
|
candidates.push({ key: key, absolute: path_1.default.join(folder, relative) });
|
|
248
387
|
};
|
|
249
388
|
try {
|
|
250
|
-
for (var _d = (
|
|
389
|
+
for (var _d = (e_5 = void 0, __values(listFiles(folder))), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
251
390
|
var relative = _e.value;
|
|
252
391
|
_loop_1(relative);
|
|
253
392
|
}
|
|
254
393
|
}
|
|
255
|
-
catch (
|
|
394
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
256
395
|
finally {
|
|
257
396
|
try {
|
|
258
397
|
if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
|
|
259
398
|
}
|
|
260
|
-
finally { if (
|
|
399
|
+
finally { if (e_5) throw e_5.error; }
|
|
261
400
|
}
|
|
262
401
|
}
|
|
263
402
|
}
|
|
264
|
-
catch (
|
|
403
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
265
404
|
finally {
|
|
266
405
|
try {
|
|
267
406
|
if (directories_1_1 && !directories_1_1.done && (_a = directories_1.return)) _a.call(directories_1);
|
|
268
407
|
}
|
|
269
|
-
finally { if (
|
|
408
|
+
finally { if (e_4) throw e_4.error; }
|
|
270
409
|
}
|
|
271
410
|
// The budget is spent in path order, not in whatever order the folder listing
|
|
272
411
|
// returns, so the same project yields the same drive - and the same warning -
|
|
@@ -294,12 +433,12 @@ var seedFromBuild = function (buildFolder, appLoc, exclude, excludeFolders) {
|
|
|
294
433
|
files[candidate.key] = file.contents;
|
|
295
434
|
}
|
|
296
435
|
}
|
|
297
|
-
catch (
|
|
436
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
298
437
|
finally {
|
|
299
438
|
try {
|
|
300
439
|
if (candidates_1_1 && !candidates_1_1.done && (_c = candidates_1.return)) _c.call(candidates_1);
|
|
301
440
|
}
|
|
302
|
-
finally { if (
|
|
441
|
+
finally { if (e_6) throw e_6.error; }
|
|
303
442
|
}
|
|
304
443
|
return { files: files, skipped: skipped };
|
|
305
444
|
};
|
|
@@ -391,6 +530,37 @@ var EXECUTABLE_TYPES = [
|
|
|
391
530
|
'text/javascript',
|
|
392
531
|
'application/javascript'
|
|
393
532
|
];
|
|
533
|
+
/**
|
|
534
|
+
* Tells the interface that this page is answered by the browser host.
|
|
535
|
+
*
|
|
536
|
+
* A mock site is the server's own interface running against a service worker,
|
|
537
|
+
* and nothing in the document said so: the API answers the same routes, the same
|
|
538
|
+
* mode and the same run times a desktop server does, so an interface could not
|
|
539
|
+
* tell a visitor what they were looking at. sasjs/server reads the tag and, where
|
|
540
|
+
* it is absent, knows the server answered the page.
|
|
541
|
+
*
|
|
542
|
+
* The insertion is reported when the page has no `<head>`, the way the other two
|
|
543
|
+
* rewrites report markup they could not find - silently leaving the tag off a
|
|
544
|
+
* page the browser host cannot mark is what the reader of the absence rule would
|
|
545
|
+
* misread as "a server answered this". The check for an existing tag is scoped
|
|
546
|
+
* to the head, because a page may mention the tag in a script or a template
|
|
547
|
+
* without carrying it, and a document-wide test would then insert nothing.
|
|
548
|
+
*/
|
|
549
|
+
var HOST_TAG = '<meta name="sasjs-host" content="browser" />';
|
|
550
|
+
var HOST_TAG_PATTERN = /<meta\s+name=["']sasjs-host["']/i;
|
|
551
|
+
var markBrowserHost = function (html, warnings) {
|
|
552
|
+
var head = /<head(\s[^>]*)?>/i.exec(html);
|
|
553
|
+
if (!head) {
|
|
554
|
+
warnings.push('no <head> element found - the sasjs-host tag was not added, so the page will read as server-answered');
|
|
555
|
+
return html;
|
|
556
|
+
}
|
|
557
|
+
var start = head.index + head[0].length;
|
|
558
|
+
var close = html.toLowerCase().indexOf('</head>', start);
|
|
559
|
+
var inHead = close < 0 ? html.slice(start) : html.slice(start, close);
|
|
560
|
+
if (HOST_TAG_PATTERN.test(inHead))
|
|
561
|
+
return html;
|
|
562
|
+
return html.slice(0, start) + HOST_TAG + html.slice(start);
|
|
563
|
+
};
|
|
394
564
|
/**
|
|
395
565
|
* Points the app at its own origin and replaces its script tags with the boot
|
|
396
566
|
* loader. Both changes are reported when the expected markup is absent, rather
|
|
@@ -399,14 +569,7 @@ var EXECUTABLE_TYPES = [
|
|
|
399
569
|
var patchIndexHtml = function (html, version) {
|
|
400
570
|
var warnings = [];
|
|
401
571
|
var patched = html;
|
|
402
|
-
|
|
403
|
-
// wants to say so: the server's own interface reads it to describe the host
|
|
404
|
-
// it is running on, and a browser host is a different thing to explain than a
|
|
405
|
-
// server process. Absence means the server answered the page.
|
|
406
|
-
if (!/<meta\s+name=["']sasjs-host["']/i.test(patched))
|
|
407
|
-
patched = patched.replace(/<head(\s[^>]*)?>/i, function (head) {
|
|
408
|
-
return "".concat(head, "\n <meta name=\"sasjs-host\" content=\"browser\" />");
|
|
409
|
-
});
|
|
572
|
+
patched = markBrowserHost(patched, warnings);
|
|
410
573
|
if (/<sasjs[^>]*>/i.test(patched)) {
|
|
411
574
|
patched = patched.replace(/(<sasjs[^>]*?)\sserverurl="[^"]*"/i, '$1 serverurl=""');
|
|
412
575
|
patched = patched.replace(/(<sasjs[^>]*?)\sloginmechanism="[^"]*"/i, '$1 loginmechanism="Default"');
|
|
@@ -417,7 +580,7 @@ var patchIndexHtml = function (html, version) {
|
|
|
417
580
|
var scripts = [];
|
|
418
581
|
var tagPattern = /<script\b([^>]*)>([\s\S]*?)<\/script>[ \t]*\r?\n?/gi;
|
|
419
582
|
patched = patched.replace(tagPattern, function (tag, source, body) {
|
|
420
|
-
var
|
|
583
|
+
var e_7, _a;
|
|
421
584
|
var attributes = parseAttributes(source);
|
|
422
585
|
var type = (attributes.type || '').trim().toLowerCase();
|
|
423
586
|
var inlined = !attributes.src;
|
|
@@ -442,16 +605,16 @@ var patchIndexHtml = function (html, version) {
|
|
|
442
605
|
continue;
|
|
443
606
|
if (attribute === 'type' && !attributes.type)
|
|
444
607
|
continue;
|
|
445
|
-
var
|
|
446
|
-
spec[
|
|
608
|
+
var name_2 = REPLAYED_ATTRIBUTES[attribute];
|
|
609
|
+
spec[name_2] = attribute === 'nomodule' ? 'true' : attributes[attribute];
|
|
447
610
|
}
|
|
448
611
|
}
|
|
449
|
-
catch (
|
|
612
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
450
613
|
finally {
|
|
451
614
|
try {
|
|
452
615
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
453
616
|
}
|
|
454
|
-
finally { if (
|
|
617
|
+
finally { if (e_7) throw e_7.error; }
|
|
455
618
|
}
|
|
456
619
|
scripts.push(spec);
|
|
457
620
|
return '';
|
|
@@ -484,7 +647,7 @@ var patchIndexHtml = function (html, version) {
|
|
|
484
647
|
function buildMockSite(target) {
|
|
485
648
|
return __awaiter(this, void 0, void 0, function () {
|
|
486
649
|
var projectDir, appLocRaw, appLoc, buildFolder, webSourceRaw, webFolder, outputFolder, streamConfig, excluded, _a, seed, skipped, jsPrograms, driveEntries, webFileCount, hostFolder, _b, _c, asset, hostVersion, swPath, snapshotSource, hasSnapshot, indexPath, _d, patched, warnings;
|
|
487
|
-
var
|
|
650
|
+
var e_8, _e;
|
|
488
651
|
var _f, _g, _h;
|
|
489
652
|
return __generator(this, function (_j) {
|
|
490
653
|
switch (_j.label) {
|
|
@@ -573,14 +736,24 @@ function buildMockSite(target) {
|
|
|
573
736
|
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, asset), path_1.default.join(hostFolder, asset));
|
|
574
737
|
}
|
|
575
738
|
}
|
|
576
|
-
catch (
|
|
739
|
+
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
577
740
|
finally {
|
|
578
741
|
try {
|
|
579
742
|
if (_c && !_c.done && (_e = _b.return)) _e.call(_b);
|
|
580
743
|
}
|
|
581
|
-
finally { if (
|
|
744
|
+
finally { if (e_8) throw e_8.error; }
|
|
582
745
|
}
|
|
583
746
|
fs_1.default.copyFileSync(API_BROWSER_BUILD, path_1.default.join(hostFolder, 'api-browser.js'));
|
|
747
|
+
// 2. the linter, bundled from the installed packages so the code routes answer
|
|
748
|
+
// with the rules this CLI resolved rather than a second implementation
|
|
749
|
+
return [4 /*yield*/, (0, exports.buildLintBundle)(path_1.default.join(hostFolder, 'lint-browser.js'))
|
|
750
|
+
// sw.js sits at the site root: a worker can only control the folder it is
|
|
751
|
+
// served from, and the app has to be controlled from /
|
|
752
|
+
];
|
|
753
|
+
case 3:
|
|
754
|
+
// 2. the linter, bundled from the installed packages so the code routes answer
|
|
755
|
+
// with the rules this CLI resolved rather than a second implementation
|
|
756
|
+
_j.sent();
|
|
584
757
|
// sw.js sits at the site root: a worker can only control the folder it is
|
|
585
758
|
// served from, and the app has to be controlled from /
|
|
586
759
|
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'sw.js'), path_1.default.join(outputFolder, 'sw.js'));
|
|
@@ -606,6 +779,7 @@ function buildMockSite(target) {
|
|
|
606
779
|
'intercept.js',
|
|
607
780
|
'boot.js',
|
|
608
781
|
'api-browser.js',
|
|
782
|
+
'lint-browser.js',
|
|
609
783
|
'webout-prelude.json'
|
|
610
784
|
]
|
|
611
785
|
.map(function (asset) { return fs_1.default.readFileSync(path_1.default.join(hostFolder, asset)); })
|
|
@@ -616,7 +790,8 @@ function buildMockSite(target) {
|
|
|
616
790
|
fs_1.default.writeFileSync(swPath, fs_1.default
|
|
617
791
|
.readFileSync(swPath, 'utf8')
|
|
618
792
|
.replace("importScripts('/host/runtime-core.js')", "importScripts('/host/runtime-core.js?v=".concat(hostVersion, "')"))
|
|
619
|
-
.replace("importScripts('/host/api-browser.js')", "importScripts('/host/api-browser.js?v=".concat(hostVersion, "')"))
|
|
793
|
+
.replace("importScripts('/host/api-browser.js')", "importScripts('/host/api-browser.js?v=".concat(hostVersion, "')"))
|
|
794
|
+
.replace("importScripts('/host/lint-browser.js')", "importScripts('/host/lint-browser.js?v=".concat(hostVersion, "')")));
|
|
620
795
|
snapshotSource = path_1.default.join(buildFolder, 'mock-snapshot.json');
|
|
621
796
|
hasSnapshot = fs_1.default.existsSync(snapshotSource);
|
|
622
797
|
if (hasSnapshot) {
|
|
@@ -649,3 +824,4 @@ function buildMockSite(target) {
|
|
|
649
824
|
});
|
|
650
825
|
});
|
|
651
826
|
}
|
|
827
|
+
var templateObject_1;
|
|
@@ -169,7 +169,8 @@ describe('browser-hosted build', function () {
|
|
|
169
169
|
'persist.js',
|
|
170
170
|
'intercept.js',
|
|
171
171
|
'boot.js',
|
|
172
|
-
'api-browser.js'
|
|
172
|
+
'api-browser.js',
|
|
173
|
+
'lint-browser.js'
|
|
173
174
|
]), _b = _a.next(); !_b.done; _b = _a.next()) {
|
|
174
175
|
asset = _b.value;
|
|
175
176
|
expect(fs_1.default.existsSync(path_1.default.join(out(), 'host', asset))).toBe(true);
|
|
@@ -209,11 +210,33 @@ describe('browser-hosted build', function () {
|
|
|
209
210
|
// a rebuild that changes either file has to change the URL
|
|
210
211
|
expect(sw).toMatch(/importScripts\('\/host\/runtime-core\.js\?v=[0-9a-f]{10}'\)/);
|
|
211
212
|
expect(sw).toMatch(/importScripts\('\/host\/api-browser\.js\?v=[0-9a-f]{10}'\)/);
|
|
213
|
+
expect(sw).toMatch(/importScripts\('\/host\/lint-browser\.js\?v=[0-9a-f]{10}'\)/);
|
|
212
214
|
expect(sw).not.toContain("importScripts('/host/runtime-core.js')");
|
|
213
215
|
return [2 /*return*/];
|
|
214
216
|
}
|
|
215
217
|
});
|
|
216
218
|
}); });
|
|
219
|
+
it('bundles the linter from the installed packages, ready for a worker', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
220
|
+
var bundle, vm, realm;
|
|
221
|
+
return __generator(this, function (_a) {
|
|
222
|
+
switch (_a.label) {
|
|
223
|
+
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
224
|
+
case 1:
|
|
225
|
+
_a.sent();
|
|
226
|
+
bundle = fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'lint-browser.js'), 'utf8');
|
|
227
|
+
// the bundle carries the package's code service, not a second linter
|
|
228
|
+
expect(bundle).toContain('SASjsCode');
|
|
229
|
+
expect(bundle).toContain('createCodeService');
|
|
230
|
+
vm = require('vm');
|
|
231
|
+
realm = { globalThis: null, console: console };
|
|
232
|
+
realm.globalThis = realm;
|
|
233
|
+
vm.createContext(realm);
|
|
234
|
+
vm.runInContext(bundle, realm);
|
|
235
|
+
expect(typeof realm.SASjsCode.createCodeService).toBe('function');
|
|
236
|
+
return [2 /*return*/];
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}); });
|
|
217
240
|
it('names the package when its browser build is missing, and keeps the previous site', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
218
241
|
var browserBuild, exists;
|
|
219
242
|
return __generator(this, function (_a) {
|
|
@@ -368,18 +391,91 @@ describe('browser-hosted build', function () {
|
|
|
368
391
|
});
|
|
369
392
|
}); });
|
|
370
393
|
it('states in the page that the API is answered here', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
371
|
-
var html;
|
|
394
|
+
var html, tag;
|
|
372
395
|
return __generator(this, function (_a) {
|
|
373
396
|
switch (_a.label) {
|
|
374
397
|
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
375
398
|
case 1:
|
|
376
399
|
_a.sent();
|
|
377
400
|
html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
401
|
+
tag = '<meta name="sasjs-host" content="browser" />';
|
|
402
|
+
expect(html).toContain(tag);
|
|
403
|
+
expect(html.indexOf(tag)).toBeLessThan(html.indexOf('</head>'));
|
|
404
|
+
// once: the tag it inserts is the one the guard then finds
|
|
405
|
+
expect(html.split('sasjs-host').length - 1).toBe(1);
|
|
406
|
+
return [2 /*return*/];
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}); });
|
|
410
|
+
it('leaves a page that already carries the host tag alone', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
411
|
+
var html;
|
|
412
|
+
return __generator(this, function (_a) {
|
|
413
|
+
switch (_a.label) {
|
|
414
|
+
case 0:
|
|
415
|
+
write(path_1.default.join(root, 'client', 'dist', 'index.html'), [
|
|
416
|
+
'<!doctype html><html><head>',
|
|
417
|
+
'<meta name="sasjs-host" content="browser">',
|
|
418
|
+
'</head><body><script src="main.abc123.js"></script></body></html>'
|
|
419
|
+
].join('\n'));
|
|
420
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
421
|
+
case 1:
|
|
422
|
+
_a.sent();
|
|
423
|
+
html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
424
|
+
expect(html.split('sasjs-host').length - 1).toBe(1);
|
|
425
|
+
return [2 /*return*/];
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}); });
|
|
429
|
+
it('inserts the tag when the page only mentions it outside the head', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
430
|
+
var html;
|
|
431
|
+
return __generator(this, function (_a) {
|
|
432
|
+
switch (_a.label) {
|
|
433
|
+
case 0:
|
|
434
|
+
write(path_1.default.join(root, 'client', 'dist', 'index.html'), [
|
|
435
|
+
'<!doctype html><html><head><title>App</title></head><body>',
|
|
436
|
+
'<script src="main.abc123.js"></script>',
|
|
437
|
+
'<template><meta name="sasjs-host" content="browser"></template>',
|
|
438
|
+
'</body></html>'
|
|
439
|
+
].join('\n'));
|
|
440
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
441
|
+
case 1:
|
|
442
|
+
_a.sent();
|
|
443
|
+
html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
444
|
+
// the template's copy is not the page's tag: the head gets its own
|
|
445
|
+
expect(html.split('sasjs-host').length - 1).toBe(2);
|
|
446
|
+
expect(html.indexOf('content="browser" />')).toBeLessThan(html.indexOf('</head>'));
|
|
447
|
+
return [2 /*return*/];
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}); });
|
|
451
|
+
it('marks a page whose head is never closed', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
452
|
+
var html;
|
|
453
|
+
return __generator(this, function (_a) {
|
|
454
|
+
switch (_a.label) {
|
|
455
|
+
case 0:
|
|
456
|
+
write(path_1.default.join(root, 'client', 'dist', 'index.html'), '<!doctype html><html><head><title>App</title><body><script src="main.abc123.js"></script></body></html>');
|
|
457
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
458
|
+
case 1:
|
|
459
|
+
_a.sent();
|
|
460
|
+
html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
381
461
|
expect(html).toContain('<meta name="sasjs-host" content="browser" />');
|
|
382
|
-
|
|
462
|
+
return [2 /*return*/];
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}); });
|
|
466
|
+
it('warns when a page has no head to mark', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
467
|
+
var html;
|
|
468
|
+
var _a;
|
|
469
|
+
return __generator(this, function (_b) {
|
|
470
|
+
switch (_b.label) {
|
|
471
|
+
case 0:
|
|
472
|
+
write(path_1.default.join(root, 'client', 'dist', 'index.html'), '<!doctype html><html><body><script src="main.abc123.js"></script></body></html>');
|
|
473
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
474
|
+
case 1:
|
|
475
|
+
_b.sent();
|
|
476
|
+
expect((_a = process.logger) === null || _a === void 0 ? void 0 : _a.warn).toHaveBeenCalledWith(expect.stringContaining('no <head> element found'));
|
|
477
|
+
html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
478
|
+
expect(html).not.toContain('sasjs-host');
|
|
383
479
|
return [2 /*return*/];
|
|
384
480
|
}
|
|
385
481
|
});
|