@sdeverywhere/plugin-check 0.3.36 → 0.3.38

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/dist/index.cjs DELETED
@@ -1,972 +0,0 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
-
29
- // src/index.ts
30
- var index_exports = {};
31
- __export(index_exports, {
32
- checkPlugin: () => checkPlugin
33
- });
34
- module.exports = __toCommonJS(index_exports);
35
-
36
- // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.26_typescript@5.2.2_yaml@2.9.0/node_modules/tsup/assets/cjs_shims.js
37
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
38
- var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
39
-
40
- // src/plugin.ts
41
- var import_node_fs2 = require("fs");
42
- var import_promises3 = require("fs/promises");
43
- var import_node_path4 = require("path");
44
- var import_url3 = require("url");
45
- var import_vite = require("vite");
46
- var import_check_core3 = require("@sdeverywhere/check-core");
47
-
48
- // src/bundle-file-ops.ts
49
- var import_promises = require("fs/promises");
50
- var import_node_path = require("path");
51
- async function downloadBundle(url, name, lastModified, bundlesDir, fetchRemoteBundle) {
52
- const fullUrl = `${url}?cb=${Date.now()}`;
53
- let bundleContent;
54
- if (fetchRemoteBundle) {
55
- bundleContent = await fetchRemoteBundle(fullUrl);
56
- } else {
57
- const response = await fetch(fullUrl);
58
- if (!response.ok) {
59
- throw new Error(`Failed to fetch bundle: HTTP ${response.status} ${response.statusText}`);
60
- }
61
- bundleContent = await response.text();
62
- }
63
- const nameParts = name.split("/");
64
- const filePath = (0, import_node_path.join)(bundlesDir, ...nameParts) + ".js";
65
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
66
- await (0, import_promises.writeFile)(filePath, bundleContent, "utf8");
67
- if (lastModified) {
68
- const mtime = new Date(lastModified);
69
- await (0, import_promises.utimes)(filePath, mtime, mtime);
70
- }
71
- return filePath;
72
- }
73
- async function copyBundle(url, newName, bundlesDir) {
74
- let srcPath;
75
- if (url === "current") {
76
- const sdePrepDir = (0, import_node_path.join)(bundlesDir, "..", "sde-prep");
77
- srcPath = (0, import_node_path.join)(sdePrepDir, "check-bundle.js");
78
- } else if (url.startsWith("file://")) {
79
- srcPath = new URL(url).pathname;
80
- } else {
81
- throw new Error(`Cannot copy bundle with URL: ${url}`);
82
- }
83
- const bundleContent = await (0, import_promises.readFile)(srcPath, "utf8");
84
- const stats = await (0, import_promises.stat)(srcPath);
85
- const sourceLastModified = stats.mtime;
86
- const nameParts = newName.split("/");
87
- const filePath = (0, import_node_path.join)(bundlesDir, ...nameParts) + ".js";
88
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
89
- await (0, import_promises.writeFile)(filePath, bundleContent, "utf8");
90
- if (sourceLastModified) {
91
- await (0, import_promises.utimes)(filePath, sourceLastModified, sourceLastModified);
92
- }
93
- return filePath;
94
- }
95
-
96
- // src/run-suite.ts
97
- var import_perf_hooks = require("perf_hooks");
98
- var import_picocolors = __toESM(require("picocolors"), 1);
99
- var import_check_core = require("@sdeverywhere/check-core");
100
- async function runTestSuite(context, config, verbose) {
101
- return new Promise((resolve, reject) => {
102
- const t0 = import_perf_hooks.performance.now();
103
- let lastPctByInc;
104
- const callbacks = {
105
- onProgress: (progress) => {
106
- const pct = Math.round(progress * 100);
107
- const pctByInc = Math.floor(pct / 5) * 5;
108
- if (lastPctByInc === void 0 || pctByInc > lastPctByInc) {
109
- lastPctByInc = pctByInc;
110
- context.log("info", `${pctByInc}%`);
111
- }
112
- },
113
- onComplete: (report) => {
114
- try {
115
- const t1 = import_perf_hooks.performance.now();
116
- const elapsedMillis = t1 - t0;
117
- const elapsedSeconds = (elapsedMillis / 1e3).toFixed(1);
118
- context.log("info", `
119
- Test suite completed in ${elapsedSeconds}s`);
120
- const allChecksPassed = printCheckSummary(context, report.checkReport, verbose);
121
- if (report.comparisonReport) {
122
- printPerfStats(context, config.comparison, report.comparisonReport);
123
- }
124
- const suiteSummary = (0, import_check_core.suiteSummaryFromReport)(report, elapsedMillis);
125
- resolve({
126
- allChecksPassed,
127
- suiteSummary
128
- });
129
- } catch (e) {
130
- reject(e);
131
- }
132
- },
133
- onError: (error) => {
134
- reject(error);
135
- }
136
- };
137
- (0, import_check_core.runSuite)(config, callbacks);
138
- });
139
- }
140
- function printCheckSummary(context, checkReport, verbose) {
141
- function printResult(indent, status, text) {
142
- if (!verbose && status === "passed" && indent > 1) {
143
- return;
144
- }
145
- let statusChar;
146
- switch (status) {
147
- case "passed":
148
- statusChar = "\u2713";
149
- break;
150
- case "failed":
151
- statusChar = "\u2717";
152
- break;
153
- case "error":
154
- statusChar = "\u203C";
155
- break;
156
- case "skipped":
157
- statusChar = "\u2013";
158
- break;
159
- default:
160
- statusChar = "";
161
- break;
162
- }
163
- const msg = `${" ".repeat(indent)}${statusChar} ${text}`;
164
- context.log("info", status === "passed" ? import_picocolors.default.green(msg) : import_picocolors.default.red(msg));
165
- }
166
- function bold(s) {
167
- return import_picocolors.default.bold(s);
168
- }
169
- function printTest(test) {
170
- const msg = `${test.name}${verbose || test.status !== "passed" ? ":" : ""}`;
171
- printResult(1, test.status, msg);
172
- }
173
- let allPassed = true;
174
- context.log("info", "\nCheck results:");
175
- for (const group of checkReport.groups) {
176
- context.log("info", `
177
- ${group.name}`);
178
- for (const test of group.tests) {
179
- if (test.status !== "passed") {
180
- allPassed = false;
181
- }
182
- printTest(test);
183
- for (const scenario of test.scenarios) {
184
- printResult(3, scenario.status, (0, import_check_core.scenarioMessage)(scenario, bold));
185
- for (const dataset of scenario.datasets) {
186
- printResult(5, dataset.status, (0, import_check_core.datasetMessage)(dataset, bold));
187
- for (const predicate of dataset.predicates) {
188
- printResult(7, predicate.result.status, (0, import_check_core.predicateMessage)(predicate, bold));
189
- }
190
- }
191
- }
192
- }
193
- }
194
- context.log("info", "");
195
- return allPassed;
196
- }
197
- function stat2(label, n) {
198
- return `${label}=${n.toFixed(1)}ms`;
199
- }
200
- function printPerfReportLine(context, perfReport) {
201
- const avg = stat2("avg", perfReport.avgTime);
202
- const min = stat2("min", perfReport.minTime);
203
- const max = stat2("max", perfReport.maxTime);
204
- context.log("info", ` ${avg} ${min} ${max}`);
205
- }
206
- function printPerfStats(context, comparisonConfig, report) {
207
- context.log("info", "\nPerformance stats:");
208
- context.log("info", ` ${comparisonConfig.bundleL.name}:`);
209
- printPerfReportLine(context, report.perfReportL);
210
- context.log("info", ` ${comparisonConfig.bundleR.name}:`);
211
- printPerfReportLine(context, report.perfReportR);
212
- context.log("info", "");
213
- }
214
-
215
- // src/vite-config-for-bundle.ts
216
- var import_fs = require("fs");
217
- var import_path = require("path");
218
- var import_url = require("url");
219
- var import_plugin_node_resolve = require("@rollup/plugin-node-resolve");
220
- var import_check_core2 = require("@sdeverywhere/check-core");
221
- var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
222
- var __dirname = (0, import_path.dirname)(__filename2);
223
- function injectModelSpec(context, modelSpec) {
224
- const prepDir = context.config.prepDir;
225
- const inputSpecs = [];
226
- for (const modelInputSpec of modelSpec.inputs) {
227
- if (modelInputSpec.defaultValue === void 0 || modelInputSpec.minValue === void 0 || modelInputSpec.maxValue === void 0) {
228
- let msg = "";
229
- msg += `WARNING: The {defaultValue,minValue,maxValue} properties are required by plugin-check, `;
230
- msg += `but are undefined in the InputSpec for '${modelInputSpec.varName}'. `;
231
- msg += `This input variable will be excluded from the model-check bundle until those properties `;
232
- msg += `are defined.`;
233
- console.warn(msg);
234
- continue;
235
- }
236
- const varId = context.canonicalVarId(modelInputSpec.varName);
237
- const inputId = modelInputSpec.inputId || varId;
238
- inputSpecs.push({
239
- inputId,
240
- varId,
241
- ...modelInputSpec
242
- });
243
- }
244
- const outputSpecs = modelSpec.outputs.map((o) => {
245
- return {
246
- varId: context.canonicalVarId(o.varName),
247
- ...o
248
- };
249
- });
250
- function readJsonListing() {
251
- const path = (0, import_path.join)(prepDir, "build", "processed.json");
252
- if ((0, import_fs.existsSync)(path)) {
253
- const json = (0, import_fs.readFileSync)(path, "utf8");
254
- return JSON.parse(json);
255
- } else {
256
- return {};
257
- }
258
- }
259
- const listing = readJsonListing();
260
- const varInstances = listing.varInstances || {};
261
- const encodedImplVars = (0, import_check_core2.encodeImplVars)(varInstances);
262
- function stagedFileSize(filename) {
263
- const path = (0, import_path.join)(prepDir, "staged", "model", filename);
264
- if ((0, import_fs.existsSync)(path)) {
265
- return (0, import_fs.statSync)(path).size;
266
- } else {
267
- return 0;
268
- }
269
- }
270
- const modelSizeInBytes = stagedFileSize("generated-model.js");
271
- const dataSizeInBytes = stagedFileSize("static-data.ts");
272
- const moduleSrc = `
273
- export const inputSpecs = ${JSON.stringify(inputSpecs)};
274
- export const outputSpecs = ${JSON.stringify(outputSpecs)};
275
- export const encodedImplVars = ${JSON.stringify(encodedImplVars)};
276
- export const modelSizeInBytes = ${modelSizeInBytes};
277
- export const dataSizeInBytes = ${dataSizeInBytes};
278
- `;
279
- const virtualModuleId = "virtual:model-spec";
280
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
281
- return {
282
- name: "vite-plugin-virtual-custom",
283
- resolveId(id) {
284
- if (id === virtualModuleId) {
285
- return resolvedVirtualModuleId;
286
- }
287
- },
288
- load(id) {
289
- if (id === resolvedVirtualModuleId) {
290
- return moduleSrc;
291
- }
292
- }
293
- };
294
- }
295
- function overrideViteResolvePlugin(viteConfig) {
296
- const resolvePlugin = viteConfig.plugins.find((p) => p.name === "vite:resolve");
297
- if (resolvePlugin === void 0) {
298
- throw new Error("Failed to locate the built-in vite:resolve plugin");
299
- }
300
- const originalResolveId = resolvePlugin.resolveId;
301
- resolvePlugin.resolveId = async function resolveId(id, importer, options) {
302
- if (id.startsWith("./implementation") && importer.includes("threads/dist-esm")) {
303
- const idFileName = id.replace("./", "");
304
- const importerFileName = (0, import_path.basename)(importer);
305
- const resolvedId = importer.replace(importerFileName, `${idFileName}.js`);
306
- return {
307
- id: resolvedId,
308
- moduleSideEffects: false
309
- };
310
- }
311
- return await originalResolveId.handler.call(this, id, importer, options);
312
- };
313
- }
314
- async function createViteConfigForBundle(context, modelSpec) {
315
- const root = (0, import_path.resolve)(__dirname, "..", "template-bundle");
316
- const prepDir = context.config.prepDir;
317
- const outDir = (0, import_path.relative)(root, prepDir);
318
- const modelWorkerPath = (0, import_path.join)(prepDir, "staged", "model", "worker.js?raw");
319
- return {
320
- // Don't use an external config file
321
- configFile: false,
322
- // Use the root directory configured above
323
- root,
324
- // Don't clear the screen in dev mode so that we can see builder output
325
- clearScreen: false,
326
- // TODO: Disable vite output by default?
327
- // logLevel: 'silent',
328
- // Configure path aliases
329
- resolve: {
330
- alias: [
331
- // Inject the configured model worker
332
- {
333
- find: "@_model_worker_",
334
- replacement: modelWorkerPath
335
- },
336
- // XXX: Prevent Vite from using the `browser` section of `threads/package.json`
337
- // since we want to force the use of the general module (under dist-esm) that chooses
338
- // the correct implementation (Web Worker vs worker_threads) at runtime. Currently
339
- // Vite's library mode is browser focused and generally chooses the right imports,
340
- // except in the case of the threads package where we want to use the generic
341
- // `implementation.js` that chooses between Web Worker and worker_threads at runtime.
342
- // Note that we could in theory set `resolve.browserField` to false, but that would
343
- // make Vite not use the browser field for all other packages, and there is not
344
- // currently a way to tell Vite to use the browser field on a case-by-case basis.
345
- // So for now we need this workaround here to make it resolve to `dist-esm`, and then
346
- // a second workaround in `overrideViteResolvePlugin` to prevent the resolver from
347
- // using the browser field when resolving the threads package.
348
- {
349
- find: "threads",
350
- replacement: "threads",
351
- customResolver: async function(source, importer, options) {
352
- const customResolver = (0, import_plugin_node_resolve.nodeResolve)({ browser: false });
353
- const resolveIdHook = customResolver.resolveId;
354
- const resolveIdFn = typeof resolveIdHook === "function" ? resolveIdHook : resolveIdHook.handler;
355
- const resolved = await resolveIdFn.call(this, source, importer, options);
356
- if (source === "threads/worker") {
357
- return resolved.id.replace("worker.mjs", "dist-esm/worker/index.js");
358
- } else {
359
- return resolved.id.replace("index.mjs", "dist-esm/index.js");
360
- }
361
- }
362
- }
363
- ]
364
- },
365
- plugins: [
366
- // Use a virtual module plugin to inject the model spec values
367
- injectModelSpec(context, modelSpec),
368
- // XXX: Install a wrapper around the built-in `vite:resolve` plugin so that we can
369
- // override the default resolver behavior that tries to resolve the `browser` section
370
- // of the `package.json` for the threads package.
371
- {
372
- name: "vite-plugin-override-resolve",
373
- configResolved(viteConfig) {
374
- overrideViteResolvePlugin(viteConfig);
375
- }
376
- }
377
- ],
378
- build: {
379
- // Write output files to the configured directory (instead of the default `dist`);
380
- // note that this must be relative to the project `root`
381
- outDir,
382
- emptyOutDir: false,
383
- // Uncomment for debugging purposes
384
- // minify: false,
385
- lib: {
386
- entry: "./src/index.ts",
387
- formats: ["es"],
388
- fileName: () => "check-bundle.js"
389
- },
390
- rollupOptions: {
391
- // Don't transform Node imports used by threads.js
392
- external: ["events", "os", "path", "url"],
393
- // XXX: Insert custom code at the top of the generated bundle that defines
394
- // the special `__non_webpack_require__` function that is used by threads.js
395
- // in its Node implementation. This import ensures that threads.js uses
396
- // the native `worker_threads` implementation when using the bundle in a
397
- // Node environment. When importing the bundle for use in the browser,
398
- // Vite will transform this import into an empty module due to the empty
399
- // polyfill that is configured in `vite-config-for-report.ts`.
400
- output: {
401
- banner: `
402
- import * as worker_threads from 'worker_threads'
403
- let __non_webpack_require__ = () => {
404
- return worker_threads;
405
- };
406
- `
407
- },
408
- onwarn: (warning, warn) => {
409
- if (warning.code !== "EVAL") {
410
- warn(warning);
411
- }
412
- }
413
- }
414
- }
415
- };
416
- }
417
-
418
- // src/vite-config-for-report.ts
419
- var import_node_fs = require("fs");
420
- var import_node_path3 = require("path");
421
- var import_node_url2 = require("url");
422
- var import_plugin_replace = __toESM(require("@rollup/plugin-replace"), 1);
423
-
424
- // src/vite-local-bundles-plugin.ts
425
- var import_promises2 = require("fs/promises");
426
- var import_node_path2 = require("path");
427
- var import_node_url = require("url");
428
- var import_chokidar = __toESM(require("chokidar"), 1);
429
- function localBundlesPlugin(bundlesDir, currentBundlePath, fetchRemoteBundle) {
430
- return {
431
- name: "sde-local-bundles",
432
- configureServer(server) {
433
- const watcher = import_chokidar.default.watch(bundlesDir, {
434
- // Don't send initial "file added" events
435
- ignoreInitial: true,
436
- // XXX: Include a delay, otherwise on macOS we sometimes get multiple
437
- // change events when a file is saved just once
438
- awaitWriteFinish: {
439
- stabilityThreshold: 200
440
- },
441
- // Watch up to 10 levels deep
442
- depth: 10
443
- });
444
- watcher.on("all", (event) => {
445
- if (event === "add" || event === "unlink") {
446
- server.ws.send("bundles-changed", {});
447
- }
448
- });
449
- server.httpServer?.on("close", () => {
450
- watcher.close();
451
- });
452
- server.ws.on("list-bundles", async (_, client) => {
453
- try {
454
- const bundles = await scanBundlesRecursively(bundlesDir, bundlesDir);
455
- const currentBundleStats = await (0, import_promises2.stat)(currentBundlePath);
456
- bundles.push({
457
- name: "current",
458
- url: "current",
459
- lastModified: currentBundleStats.mtime.toISOString()
460
- });
461
- client.send("list-bundles-success", { bundles });
462
- } catch (error) {
463
- console.error(`[sde-local-bundles] Failed to list bundles:`, error);
464
- client.send("list-bundles-error", { error: error.message });
465
- }
466
- });
467
- server.ws.on("load-bundle", async (data, client) => {
468
- const { url, name } = data;
469
- try {
470
- let sourceCode;
471
- if (url.startsWith("file://")) {
472
- const filePath = (0, import_node_url.fileURLToPath)(url);
473
- sourceCode = await (0, import_promises2.readFile)(filePath, "utf-8");
474
- } else if (url.startsWith("https://") || url.startsWith("http://")) {
475
- const fullUrl = `${url}?cb=${Date.now()}`;
476
- if (fetchRemoteBundle) {
477
- sourceCode = await fetchRemoteBundle(fullUrl);
478
- } else {
479
- const response = await fetch(fullUrl);
480
- if (!response.ok) {
481
- throw new Error(`Failed to fetch bundle: ${response.status} ${response.statusText}`);
482
- }
483
- sourceCode = await response.text();
484
- }
485
- } else {
486
- throw new Error(`Unsupported URL scheme: ${url}`);
487
- }
488
- client.send("load-bundle-success", { name, url, sourceCode });
489
- } catch (error) {
490
- console.error(`[sde-local-bundles] Failed to load bundle:`, error);
491
- client.send("load-bundle-error", { name, url, error: error.message });
492
- }
493
- });
494
- server.ws.on("download-bundle", async (data, client) => {
495
- const { url, name, lastModified } = data;
496
- try {
497
- console.log(`[sde-local-bundles] Downloading bundle: name=${name} url=${url}`);
498
- const filePath = await downloadBundle(url, name, lastModified, bundlesDir, fetchRemoteBundle);
499
- console.log(`[sde-local-bundles] Downloaded bundle to ${filePath}`);
500
- client.send("download-bundle-success", { name, filePath: `${name}.js` });
501
- } catch (error) {
502
- console.error(`[sde-local-bundles] Failed to download bundle:`, error);
503
- client.send("download-bundle-error", { name, error: error.message });
504
- }
505
- });
506
- server.ws.on("copy-bundle", async (data, client) => {
507
- const { url, name, newName } = data;
508
- try {
509
- console.log(`[sde-local-bundles] Copying bundle: src=${name} dst=${newName}`);
510
- const filePath = await copyBundle(url, newName, bundlesDir);
511
- console.log(`[sde-local-bundles] Copied bundle to ${filePath}`);
512
- client.send("copy-bundle-success", { name: newName, filePath: `${newName}.js` });
513
- } catch (error) {
514
- console.error(`[sde-local-bundles] Failed to copy bundle:`, error);
515
- client.send("copy-bundle-error", { name, error: error.message });
516
- }
517
- });
518
- }
519
- };
520
- }
521
- async function scanBundlesRecursively(dir, baseDir) {
522
- const bundles = [];
523
- const entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
524
- for (const entry of entries) {
525
- const fullPath = (0, import_node_path2.join)(dir, entry.name);
526
- if (entry.isDirectory()) {
527
- const subBundles = await scanBundlesRecursively(fullPath, baseDir);
528
- bundles.push(...subBundles);
529
- } else if (entry.isFile() && entry.name.endsWith(".js")) {
530
- const stats = await (0, import_promises2.stat)(fullPath);
531
- const relativePath = (0, import_node_path2.relative)(baseDir, fullPath);
532
- const name = relativePath.replace(/\.js$/, "").split(import_node_path2.sep).join("/");
533
- bundles.push({
534
- name,
535
- url: (0, import_node_url.pathToFileURL)(fullPath).toString(),
536
- lastModified: stats.mtime.toISOString()
537
- });
538
- }
539
- }
540
- return bundles;
541
- }
542
-
543
- // src/vite-config-for-report.ts
544
- var __filename3 = (0, import_node_url2.fileURLToPath)(importMetaUrl);
545
- var __dirname2 = (0, import_node_path3.dirname)(__filename3);
546
- function createViteConfigForReport(mode, options, projDir, prepDir, currentBundleSpec, baselineBundleSpec, testConfigPath, suiteSummary) {
547
- const root = (0, import_node_path3.resolve)(__dirname2, "..", "template-report");
548
- const bundlesDir = (0, import_node_path3.resolve)(projDir, "bundles");
549
- if (!(0, import_node_fs.existsSync)(bundlesDir)) {
550
- (0, import_node_fs.mkdirSync)(bundlesDir, { recursive: true });
551
- }
552
- const templateSrcDir = (0, import_node_path3.resolve)(root, "src");
553
- const relProjDir = (0, import_node_path3.relative)(templateSrcDir, projDir);
554
- const relProjDirPath = relProjDir.replaceAll("\\", "/");
555
- const bundlesPath = `${relProjDirPath}/bundles/**/*.js`;
556
- let reportPath;
557
- if (options?.reportPath) {
558
- reportPath = options.reportPath;
559
- } else {
560
- reportPath = (0, import_node_path3.join)(prepDir, "check-report");
561
- }
562
- const outDir = (0, import_node_path3.relative)(root, reportPath);
563
- const suiteSummaryJson = suiteSummary ? JSON.stringify(suiteSummary) : "";
564
- const alias = (find, replacement) => {
565
- return {
566
- find,
567
- replacement
568
- };
569
- };
570
- const noopPolyfillAlias = (find) => {
571
- return {
572
- find,
573
- replacement: "/polyfills/noop-polyfills.ts"
574
- };
575
- };
576
- return {
577
- // Don't use an external config file
578
- configFile: false,
579
- // Use the root directory configured above
580
- root,
581
- // Use `.` as the base directory (instead of the default `/`); this controls
582
- // how the path to the js/css files are generated in `index.html`
583
- base: "",
584
- // Use a custom cache directory under `prepDir`, as otherwise Vite will use
585
- // `packages/plugin-check/template-report/node_modules/.vite`, and we want to
586
- // avoid generating files in `template-report` (which should be read-only)
587
- cacheDir: (0, import_node_path3.join)(prepDir, ".vite-check-report"),
588
- // Load static files from `static` (instead of the default `public`)
589
- // publicDir: 'static',
590
- // Don't clear the screen in dev mode so that we can see builder output
591
- clearScreen: false,
592
- // TODO
593
- // logLevel: 'silent',
594
- optimizeDeps: {
595
- // Prevent Vite from examining other html files when scanning entrypoints
596
- // for dependency optimization
597
- entries: ["index.html"],
598
- // XXX: When plugin-check is installed via pnpm, the Vite dev server seems
599
- // to have no trouble resolving other dependencies using the optimizeDeps
600
- // mechanism. However, this fails when the package is installed via yarn
601
- // or npm (probably due to the fact that the `template-report` directory
602
- // is located under the top-level `node_modules` directory); in the browser,
603
- // there will be "import not found" errors for the packages referenced below.
604
- // As a terrible workaround, explicitly include the direct dependencies so
605
- // that Vite optimizes them; this works for pnpm, yarn, and npm. We should
606
- // find a less fragile solution.
607
- include: [
608
- // from check-core
609
- "@sdeverywhere/check-core > assert-never",
610
- "@sdeverywhere/check-core > ajv",
611
- "@sdeverywhere/check-core > neverthrow",
612
- "@sdeverywhere/check-core > yaml",
613
- // from check-ui-shell
614
- "@sdeverywhere/check-ui-shell > fontfaceobserver",
615
- "@sdeverywhere/check-ui-shell > copy-text-to-clipboard",
616
- "@sdeverywhere/check-ui-shell > chart.js"
617
- ],
618
- exclude: [
619
- // XXX: The threads.js implementation references `tiny-worker` as an optional
620
- // dependency, but it doesn't get used at runtime, so we can just exclude it
621
- // so that Vite doesn't complain in dev mode
622
- "tiny-worker"
623
- // XXX: Similarly, chart.js treats `moment` as an optional dependency, but we
624
- // don't use it at runtime; we need to exclude it here, otherwise Vite will
625
- // complain about missing dependencies in dev mode
626
- // 'moment'
627
- ]
628
- },
629
- // Configure path aliases
630
- resolve: {
631
- alias: [
632
- // Use the configured "baseline" bundle if defined, otherwise use the "empty" bundle
633
- // (which will cause comparison tests to be skipped)
634
- alias("@_baseline_bundle_", baselineBundleSpec?.path || "/src/empty-bundle.ts"),
635
- // Use the configured "current" bundle
636
- alias("@_current_bundle_", currentBundleSpec.path),
637
- // Use the configured test config file
638
- alias("@_test_config_", testConfigPath),
639
- // Make the overlay use the `messages.html` file that is written to the prep directory
640
- alias("@_prep_", prepDir),
641
- // XXX: Include no-op polyfills for these modules that are used in the Node-specific
642
- // implementation of threads.js; this allows us to use one bundle that works in both
643
- // Node and browser environments
644
- noopPolyfillAlias("events"),
645
- noopPolyfillAlias("fs"),
646
- noopPolyfillAlias("os"),
647
- noopPolyfillAlias("path"),
648
- noopPolyfillAlias("url"),
649
- noopPolyfillAlias("worker_threads")
650
- ]
651
- },
652
- // Inject special values into the generated JS
653
- define: {
654
- // Inject the summary JSON into the build
655
- __SUITE_SUMMARY_JSON__: JSON.stringify(suiteSummaryJson),
656
- // Inject the baseline bundle name
657
- __BASELINE_NAME__: JSON.stringify(baselineBundleSpec?.name || ""),
658
- // Inject the current bundle name
659
- __CURRENT_NAME__: JSON.stringify(currentBundleSpec.name),
660
- // Inject the remote bundles URL
661
- __REMOTE_BUNDLES_URL__: JSON.stringify(options?.remoteBundlesUrl || "")
662
- },
663
- plugins: [
664
- // Inject special values into the generated JS
665
- // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's
666
- // built-in `define` feature because the latter does not seem to run before
667
- // the glob handler (which requires the glob to be injected as a literal)
668
- (0, import_plugin_replace.default)({
669
- preventAssignment: true,
670
- delimiters: ["", ""],
671
- values: {
672
- // Inject the path for baseline bundles
673
- // XXX: Note that we use './bundles/**/*.txt' instead of something special
674
- // like './__BASELINE_BUNDLES_PATH__' because sometimes Vite's dependency
675
- // scanner sees the latter (instead of the injected path) and reports
676
- // an error since the path does not exist. As a workaround, we use
677
- // './bundles/**/*.txt', which gets interpreted as the valid path
678
- // '.../template-report/src/bundles/**/*.txt' (see `bundles/unused.txt`).
679
- "./bundles/**/*.txt": bundlesPath
680
- }
681
- }),
682
- // When local development mode is active, enable the local bundles plugin that
683
- // allows the report app to access the local bundles directory
684
- ...mode === "watch" ? [localBundlesPlugin(bundlesDir, currentBundleSpec.path, options?.fetchRemoteBundle)] : []
685
- ],
686
- build: {
687
- // Write output files to the configured directory (instead of the default `dist`);
688
- // note that this must be relative to the project `root`
689
- outDir,
690
- // Write js/css files to `public` (instead of the default `<outDir>/assets`)
691
- assetsDir: "",
692
- rollupOptions: {
693
- output: {
694
- // XXX: Prevent vite from creating a separate `vendor.js` file
695
- manualChunks: void 0
696
- },
697
- onwarn: (warning, warn) => {
698
- if (warning.code !== "EVAL") {
699
- warn(warning);
700
- }
701
- }
702
- }
703
- },
704
- server: {
705
- // Run the dev server at `localhost:8081` by default
706
- port: options?.serverPort || 8081,
707
- // Open the app in the browser by default
708
- open: "/index.html",
709
- // XXX: Add a small delay, otherwise on macOS we sometimes get multiple
710
- // change events when a file is saved just once. That is a relatively
711
- // harmless issue except that it causes redundant messages in the console
712
- // and can cause extra churn when refreshing the app.
713
- watch: {
714
- awaitWriteFinish: {
715
- stabilityThreshold: 100
716
- }
717
- }
718
- }
719
- };
720
- }
721
-
722
- // src/vite-config-for-tests.ts
723
- var import_path2 = require("path");
724
- var import_url2 = require("url");
725
- var import_plugin_replace2 = __toESM(require("@rollup/plugin-replace"), 1);
726
- var __filename4 = (0, import_url2.fileURLToPath)(importMetaUrl);
727
- var __dirname3 = (0, import_path2.dirname)(__filename4);
728
- function createViteConfigForTests(mode, projDir, prepDir) {
729
- const root = (0, import_path2.resolve)(__dirname3, "..", "template-tests");
730
- const templateSrcDir = (0, import_path2.resolve)(root, "src");
731
- const relProjDir = (0, import_path2.relative)(templateSrcDir, projDir);
732
- const relProjDirPath = relProjDir.replaceAll("\\", "/");
733
- const yamlCheckGlobPatterns = `['${relProjDirPath}/**/checks/*.yaml', '${relProjDirPath}/**/*.check.yaml']`;
734
- const yamlComparisonGlobPatterns = `['${relProjDirPath}/**/comparisons/*.yaml']`;
735
- const outDir = (0, import_path2.relative)(root, prepDir);
736
- return {
737
- // Don't use an external config file
738
- configFile: false,
739
- // Use the root directory configured above
740
- root,
741
- // Don't clear the screen in dev mode so that we can see builder output
742
- clearScreen: false,
743
- // TODO: Disable vite output by default?
744
- // logLevel: 'silent',
745
- plugins: [
746
- // Inject special values into the generated JS
747
- // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's
748
- // built-in `define` feature because the latter does not seem to run before
749
- // the glob handler (which requires the glob to be injected as a literal)
750
- (0, import_plugin_replace2.default)({
751
- preventAssignment: true,
752
- delimiters: ["", ""],
753
- values: {
754
- // Inject the glob patterns for matching model check yaml files
755
- '"./__YAML_CHECK_GLOB_PATTERNS__"': yamlCheckGlobPatterns,
756
- // Inject the glob patterns for matching model comparison yaml files
757
- '"./__YAML_COMPARISON_GLOB_PATTERNS__"': yamlComparisonGlobPatterns
758
- }
759
- })
760
- ],
761
- build: {
762
- // Write output files to the configured directory (instead of the default `dist`);
763
- // note that this must be relative to the project `root`
764
- outDir,
765
- emptyOutDir: false,
766
- lib: {
767
- entry: "./src/index.ts",
768
- formats: ["es"],
769
- fileName: () => "check-tests.js"
770
- },
771
- // Enable watch mode if requested
772
- watch: mode === "watch" && {},
773
- rollupOptions: {
774
- // Prevent dependencies from being included in packaged library
775
- // TODO: For now we include check-core in the packaged library so that its
776
- // dependencies are correctly resolved at runtime. Ideally this would only
777
- // include a couple functions that are used for defining tests, but Vite 2.x
778
- // does not implement tree shaking for ES libraries, which means the generated
779
- // library is much larger than it needs to be. Once we upgrade to Vite 3.x,
780
- // the generated library should be smaller; see related fix:
781
- // https://github.com/vitejs/vite/pull/8737
782
- // external: Object.keys(pkg.dependencies)
783
- }
784
- }
785
- };
786
- }
787
-
788
- // src/plugin.ts
789
- function checkPlugin(options) {
790
- return new CheckPlugin(options);
791
- }
792
- var CheckPlugin = class {
793
- constructor(options) {
794
- this.options = options;
795
- this.firstBuild = true;
796
- }
797
- async watch(config) {
798
- if (this.options?.testConfigPath === void 0) {
799
- await this.genTestConfig("watch", config);
800
- }
801
- const testOptions = await this.resolveTestOptions("watch", config);
802
- const viteConfig = await this.createViteConfigForReport("watch", config, testOptions, void 0);
803
- const server = await (0, import_vite.createServer)(viteConfig);
804
- await server.listen();
805
- }
806
- // TODO: Note that this plugin runs as a `postBuild` step because it currently
807
- // needs to run after other plugins, and those plugins need to run after the
808
- // staged files are copied to their final destination(s). We should probably
809
- // make it configurable so that it can either be run as a `postGenerate` or a
810
- // `postBuild` step.
811
- async postBuild(context, modelSpec) {
812
- const firstBuild = this.firstBuild;
813
- this.firstBuild = false;
814
- if (this.options?.current?.path === void 0 && this.options?.current?.url === void 0) {
815
- if (context.config.mode === "development") {
816
- await this.copyPreviousBundle(context.config);
817
- }
818
- context.log("info", "Generating model check bundle...");
819
- await this.genCurrentBundle(context, modelSpec);
820
- }
821
- if (this.options?.testConfigPath === void 0) {
822
- if (context.config.mode === "production" || firstBuild) {
823
- context.log("info", "Generating model check test configuration...");
824
- await this.genTestConfig("bundle", context.config);
825
- }
826
- }
827
- if (context.config.mode === "production") {
828
- const testOptions = await this.resolveTestOptions("bundle", context.config);
829
- return this.runChecks(context, testOptions);
830
- } else {
831
- return true;
832
- }
833
- }
834
- async copyPreviousBundle(config) {
835
- const currentBundleFile = (0, import_node_path4.join)(config.prepDir, "check-bundle.js");
836
- if ((0, import_node_fs2.existsSync)(currentBundleFile)) {
837
- const bundlesDir = (0, import_node_path4.join)(config.rootDir, "bundles");
838
- if (!(0, import_node_fs2.existsSync)(bundlesDir)) {
839
- await (0, import_promises3.mkdir)(bundlesDir, { recursive: true });
840
- }
841
- const previousBundleFile = (0, import_node_path4.join)(bundlesDir, "previous.js");
842
- await (0, import_promises3.copyFile)(currentBundleFile, previousBundleFile);
843
- }
844
- }
845
- async genCurrentBundle(context, modelSpec) {
846
- const viteConfig = await createViteConfigForBundle(context, modelSpec);
847
- await (0, import_vite.build)(viteConfig);
848
- }
849
- async genTestConfig(mode, config) {
850
- const rootDir = config.rootDir;
851
- const prepDir = config.prepDir;
852
- const viteConfig = createViteConfigForTests(mode, rootDir, prepDir);
853
- await (0, import_vite.build)(viteConfig);
854
- }
855
- async runChecks(context, testOptions) {
856
- context.log("info", "Running model checks...");
857
- async function importBundleModule(bundleSpec) {
858
- return import(relativeToSourcePath(bundleSpec.path));
859
- }
860
- const moduleR = await importBundleModule(testOptions.currentBundleSpec);
861
- const bundleR = moduleR.createBundle();
862
- const bundleNameR = testOptions.currentBundleSpec.name;
863
- let bundleL;
864
- let bundleNameL;
865
- if (testOptions.baselineBundleSpec !== void 0) {
866
- const moduleL = await importBundleModule(testOptions.baselineBundleSpec);
867
- const rawBundleL = moduleL.createBundle();
868
- if (rawBundleL.version === bundleR.version) {
869
- bundleL = rawBundleL;
870
- bundleNameL = testOptions.baselineBundleSpec.name || "base";
871
- } else {
872
- console.warn(
873
- `WARNING: Bundle version mismatch (baseline=${rawBundleL.version} current=${bundleR.version}); check tests will be run but comparisons will be skipped`
874
- );
875
- }
876
- }
877
- const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath));
878
- const configInitOptions = {
879
- bundleNameL,
880
- bundleNameR
881
- };
882
- const configOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, configInitOptions);
883
- const checkConfig = await (0, import_check_core3.createConfig)(configOptions);
884
- const result = await runTestSuite(
885
- context,
886
- checkConfig,
887
- /*verbose=*/
888
- false
889
- );
890
- context.log("info", "Building model check report");
891
- const viteConfig = await this.createViteConfigForReport("bundle", context.config, testOptions, result.suiteSummary);
892
- await (0, import_vite.build)(viteConfig);
893
- return result.allChecksPassed;
894
- }
895
- async resolveTestOptions(mode, config) {
896
- const fetchRemoteBundle = this.options?.fetchRemoteBundle;
897
- async function resolveBundle(bundle) {
898
- if (bundle?.url !== void 0) {
899
- const localBundlePath = await downloadBundle(
900
- bundle.url,
901
- bundle.name,
902
- // TODO: We don't know the last modified time of the remote bundle here, so we use
903
- // undefined (which means the local file will be created with the current timestamp)
904
- void 0,
905
- (0, import_node_path4.join)(config.rootDir, "bundles"),
906
- fetchRemoteBundle
907
- );
908
- return {
909
- name: bundle.name,
910
- path: localBundlePath
911
- };
912
- } else if (bundle?.path !== void 0) {
913
- return {
914
- name: bundle.name,
915
- path: bundle.path
916
- };
917
- } else {
918
- return {
919
- name: bundle?.name || "current",
920
- path: (0, import_node_path4.join)(config.prepDir, "check-bundle.js")
921
- };
922
- }
923
- }
924
- const currentBundleSpec = await resolveBundle(this.options?.current);
925
- let baselineBundleSpec;
926
- if (mode === "bundle" && this.options?.baseline) {
927
- try {
928
- baselineBundleSpec = await resolveBundle(this.options.baseline);
929
- } catch (e) {
930
- const name = this.options.baseline.name;
931
- const loc = this.options.baseline.url || this.options.baseline.path;
932
- console.warn(
933
- `WARNING: Failed to load '${name}' bundle from '${loc}'; check tests will be run but comparisons will be skipped. Cause:`,
934
- e
935
- );
936
- }
937
- }
938
- let testConfigPath;
939
- if (this.options?.testConfigPath === void 0) {
940
- testConfigPath = (0, import_node_path4.join)(config.prepDir, "check-tests.js");
941
- } else {
942
- testConfigPath = this.options.testConfigPath;
943
- }
944
- return {
945
- currentBundleSpec,
946
- baselineBundleSpec,
947
- testConfigPath
948
- };
949
- }
950
- async createViteConfigForReport(mode, config, testOptions, suiteSummary) {
951
- return createViteConfigForReport(
952
- mode,
953
- this.options,
954
- config.rootDir,
955
- config.prepDir,
956
- testOptions.currentBundleSpec,
957
- testOptions.baselineBundleSpec,
958
- testOptions.testConfigPath,
959
- suiteSummary
960
- );
961
- }
962
- };
963
- function relativeToSourcePath(filePath) {
964
- const srcDir = (0, import_node_path4.dirname)((0, import_url3.fileURLToPath)(importMetaUrl));
965
- const relPath = (0, import_node_path4.relative)(srcDir, filePath);
966
- return relPath.replaceAll("\\", "/");
967
- }
968
- // Annotate the CommonJS export names for ESM import in node:
969
- 0 && (module.exports = {
970
- checkPlugin
971
- });
972
- //# sourceMappingURL=index.cjs.map