@sasjs/cli 6.15.0 → 6.17.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 +300 -13
- package/build/commands/build/internal/mockSite/mockSite.d.ts +10 -0
- package/build/commands/build/internal/mockSite/mockSite.js +202 -36
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +118 -6
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +279 -88
- package/package.json +2 -1
|
@@ -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
|
};
|
|
@@ -333,12 +472,12 @@ var assertSafeOutputFolder = function (outputFolder, projectDir, buildFolder, we
|
|
|
333
472
|
!isSameOrChild(buildFolder, outputFolder))
|
|
334
473
|
throw refusal('that folder is outside the project');
|
|
335
474
|
};
|
|
336
|
-
var
|
|
475
|
+
var countPrograms = function (files, extension) {
|
|
337
476
|
return Object.keys(files).filter(function (key) {
|
|
338
477
|
var inProgramFolder = PROGRAM_FOLDERS.some(function (folder) {
|
|
339
478
|
return key.includes("/".concat(folder, "/"));
|
|
340
479
|
});
|
|
341
|
-
return inProgramFolder && key.toLowerCase().endsWith(
|
|
480
|
+
return inProgramFolder && key.toLowerCase().endsWith(extension);
|
|
342
481
|
}).length;
|
|
343
482
|
};
|
|
344
483
|
/** The script-tag attributes that change how an app's script runs. */
|
|
@@ -441,7 +580,7 @@ var patchIndexHtml = function (html, version) {
|
|
|
441
580
|
var scripts = [];
|
|
442
581
|
var tagPattern = /<script\b([^>]*)>([\s\S]*?)<\/script>[ \t]*\r?\n?/gi;
|
|
443
582
|
patched = patched.replace(tagPattern, function (tag, source, body) {
|
|
444
|
-
var
|
|
583
|
+
var e_7, _a;
|
|
445
584
|
var attributes = parseAttributes(source);
|
|
446
585
|
var type = (attributes.type || '').trim().toLowerCase();
|
|
447
586
|
var inlined = !attributes.src;
|
|
@@ -466,16 +605,16 @@ var patchIndexHtml = function (html, version) {
|
|
|
466
605
|
continue;
|
|
467
606
|
if (attribute === 'type' && !attributes.type)
|
|
468
607
|
continue;
|
|
469
|
-
var
|
|
470
|
-
spec[
|
|
608
|
+
var name_2 = REPLAYED_ATTRIBUTES[attribute];
|
|
609
|
+
spec[name_2] = attribute === 'nomodule' ? 'true' : attributes[attribute];
|
|
471
610
|
}
|
|
472
611
|
}
|
|
473
|
-
catch (
|
|
612
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
474
613
|
finally {
|
|
475
614
|
try {
|
|
476
615
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
477
616
|
}
|
|
478
|
-
finally { if (
|
|
617
|
+
finally { if (e_7) throw e_7.error; }
|
|
479
618
|
}
|
|
480
619
|
scripts.push(spec);
|
|
481
620
|
return '';
|
|
@@ -507,11 +646,11 @@ var patchIndexHtml = function (html, version) {
|
|
|
507
646
|
*/
|
|
508
647
|
function buildMockSite(target) {
|
|
509
648
|
return __awaiter(this, void 0, void 0, function () {
|
|
510
|
-
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;
|
|
511
|
-
var
|
|
512
|
-
var _f, _g, _h;
|
|
513
|
-
return __generator(this, function (
|
|
514
|
-
switch (
|
|
649
|
+
var projectDir, appLocRaw, appLoc, buildFolder, webSourceRaw, webFolder, outputFolder, streamConfig, excluded, _a, seed, skipped, jsPrograms, sasPrograms, driveEntries, webFileCount, hostFolder, _b, _c, asset, hostVersion, swPath, snapshotSource, hasSnapshot, indexPath, _d, patched, warnings;
|
|
650
|
+
var e_8, _e;
|
|
651
|
+
var _f, _g, _h, _j;
|
|
652
|
+
return __generator(this, function (_k) {
|
|
653
|
+
switch (_k.label) {
|
|
515
654
|
case 0:
|
|
516
655
|
projectDir = process.projectDir || process.cwd();
|
|
517
656
|
appLocRaw = target.appLoc;
|
|
@@ -523,7 +662,7 @@ function buildMockSite(target) {
|
|
|
523
662
|
path_1.default.join(projectDir, 'sasjsbuild');
|
|
524
663
|
return [4 /*yield*/, (0, utils_1.folderExists)(buildFolder)];
|
|
525
664
|
case 1:
|
|
526
|
-
if (!(
|
|
665
|
+
if (!(_k.sent())) {
|
|
527
666
|
throw new Error("Build folder not found: ".concat(buildFolder, ". Run `sasjs compile` first."));
|
|
528
667
|
}
|
|
529
668
|
webSourceRaw = (_f = target.streamConfig) === null || _f === void 0 ? void 0 : _f.webSourcePath;
|
|
@@ -533,7 +672,7 @@ function buildMockSite(target) {
|
|
|
533
672
|
webFolder = path_1.default.resolve(projectDir, webSourceRaw);
|
|
534
673
|
return [4 /*yield*/, (0, utils_1.folderExists)(webFolder)];
|
|
535
674
|
case 2:
|
|
536
|
-
if (!(
|
|
675
|
+
if (!(_k.sent())) {
|
|
537
676
|
throw new Error("Web source folder not found: ".concat(webFolder, ". Check streamConfig.webSourcePath - build the frontend first."));
|
|
538
677
|
}
|
|
539
678
|
if (!fs_1.default.existsSync(path_1.default.join(webFolder, 'index.html'))) {
|
|
@@ -551,12 +690,26 @@ function buildMockSite(target) {
|
|
|
551
690
|
excluded.push("/sasjs/drive/files".concat(appLoc, "/services/").concat(streamConfig.streamWebFolder, "/"));
|
|
552
691
|
}
|
|
553
692
|
_a = seedFromBuild(buildFolder, appLoc, excluded, [outputFolder]), seed = _a.files, skipped = _a.skipped;
|
|
554
|
-
jsPrograms =
|
|
693
|
+
jsPrograms = countPrograms(seed, '.js');
|
|
694
|
+
sasPrograms = countPrograms(seed, '.sas');
|
|
555
695
|
driveEntries = Object.keys(seed).length;
|
|
556
|
-
|
|
557
|
-
|
|
696
|
+
// A build holding SAS and no JavaScript is a SAS target: its services cannot
|
|
697
|
+
// run here, and seeding them would produce a site whose buttons answer 400.
|
|
698
|
+
// A build holding NEITHER is a frontend host - the app is the whole thing and
|
|
699
|
+
// the worker answers its API - which is what an interface with no services
|
|
700
|
+
// compiles to, and the case this site is built for.
|
|
701
|
+
if (jsPrograms === 0 && sasPrograms > 0) {
|
|
702
|
+
throw new Error("No .js programs in ".concat(buildFolder, "/services, /jobs or /tests, but ").concat(sasPrograms, " SAS program(s). This target builds SAS code, which cannot run in a browser. ") +
|
|
558
703
|
'Build a target whose services are JavaScript (typically a mocks project).');
|
|
559
704
|
}
|
|
705
|
+
// No PROGRAMS is a different thing again: a compile that produced nothing, or
|
|
706
|
+
// a stale sasjsbuild from an unrelated command - `sasjs doc` leaves a folder
|
|
707
|
+
// behind that is not a program folder but does reach the drive, so the count
|
|
708
|
+
// that decides this is of programs, not of files.
|
|
709
|
+
if (jsPrograms + sasPrograms === 0) {
|
|
710
|
+
(_h = process.logger) === null || _h === void 0 ? void 0 : _h.warn("No compiled programs in ".concat(buildFolder, ": this site ships with an empty drive. ") +
|
|
711
|
+
'That is expected for an interface with no services; for anything else, check that compile wrote the target.');
|
|
712
|
+
}
|
|
560
713
|
// The package supplies the drive rules the worker answers with, so an install
|
|
561
714
|
// without its browser build is a build that cannot run. Checked here, with the
|
|
562
715
|
// other refusals, so a missing dependency leaves the previous site in place -
|
|
@@ -567,7 +720,7 @@ function buildMockSite(target) {
|
|
|
567
720
|
'or check that @sasjs/api still publishes build/browser.js.');
|
|
568
721
|
}
|
|
569
722
|
if (skipped.length) {
|
|
570
|
-
(
|
|
723
|
+
(_j = process.logger) === null || _j === void 0 ? void 0 : _j.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: ") +
|
|
571
724
|
"".concat(skipped
|
|
572
725
|
.slice(0, 3)
|
|
573
726
|
.map(function (file) {
|
|
@@ -597,14 +750,24 @@ function buildMockSite(target) {
|
|
|
597
750
|
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, asset), path_1.default.join(hostFolder, asset));
|
|
598
751
|
}
|
|
599
752
|
}
|
|
600
|
-
catch (
|
|
753
|
+
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
601
754
|
finally {
|
|
602
755
|
try {
|
|
603
756
|
if (_c && !_c.done && (_e = _b.return)) _e.call(_b);
|
|
604
757
|
}
|
|
605
|
-
finally { if (
|
|
758
|
+
finally { if (e_8) throw e_8.error; }
|
|
606
759
|
}
|
|
607
760
|
fs_1.default.copyFileSync(API_BROWSER_BUILD, path_1.default.join(hostFolder, 'api-browser.js'));
|
|
761
|
+
// 2. the linter, bundled from the installed packages so the code routes answer
|
|
762
|
+
// with the rules this CLI resolved rather than a second implementation
|
|
763
|
+
return [4 /*yield*/, (0, exports.buildLintBundle)(path_1.default.join(hostFolder, 'lint-browser.js'))
|
|
764
|
+
// sw.js sits at the site root: a worker can only control the folder it is
|
|
765
|
+
// served from, and the app has to be controlled from /
|
|
766
|
+
];
|
|
767
|
+
case 3:
|
|
768
|
+
// 2. the linter, bundled from the installed packages so the code routes answer
|
|
769
|
+
// with the rules this CLI resolved rather than a second implementation
|
|
770
|
+
_k.sent();
|
|
608
771
|
// sw.js sits at the site root: a worker can only control the folder it is
|
|
609
772
|
// served from, and the app has to be controlled from /
|
|
610
773
|
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'sw.js'), path_1.default.join(outputFolder, 'sw.js'));
|
|
@@ -630,6 +793,7 @@ function buildMockSite(target) {
|
|
|
630
793
|
'intercept.js',
|
|
631
794
|
'boot.js',
|
|
632
795
|
'api-browser.js',
|
|
796
|
+
'lint-browser.js',
|
|
633
797
|
'webout-prelude.json'
|
|
634
798
|
]
|
|
635
799
|
.map(function (asset) { return fs_1.default.readFileSync(path_1.default.join(hostFolder, asset)); })
|
|
@@ -640,7 +804,8 @@ function buildMockSite(target) {
|
|
|
640
804
|
fs_1.default.writeFileSync(swPath, fs_1.default
|
|
641
805
|
.readFileSync(swPath, 'utf8')
|
|
642
806
|
.replace("importScripts('/host/runtime-core.js')", "importScripts('/host/runtime-core.js?v=".concat(hostVersion, "')"))
|
|
643
|
-
.replace("importScripts('/host/api-browser.js')", "importScripts('/host/api-browser.js?v=".concat(hostVersion, "')"))
|
|
807
|
+
.replace("importScripts('/host/api-browser.js')", "importScripts('/host/api-browser.js?v=".concat(hostVersion, "')"))
|
|
808
|
+
.replace("importScripts('/host/lint-browser.js')", "importScripts('/host/lint-browser.js?v=".concat(hostVersion, "')")));
|
|
644
809
|
snapshotSource = path_1.default.join(buildFolder, 'mock-snapshot.json');
|
|
645
810
|
hasSnapshot = fs_1.default.existsSync(snapshotSource);
|
|
646
811
|
if (hasSnapshot) {
|
|
@@ -673,3 +838,4 @@ function buildMockSite(target) {
|
|
|
673
838
|
});
|
|
674
839
|
});
|
|
675
840
|
}
|
|
841
|
+
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) {
|
|
@@ -667,6 +690,95 @@ describe('browser-hosted build', function () {
|
|
|
667
690
|
}
|
|
668
691
|
});
|
|
669
692
|
}); });
|
|
693
|
+
/**
|
|
694
|
+
* The case this build exists for: an interface with no services at all.
|
|
695
|
+
* sasjs/server's own frontend compiles to nothing, and the worker answers its
|
|
696
|
+
* API - so the target must build, ship the web app, and say out loud that the
|
|
697
|
+
* drive it ships is empty.
|
|
698
|
+
*/
|
|
699
|
+
it('builds an interface that has no services, and says the drive is empty', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
700
|
+
var _a, _b, folder, warn, result;
|
|
701
|
+
var e_4, _c;
|
|
702
|
+
var _d;
|
|
703
|
+
return __generator(this, function (_e) {
|
|
704
|
+
switch (_e.label) {
|
|
705
|
+
case 0:
|
|
706
|
+
try {
|
|
707
|
+
// nothing a drive could be seeded from: no services, no sync folder
|
|
708
|
+
for (_a = __values(['services', 'mock-storage']), _b = _a.next(); !_b.done; _b = _a.next()) {
|
|
709
|
+
folder = _b.value;
|
|
710
|
+
fs_1.default.rmSync(path_1.default.join(root, 'sasjsbuild', folder), {
|
|
711
|
+
recursive: true,
|
|
712
|
+
force: true
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
717
|
+
finally {
|
|
718
|
+
try {
|
|
719
|
+
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
|
|
720
|
+
}
|
|
721
|
+
finally { if (e_4) throw e_4.error; }
|
|
722
|
+
}
|
|
723
|
+
warn = jest.spyOn((_d = process.logger) !== null && _d !== void 0 ? _d : console, 'warn');
|
|
724
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
725
|
+
case 1:
|
|
726
|
+
result = _e.sent();
|
|
727
|
+
expect(result.jsPrograms).toBe(0);
|
|
728
|
+
expect(result.driveEntries).toBe(0);
|
|
729
|
+
expect(fs_1.default.existsSync(path_1.default.join(out(), 'index.html'))).toBe(true);
|
|
730
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('ships with an empty drive'));
|
|
731
|
+
warn.mockRestore();
|
|
732
|
+
return [2 /*return*/];
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
}); });
|
|
736
|
+
/**
|
|
737
|
+
* A stale build folder is the case the warning exists for: `sasjs doc` and
|
|
738
|
+
* other commands leave folders behind that are not program folders, so a count
|
|
739
|
+
* of files on the drive says nothing about whether anything was compiled.
|
|
740
|
+
*/
|
|
741
|
+
it('warns when the build folder holds only folders that are not programs', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
742
|
+
var _a, _b, folder, warn, result;
|
|
743
|
+
var e_5, _c;
|
|
744
|
+
var _d;
|
|
745
|
+
return __generator(this, function (_e) {
|
|
746
|
+
switch (_e.label) {
|
|
747
|
+
case 0:
|
|
748
|
+
try {
|
|
749
|
+
for (_a = __values(['services', 'mock-storage']), _b = _a.next(); !_b.done; _b = _a.next()) {
|
|
750
|
+
folder = _b.value;
|
|
751
|
+
fs_1.default.rmSync(path_1.default.join(root, 'sasjsbuild', folder), {
|
|
752
|
+
recursive: true,
|
|
753
|
+
force: true
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
758
|
+
finally {
|
|
759
|
+
try {
|
|
760
|
+
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
|
|
761
|
+
}
|
|
762
|
+
finally { if (e_5) throw e_5.error; }
|
|
763
|
+
}
|
|
764
|
+
write(path_1.default.join(root, 'sasjsbuild', 'docs', 'index.html'), '<html></html>');
|
|
765
|
+
warn = jest.spyOn((_d = process.logger) !== null && _d !== void 0 ? _d : console, 'warn');
|
|
766
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())
|
|
767
|
+
// the drive is NOT empty - the docs folder reaches it - so the file count
|
|
768
|
+
// alone would have kept quiet
|
|
769
|
+
];
|
|
770
|
+
case 1:
|
|
771
|
+
result = _e.sent();
|
|
772
|
+
// the drive is NOT empty - the docs folder reaches it - so the file count
|
|
773
|
+
// alone would have kept quiet
|
|
774
|
+
expect(result.driveEntries).toBeGreaterThan(0);
|
|
775
|
+
expect(result.jsPrograms).toBe(0);
|
|
776
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('No compiled programs'));
|
|
777
|
+
warn.mockRestore();
|
|
778
|
+
return [2 /*return*/];
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
}); });
|
|
670
782
|
it('refuses to build without a web app', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
671
783
|
return __generator(this, function (_a) {
|
|
672
784
|
switch (_a.label) {
|
|
@@ -750,8 +862,8 @@ describe('browser-hosted build', function () {
|
|
|
750
862
|
});
|
|
751
863
|
}); });
|
|
752
864
|
it('refuses a mockWebFolder that would delete the project or its inputs', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
753
|
-
var _a, _b, mockWebFolder,
|
|
754
|
-
var
|
|
865
|
+
var _a, _b, mockWebFolder, e_6_1;
|
|
866
|
+
var e_6, _c;
|
|
755
867
|
return __generator(this, function (_d) {
|
|
756
868
|
switch (_d.label) {
|
|
757
869
|
case 0:
|
|
@@ -782,14 +894,14 @@ describe('browser-hosted build', function () {
|
|
|
782
894
|
return [3 /*break*/, 1];
|
|
783
895
|
case 4: return [3 /*break*/, 7];
|
|
784
896
|
case 5:
|
|
785
|
-
|
|
786
|
-
|
|
897
|
+
e_6_1 = _d.sent();
|
|
898
|
+
e_6 = { error: e_6_1 };
|
|
787
899
|
return [3 /*break*/, 7];
|
|
788
900
|
case 6:
|
|
789
901
|
try {
|
|
790
902
|
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
|
|
791
903
|
}
|
|
792
|
-
finally { if (
|
|
904
|
+
finally { if (e_6) throw e_6.error; }
|
|
793
905
|
return [7 /*endfinally*/];
|
|
794
906
|
case 7:
|
|
795
907
|
expect(fs_1.default.existsSync(path_1.default.join(root, 'client', 'dist', 'index.html'))).toBe(true);
|