@sasjs/cli 6.0.0 → 6.2.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/build.js +20 -10
- package/build/commands/build/internal/mockSite/assets/intercept.js +172 -0
- package/build/commands/build/internal/mockSite/assets/md5.js +75 -0
- package/build/commands/build/internal/mockSite/assets/persist.js +222 -0
- package/build/commands/build/internal/mockSite/assets/runtime-core.js +1230 -0
- package/build/commands/build/internal/mockSite/assets/sw.js +317 -0
- package/build/commands/build/internal/mockSite/mockSite.d.ts +38 -0
- package/build/commands/build/internal/mockSite/mockSite.js +475 -0
- package/build/commands/build/internal/mockSite/spec/intercept.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/intercept.spec.js +267 -0
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +497 -0
- package/build/commands/build/internal/mockSite/spec/persist.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/persist.spec.js +476 -0
- package/build/commands/build/internal/mockSite/spec/runtime-core.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +802 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +716 -0
- package/build/commands/index.d.ts +1 -0
- package/build/commands/index.js +3 -1
- package/package.json +8 -7
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __values = (this && this.__values) || function(o) {
|
|
39
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
40
|
+
if (m) return m.call(o);
|
|
41
|
+
if (o && typeof o.length === "number") return {
|
|
42
|
+
next: function () {
|
|
43
|
+
if (o && i >= o.length) o = void 0;
|
|
44
|
+
return { value: o && o[i++], done: !o };
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
48
|
+
};
|
|
49
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
50
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
51
|
+
if (!m) return o;
|
|
52
|
+
var i = m.call(o), r, ar = [], e;
|
|
53
|
+
try {
|
|
54
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
55
|
+
}
|
|
56
|
+
catch (error) { e = { error: error }; }
|
|
57
|
+
finally {
|
|
58
|
+
try {
|
|
59
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
60
|
+
}
|
|
61
|
+
finally { if (e) throw e.error; }
|
|
62
|
+
}
|
|
63
|
+
return ar;
|
|
64
|
+
};
|
|
65
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
66
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
67
|
+
if (ar || !(i in from)) {
|
|
68
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
69
|
+
ar[i] = from[i];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
73
|
+
};
|
|
74
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
75
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
76
|
+
};
|
|
77
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
78
|
+
exports.MAX_SEED_BYTES = exports.MAX_BINARY_SEED_BYTES = void 0;
|
|
79
|
+
exports.buildMockSite = buildMockSite;
|
|
80
|
+
var crypto_1 = require("crypto");
|
|
81
|
+
var fs_1 = __importDefault(require("fs"));
|
|
82
|
+
var path_1 = __importDefault(require("path"));
|
|
83
|
+
var utils_1 = require("@sasjs/utils");
|
|
84
|
+
var ASSETS_FOLDER = path_1.default.join(__dirname, 'assets');
|
|
85
|
+
var toPosix = function (value) { return value.split(path_1.default.sep).join('/'); };
|
|
86
|
+
var listFiles = function (folder, base) {
|
|
87
|
+
var e_1, _a;
|
|
88
|
+
if (base === void 0) { base = folder; }
|
|
89
|
+
var entries = fs_1.default.readdirSync(folder, { withFileTypes: true });
|
|
90
|
+
var files = [];
|
|
91
|
+
try {
|
|
92
|
+
for (var entries_1 = __values(entries), entries_1_1 = entries_1.next(); !entries_1_1.done; entries_1_1 = entries_1.next()) {
|
|
93
|
+
var entry = entries_1_1.value;
|
|
94
|
+
var absolute = path_1.default.join(folder, entry.name);
|
|
95
|
+
if (entry.isDirectory())
|
|
96
|
+
files.push.apply(files, __spreadArray([], __read(listFiles(absolute, base)), false));
|
|
97
|
+
else if (entry.isFile())
|
|
98
|
+
files.push(toPosix(path_1.default.relative(base, absolute)));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
102
|
+
finally {
|
|
103
|
+
try {
|
|
104
|
+
if (entries_1_1 && !entries_1_1.done && (_a = entries_1.return)) _a.call(entries_1);
|
|
105
|
+
}
|
|
106
|
+
finally { if (e_1) throw e_1.error; }
|
|
107
|
+
}
|
|
108
|
+
return files;
|
|
109
|
+
};
|
|
110
|
+
var copyFolder = function (from, to) {
|
|
111
|
+
var e_2, _a;
|
|
112
|
+
fs_1.default.mkdirSync(to, { recursive: true });
|
|
113
|
+
try {
|
|
114
|
+
for (var _b = __values(fs_1.default.readdirSync(from, { withFileTypes: true })), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
115
|
+
var entry = _c.value;
|
|
116
|
+
var source = path_1.default.join(from, entry.name);
|
|
117
|
+
var target = path_1.default.join(to, entry.name);
|
|
118
|
+
if (entry.isDirectory())
|
|
119
|
+
copyFolder(source, target);
|
|
120
|
+
else
|
|
121
|
+
fs_1.default.copyFileSync(source, target);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
125
|
+
finally {
|
|
126
|
+
try {
|
|
127
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
128
|
+
}
|
|
129
|
+
finally { if (e_2) throw e_2.error; }
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* The drive seed comes from the COMPILE OUTPUT, not the sources: `sasjs
|
|
134
|
+
* compile` copies every service folder into sasjsbuild/services and then
|
|
135
|
+
* copies the sync folder's contents over the build folder root, so sasjsbuild
|
|
136
|
+
* already holds exactly what a SASjs server would receive on its drive.
|
|
137
|
+
*
|
|
138
|
+
* Root-level files are skipped on purpose - those are the service pack (.sas),
|
|
139
|
+
* its manifest (.json), the compile tree and the zip, none of which belong on
|
|
140
|
+
* the drive.
|
|
141
|
+
*/
|
|
142
|
+
var PROGRAM_FOLDERS = ['services', 'jobs', 'tests'];
|
|
143
|
+
/**
|
|
144
|
+
* The bounds on what the browser drive seeds. A byte entry is carried as base64
|
|
145
|
+
* in vfs-seed.json and held in the worker's memory, so a large artefact - an
|
|
146
|
+
* export, a media file, a vendored archive - makes the generated site
|
|
147
|
+
* unloadable: one file over the per-file bound, or enough files to pass the
|
|
148
|
+
* total, is left off the drive and named in a warning, as every non-text file
|
|
149
|
+
* was when the drive carried text only. Text has no bound: a program folder is
|
|
150
|
+
* text by nature and the drive has always carried it.
|
|
151
|
+
*/
|
|
152
|
+
exports.MAX_BINARY_SEED_BYTES = 10 * 1024 * 1024;
|
|
153
|
+
exports.MAX_SEED_BYTES = 25 * 1024 * 1024;
|
|
154
|
+
/**
|
|
155
|
+
* What the drive stores for a file: its text when the bytes survive a UTF-8
|
|
156
|
+
* round trip, and behind the byte marker (BINARY_PREFIX in runtime-core.js)
|
|
157
|
+
* otherwise. An archive, an image or an xlsx therefore lands on the drive as
|
|
158
|
+
* bytes, so a service that downloads a project-supplied file finds it.
|
|
159
|
+
*
|
|
160
|
+
* Text that begins with U+0000 is escaped, because the runtime reads a leading
|
|
161
|
+
* NUL as its own marker - the same rule the runtime applies to a write.
|
|
162
|
+
*/
|
|
163
|
+
var DRIVE_BINARY_PREFIX = '\u0000base64:';
|
|
164
|
+
var DRIVE_TEXT_PREFIX = '\u0000text:';
|
|
165
|
+
/** A byte count in MB, for the message that names a file too large to seed. */
|
|
166
|
+
var megabytes = function (bytes) {
|
|
167
|
+
return "".concat(Math.round((bytes / 1024 / 1024) * 10) / 10, " MB");
|
|
168
|
+
};
|
|
169
|
+
var readDriveFile = function (absolute) {
|
|
170
|
+
var buffer = fs_1.default.readFileSync(absolute);
|
|
171
|
+
var text = buffer.toString('utf8');
|
|
172
|
+
if (!Buffer.from(text, 'utf8').equals(buffer))
|
|
173
|
+
return {
|
|
174
|
+
contents: DRIVE_BINARY_PREFIX + buffer.toString('base64'),
|
|
175
|
+
// the raw size, which is what the seed bounds are measured in
|
|
176
|
+
bytes: buffer.length
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
contents: text.startsWith('\u0000') ? DRIVE_TEXT_PREFIX + text : text
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
var seedFromBuild = function (buildFolder, appLoc, exclude, excludeFolders) {
|
|
183
|
+
var e_3, _a, e_4, _b, e_5, _c;
|
|
184
|
+
if (exclude === void 0) { exclude = []; }
|
|
185
|
+
if (excludeFolders === void 0) { excludeFolders = []; }
|
|
186
|
+
var files = {};
|
|
187
|
+
var skipped = [];
|
|
188
|
+
var driveBase = "/sasjs/drive/files".concat(appLoc);
|
|
189
|
+
var seededBytes = 0;
|
|
190
|
+
// Every folder of the build goes on the drive, so a sync folder's content (a
|
|
191
|
+
// licence store, for instance) survives as well as the program folders.
|
|
192
|
+
var directories = fs_1.default
|
|
193
|
+
.readdirSync(buildFolder, { withFileTypes: true })
|
|
194
|
+
.filter(function (entry) { return entry.isDirectory(); });
|
|
195
|
+
var candidates = [];
|
|
196
|
+
try {
|
|
197
|
+
for (var directories_1 = __values(directories), directories_1_1 = directories_1.next(); !directories_1_1.done; directories_1_1 = directories_1.next()) {
|
|
198
|
+
var entry = directories_1_1.value;
|
|
199
|
+
var folder = path_1.default.join(buildFolder, entry.name);
|
|
200
|
+
if (excludeFolders.includes(path_1.default.resolve(folder)))
|
|
201
|
+
continue;
|
|
202
|
+
var _loop_1 = function (relative) {
|
|
203
|
+
var key = "".concat(driveBase, "/").concat(entry.name, "/").concat(relative);
|
|
204
|
+
if (exclude.some(function (prefix) { return key.startsWith(prefix); }))
|
|
205
|
+
return "continue";
|
|
206
|
+
candidates.push({ key: key, absolute: path_1.default.join(folder, relative) });
|
|
207
|
+
};
|
|
208
|
+
try {
|
|
209
|
+
for (var _d = (e_4 = void 0, __values(listFiles(folder))), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
210
|
+
var relative = _e.value;
|
|
211
|
+
_loop_1(relative);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
215
|
+
finally {
|
|
216
|
+
try {
|
|
217
|
+
if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
|
|
218
|
+
}
|
|
219
|
+
finally { if (e_4) throw e_4.error; }
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
224
|
+
finally {
|
|
225
|
+
try {
|
|
226
|
+
if (directories_1_1 && !directories_1_1.done && (_a = directories_1.return)) _a.call(directories_1);
|
|
227
|
+
}
|
|
228
|
+
finally { if (e_3) throw e_3.error; }
|
|
229
|
+
}
|
|
230
|
+
// The budget is spent in path order, not in whatever order the folder listing
|
|
231
|
+
// returns, so the same project yields the same drive - and the same warning -
|
|
232
|
+
// on every machine and filesystem.
|
|
233
|
+
candidates.sort(function (first, second) {
|
|
234
|
+
return first.key < second.key ? -1 : first.key > second.key ? 1 : 0;
|
|
235
|
+
});
|
|
236
|
+
try {
|
|
237
|
+
for (var candidates_1 = __values(candidates), candidates_1_1 = candidates_1.next(); !candidates_1_1.done; candidates_1_1 = candidates_1.next()) {
|
|
238
|
+
var candidate = candidates_1_1.value;
|
|
239
|
+
var file = readDriveFile(candidate.absolute);
|
|
240
|
+
// a byte entry counts against both bounds; a text entry counts against
|
|
241
|
+
// neither, because the drive has always carried the program folders
|
|
242
|
+
if (file.bytes !== undefined) {
|
|
243
|
+
if (file.bytes > exports.MAX_BINARY_SEED_BYTES) {
|
|
244
|
+
skipped.push({ key: candidate.key, bytes: file.bytes, limit: 'file' });
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (seededBytes + file.bytes > exports.MAX_SEED_BYTES) {
|
|
248
|
+
skipped.push({ key: candidate.key, bytes: file.bytes, limit: 'total' });
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
seededBytes += file.bytes;
|
|
252
|
+
}
|
|
253
|
+
files[candidate.key] = file.contents;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
257
|
+
finally {
|
|
258
|
+
try {
|
|
259
|
+
if (candidates_1_1 && !candidates_1_1.done && (_c = candidates_1.return)) _c.call(candidates_1);
|
|
260
|
+
}
|
|
261
|
+
finally { if (e_5) throw e_5.error; }
|
|
262
|
+
}
|
|
263
|
+
return { files: files, skipped: skipped };
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* `mockWebFolder` decides a folder that is deleted before it is written, so it
|
|
267
|
+
* has to name a folder of its own. Anything that would delete the project, the
|
|
268
|
+
* build output the drive is seeded from, or the web app it is copied from is
|
|
269
|
+
* refused before a single file is touched.
|
|
270
|
+
*/
|
|
271
|
+
var assertSafeOutputFolder = function (outputFolder, projectDir, buildFolder, webFolder) {
|
|
272
|
+
var isSameOrChild = function (parent, child) {
|
|
273
|
+
var relative = path_1.default.relative(parent, child);
|
|
274
|
+
return (relative === '' ||
|
|
275
|
+
(relative !== '..' &&
|
|
276
|
+
!relative.startsWith('..' + path_1.default.sep) &&
|
|
277
|
+
!path_1.default.isAbsolute(relative)));
|
|
278
|
+
};
|
|
279
|
+
var refusal = function (why) {
|
|
280
|
+
return new Error("Refusing to write the mock site to ".concat(outputFolder, ": ").concat(why, ". ") +
|
|
281
|
+
'Set streamConfig.mockWebFolder to a folder of its own, for example "sasjsbuild/mock".');
|
|
282
|
+
};
|
|
283
|
+
if (isSameOrChild(outputFolder, projectDir))
|
|
284
|
+
throw refusal('that folder is the project folder or contains it');
|
|
285
|
+
if (isSameOrChild(outputFolder, buildFolder))
|
|
286
|
+
throw refusal('that folder holds the build output the drive is seeded from');
|
|
287
|
+
if (isSameOrChild(outputFolder, webFolder))
|
|
288
|
+
throw refusal('that folder holds the web app the site is copied from');
|
|
289
|
+
if (isSameOrChild(webFolder, outputFolder))
|
|
290
|
+
throw refusal('that folder is inside the web app the site is copied from, so copying it would copy itself');
|
|
291
|
+
if (!isSameOrChild(projectDir, outputFolder) &&
|
|
292
|
+
!isSameOrChild(buildFolder, outputFolder))
|
|
293
|
+
throw refusal('that folder is outside the project');
|
|
294
|
+
};
|
|
295
|
+
var countJsPrograms = function (files) {
|
|
296
|
+
return Object.keys(files).filter(function (key) {
|
|
297
|
+
var inProgramFolder = PROGRAM_FOLDERS.some(function (folder) {
|
|
298
|
+
return key.includes("/".concat(folder, "/"));
|
|
299
|
+
});
|
|
300
|
+
return inProgramFolder && key.toLowerCase().endsWith('.js');
|
|
301
|
+
}).length;
|
|
302
|
+
};
|
|
303
|
+
var loaderScript = function (mainScript, version) { return "<script>\n // Boot order: register the service worker that hosts the mock backend, wait\n // until it controls this page, hand it the persisted drive, install the XHR\n // bridge, then start the app.\n (function () {\n function load(src, next, isModule) {\n var s = document.createElement('script')\n if (isModule) s.type = 'module'\n s.src = src\n s.onload = next\n s.onerror = function () { console.error('[mock] failed to load ' + src); next() }\n document.body.appendChild(s)\n }\n function start() {\n load('host/persist.js?v=".concat(version, "', function () {\n Promise.resolve(window.__sasjsMockReady).then(function (boot) {\n if (boot && boot.error) console.error('[mock] boot: ' + boot.error)\n load('host/intercept.js?v=").concat(version, "', function () {\n load('").concat(mainScript, "', function () {}, true)\n })\n })\n })\n }\n if (!('serviceWorker' in navigator)) {\n console.error('[mock] service workers are unavailable - the mock backend cannot run')\n start()\n return\n }\n navigator.serviceWorker\n .register('/sw.js', { scope: '/' })\n .then(function () { return navigator.serviceWorker.ready })\n .then(function () {\n if (navigator.serviceWorker.controller) return\n return new Promise(function (resolve) {\n var timer = setTimeout(resolve, 4000)\n navigator.serviceWorker.addEventListener('controllerchange', function () {\n clearTimeout(timer)\n resolve()\n })\n })\n })\n .then(function () {\n if (navigator.serviceWorker.controller) return start()\n if (!sessionStorage.getItem('mock-sw-reloaded')) {\n sessionStorage.setItem('mock-sw-reloaded', '1')\n location.reload()\n return\n }\n console.error('[mock] no service worker controller - requests will fail')\n start()\n })\n .catch(function (err) {\n console.error('[mock] service worker registration failed: ' + err)\n start()\n })\n })()\n </script>"); };
|
|
304
|
+
/**
|
|
305
|
+
* Points the app at its own origin and replaces its bundle tag with the
|
|
306
|
+
* boot loader. Both changes are reported when the expected markup is absent,
|
|
307
|
+
* rather than producing a site that silently talks to a real server.
|
|
308
|
+
*/
|
|
309
|
+
var patchIndexHtml = function (html, version) {
|
|
310
|
+
var warnings = [];
|
|
311
|
+
var patched = html;
|
|
312
|
+
if (/<sasjs[^>]*>/i.test(patched)) {
|
|
313
|
+
patched = patched.replace(/(<sasjs[^>]*?)\sserverurl="[^"]*"/i, '$1 serverurl=""');
|
|
314
|
+
patched = patched.replace(/(<sasjs[^>]*?)\sloginmechanism="[^"]*"/i, '$1 loginmechanism="Default"');
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
warnings.push('no <sasjs> element found - check the adapter configuration manually');
|
|
318
|
+
}
|
|
319
|
+
var mainMatch = patched.match(/<script[^>]*src="([^"]*main[^"]*\.js)"[^>]*><\/script>/i);
|
|
320
|
+
if (mainMatch) {
|
|
321
|
+
patched = patched.replace(mainMatch[0], loaderScript(mainMatch[1], version));
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
warnings.push('no main bundle script tag found - the mock loader was not installed');
|
|
325
|
+
}
|
|
326
|
+
return { patched: patched, warnings: warnings };
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Builds a static site that runs the project's JS services (its mocks) in the
|
|
330
|
+
* browser, so a frontend can be tested with no server and no SAS.
|
|
331
|
+
*
|
|
332
|
+
* The output is an ordinary folder of static files: serve it over http(s) and
|
|
333
|
+
* the app behaves as though it were talking to a single-user SASjs server. The
|
|
334
|
+
* work happens in a service worker, so every request is still a real request -
|
|
335
|
+
* visible in DevTools > Network, marked "(from ServiceWorker)" - and the drive
|
|
336
|
+
* it writes to is persisted in IndexedDB, so refreshes keep their state.
|
|
337
|
+
*
|
|
338
|
+
* Everything comes from the target: appLoc, streamConfig.mockWebFolder for the
|
|
339
|
+
* output, streamConfig.webSourcePath for the built app, and the compile output
|
|
340
|
+
* for the drive.
|
|
341
|
+
*/
|
|
342
|
+
function buildMockSite(target) {
|
|
343
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
344
|
+
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;
|
|
345
|
+
var e_6, _e;
|
|
346
|
+
var _f, _g, _h;
|
|
347
|
+
return __generator(this, function (_j) {
|
|
348
|
+
switch (_j.label) {
|
|
349
|
+
case 0:
|
|
350
|
+
projectDir = process.projectDir || process.cwd();
|
|
351
|
+
appLocRaw = target.appLoc;
|
|
352
|
+
if (!appLocRaw) {
|
|
353
|
+
throw new Error('No appLoc. Set appLoc on the target - it has to match the appLoc the app is built with.');
|
|
354
|
+
}
|
|
355
|
+
appLoc = '/' + appLocRaw.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
356
|
+
buildFolder = process.sasjsConstants.buildDestinationFolder ||
|
|
357
|
+
path_1.default.join(projectDir, 'sasjsbuild');
|
|
358
|
+
return [4 /*yield*/, (0, utils_1.folderExists)(buildFolder)];
|
|
359
|
+
case 1:
|
|
360
|
+
if (!(_j.sent())) {
|
|
361
|
+
throw new Error("Build folder not found: ".concat(buildFolder, ". Run `sasjs compile` first."));
|
|
362
|
+
}
|
|
363
|
+
webSourceRaw = (_f = target.streamConfig) === null || _f === void 0 ? void 0 : _f.webSourcePath;
|
|
364
|
+
if (!webSourceRaw) {
|
|
365
|
+
throw new Error('No built web app. Set streamConfig.webSourcePath on the target (for example ./client/dist), and build the frontend first.');
|
|
366
|
+
}
|
|
367
|
+
webFolder = path_1.default.resolve(projectDir, webSourceRaw);
|
|
368
|
+
return [4 /*yield*/, (0, utils_1.folderExists)(webFolder)];
|
|
369
|
+
case 2:
|
|
370
|
+
if (!(_j.sent())) {
|
|
371
|
+
throw new Error("Web source folder not found: ".concat(webFolder, ". Check streamConfig.webSourcePath - build the frontend first."));
|
|
372
|
+
}
|
|
373
|
+
if (!fs_1.default.existsSync(path_1.default.join(webFolder, 'index.html'))) {
|
|
374
|
+
throw new Error("".concat(webFolder, " does not contain an index.html - is streamConfig.webSourcePath pointing at the built app?"));
|
|
375
|
+
}
|
|
376
|
+
outputFolder = ((_g = target.streamConfig) === null || _g === void 0 ? void 0 : _g.mockWebFolder)
|
|
377
|
+
? path_1.default.resolve(projectDir, target.streamConfig.mockWebFolder)
|
|
378
|
+
: path_1.default.join(buildFolder, 'mock');
|
|
379
|
+
assertSafeOutputFolder(outputFolder, projectDir, buildFolder, webFolder);
|
|
380
|
+
streamConfig = target.streamConfig;
|
|
381
|
+
excluded = [];
|
|
382
|
+
// streamWebFolder is defaulted by the config validator, so a streamed app
|
|
383
|
+
// always names its folder.
|
|
384
|
+
if ((streamConfig === null || streamConfig === void 0 ? void 0 : streamConfig.streamWeb) && streamConfig.streamWebFolder) {
|
|
385
|
+
excluded.push("/sasjs/drive/files".concat(appLoc, "/services/").concat(streamConfig.streamWebFolder, "/"));
|
|
386
|
+
}
|
|
387
|
+
_a = seedFromBuild(buildFolder, appLoc, excluded, [outputFolder]), seed = _a.files, skipped = _a.skipped;
|
|
388
|
+
jsPrograms = countJsPrograms(seed);
|
|
389
|
+
driveEntries = Object.keys(seed).length;
|
|
390
|
+
if (jsPrograms === 0) {
|
|
391
|
+
throw new Error("No .js programs in ".concat(buildFolder, "/services, /jobs or /tests. This target builds SAS code, which cannot run in a browser. ") +
|
|
392
|
+
'Build a target whose services are JavaScript (typically a mocks project).');
|
|
393
|
+
}
|
|
394
|
+
if (skipped.length) {
|
|
395
|
+
(_h = process.logger) === null || _h === void 0 ? void 0 : _h.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: ") +
|
|
396
|
+
"".concat(skipped
|
|
397
|
+
.slice(0, 3)
|
|
398
|
+
.map(function (file) {
|
|
399
|
+
return "".concat(file.key, " (").concat(megabytes(file.bytes), ", ").concat(file.limit === 'file'
|
|
400
|
+
? 'over the per-file limit'
|
|
401
|
+
: 'over the drive budget', ")");
|
|
402
|
+
})
|
|
403
|
+
.join(', ')).concat(skipped.length > 3 ? ', ...' : ''));
|
|
404
|
+
}
|
|
405
|
+
// Only now is the previous site expendable.
|
|
406
|
+
fs_1.default.rmSync(outputFolder, { recursive: true, force: true });
|
|
407
|
+
fs_1.default.mkdirSync(outputFolder, { recursive: true });
|
|
408
|
+
// 1. the app
|
|
409
|
+
copyFolder(webFolder, outputFolder);
|
|
410
|
+
webFileCount = listFiles(outputFolder).length;
|
|
411
|
+
hostFolder = path_1.default.join(outputFolder, 'host');
|
|
412
|
+
fs_1.default.mkdirSync(hostFolder, { recursive: true });
|
|
413
|
+
try {
|
|
414
|
+
for (_b = __values([
|
|
415
|
+
'md5.js',
|
|
416
|
+
'runtime-core.js',
|
|
417
|
+
'persist.js',
|
|
418
|
+
'intercept.js'
|
|
419
|
+
]), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
420
|
+
asset = _c.value;
|
|
421
|
+
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, asset), path_1.default.join(hostFolder, asset));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
425
|
+
finally {
|
|
426
|
+
try {
|
|
427
|
+
if (_c && !_c.done && (_e = _b.return)) _e.call(_b);
|
|
428
|
+
}
|
|
429
|
+
finally { if (e_6) throw e_6.error; }
|
|
430
|
+
}
|
|
431
|
+
// sw.js sits at the site root: a worker can only control the folder it is
|
|
432
|
+
// served from, and the app has to be controlled from /
|
|
433
|
+
fs_1.default.copyFileSync(path_1.default.join(ASSETS_FOLDER, 'sw.js'), path_1.default.join(outputFolder, 'sw.js'));
|
|
434
|
+
hostVersion = (0, crypto_1.createHash)('md5')
|
|
435
|
+
.update(['runtime-core.js', 'persist.js', 'intercept.js']
|
|
436
|
+
.map(function (asset) { return fs_1.default.readFileSync(path_1.default.join(hostFolder, asset)); })
|
|
437
|
+
.reduce(function (all, buf) { return Buffer.concat([all, buf]); }, Buffer.alloc(0)))
|
|
438
|
+
.digest('hex')
|
|
439
|
+
.slice(0, 10);
|
|
440
|
+
swPath = path_1.default.join(outputFolder, 'sw.js');
|
|
441
|
+
fs_1.default.writeFileSync(swPath, fs_1.default
|
|
442
|
+
.readFileSync(swPath, 'utf8')
|
|
443
|
+
.replace("importScripts('/host/runtime-core.js')", "importScripts('/host/runtime-core.js?v=".concat(hostVersion, "')")));
|
|
444
|
+
snapshotSource = path_1.default.join(buildFolder, 'mock-snapshot.json');
|
|
445
|
+
hasSnapshot = fs_1.default.existsSync(snapshotSource);
|
|
446
|
+
if (hasSnapshot) {
|
|
447
|
+
fs_1.default.copyFileSync(snapshotSource, path_1.default.join(hostFolder, 'vfs-snapshot.json'));
|
|
448
|
+
}
|
|
449
|
+
// 3. the drive, laid out from the compile output
|
|
450
|
+
fs_1.default.writeFileSync(path_1.default.join(hostFolder, 'vfs-seed.json'), JSON.stringify({ files: seed }));
|
|
451
|
+
fs_1.default.writeFileSync(path_1.default.join(hostFolder, 'mock-config.json'), JSON.stringify({
|
|
452
|
+
appLoc: appLoc,
|
|
453
|
+
seedPath: '/host/vfs-seed.json',
|
|
454
|
+
driveEntries: driveEntries,
|
|
455
|
+
jsPrograms: jsPrograms,
|
|
456
|
+
programFolders: PROGRAM_FOLDERS,
|
|
457
|
+
snapshot: hasSnapshot,
|
|
458
|
+
builtAt: new Date().toISOString()
|
|
459
|
+
}, null, 2));
|
|
460
|
+
indexPath = path_1.default.join(outputFolder, 'index.html');
|
|
461
|
+
_d = patchIndexHtml(fs_1.default.readFileSync(indexPath, 'utf8'), hostVersion), patched = _d.patched, warnings = _d.warnings;
|
|
462
|
+
fs_1.default.writeFileSync(indexPath, patched);
|
|
463
|
+
warnings.forEach(function (warning) { var _a; return (_a = process.logger) === null || _a === void 0 ? void 0 : _a.warn(warning); });
|
|
464
|
+
return [2 /*return*/, {
|
|
465
|
+
outputFolder: outputFolder,
|
|
466
|
+
appLoc: appLoc,
|
|
467
|
+
driveEntries: driveEntries,
|
|
468
|
+
jsPrograms: jsPrograms,
|
|
469
|
+
webFileCount: webFileCount,
|
|
470
|
+
snapshot: hasSnapshot
|
|
471
|
+
}];
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|