@sdeverywhere/plugin-check 0.3.19 → 0.3.21

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/bin/sde-check.js CHANGED
@@ -11,8 +11,7 @@ import { join as joinPath } from 'path'
11
11
  function printUsage() {
12
12
  console.log()
13
13
  console.log('Usage:')
14
- console.log(' sim-check baseline --save\t\tcopy the latest bundle to the `baselines` directory')
15
- // console.log(' sim-check baseline --clear-all\tremove all bundles from the `baselines` directory')
14
+ console.log(' sde-check save-current-bundle\t\tcopy the latest bundle to the `bundles` directory')
16
15
  console.log()
17
16
  }
18
17
 
@@ -31,19 +30,14 @@ function timestamp() {
31
30
  }
32
31
 
33
32
  const args = process.argv.slice(2)
34
- if (args.length !== 2 || args[0] !== 'baseline') {
35
- printUsage()
36
- process.exit(1)
37
- }
38
-
39
- if (args[1] !== '--save') {
33
+ if (args.length !== 2 || args[0] !== 'save-current-bundle') {
40
34
  printUsage()
41
35
  process.exit(1)
42
36
  }
43
37
 
44
38
  // TODO: For now we make a number of assumptions (e.g., that the bundle will be copied to
45
- // the `baselines` directory under the current working directory, that the bundle filename
46
- // will contain the current timestamp); should make these configurable
39
+ // the `bundles` directory under the current working directory, that the bundle filename
40
+ // will contain the current timestamp); we should make these configurable
47
41
  const prepDir = joinPath(process.cwd(), 'sde-prep')
48
42
  const srcBundleFile = joinPath(prepDir, 'check-bundle.js')
49
43
  if (!existsSync(srcBundleFile)) {
@@ -51,10 +45,10 @@ if (!existsSync(srcBundleFile)) {
51
45
  process.exit(1)
52
46
  }
53
47
 
54
- const baselinesDir = joinPath(process.cwd(), 'baselines')
55
- if (!existsSync(baselinesDir)) {
56
- mkdirSync(baselinesDir, { recursive: true })
48
+ const bundlesDir = joinPath(process.cwd(), 'bundles')
49
+ if (!existsSync(bundlesDir)) {
50
+ mkdirSync(bundlesDir, { recursive: true })
57
51
  }
58
52
 
59
- const dstBundleFile = joinPath(baselinesDir, `${timestamp()}.js`)
53
+ const dstBundleFile = joinPath(bundlesDir, `${timestamp()}.js`)
60
54
  copyFileSync(srcBundleFile, dstBundleFile)
package/dist/index.cjs CHANGED
@@ -27,24 +27,65 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
29
  // src/index.ts
30
- var src_exports = {};
31
- __export(src_exports, {
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
32
  checkPlugin: () => checkPlugin
33
33
  });
34
- module.exports = __toCommonJS(src_exports);
34
+ module.exports = __toCommonJS(index_exports);
35
35
 
36
- // ../../node_modules/.pnpm/tsup@8.2.4_postcss@8.5.6_typescript@5.2.2/node_modules/tsup/assets/cjs_shims.js
37
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
36
+ // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_typescript@5.2.2/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
38
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
39
39
 
40
40
  // src/plugin.ts
41
- var import_fs3 = require("fs");
42
- var import_promises = require("fs/promises");
43
- var import_path4 = require("path");
44
- var import_url4 = require("url");
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
45
  var import_vite = require("vite");
46
- var import_chokidar = __toESM(require("chokidar"), 1);
47
- var import_check_core2 = require("@sdeverywhere/check-core");
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) {
52
+ const response = await fetch(url);
53
+ if (!response.ok) {
54
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
55
+ }
56
+ const bundleContent = await response.text();
57
+ const nameParts = name.split("/");
58
+ const filePath = (0, import_node_path.join)(bundlesDir, ...nameParts) + ".js";
59
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
60
+ await (0, import_promises.writeFile)(filePath, bundleContent, "utf8");
61
+ if (lastModified) {
62
+ const mtime = new Date(lastModified);
63
+ await (0, import_promises.utimes)(filePath, mtime, mtime);
64
+ }
65
+ return filePath;
66
+ }
67
+ async function copyBundle(url, newName, bundlesDir) {
68
+ let srcPath;
69
+ if (url === "current") {
70
+ const sdePrepDir = (0, import_node_path.join)(bundlesDir, "..", "sde-prep");
71
+ srcPath = (0, import_node_path.join)(sdePrepDir, "check-bundle.js");
72
+ } else if (url.startsWith("file://")) {
73
+ srcPath = new URL(url).pathname;
74
+ } else {
75
+ throw new Error(`Cannot copy bundle with URL: ${url}`);
76
+ }
77
+ const bundleContent = await (0, import_promises.readFile)(srcPath, "utf8");
78
+ const stats = await (0, import_promises.stat)(srcPath);
79
+ const sourceLastModified = stats.mtime;
80
+ const nameParts = newName.split("/");
81
+ const filePath = (0, import_node_path.join)(bundlesDir, ...nameParts) + ".js";
82
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
83
+ await (0, import_promises.writeFile)(filePath, bundleContent, "utf8");
84
+ if (sourceLastModified) {
85
+ await (0, import_promises.utimes)(filePath, sourceLastModified, sourceLastModified);
86
+ }
87
+ return filePath;
88
+ }
48
89
 
49
90
  // src/run-suite.ts
50
91
  var import_perf_hooks = require("perf_hooks");
@@ -66,14 +107,15 @@ async function runTestSuite(context, config, verbose) {
66
107
  onComplete: (report) => {
67
108
  try {
68
109
  const t1 = import_perf_hooks.performance.now();
69
- const elapsed = ((t1 - t0) / 1e3).toFixed(1);
110
+ const elapsedMillis = t1 - t0;
111
+ const elapsedSeconds = (elapsedMillis / 1e3).toFixed(1);
70
112
  context.log("info", `
71
- Test suite completed in ${elapsed}s`);
113
+ Test suite completed in ${elapsedSeconds}s`);
72
114
  const allChecksPassed = printCheckSummary(context, report.checkReport, verbose);
73
115
  if (report.comparisonReport) {
74
116
  printPerfStats(context, config.comparison, report.comparisonReport);
75
117
  }
76
- const suiteSummary = (0, import_check_core.suiteSummaryFromReport)(report);
118
+ const suiteSummary = (0, import_check_core.suiteSummaryFromReport)(report, elapsedMillis);
77
119
  resolve({
78
120
  allChecksPassed,
79
121
  suiteSummary
@@ -105,6 +147,9 @@ function printCheckSummary(context, checkReport, verbose) {
105
147
  case "error":
106
148
  statusChar = "\u203C";
107
149
  break;
150
+ case "skipped":
151
+ statusChar = "\u2013";
152
+ break;
108
153
  default:
109
154
  statusChar = "";
110
155
  break;
@@ -143,13 +188,13 @@ ${group.name}`);
143
188
  context.log("info", "");
144
189
  return allPassed;
145
190
  }
146
- function stat(label, n) {
191
+ function stat2(label, n) {
147
192
  return `${label}=${n.toFixed(1)}ms`;
148
193
  }
149
194
  function printPerfReportLine(context, perfReport) {
150
- const avg = stat("avg", perfReport.avgTime);
151
- const min = stat("min", perfReport.minTime);
152
- const max = stat("max", perfReport.maxTime);
195
+ const avg = stat2("avg", perfReport.avgTime);
196
+ const min = stat2("min", perfReport.minTime);
197
+ const max = stat2("max", perfReport.maxTime);
153
198
  context.log("info", ` ${avg} ${min} ${max}`);
154
199
  }
155
200
  function printPerfStats(context, comparisonConfig, report) {
@@ -166,9 +211,11 @@ var import_fs = require("fs");
166
211
  var import_path = require("path");
167
212
  var import_url = require("url");
168
213
  var import_plugin_node_resolve = require("@rollup/plugin-node-resolve");
214
+ var import_check_core2 = require("@sdeverywhere/check-core");
169
215
  var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
170
216
  var __dirname = (0, import_path.dirname)(__filename2);
171
217
  function injectModelSpec(context, modelSpec) {
218
+ const prepDir = context.config.prepDir;
172
219
  const inputSpecs = [];
173
220
  for (const modelInputSpec of modelSpec.inputs) {
174
221
  if (modelInputSpec.defaultValue === void 0 || modelInputSpec.minValue === void 0 || modelInputSpec.maxValue === void 0) {
@@ -194,8 +241,19 @@ function injectModelSpec(context, modelSpec) {
194
241
  ...o
195
242
  };
196
243
  });
244
+ function readJsonListing() {
245
+ const path = (0, import_path.join)(prepDir, "build", "processed.json");
246
+ if ((0, import_fs.existsSync)(path)) {
247
+ const json = (0, import_fs.readFileSync)(path, "utf8");
248
+ return JSON.parse(json);
249
+ } else {
250
+ return {};
251
+ }
252
+ }
253
+ const listing = readJsonListing();
254
+ const varInstances = listing.varInstances || {};
255
+ const encodedImplVars = (0, import_check_core2.encodeImplVars)(varInstances);
197
256
  function stagedFileSize(filename) {
198
- const prepDir = context.config.prepDir;
199
257
  const path = (0, import_path.join)(prepDir, "staged", "model", filename);
200
258
  if ((0, import_fs.existsSync)(path)) {
201
259
  return (0, import_fs.statSync)(path).size;
@@ -208,6 +266,7 @@ function injectModelSpec(context, modelSpec) {
208
266
  const moduleSrc = `
209
267
  export const inputSpecs = ${JSON.stringify(inputSpecs)};
210
268
  export const outputSpecs = ${JSON.stringify(outputSpecs)};
269
+ export const encodedImplVars = ${JSON.stringify(encodedImplVars)};
211
270
  export const modelSizeInBytes = ${modelSizeInBytes};
212
271
  export const dataSizeInBytes = ${dataSizeInBytes};
213
272
  `;
@@ -285,7 +344,9 @@ async function createViteConfigForBundle(context, modelSpec) {
285
344
  replacement: "threads",
286
345
  customResolver: async function(source, importer, options) {
287
346
  const customResolver = (0, import_plugin_node_resolve.nodeResolve)({ browser: false });
288
- const resolved = await customResolver.resolveId.call(this, source, importer, options);
347
+ const resolveIdHook = customResolver.resolveId;
348
+ const resolveIdFn = typeof resolveIdHook === "function" ? resolveIdHook : resolveIdHook.handler;
349
+ const resolved = await resolveIdFn.call(this, source, importer, options);
289
350
  if (source === "threads/worker") {
290
351
  return resolved.id.replace("worker.mjs", "dist-esm/worker/index.js");
291
352
  } else {
@@ -349,29 +410,116 @@ let __non_webpack_require__ = () => {
349
410
  }
350
411
 
351
412
  // src/vite-config-for-report.ts
352
- var import_fs2 = require("fs");
353
- var import_path2 = require("path");
354
- var import_url2 = require("url");
413
+ var import_node_fs = require("fs");
414
+ var import_node_path3 = require("path");
415
+ var import_node_url2 = require("url");
355
416
  var import_plugin_replace = __toESM(require("@rollup/plugin-replace"), 1);
356
- var __filename3 = (0, import_url2.fileURLToPath)(importMetaUrl);
357
- var __dirname2 = (0, import_path2.dirname)(__filename3);
358
- function createViteConfigForReport(options, projDir, prepDir, currentBundleName, currentBundlePath, testConfigPath, suiteSummary) {
359
- const root = (0, import_path2.resolve)(__dirname2, "..", "template-report");
360
- const baselinesDir = (0, import_path2.resolve)(projDir, "baselines");
361
- if (!(0, import_fs2.existsSync)(baselinesDir)) {
362
- (0, import_fs2.mkdirSync)(baselinesDir, { recursive: true });
417
+
418
+ // src/vite-local-bundles-plugin.ts
419
+ var import_promises2 = require("fs/promises");
420
+ var import_node_path2 = require("path");
421
+ var import_node_url = require("url");
422
+ var import_chokidar = __toESM(require("chokidar"), 1);
423
+ function localBundlesPlugin(bundlesDir) {
424
+ return {
425
+ name: "sde-local-bundles",
426
+ configureServer(server) {
427
+ const watcher = import_chokidar.default.watch(bundlesDir, {
428
+ // Don't send initial "file added" events
429
+ ignoreInitial: true,
430
+ // XXX: Include a delay, otherwise on macOS we sometimes get multiple
431
+ // change events when a file is saved just once
432
+ awaitWriteFinish: {
433
+ stabilityThreshold: 200
434
+ },
435
+ // Watch up to 10 levels deep
436
+ depth: 10
437
+ });
438
+ watcher.on("all", () => {
439
+ server.ws.send("bundles-changed", {});
440
+ });
441
+ server.httpServer?.on("close", () => {
442
+ watcher.close();
443
+ });
444
+ server.ws.on("list-bundles", async (_, client) => {
445
+ try {
446
+ const bundles = await scanBundlesRecursively(bundlesDir, bundlesDir);
447
+ client.send("list-bundles-success", { bundles });
448
+ } catch (error) {
449
+ console.error(`[sde-local-bundles] Failed to list bundles:`, error);
450
+ client.send("list-bundles-error", { error: error.message });
451
+ }
452
+ });
453
+ server.ws.on("download-bundle", async (data, client) => {
454
+ const { url, name, lastModified } = data;
455
+ try {
456
+ console.log(`[sde-local-bundles] Downloading bundle: name=${name} url=${url}`);
457
+ const filePath = await downloadBundle(url, name, lastModified, bundlesDir);
458
+ console.log(`[sde-local-bundles] Downloaded bundle to ${filePath}`);
459
+ client.send("download-bundle-success", { name, filePath: `${name}.js` });
460
+ } catch (error) {
461
+ console.error(`[sde-local-bundles] Failed to download bundle:`, error);
462
+ client.send("download-bundle-error", { name, error: error.message });
463
+ }
464
+ });
465
+ server.ws.on("copy-bundle", async (data, client) => {
466
+ const { url, name, newName } = data;
467
+ try {
468
+ console.log(`[sde-local-bundles] Copying bundle: src=${name} dst=${newName}`);
469
+ const filePath = await copyBundle(url, newName, bundlesDir);
470
+ console.log(`[sde-local-bundles] Copied bundle to ${filePath}`);
471
+ client.send("copy-bundle-success", { name: newName, filePath: `${newName}.js` });
472
+ } catch (error) {
473
+ console.error(`[sde-local-bundles] Failed to copy bundle:`, error);
474
+ client.send("copy-bundle-error", { name, error: error.message });
475
+ }
476
+ });
477
+ }
478
+ };
479
+ }
480
+ async function scanBundlesRecursively(dir, baseDir) {
481
+ const bundles = [];
482
+ const entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
483
+ for (const entry of entries) {
484
+ const fullPath = (0, import_node_path2.join)(dir, entry.name);
485
+ if (entry.isDirectory()) {
486
+ const subBundles = await scanBundlesRecursively(fullPath, baseDir);
487
+ bundles.push(...subBundles);
488
+ } else if (entry.isFile() && entry.name.endsWith(".js")) {
489
+ const stats = await (0, import_promises2.stat)(fullPath);
490
+ const relativePath = (0, import_node_path2.relative)(baseDir, fullPath);
491
+ const name = relativePath.replace(/\.js$/, "").split(import_node_path2.sep).join("/");
492
+ bundles.push({
493
+ name,
494
+ url: (0, import_node_url.pathToFileURL)(fullPath).toString(),
495
+ lastModified: stats.mtime.toISOString()
496
+ });
497
+ }
363
498
  }
364
- const templateSrcDir = (0, import_path2.resolve)(root, "src");
365
- const relProjDir = (0, import_path2.relative)(templateSrcDir, projDir);
499
+ return bundles;
500
+ }
501
+
502
+ // src/vite-config-for-report.ts
503
+ var __filename3 = (0, import_node_url2.fileURLToPath)(importMetaUrl);
504
+ var __dirname2 = (0, import_node_path3.dirname)(__filename3);
505
+ function createViteConfigForReport(mode, options, projDir, prepDir, currentBundleSpec, baselineBundleSpec, testConfigPath, suiteSummary) {
506
+ const root = (0, import_node_path3.resolve)(__dirname2, "..", "template-report");
507
+ const bundlesDir = (0, import_node_path3.resolve)(projDir, "bundles");
508
+ if (!(0, import_node_fs.existsSync)(bundlesDir)) {
509
+ (0, import_node_fs.mkdirSync)(bundlesDir, { recursive: true });
510
+ }
511
+ const templateSrcDir = (0, import_node_path3.resolve)(root, "src");
512
+ const relProjDir = (0, import_node_path3.relative)(templateSrcDir, projDir);
366
513
  const relProjDirPath = relProjDir.replaceAll("\\", "/");
367
- const baselinesPath = `${relProjDirPath}/baselines/*.js`;
514
+ const bundlesPath = `${relProjDirPath}/bundles/*.js`;
515
+ const currentBundleLastModified = (0, import_node_fs.statSync)(currentBundleSpec.path).mtime.toISOString();
368
516
  let reportPath;
369
517
  if (options?.reportPath) {
370
518
  reportPath = options.reportPath;
371
519
  } else {
372
- reportPath = (0, import_path2.join)(prepDir, "check-report");
520
+ reportPath = (0, import_node_path3.join)(prepDir, "check-report");
373
521
  }
374
- const outDir = (0, import_path2.relative)(root, reportPath);
522
+ const outDir = (0, import_node_path3.relative)(root, reportPath);
375
523
  const suiteSummaryJson = suiteSummary ? JSON.stringify(suiteSummary) : "";
376
524
  const alias = (find, replacement) => {
377
525
  return {
@@ -396,7 +544,7 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
396
544
  // Use a custom cache directory under `prepDir`, as otherwise Vite will use
397
545
  // `packages/plugin-check/template-report/node_modules/.vite`, and we want to
398
546
  // avoid generating files in `template-report` (which should be read-only)
399
- cacheDir: (0, import_path2.join)(prepDir, ".vite-check-report"),
547
+ cacheDir: (0, import_node_path3.join)(prepDir, ".vite-check-report"),
400
548
  // Load static files from `static` (instead of the default `public`)
401
549
  // publicDir: 'static',
402
550
  // Don't clear the screen in dev mode so that we can see builder output
@@ -443,9 +591,9 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
443
591
  alias: [
444
592
  // Use the configured "baseline" bundle if defined, otherwise use the "empty" bundle
445
593
  // (which will cause comparison tests to be skipped)
446
- alias("@_baseline_bundle_", options?.baseline ? options.baseline.path : "/src/empty-bundle.ts"),
594
+ alias("@_baseline_bundle_", baselineBundleSpec?.path || "/src/empty-bundle.ts"),
447
595
  // Use the configured "current" bundle
448
- alias("@_current_bundle_", currentBundlePath),
596
+ alias("@_current_bundle_", currentBundleSpec.path),
449
597
  // Use the configured test config file
450
598
  alias("@_test_config_", testConfigPath),
451
599
  // Make the overlay use the `messages.html` file that is written to the prep directory
@@ -465,10 +613,14 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
465
613
  define: {
466
614
  // Inject the summary JSON into the build
467
615
  __SUITE_SUMMARY_JSON__: JSON.stringify(suiteSummaryJson),
468
- // Inject the baseline branch name
469
- __BASELINE_NAME__: JSON.stringify(options?.baseline?.name || ""),
470
- // Inject the current branch name
471
- __CURRENT_NAME__: JSON.stringify(currentBundleName)
616
+ // Inject the baseline bundle name
617
+ __BASELINE_NAME__: JSON.stringify(baselineBundleSpec?.name || ""),
618
+ // Inject the current bundle name
619
+ __CURRENT_NAME__: JSON.stringify(currentBundleSpec.name),
620
+ // Inject the last modified time of the current bundle
621
+ __CURRENT_BUNDLE_LAST_MODIFIED__: JSON.stringify(currentBundleLastModified),
622
+ // Inject the remote bundles URL
623
+ __REMOTE_BUNDLES_URL__: JSON.stringify(options?.remoteBundlesUrl || "")
472
624
  },
473
625
  plugins: [
474
626
  // Inject special values into the generated JS
@@ -480,15 +632,18 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
480
632
  delimiters: ["", ""],
481
633
  values: {
482
634
  // Inject the path for baseline bundles
483
- // XXX: Note that we use './baselines/*.txt' instead of something special
635
+ // XXX: Note that we use './bundles/*.txt' instead of something special
484
636
  // like './__BASELINE_BUNDLES_PATH__' because sometimes Vite's dependency
485
637
  // scanner sees the latter (instead of the injected path) and reports
486
638
  // an error since the path does not exist. As a workaround, we use
487
- // './baselines/*.txt', which gets interpreted as the valid path
488
- // '.../template-report/src/baselines/*.txt' (see `baselines/unused.txt`).
489
- "./baselines/*.txt": baselinesPath
639
+ // './bundles/*.txt', which gets interpreted as the valid path
640
+ // '.../template-report/src/bundles/*.txt' (see `bundles/unused.txt`).
641
+ "./bundles/*.txt": bundlesPath
490
642
  }
491
- })
643
+ }),
644
+ // When local development mode is active, enable the local bundles plugin that
645
+ // allows the report app to access the local bundles directory
646
+ ...mode === "watch" ? [localBundlesPlugin(bundlesDir)] : []
492
647
  ],
493
648
  build: {
494
649
  // Write output files to the configured directory (instead of the default `dist`);
@@ -527,19 +682,19 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
527
682
  }
528
683
 
529
684
  // src/vite-config-for-tests.ts
530
- var import_path3 = require("path");
531
- var import_url3 = require("url");
685
+ var import_path2 = require("path");
686
+ var import_url2 = require("url");
532
687
  var import_plugin_replace2 = __toESM(require("@rollup/plugin-replace"), 1);
533
- var __filename4 = (0, import_url3.fileURLToPath)(importMetaUrl);
534
- var __dirname3 = (0, import_path3.dirname)(__filename4);
535
- function createViteConfigForTests(projDir, prepDir, mode) {
536
- const root = (0, import_path3.resolve)(__dirname3, "..", "template-tests");
537
- const templateSrcDir = (0, import_path3.resolve)(root, "src");
538
- const relProjDir = (0, import_path3.relative)(templateSrcDir, projDir);
688
+ var __filename4 = (0, import_url2.fileURLToPath)(importMetaUrl);
689
+ var __dirname3 = (0, import_path2.dirname)(__filename4);
690
+ function createViteConfigForTests(mode, projDir, prepDir) {
691
+ const root = (0, import_path2.resolve)(__dirname3, "..", "template-tests");
692
+ const templateSrcDir = (0, import_path2.resolve)(root, "src");
693
+ const relProjDir = (0, import_path2.relative)(templateSrcDir, projDir);
539
694
  const relProjDirPath = relProjDir.replaceAll("\\", "/");
540
695
  const yamlCheckGlobPatterns = `['${relProjDirPath}/**/checks/*.yaml', '${relProjDirPath}/**/*.check.yaml']`;
541
696
  const yamlComparisonGlobPatterns = `['${relProjDirPath}/**/comparisons/*.yaml']`;
542
- const outDir = (0, import_path3.relative)(root, prepDir);
697
+ const outDir = (0, import_path2.relative)(root, prepDir);
543
698
  return {
544
699
  // Don't use an external config file
545
700
  configFile: false,
@@ -603,26 +758,12 @@ var CheckPlugin = class {
603
758
  }
604
759
  async watch(config) {
605
760
  if (this.options?.testConfigPath === void 0) {
606
- await this.genTestConfig(config, "watch");
761
+ await this.genTestConfig("watch", config);
607
762
  }
608
- const testOptions = this.resolveTestOptions(config);
609
- const viteConfig = this.createViteConfigForReport(config, testOptions, void 0);
763
+ const testOptions = await this.resolveTestOptions("watch", config);
764
+ const viteConfig = await this.createViteConfigForReport("watch", config, testOptions, void 0);
610
765
  const server = await (0, import_vite.createServer)(viteConfig);
611
766
  await server.listen();
612
- const baselinesDir = "baselines";
613
- const watcher = import_chokidar.default.watch(baselinesDir, {
614
- // Watch paths are resolved relative to the project root directory
615
- cwd: config.rootDir,
616
- // Don't send initial "file added" events
617
- ignoreInitial: true,
618
- // XXX: Include a delay, otherwise on macOS we sometimes get multiple
619
- // change events when a file is saved just once
620
- awaitWriteFinish: {
621
- stabilityThreshold: 200
622
- }
623
- });
624
- watcher.on("add", () => server.restart());
625
- watcher.on("unlink", () => server.restart());
626
767
  }
627
768
  // TODO: Note that this plugin runs as a `postBuild` step because it currently
628
769
  // needs to run after other plugins, and those plugins need to run after the
@@ -632,7 +773,7 @@ var CheckPlugin = class {
632
773
  async postBuild(context, modelSpec) {
633
774
  const firstBuild = this.firstBuild;
634
775
  this.firstBuild = false;
635
- if (this.options?.current === void 0) {
776
+ if (this.options?.current?.path === void 0 && this.options?.current?.url === void 0) {
636
777
  if (context.config.mode === "development") {
637
778
  await this.copyPreviousBundle(context.config);
638
779
  }
@@ -642,50 +783,57 @@ var CheckPlugin = class {
642
783
  if (this.options?.testConfigPath === void 0) {
643
784
  if (context.config.mode === "production" || firstBuild) {
644
785
  context.log("info", "Generating model check test configuration...");
645
- await this.genTestConfig(context.config, "build");
786
+ await this.genTestConfig("bundle", context.config);
646
787
  }
647
788
  }
648
789
  if (context.config.mode === "production") {
649
- const testOptions = this.resolveTestOptions(context.config);
790
+ const testOptions = await this.resolveTestOptions("bundle", context.config);
650
791
  return this.runChecks(context, testOptions);
651
792
  } else {
652
793
  return true;
653
794
  }
654
795
  }
655
796
  async copyPreviousBundle(config) {
656
- const currentBundleFile = (0, import_path4.join)(config.prepDir, "check-bundle.js");
657
- if ((0, import_fs3.existsSync)(currentBundleFile)) {
658
- const baselinesDir = (0, import_path4.join)(config.rootDir, "baselines");
659
- if (!(0, import_fs3.existsSync)(baselinesDir)) {
660
- await (0, import_promises.mkdir)(baselinesDir, { recursive: true });
797
+ const currentBundleFile = (0, import_node_path4.join)(config.prepDir, "check-bundle.js");
798
+ if ((0, import_node_fs2.existsSync)(currentBundleFile)) {
799
+ const bundlesDir = (0, import_node_path4.join)(config.rootDir, "bundles");
800
+ if (!(0, import_node_fs2.existsSync)(bundlesDir)) {
801
+ await (0, import_promises3.mkdir)(bundlesDir, { recursive: true });
661
802
  }
662
- const previousBundleFile = (0, import_path4.join)(baselinesDir, "previous.js");
663
- await (0, import_promises.copyFile)(currentBundleFile, previousBundleFile);
803
+ const previousBundleFile = (0, import_node_path4.join)(bundlesDir, "previous.js");
804
+ await (0, import_promises3.copyFile)(currentBundleFile, previousBundleFile);
664
805
  }
665
806
  }
666
807
  async genCurrentBundle(context, modelSpec) {
667
808
  const viteConfig = await createViteConfigForBundle(context, modelSpec);
668
809
  await (0, import_vite.build)(viteConfig);
669
810
  }
670
- async genTestConfig(config, mode) {
811
+ async genTestConfig(mode, config) {
671
812
  const rootDir = config.rootDir;
672
813
  const prepDir = config.prepDir;
673
- const viteConfig = createViteConfigForTests(rootDir, prepDir, mode);
814
+ const viteConfig = createViteConfigForTests(mode, rootDir, prepDir);
674
815
  await (0, import_vite.build)(viteConfig);
675
816
  }
676
817
  async runChecks(context, testOptions) {
677
818
  context.log("info", "Running model checks...");
678
- const moduleR = await import(relativeToSourcePath(testOptions.currentBundlePath));
819
+ async function importBundleModule(bundleSpec) {
820
+ return import(relativeToSourcePath(bundleSpec.path));
821
+ }
822
+ const moduleR = await importBundleModule(testOptions.currentBundleSpec);
679
823
  const bundleR = moduleR.createBundle();
680
- const bundleNameR = testOptions.currentBundleName;
824
+ const bundleNameR = testOptions.currentBundleSpec.name;
681
825
  let bundleL;
682
826
  let bundleNameL;
683
- if (this.options?.baseline) {
684
- const moduleL = await import(relativeToSourcePath(this.options.baseline.path));
827
+ if (testOptions.baselineBundleSpec !== void 0) {
828
+ const moduleL = await importBundleModule(testOptions.baselineBundleSpec);
685
829
  const rawBundleL = moduleL.createBundle();
686
830
  if (rawBundleL.version === bundleR.version) {
687
831
  bundleL = rawBundleL;
688
- bundleNameL = this.options.baseline.name;
832
+ bundleNameL = testOptions.baselineBundleSpec.name || "base";
833
+ } else {
834
+ console.warn(
835
+ `WARNING: Bundle version mismatch (baseline=${rawBundleL.version} current=${bundleR.version}); check tests will be run but comparisons will be skipped`
836
+ );
689
837
  }
690
838
  }
691
839
  const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath));
@@ -694,7 +842,7 @@ var CheckPlugin = class {
694
842
  bundleNameR
695
843
  };
696
844
  const configOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, configInitOptions);
697
- const checkConfig = await (0, import_check_core2.createConfig)(configOptions);
845
+ const checkConfig = await (0, import_check_core3.createConfig)(configOptions);
698
846
  const result = await runTestSuite(
699
847
  context,
700
848
  checkConfig,
@@ -702,47 +850,79 @@ var CheckPlugin = class {
702
850
  false
703
851
  );
704
852
  context.log("info", "Building model check report");
705
- const viteConfig = this.createViteConfigForReport(context.config, testOptions, result.suiteSummary);
853
+ const viteConfig = await this.createViteConfigForReport("bundle", context.config, testOptions, result.suiteSummary);
706
854
  await (0, import_vite.build)(viteConfig);
707
855
  return result.allChecksPassed;
708
856
  }
709
- resolveTestOptions(config) {
710
- let currentBundleName;
711
- let currentBundlePath;
712
- if (this.options?.current === void 0) {
713
- currentBundleName = "current";
714
- currentBundlePath = (0, import_path4.join)(config.prepDir, "check-bundle.js");
715
- } else {
716
- currentBundleName = this.options.current.name;
717
- currentBundlePath = this.options.current.path;
857
+ async resolveTestOptions(mode, config) {
858
+ async function resolveBundle(bundle) {
859
+ if (bundle?.url !== void 0) {
860
+ const localBundlePath = await downloadBundle(
861
+ bundle.url,
862
+ bundle.name,
863
+ // TODO: We don't know the last modified time of the remote bundle here, so we use
864
+ // undefined (which means the local file will be created with the current timestamp)
865
+ void 0,
866
+ (0, import_node_path4.join)(config.rootDir, "bundles")
867
+ );
868
+ return {
869
+ name: bundle.name,
870
+ path: localBundlePath
871
+ };
872
+ } else if (bundle?.path !== void 0) {
873
+ return {
874
+ name: bundle.name,
875
+ path: bundle.path
876
+ };
877
+ } else {
878
+ return {
879
+ name: bundle?.name || "current",
880
+ path: (0, import_node_path4.join)(config.prepDir, "check-bundle.js")
881
+ };
882
+ }
883
+ }
884
+ const currentBundleSpec = await resolveBundle(mode === "bundle" ? this.options?.current : void 0);
885
+ let baselineBundleSpec;
886
+ if (mode === "bundle" && this.options?.baseline) {
887
+ try {
888
+ baselineBundleSpec = await resolveBundle(this.options.baseline);
889
+ } catch (e) {
890
+ const name = this.options.baseline.name;
891
+ const loc = this.options.baseline.url || this.options.baseline.path;
892
+ console.warn(
893
+ `WARNING: Failed to load '${name}' bundle from '${loc}'; check tests will be run but comparisons will be skipped. Cause:`,
894
+ e
895
+ );
896
+ }
718
897
  }
719
898
  let testConfigPath;
720
899
  if (this.options?.testConfigPath === void 0) {
721
- testConfigPath = (0, import_path4.join)(config.prepDir, "check-tests.js");
900
+ testConfigPath = (0, import_node_path4.join)(config.prepDir, "check-tests.js");
722
901
  } else {
723
902
  testConfigPath = this.options.testConfigPath;
724
903
  }
725
904
  return {
726
- currentBundleName,
727
- currentBundlePath,
905
+ currentBundleSpec,
906
+ baselineBundleSpec,
728
907
  testConfigPath
729
908
  };
730
909
  }
731
- createViteConfigForReport(config, testOptions, suiteSummary) {
910
+ async createViteConfigForReport(mode, config, testOptions, suiteSummary) {
732
911
  return createViteConfigForReport(
912
+ mode,
733
913
  this.options,
734
914
  config.rootDir,
735
915
  config.prepDir,
736
- testOptions.currentBundleName,
737
- testOptions.currentBundlePath,
916
+ testOptions.currentBundleSpec,
917
+ testOptions.baselineBundleSpec,
738
918
  testOptions.testConfigPath,
739
919
  suiteSummary
740
920
  );
741
921
  }
742
922
  };
743
923
  function relativeToSourcePath(filePath) {
744
- const srcDir = (0, import_path4.dirname)((0, import_url4.fileURLToPath)(importMetaUrl));
745
- const relPath = (0, import_path4.relative)(srcDir, filePath);
924
+ const srcDir = (0, import_node_path4.dirname)((0, import_url3.fileURLToPath)(importMetaUrl));
925
+ const relPath = (0, import_node_path4.relative)(srcDir, filePath);
746
926
  return relPath.replaceAll("\\", "/");
747
927
  }
748
928
  // Annotate the CommonJS export names for ESM import in node: