@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/dist/index.js CHANGED
@@ -1,12 +1,53 @@
1
1
  // src/plugin.ts
2
2
  import { existsSync as existsSync3 } from "fs";
3
- import { copyFile, mkdir } from "fs/promises";
4
- import { dirname as dirname4, join as joinPath3, relative as relative4 } from "path";
3
+ import { copyFile, mkdir as mkdir2 } from "fs/promises";
4
+ import { dirname as dirname5, join as joinPath5, relative as relative5 } from "path";
5
5
  import { fileURLToPath as fileURLToPath4 } from "url";
6
6
  import { build, createServer } from "vite";
7
- import chokidar from "chokidar";
8
7
  import { createConfig } from "@sdeverywhere/check-core";
9
8
 
9
+ // src/bundle-file-ops.ts
10
+ import { mkdir, readFile, stat, utimes, writeFile } from "fs/promises";
11
+ import { dirname, join as joinPath } from "path";
12
+ async function downloadBundle(url, name, lastModified, bundlesDir) {
13
+ const response = await fetch(url);
14
+ if (!response.ok) {
15
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
16
+ }
17
+ const bundleContent = await response.text();
18
+ const nameParts = name.split("/");
19
+ const filePath = joinPath(bundlesDir, ...nameParts) + ".js";
20
+ await mkdir(dirname(filePath), { recursive: true });
21
+ await writeFile(filePath, bundleContent, "utf8");
22
+ if (lastModified) {
23
+ const mtime = new Date(lastModified);
24
+ await utimes(filePath, mtime, mtime);
25
+ }
26
+ return filePath;
27
+ }
28
+ async function copyBundle(url, newName, bundlesDir) {
29
+ let srcPath;
30
+ if (url === "current") {
31
+ const sdePrepDir = joinPath(bundlesDir, "..", "sde-prep");
32
+ srcPath = joinPath(sdePrepDir, "check-bundle.js");
33
+ } else if (url.startsWith("file://")) {
34
+ srcPath = new URL(url).pathname;
35
+ } else {
36
+ throw new Error(`Cannot copy bundle with URL: ${url}`);
37
+ }
38
+ const bundleContent = await readFile(srcPath, "utf8");
39
+ const stats = await stat(srcPath);
40
+ const sourceLastModified = stats.mtime;
41
+ const nameParts = newName.split("/");
42
+ const filePath = joinPath(bundlesDir, ...nameParts) + ".js";
43
+ await mkdir(dirname(filePath), { recursive: true });
44
+ await writeFile(filePath, bundleContent, "utf8");
45
+ if (sourceLastModified) {
46
+ await utimes(filePath, sourceLastModified, sourceLastModified);
47
+ }
48
+ return filePath;
49
+ }
50
+
10
51
  // src/run-suite.ts
11
52
  import { performance } from "perf_hooks";
12
53
  import pico from "picocolors";
@@ -33,14 +74,15 @@ async function runTestSuite(context, config, verbose) {
33
74
  onComplete: (report) => {
34
75
  try {
35
76
  const t1 = performance.now();
36
- const elapsed = ((t1 - t0) / 1e3).toFixed(1);
77
+ const elapsedMillis = t1 - t0;
78
+ const elapsedSeconds = (elapsedMillis / 1e3).toFixed(1);
37
79
  context.log("info", `
38
- Test suite completed in ${elapsed}s`);
80
+ Test suite completed in ${elapsedSeconds}s`);
39
81
  const allChecksPassed = printCheckSummary(context, report.checkReport, verbose);
40
82
  if (report.comparisonReport) {
41
83
  printPerfStats(context, config.comparison, report.comparisonReport);
42
84
  }
43
- const suiteSummary = suiteSummaryFromReport(report);
85
+ const suiteSummary = suiteSummaryFromReport(report, elapsedMillis);
44
86
  resolve({
45
87
  allChecksPassed,
46
88
  suiteSummary
@@ -72,6 +114,9 @@ function printCheckSummary(context, checkReport, verbose) {
72
114
  case "error":
73
115
  statusChar = "\u203C";
74
116
  break;
117
+ case "skipped":
118
+ statusChar = "\u2013";
119
+ break;
75
120
  default:
76
121
  statusChar = "";
77
122
  break;
@@ -110,13 +155,13 @@ ${group.name}`);
110
155
  context.log("info", "");
111
156
  return allPassed;
112
157
  }
113
- function stat(label, n) {
158
+ function stat2(label, n) {
114
159
  return `${label}=${n.toFixed(1)}ms`;
115
160
  }
116
161
  function printPerfReportLine(context, perfReport) {
117
- const avg = stat("avg", perfReport.avgTime);
118
- const min = stat("min", perfReport.minTime);
119
- const max = stat("max", perfReport.maxTime);
162
+ const avg = stat2("avg", perfReport.avgTime);
163
+ const min = stat2("min", perfReport.minTime);
164
+ const max = stat2("max", perfReport.maxTime);
120
165
  context.log("info", ` ${avg} ${min} ${max}`);
121
166
  }
122
167
  function printPerfStats(context, comparisonConfig, report) {
@@ -129,13 +174,15 @@ function printPerfStats(context, comparisonConfig, report) {
129
174
  }
130
175
 
131
176
  // src/vite-config-for-bundle.ts
132
- import { existsSync, statSync } from "fs";
133
- import { basename, dirname, join as joinPath, relative, resolve as resolvePath } from "path";
177
+ import { existsSync, readFileSync, statSync } from "fs";
178
+ import { basename, dirname as dirname2, join as joinPath2, relative, resolve as resolvePath } from "path";
134
179
  import { fileURLToPath } from "url";
135
180
  import { nodeResolve } from "@rollup/plugin-node-resolve";
181
+ import { encodeImplVars } from "@sdeverywhere/check-core";
136
182
  var __filename2 = fileURLToPath(import.meta.url);
137
- var __dirname2 = dirname(__filename2);
183
+ var __dirname2 = dirname2(__filename2);
138
184
  function injectModelSpec(context, modelSpec) {
185
+ const prepDir = context.config.prepDir;
139
186
  const inputSpecs = [];
140
187
  for (const modelInputSpec of modelSpec.inputs) {
141
188
  if (modelInputSpec.defaultValue === void 0 || modelInputSpec.minValue === void 0 || modelInputSpec.maxValue === void 0) {
@@ -161,9 +208,20 @@ function injectModelSpec(context, modelSpec) {
161
208
  ...o
162
209
  };
163
210
  });
211
+ function readJsonListing() {
212
+ const path = joinPath2(prepDir, "build", "processed.json");
213
+ if (existsSync(path)) {
214
+ const json = readFileSync(path, "utf8");
215
+ return JSON.parse(json);
216
+ } else {
217
+ return {};
218
+ }
219
+ }
220
+ const listing = readJsonListing();
221
+ const varInstances = listing.varInstances || {};
222
+ const encodedImplVars = encodeImplVars(varInstances);
164
223
  function stagedFileSize(filename) {
165
- const prepDir = context.config.prepDir;
166
- const path = joinPath(prepDir, "staged", "model", filename);
224
+ const path = joinPath2(prepDir, "staged", "model", filename);
167
225
  if (existsSync(path)) {
168
226
  return statSync(path).size;
169
227
  } else {
@@ -175,6 +233,7 @@ function injectModelSpec(context, modelSpec) {
175
233
  const moduleSrc = `
176
234
  export const inputSpecs = ${JSON.stringify(inputSpecs)};
177
235
  export const outputSpecs = ${JSON.stringify(outputSpecs)};
236
+ export const encodedImplVars = ${JSON.stringify(encodedImplVars)};
178
237
  export const modelSizeInBytes = ${modelSizeInBytes};
179
238
  export const dataSizeInBytes = ${dataSizeInBytes};
180
239
  `;
@@ -217,7 +276,7 @@ async function createViteConfigForBundle(context, modelSpec) {
217
276
  const root = resolvePath(__dirname2, "..", "template-bundle");
218
277
  const prepDir = context.config.prepDir;
219
278
  const outDir = relative(root, prepDir);
220
- const modelWorkerPath = joinPath(prepDir, "staged", "model", "worker.js?raw");
279
+ const modelWorkerPath = joinPath2(prepDir, "staged", "model", "worker.js?raw");
221
280
  return {
222
281
  // Don't use an external config file
223
282
  configFile: false,
@@ -252,7 +311,9 @@ async function createViteConfigForBundle(context, modelSpec) {
252
311
  replacement: "threads",
253
312
  customResolver: async function(source, importer, options) {
254
313
  const customResolver = nodeResolve({ browser: false });
255
- const resolved = await customResolver.resolveId.call(this, source, importer, options);
314
+ const resolveIdHook = customResolver.resolveId;
315
+ const resolveIdFn = typeof resolveIdHook === "function" ? resolveIdHook : resolveIdHook.handler;
316
+ const resolved = await resolveIdFn.call(this, source, importer, options);
256
317
  if (source === "threads/worker") {
257
318
  return resolved.id.replace("worker.mjs", "dist-esm/worker/index.js");
258
319
  } else {
@@ -316,29 +377,116 @@ let __non_webpack_require__ = () => {
316
377
  }
317
378
 
318
379
  // src/vite-config-for-report.ts
319
- import { existsSync as existsSync2, mkdirSync } from "fs";
320
- import { dirname as dirname2, relative as relative2, join as joinPath2, resolve as resolvePath2 } from "path";
380
+ import { existsSync as existsSync2, mkdirSync, statSync as statSync2 } from "fs";
381
+ import { dirname as dirname3, relative as relative3, join as joinPath4, resolve as resolvePath2 } from "path";
321
382
  import { fileURLToPath as fileURLToPath2 } from "url";
322
383
  import replace from "@rollup/plugin-replace";
384
+
385
+ // src/vite-local-bundles-plugin.ts
386
+ import { readdir, stat as stat3 } from "fs/promises";
387
+ import { join as joinPath3, relative as relative2, sep } from "path";
388
+ import { pathToFileURL } from "url";
389
+ import chokidar from "chokidar";
390
+ function localBundlesPlugin(bundlesDir) {
391
+ return {
392
+ name: "sde-local-bundles",
393
+ configureServer(server) {
394
+ const watcher = chokidar.watch(bundlesDir, {
395
+ // Don't send initial "file added" events
396
+ ignoreInitial: true,
397
+ // XXX: Include a delay, otherwise on macOS we sometimes get multiple
398
+ // change events when a file is saved just once
399
+ awaitWriteFinish: {
400
+ stabilityThreshold: 200
401
+ },
402
+ // Watch up to 10 levels deep
403
+ depth: 10
404
+ });
405
+ watcher.on("all", () => {
406
+ server.ws.send("bundles-changed", {});
407
+ });
408
+ server.httpServer?.on("close", () => {
409
+ watcher.close();
410
+ });
411
+ server.ws.on("list-bundles", async (_, client) => {
412
+ try {
413
+ const bundles = await scanBundlesRecursively(bundlesDir, bundlesDir);
414
+ client.send("list-bundles-success", { bundles });
415
+ } catch (error) {
416
+ console.error(`[sde-local-bundles] Failed to list bundles:`, error);
417
+ client.send("list-bundles-error", { error: error.message });
418
+ }
419
+ });
420
+ server.ws.on("download-bundle", async (data, client) => {
421
+ const { url, name, lastModified } = data;
422
+ try {
423
+ console.log(`[sde-local-bundles] Downloading bundle: name=${name} url=${url}`);
424
+ const filePath = await downloadBundle(url, name, lastModified, bundlesDir);
425
+ console.log(`[sde-local-bundles] Downloaded bundle to ${filePath}`);
426
+ client.send("download-bundle-success", { name, filePath: `${name}.js` });
427
+ } catch (error) {
428
+ console.error(`[sde-local-bundles] Failed to download bundle:`, error);
429
+ client.send("download-bundle-error", { name, error: error.message });
430
+ }
431
+ });
432
+ server.ws.on("copy-bundle", async (data, client) => {
433
+ const { url, name, newName } = data;
434
+ try {
435
+ console.log(`[sde-local-bundles] Copying bundle: src=${name} dst=${newName}`);
436
+ const filePath = await copyBundle(url, newName, bundlesDir);
437
+ console.log(`[sde-local-bundles] Copied bundle to ${filePath}`);
438
+ client.send("copy-bundle-success", { name: newName, filePath: `${newName}.js` });
439
+ } catch (error) {
440
+ console.error(`[sde-local-bundles] Failed to copy bundle:`, error);
441
+ client.send("copy-bundle-error", { name, error: error.message });
442
+ }
443
+ });
444
+ }
445
+ };
446
+ }
447
+ async function scanBundlesRecursively(dir, baseDir) {
448
+ const bundles = [];
449
+ const entries = await readdir(dir, { withFileTypes: true });
450
+ for (const entry of entries) {
451
+ const fullPath = joinPath3(dir, entry.name);
452
+ if (entry.isDirectory()) {
453
+ const subBundles = await scanBundlesRecursively(fullPath, baseDir);
454
+ bundles.push(...subBundles);
455
+ } else if (entry.isFile() && entry.name.endsWith(".js")) {
456
+ const stats = await stat3(fullPath);
457
+ const relativePath = relative2(baseDir, fullPath);
458
+ const name = relativePath.replace(/\.js$/, "").split(sep).join("/");
459
+ bundles.push({
460
+ name,
461
+ url: pathToFileURL(fullPath).toString(),
462
+ lastModified: stats.mtime.toISOString()
463
+ });
464
+ }
465
+ }
466
+ return bundles;
467
+ }
468
+
469
+ // src/vite-config-for-report.ts
323
470
  var __filename3 = fileURLToPath2(import.meta.url);
324
- var __dirname3 = dirname2(__filename3);
325
- function createViteConfigForReport(options, projDir, prepDir, currentBundleName, currentBundlePath, testConfigPath, suiteSummary) {
471
+ var __dirname3 = dirname3(__filename3);
472
+ function createViteConfigForReport(mode, options, projDir, prepDir, currentBundleSpec, baselineBundleSpec, testConfigPath, suiteSummary) {
326
473
  const root = resolvePath2(__dirname3, "..", "template-report");
327
- const baselinesDir = resolvePath2(projDir, "baselines");
328
- if (!existsSync2(baselinesDir)) {
329
- mkdirSync(baselinesDir, { recursive: true });
474
+ const bundlesDir = resolvePath2(projDir, "bundles");
475
+ if (!existsSync2(bundlesDir)) {
476
+ mkdirSync(bundlesDir, { recursive: true });
330
477
  }
331
478
  const templateSrcDir = resolvePath2(root, "src");
332
- const relProjDir = relative2(templateSrcDir, projDir);
479
+ const relProjDir = relative3(templateSrcDir, projDir);
333
480
  const relProjDirPath = relProjDir.replaceAll("\\", "/");
334
- const baselinesPath = `${relProjDirPath}/baselines/*.js`;
481
+ const bundlesPath = `${relProjDirPath}/bundles/*.js`;
482
+ const currentBundleLastModified = statSync2(currentBundleSpec.path).mtime.toISOString();
335
483
  let reportPath;
336
484
  if (options?.reportPath) {
337
485
  reportPath = options.reportPath;
338
486
  } else {
339
- reportPath = joinPath2(prepDir, "check-report");
487
+ reportPath = joinPath4(prepDir, "check-report");
340
488
  }
341
- const outDir = relative2(root, reportPath);
489
+ const outDir = relative3(root, reportPath);
342
490
  const suiteSummaryJson = suiteSummary ? JSON.stringify(suiteSummary) : "";
343
491
  const alias = (find, replacement) => {
344
492
  return {
@@ -363,7 +511,7 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
363
511
  // Use a custom cache directory under `prepDir`, as otherwise Vite will use
364
512
  // `packages/plugin-check/template-report/node_modules/.vite`, and we want to
365
513
  // avoid generating files in `template-report` (which should be read-only)
366
- cacheDir: joinPath2(prepDir, ".vite-check-report"),
514
+ cacheDir: joinPath4(prepDir, ".vite-check-report"),
367
515
  // Load static files from `static` (instead of the default `public`)
368
516
  // publicDir: 'static',
369
517
  // Don't clear the screen in dev mode so that we can see builder output
@@ -410,9 +558,9 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
410
558
  alias: [
411
559
  // Use the configured "baseline" bundle if defined, otherwise use the "empty" bundle
412
560
  // (which will cause comparison tests to be skipped)
413
- alias("@_baseline_bundle_", options?.baseline ? options.baseline.path : "/src/empty-bundle.ts"),
561
+ alias("@_baseline_bundle_", baselineBundleSpec?.path || "/src/empty-bundle.ts"),
414
562
  // Use the configured "current" bundle
415
- alias("@_current_bundle_", currentBundlePath),
563
+ alias("@_current_bundle_", currentBundleSpec.path),
416
564
  // Use the configured test config file
417
565
  alias("@_test_config_", testConfigPath),
418
566
  // Make the overlay use the `messages.html` file that is written to the prep directory
@@ -432,10 +580,14 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
432
580
  define: {
433
581
  // Inject the summary JSON into the build
434
582
  __SUITE_SUMMARY_JSON__: JSON.stringify(suiteSummaryJson),
435
- // Inject the baseline branch name
436
- __BASELINE_NAME__: JSON.stringify(options?.baseline?.name || ""),
437
- // Inject the current branch name
438
- __CURRENT_NAME__: JSON.stringify(currentBundleName)
583
+ // Inject the baseline bundle name
584
+ __BASELINE_NAME__: JSON.stringify(baselineBundleSpec?.name || ""),
585
+ // Inject the current bundle name
586
+ __CURRENT_NAME__: JSON.stringify(currentBundleSpec.name),
587
+ // Inject the last modified time of the current bundle
588
+ __CURRENT_BUNDLE_LAST_MODIFIED__: JSON.stringify(currentBundleLastModified),
589
+ // Inject the remote bundles URL
590
+ __REMOTE_BUNDLES_URL__: JSON.stringify(options?.remoteBundlesUrl || "")
439
591
  },
440
592
  plugins: [
441
593
  // Inject special values into the generated JS
@@ -447,15 +599,18 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
447
599
  delimiters: ["", ""],
448
600
  values: {
449
601
  // Inject the path for baseline bundles
450
- // XXX: Note that we use './baselines/*.txt' instead of something special
602
+ // XXX: Note that we use './bundles/*.txt' instead of something special
451
603
  // like './__BASELINE_BUNDLES_PATH__' because sometimes Vite's dependency
452
604
  // scanner sees the latter (instead of the injected path) and reports
453
605
  // an error since the path does not exist. As a workaround, we use
454
- // './baselines/*.txt', which gets interpreted as the valid path
455
- // '.../template-report/src/baselines/*.txt' (see `baselines/unused.txt`).
456
- "./baselines/*.txt": baselinesPath
606
+ // './bundles/*.txt', which gets interpreted as the valid path
607
+ // '.../template-report/src/bundles/*.txt' (see `bundles/unused.txt`).
608
+ "./bundles/*.txt": bundlesPath
457
609
  }
458
- })
610
+ }),
611
+ // When local development mode is active, enable the local bundles plugin that
612
+ // allows the report app to access the local bundles directory
613
+ ...mode === "watch" ? [localBundlesPlugin(bundlesDir)] : []
459
614
  ],
460
615
  build: {
461
616
  // Write output files to the configured directory (instead of the default `dist`);
@@ -494,19 +649,19 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
494
649
  }
495
650
 
496
651
  // src/vite-config-for-tests.ts
497
- import { dirname as dirname3, relative as relative3, resolve as resolvePath3 } from "path";
652
+ import { dirname as dirname4, relative as relative4, resolve as resolvePath3 } from "path";
498
653
  import { fileURLToPath as fileURLToPath3 } from "url";
499
654
  import replace2 from "@rollup/plugin-replace";
500
655
  var __filename4 = fileURLToPath3(import.meta.url);
501
- var __dirname4 = dirname3(__filename4);
502
- function createViteConfigForTests(projDir, prepDir, mode) {
656
+ var __dirname4 = dirname4(__filename4);
657
+ function createViteConfigForTests(mode, projDir, prepDir) {
503
658
  const root = resolvePath3(__dirname4, "..", "template-tests");
504
659
  const templateSrcDir = resolvePath3(root, "src");
505
- const relProjDir = relative3(templateSrcDir, projDir);
660
+ const relProjDir = relative4(templateSrcDir, projDir);
506
661
  const relProjDirPath = relProjDir.replaceAll("\\", "/");
507
662
  const yamlCheckGlobPatterns = `['${relProjDirPath}/**/checks/*.yaml', '${relProjDirPath}/**/*.check.yaml']`;
508
663
  const yamlComparisonGlobPatterns = `['${relProjDirPath}/**/comparisons/*.yaml']`;
509
- const outDir = relative3(root, prepDir);
664
+ const outDir = relative4(root, prepDir);
510
665
  return {
511
666
  // Don't use an external config file
512
667
  configFile: false,
@@ -570,26 +725,12 @@ var CheckPlugin = class {
570
725
  }
571
726
  async watch(config) {
572
727
  if (this.options?.testConfigPath === void 0) {
573
- await this.genTestConfig(config, "watch");
728
+ await this.genTestConfig("watch", config);
574
729
  }
575
- const testOptions = this.resolveTestOptions(config);
576
- const viteConfig = this.createViteConfigForReport(config, testOptions, void 0);
730
+ const testOptions = await this.resolveTestOptions("watch", config);
731
+ const viteConfig = await this.createViteConfigForReport("watch", config, testOptions, void 0);
577
732
  const server = await createServer(viteConfig);
578
733
  await server.listen();
579
- const baselinesDir = "baselines";
580
- const watcher = chokidar.watch(baselinesDir, {
581
- // Watch paths are resolved relative to the project root directory
582
- cwd: config.rootDir,
583
- // Don't send initial "file added" events
584
- ignoreInitial: true,
585
- // XXX: Include a delay, otherwise on macOS we sometimes get multiple
586
- // change events when a file is saved just once
587
- awaitWriteFinish: {
588
- stabilityThreshold: 200
589
- }
590
- });
591
- watcher.on("add", () => server.restart());
592
- watcher.on("unlink", () => server.restart());
593
734
  }
594
735
  // TODO: Note that this plugin runs as a `postBuild` step because it currently
595
736
  // needs to run after other plugins, and those plugins need to run after the
@@ -599,7 +740,7 @@ var CheckPlugin = class {
599
740
  async postBuild(context, modelSpec) {
600
741
  const firstBuild = this.firstBuild;
601
742
  this.firstBuild = false;
602
- if (this.options?.current === void 0) {
743
+ if (this.options?.current?.path === void 0 && this.options?.current?.url === void 0) {
603
744
  if (context.config.mode === "development") {
604
745
  await this.copyPreviousBundle(context.config);
605
746
  }
@@ -609,24 +750,24 @@ var CheckPlugin = class {
609
750
  if (this.options?.testConfigPath === void 0) {
610
751
  if (context.config.mode === "production" || firstBuild) {
611
752
  context.log("info", "Generating model check test configuration...");
612
- await this.genTestConfig(context.config, "build");
753
+ await this.genTestConfig("bundle", context.config);
613
754
  }
614
755
  }
615
756
  if (context.config.mode === "production") {
616
- const testOptions = this.resolveTestOptions(context.config);
757
+ const testOptions = await this.resolveTestOptions("bundle", context.config);
617
758
  return this.runChecks(context, testOptions);
618
759
  } else {
619
760
  return true;
620
761
  }
621
762
  }
622
763
  async copyPreviousBundle(config) {
623
- const currentBundleFile = joinPath3(config.prepDir, "check-bundle.js");
764
+ const currentBundleFile = joinPath5(config.prepDir, "check-bundle.js");
624
765
  if (existsSync3(currentBundleFile)) {
625
- const baselinesDir = joinPath3(config.rootDir, "baselines");
626
- if (!existsSync3(baselinesDir)) {
627
- await mkdir(baselinesDir, { recursive: true });
766
+ const bundlesDir = joinPath5(config.rootDir, "bundles");
767
+ if (!existsSync3(bundlesDir)) {
768
+ await mkdir2(bundlesDir, { recursive: true });
628
769
  }
629
- const previousBundleFile = joinPath3(baselinesDir, "previous.js");
770
+ const previousBundleFile = joinPath5(bundlesDir, "previous.js");
630
771
  await copyFile(currentBundleFile, previousBundleFile);
631
772
  }
632
773
  }
@@ -634,25 +775,32 @@ var CheckPlugin = class {
634
775
  const viteConfig = await createViteConfigForBundle(context, modelSpec);
635
776
  await build(viteConfig);
636
777
  }
637
- async genTestConfig(config, mode) {
778
+ async genTestConfig(mode, config) {
638
779
  const rootDir = config.rootDir;
639
780
  const prepDir = config.prepDir;
640
- const viteConfig = createViteConfigForTests(rootDir, prepDir, mode);
781
+ const viteConfig = createViteConfigForTests(mode, rootDir, prepDir);
641
782
  await build(viteConfig);
642
783
  }
643
784
  async runChecks(context, testOptions) {
644
785
  context.log("info", "Running model checks...");
645
- const moduleR = await import(relativeToSourcePath(testOptions.currentBundlePath));
786
+ async function importBundleModule(bundleSpec) {
787
+ return import(relativeToSourcePath(bundleSpec.path));
788
+ }
789
+ const moduleR = await importBundleModule(testOptions.currentBundleSpec);
646
790
  const bundleR = moduleR.createBundle();
647
- const bundleNameR = testOptions.currentBundleName;
791
+ const bundleNameR = testOptions.currentBundleSpec.name;
648
792
  let bundleL;
649
793
  let bundleNameL;
650
- if (this.options?.baseline) {
651
- const moduleL = await import(relativeToSourcePath(this.options.baseline.path));
794
+ if (testOptions.baselineBundleSpec !== void 0) {
795
+ const moduleL = await importBundleModule(testOptions.baselineBundleSpec);
652
796
  const rawBundleL = moduleL.createBundle();
653
797
  if (rawBundleL.version === bundleR.version) {
654
798
  bundleL = rawBundleL;
655
- bundleNameL = this.options.baseline.name;
799
+ bundleNameL = testOptions.baselineBundleSpec.name || "base";
800
+ } else {
801
+ console.warn(
802
+ `WARNING: Bundle version mismatch (baseline=${rawBundleL.version} current=${bundleR.version}); check tests will be run but comparisons will be skipped`
803
+ );
656
804
  }
657
805
  }
658
806
  const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath));
@@ -669,47 +817,79 @@ var CheckPlugin = class {
669
817
  false
670
818
  );
671
819
  context.log("info", "Building model check report");
672
- const viteConfig = this.createViteConfigForReport(context.config, testOptions, result.suiteSummary);
820
+ const viteConfig = await this.createViteConfigForReport("bundle", context.config, testOptions, result.suiteSummary);
673
821
  await build(viteConfig);
674
822
  return result.allChecksPassed;
675
823
  }
676
- resolveTestOptions(config) {
677
- let currentBundleName;
678
- let currentBundlePath;
679
- if (this.options?.current === void 0) {
680
- currentBundleName = "current";
681
- currentBundlePath = joinPath3(config.prepDir, "check-bundle.js");
682
- } else {
683
- currentBundleName = this.options.current.name;
684
- currentBundlePath = this.options.current.path;
824
+ async resolveTestOptions(mode, config) {
825
+ async function resolveBundle(bundle) {
826
+ if (bundle?.url !== void 0) {
827
+ const localBundlePath = await downloadBundle(
828
+ bundle.url,
829
+ bundle.name,
830
+ // TODO: We don't know the last modified time of the remote bundle here, so we use
831
+ // undefined (which means the local file will be created with the current timestamp)
832
+ void 0,
833
+ joinPath5(config.rootDir, "bundles")
834
+ );
835
+ return {
836
+ name: bundle.name,
837
+ path: localBundlePath
838
+ };
839
+ } else if (bundle?.path !== void 0) {
840
+ return {
841
+ name: bundle.name,
842
+ path: bundle.path
843
+ };
844
+ } else {
845
+ return {
846
+ name: bundle?.name || "current",
847
+ path: joinPath5(config.prepDir, "check-bundle.js")
848
+ };
849
+ }
850
+ }
851
+ const currentBundleSpec = await resolveBundle(mode === "bundle" ? this.options?.current : void 0);
852
+ let baselineBundleSpec;
853
+ if (mode === "bundle" && this.options?.baseline) {
854
+ try {
855
+ baselineBundleSpec = await resolveBundle(this.options.baseline);
856
+ } catch (e) {
857
+ const name = this.options.baseline.name;
858
+ const loc = this.options.baseline.url || this.options.baseline.path;
859
+ console.warn(
860
+ `WARNING: Failed to load '${name}' bundle from '${loc}'; check tests will be run but comparisons will be skipped. Cause:`,
861
+ e
862
+ );
863
+ }
685
864
  }
686
865
  let testConfigPath;
687
866
  if (this.options?.testConfigPath === void 0) {
688
- testConfigPath = joinPath3(config.prepDir, "check-tests.js");
867
+ testConfigPath = joinPath5(config.prepDir, "check-tests.js");
689
868
  } else {
690
869
  testConfigPath = this.options.testConfigPath;
691
870
  }
692
871
  return {
693
- currentBundleName,
694
- currentBundlePath,
872
+ currentBundleSpec,
873
+ baselineBundleSpec,
695
874
  testConfigPath
696
875
  };
697
876
  }
698
- createViteConfigForReport(config, testOptions, suiteSummary) {
877
+ async createViteConfigForReport(mode, config, testOptions, suiteSummary) {
699
878
  return createViteConfigForReport(
879
+ mode,
700
880
  this.options,
701
881
  config.rootDir,
702
882
  config.prepDir,
703
- testOptions.currentBundleName,
704
- testOptions.currentBundlePath,
883
+ testOptions.currentBundleSpec,
884
+ testOptions.baselineBundleSpec,
705
885
  testOptions.testConfigPath,
706
886
  suiteSummary
707
887
  );
708
888
  }
709
889
  };
710
890
  function relativeToSourcePath(filePath) {
711
- const srcDir = dirname4(fileURLToPath4(import.meta.url));
712
- const relPath = relative4(srcDir, filePath);
891
+ const srcDir = dirname5(fileURLToPath4(import.meta.url));
892
+ const relPath = relative5(srcDir, filePath);
713
893
  return relPath.replaceAll("\\", "/");
714
894
  }
715
895
  export {