@sdeverywhere/plugin-check 0.3.1 → 0.3.4

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
@@ -130,7 +130,7 @@ function printPerfStats(context, comparisonConfig, report) {
130
130
 
131
131
  // src/vite-config-for-bundle.ts
132
132
  import { existsSync, statSync } from "fs";
133
- import { dirname, join as joinPath, relative, resolve as resolvePath } from "path";
133
+ import { basename, dirname, join as joinPath, relative, resolve as resolvePath } from "path";
134
134
  import { fileURLToPath } from "url";
135
135
  import { nodeResolve } from "@rollup/plugin-node-resolve";
136
136
 
@@ -152,8 +152,8 @@ function sdeNameForVensimVarName(varName) {
152
152
  }
153
153
 
154
154
  // src/vite-config-for-bundle.ts
155
- var __filename = fileURLToPath(import.meta.url);
156
- var __dirname = dirname(__filename);
155
+ var __filename2 = fileURLToPath(import.meta.url);
156
+ var __dirname2 = dirname(__filename2);
157
157
  function injectModelSpec(prepDir, modelSpec) {
158
158
  const inputSpecs = modelSpec.inputs.map((i) => {
159
159
  const varId = sdeNameForVensimVarName(i.varName);
@@ -202,20 +202,58 @@ export const dataSizeInBytes = ${dataSizeInBytes};
202
202
  }
203
203
  };
204
204
  }
205
+ function overrideViteResolvePlugin(viteConfig) {
206
+ const resolvePlugin = viteConfig.plugins.find((p) => p.name === "vite:resolve");
207
+ if (resolvePlugin === void 0) {
208
+ throw new Error("Failed to locate the built-in vite:resolve plugin");
209
+ }
210
+ const originalResolveId = resolvePlugin.resolveId;
211
+ resolvePlugin.resolveId = async function resolveId(id, importer, options) {
212
+ if (id.startsWith("./implementation") && importer.includes("threads/dist-esm")) {
213
+ const idFileName = id.replace("./", "");
214
+ const importerFileName = basename(importer);
215
+ const resolvedId = importer.replace(importerFileName, `${idFileName}.js`);
216
+ return {
217
+ id: resolvedId,
218
+ moduleSideEffects: false
219
+ };
220
+ }
221
+ return originalResolveId.call(this, id, importer, options);
222
+ };
223
+ }
205
224
  async function createViteConfigForBundle(prepDir, modelSpec) {
206
- const root = resolvePath(__dirname, "..", "template-bundle");
225
+ const root = resolvePath(__dirname2, "..", "template-bundle");
207
226
  const outDir = relative(root, prepDir);
208
227
  const modelWorkerPath = joinPath(prepDir, "staged", "model", "worker.js?raw");
209
228
  return {
229
+ // Don't use an external config file
210
230
  configFile: false,
231
+ // Use the root directory configured above
211
232
  root,
233
+ // Don't clear the screen in dev mode so that we can see builder output
212
234
  clearScreen: false,
235
+ // TODO: Disable vite output by default?
236
+ // logLevel: 'silent',
237
+ // Configure path aliases
213
238
  resolve: {
214
239
  alias: [
240
+ // Inject the configured model worker
215
241
  {
216
242
  find: "@_model_worker_",
217
243
  replacement: modelWorkerPath
218
244
  },
245
+ // XXX: Prevent Vite from using the `browser` section of `threads/package.json`
246
+ // since we want to force the use of the general module (under dist-esm) that chooses
247
+ // the correct implementation (Web Worker vs worker_threads) at runtime. Currently
248
+ // Vite's library mode is browser focused and generally chooses the right imports,
249
+ // except in the case of the threads package where we want to use the generic
250
+ // `implementation.js` that chooses between Web Worker and worker_threads at runtime.
251
+ // Note that we could in theory set `resolve.browserField` to false, but that would
252
+ // make Vite not use the browser field for all other packages, and there is not
253
+ // currently a way to tell Vite to use the browser field on a case-by-case basis.
254
+ // So for now we need this workaround here to make it resolve to `dist-esm`, and then
255
+ // a second workaround in `overrideViteResolvePlugin` to prevent the resolver from
256
+ // using the browser field when resolving the threads package.
219
257
  {
220
258
  find: "threads",
221
259
  replacement: "threads",
@@ -232,18 +270,40 @@ async function createViteConfigForBundle(prepDir, modelSpec) {
232
270
  ]
233
271
  },
234
272
  plugins: [
235
- injectModelSpec(prepDir, modelSpec)
273
+ // Use a virtual module plugin to inject the model spec values
274
+ injectModelSpec(prepDir, modelSpec),
275
+ // XXX: Install a wrapper around the built-in `vite:resolve` plugin so that we can
276
+ // override the default resolver behavior that tries to resolve the `browser` section
277
+ // of the `package.json` for the threads package.
278
+ {
279
+ name: "vite-plugin-override-resolve",
280
+ configResolved(viteConfig) {
281
+ overrideViteResolvePlugin(viteConfig);
282
+ }
283
+ }
236
284
  ],
237
285
  build: {
286
+ // Write output files to the configured directory (instead of the default `dist`);
287
+ // note that this must be relative to the project `root`
238
288
  outDir,
239
289
  emptyOutDir: false,
290
+ // Uncomment for debugging purposes
291
+ // minify: false,
240
292
  lib: {
241
293
  entry: "./src/index.ts",
242
294
  formats: ["es"],
243
295
  fileName: () => "check-bundle.js"
244
296
  },
245
297
  rollupOptions: {
298
+ // Don't transform Node imports used by threads.js
246
299
  external: ["events", "os", "path", "url"],
300
+ // XXX: Insert custom code at the top of the generated bundle that defines
301
+ // the special `__non_webpack_require__` function that is used by threads.js
302
+ // in its Node implementation. This import ensures that threads.js uses
303
+ // the native `worker_threads` implementation when using the bundle in a
304
+ // Node environment. When importing the bundle for use in the browser,
305
+ // Vite will transform this import into an empty module due to the empty
306
+ // polyfill that is configured in `vite-config-for-report.ts`.
247
307
  output: {
248
308
  banner: `
249
309
  import * as worker_threads from 'worker_threads'
@@ -266,17 +326,16 @@ let __non_webpack_require__ = () => {
266
326
  import { dirname as dirname2, relative as relative2, join as joinPath2, resolve as resolvePath2 } from "path";
267
327
  import { fileURLToPath as fileURLToPath2 } from "url";
268
328
  import replace from "@rollup/plugin-replace";
269
- var __filename2 = fileURLToPath2(import.meta.url);
270
- var __dirname2 = dirname2(__filename2);
329
+ var __filename3 = fileURLToPath2(import.meta.url);
330
+ var __dirname3 = dirname2(__filename3);
271
331
  function createViteConfigForReport(options, projDir, prepDir, currentBundleName, currentBundlePath, testConfigPath, suiteSummary) {
272
- var _a;
273
- const root = resolvePath2(__dirname2, "..", "template-report");
332
+ const root = resolvePath2(__dirname3, "..", "template-report");
274
333
  const templateSrcDir = resolvePath2(root, "src");
275
334
  const relProjDir = relative2(templateSrcDir, projDir);
276
335
  const relProjDirPath = relProjDir.replaceAll("\\", "/");
277
336
  const baselinesPath = `${relProjDirPath}/baselines/*.js`;
278
337
  let reportPath;
279
- if (options == null ? void 0 : options.reportPath) {
338
+ if (options?.reportPath) {
280
339
  reportPath = options.reportPath;
281
340
  } else {
282
341
  reportPath = joinPath2(prepDir, "check-report");
@@ -296,57 +355,113 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
296
355
  };
297
356
  };
298
357
  return {
358
+ // Don't use an external config file
299
359
  configFile: false,
360
+ // Use the root directory configured above
300
361
  root,
362
+ // Use `.` as the base directory (instead of the default `/`); this controls
363
+ // how the path to the js/css files are generated in `index.html`
301
364
  base: "",
365
+ // Use a custom cache directory under `prepDir`, as otherwise Vite will use
366
+ // `packages/plugin-check/template-report/node_modules/.vite`, and we want to
367
+ // avoid generating files in `template-report` (which should be read-only)
302
368
  cacheDir: joinPath2(prepDir, ".vite-check-report"),
369
+ // Load static files from `static` (instead of the default `public`)
370
+ // publicDir: 'static',
371
+ // Don't clear the screen in dev mode so that we can see builder output
303
372
  clearScreen: false,
373
+ // TODO
374
+ // logLevel: 'silent',
304
375
  optimizeDeps: {
376
+ // Prevent Vite from examining other html files when scanning entrypoints
377
+ // for dependency optimization
305
378
  entries: ["index.html"],
379
+ // XXX: When plugin-check is installed via pnpm, the Vite dev server seems
380
+ // to have no trouble resolving other dependencies using the optimizeDeps
381
+ // mechanism. However, this fails when the package is installed via yarn
382
+ // or npm (probably due to the fact that the `template-report` directory
383
+ // is located under the top-level `node_modules` directory); in the browser,
384
+ // there will be "import not found" errors for the packages referenced below.
385
+ // As a terrible workaround, explicitly include the direct dependencies so
386
+ // that Vite optimizes them; this works for pnpm, yarn, and npm. We should
387
+ // find a less fragile solution.
306
388
  include: [
307
- "assert-never",
308
- "ajv",
309
- "neverthrow",
310
- "yaml",
311
- "fontfaceobserver",
312
- "copy-text-to-clipboard",
313
- "chart.js"
389
+ // from check-core
390
+ "@sdeverywhere/check-core > assert-never",
391
+ "@sdeverywhere/check-core > ajv",
392
+ "@sdeverywhere/check-core > neverthrow",
393
+ "@sdeverywhere/check-core > yaml",
394
+ // from check-ui-shell
395
+ "@sdeverywhere/check-ui-shell > fontfaceobserver",
396
+ "@sdeverywhere/check-ui-shell > copy-text-to-clipboard",
397
+ "@sdeverywhere/check-ui-shell > chart.js"
314
398
  ],
315
399
  exclude: [
316
- "tiny-worker",
317
- "moment"
400
+ // XXX: The threads.js implementation references `tiny-worker` as an optional
401
+ // dependency, but it doesn't get used at runtime, so we can just exclude it
402
+ // so that Vite doesn't complain in dev mode
403
+ "tiny-worker"
404
+ // XXX: Similarly, chart.js treats `moment` as an optional dependency, but we
405
+ // don't use it at runtime; we need to exclude it here, otherwise Vite will
406
+ // complain about missing dependencies in dev mode
407
+ // 'moment'
318
408
  ]
319
409
  },
410
+ // Configure path aliases
320
411
  resolve: {
321
412
  alias: [
322
- alias("@_baseline_bundle_", (options == null ? void 0 : options.baseline) ? options.baseline.path : "/src/empty-bundle.ts"),
413
+ // Use the configured "baseline" bundle if defined, otherwise use the "empty" bundle
414
+ // (which will cause comparison tests to be skipped)
415
+ alias("@_baseline_bundle_", options?.baseline ? options.baseline.path : "/src/empty-bundle.ts"),
416
+ // Use the configured "current" bundle
323
417
  alias("@_current_bundle_", currentBundlePath),
418
+ // Use the configured test config file
324
419
  alias("@_test_config_", testConfigPath),
420
+ // Make the overlay use the `messages.html` file that is written to the prep directory
325
421
  alias("@_prep_", prepDir),
422
+ // XXX: Include no-op polyfills for these modules that are used in the Node-specific
423
+ // implementation of threads.js; this allows us to use one bundle that works in both
424
+ // Node and browser environments
326
425
  noopPolyfillAlias("events"),
426
+ noopPolyfillAlias("fs"),
327
427
  noopPolyfillAlias("os"),
328
428
  noopPolyfillAlias("path"),
329
- noopPolyfillAlias("url")
429
+ noopPolyfillAlias("url"),
430
+ noopPolyfillAlias("worker_threads")
330
431
  ]
331
432
  },
433
+ // Inject special values into the generated JS
332
434
  define: {
435
+ // Inject the summary JSON into the build
333
436
  __SUITE_SUMMARY_JSON__: JSON.stringify(suiteSummaryJson),
334
- __BASELINE_NAME__: JSON.stringify(((_a = options == null ? void 0 : options.baseline) == null ? void 0 : _a.name) || ""),
437
+ // Inject the baseline branch name
438
+ __BASELINE_NAME__: JSON.stringify(options?.baseline?.name || ""),
439
+ // Inject the current branch name
335
440
  __CURRENT_NAME__: JSON.stringify(currentBundleName)
336
441
  },
337
442
  plugins: [
443
+ // Inject special values into the generated JS
444
+ // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's
445
+ // built-in `define` feature because the latter does not seem to run before
446
+ // the glob handler (which requires the glob to be injected as a literal)
338
447
  replace({
339
448
  preventAssignment: true,
449
+ delimiters: ["", ""],
340
450
  values: {
341
- __BASELINE_BUNDLES_PATH__: JSON.stringify(baselinesPath)
451
+ // Inject the path for baseline bundles
452
+ "./__BASELINE_BUNDLES_PATH__": baselinesPath
342
453
  }
343
454
  })
344
455
  ],
345
456
  build: {
457
+ // Write output files to the configured directory (instead of the default `dist`);
458
+ // note that this must be relative to the project `root`
346
459
  outDir,
460
+ // Write js/css files to `public` (instead of the default `<outDir>/assets`)
347
461
  assetsDir: "",
348
462
  rollupOptions: {
349
463
  output: {
464
+ // XXX: Prevent vite from creating a separate `vendor.js` file
350
465
  manualChunks: void 0
351
466
  },
352
467
  onwarn: (warning, warn) => {
@@ -357,8 +472,14 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
357
472
  }
358
473
  },
359
474
  server: {
360
- port: (options == null ? void 0 : options.serverPort) || 8081,
475
+ // Run the dev server at `localhost:8081` by default
476
+ port: options?.serverPort || 8081,
477
+ // Open the app in the browser by default
361
478
  open: "/index.html",
479
+ // XXX: Add a small delay, otherwise on macOS we sometimes get multiple
480
+ // change events when a file is saved just once. That is a relatively
481
+ // harmless issue except that it causes redundant messages in the console
482
+ // and can cause extra churn when refreshing the app.
362
483
  watch: {
363
484
  awaitWriteFinish: {
364
485
  stabilityThreshold: 100
@@ -371,23 +492,42 @@ function createViteConfigForReport(options, projDir, prepDir, currentBundleName,
371
492
  // src/vite-config-for-tests.ts
372
493
  import { dirname as dirname3, relative as relative3, resolve as resolvePath3 } from "path";
373
494
  import { fileURLToPath as fileURLToPath3 } from "url";
374
- var __filename3 = fileURLToPath3(import.meta.url);
375
- var __dirname3 = dirname3(__filename3);
495
+ import replace2 from "@rollup/plugin-replace";
496
+ var __filename4 = fileURLToPath3(import.meta.url);
497
+ var __dirname4 = dirname3(__filename4);
376
498
  function createViteConfigForTests(projDir, prepDir, mode) {
377
- const root = resolvePath3(__dirname3, "..", "template-tests");
499
+ const root = resolvePath3(__dirname4, "..", "template-tests");
378
500
  const templateSrcDir = resolvePath3(root, "src");
379
501
  const relProjDir = relative3(templateSrcDir, projDir);
380
502
  const relProjDirPath = relProjDir.replaceAll("\\", "/");
381
503
  const yamlPath = `${relProjDirPath}/**/*.check.yaml`;
382
504
  const outDir = relative3(root, prepDir);
383
505
  return {
506
+ // Don't use an external config file
384
507
  configFile: false,
508
+ // Use the root directory configured above
385
509
  root,
510
+ // Don't clear the screen in dev mode so that we can see builder output
386
511
  clearScreen: false,
387
- define: {
388
- __YAML_PATH__: JSON.stringify(yamlPath)
389
- },
512
+ // TODO: Disable vite output by default?
513
+ // logLevel: 'silent',
514
+ plugins: [
515
+ // Inject special values into the generated JS
516
+ // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's
517
+ // built-in `define` feature because the latter does not seem to run before
518
+ // the glob handler (which requires the glob to be injected as a literal)
519
+ replace2({
520
+ preventAssignment: true,
521
+ delimiters: ["", ""],
522
+ values: {
523
+ // Inject the glob pattern for matching check yaml files
524
+ "./__YAML_PATH__": yamlPath
525
+ }
526
+ })
527
+ ],
390
528
  build: {
529
+ // Write output files to the configured directory (instead of the default `dist`);
530
+ // note that this must be relative to the project `root`
391
531
  outDir,
392
532
  emptyOutDir: false,
393
533
  lib: {
@@ -395,8 +535,19 @@ function createViteConfigForTests(projDir, prepDir, mode) {
395
535
  formats: ["es"],
396
536
  fileName: () => "check-tests.js"
397
537
  },
538
+ // Enable watch mode if requested
398
539
  watch: mode === "watch" && {},
399
- rollupOptions: {}
540
+ rollupOptions: {
541
+ // Prevent dependencies from being included in packaged library
542
+ // TODO: For now we include check-core in the packaged library so that its
543
+ // dependencies are correctly resolved at runtime. Ideally this would only
544
+ // include a couple functions that are used for defining tests, but Vite 2.x
545
+ // does not implement tree shaking for ES libraries, which means the generated
546
+ // library is much larger than it needs to be. Once we upgrade to Vite 3.x,
547
+ // the generated library should be smaller; see related fix:
548
+ // https://github.com/vitejs/vite/pull/8737
549
+ // external: Object.keys(pkg.dependencies)
550
+ }
400
551
  }
401
552
  };
402
553
  }
@@ -411,8 +562,7 @@ var CheckPlugin = class {
411
562
  this.firstBuild = true;
412
563
  }
413
564
  async watch(config) {
414
- var _a;
415
- if (((_a = this.options) == null ? void 0 : _a.testConfigPath) === void 0) {
565
+ if (this.options?.testConfigPath === void 0) {
416
566
  await this.genTestConfig(config, "watch");
417
567
  }
418
568
  const testOptions = this.resolveTestOptions(config);
@@ -421,8 +571,12 @@ var CheckPlugin = class {
421
571
  await server.listen();
422
572
  const baselinesDir = "baselines";
423
573
  const watcher = chokidar.watch(baselinesDir, {
574
+ // Watch paths are resolved relative to the project root directory
424
575
  cwd: config.rootDir,
576
+ // Don't send initial "file added" events
425
577
  ignoreInitial: true,
578
+ // XXX: Include a delay, otherwise on macOS we sometimes get multiple
579
+ // change events when a file is saved just once
426
580
  awaitWriteFinish: {
427
581
  stabilityThreshold: 200
428
582
  }
@@ -430,18 +584,22 @@ var CheckPlugin = class {
430
584
  watcher.on("add", () => server.restart());
431
585
  watcher.on("unlink", () => server.restart());
432
586
  }
587
+ // TODO: Note that this plugin runs as a `postBuild` step because it currently
588
+ // needs to run after other plugins, and those plugins need to run after the
589
+ // staged files are copied to their final destination(s). We should probably
590
+ // make it configurable so that it can either be run as a `postGenerate` or a
591
+ // `postBuild` step.
433
592
  async postBuild(context, modelSpec) {
434
- var _a, _b;
435
593
  const firstBuild = this.firstBuild;
436
594
  this.firstBuild = false;
437
- if (((_a = this.options) == null ? void 0 : _a.current) === void 0) {
595
+ if (this.options?.current === void 0) {
438
596
  if (context.config.mode === "development") {
439
597
  await this.copyPreviousBundle(context.config);
440
598
  }
441
599
  context.log("info", "Generating model check bundle...");
442
600
  await this.genCurrentBundle(context.config, modelSpec);
443
601
  }
444
- if (((_b = this.options) == null ? void 0 : _b.testConfigPath) === void 0) {
602
+ if (this.options?.testConfigPath === void 0) {
445
603
  if (context.config.mode === "production" || firstBuild) {
446
604
  context.log("info", "Generating model check test configuration...");
447
605
  await this.genTestConfig(context.config, "build");
@@ -477,14 +635,13 @@ var CheckPlugin = class {
477
635
  await build(viteConfig);
478
636
  }
479
637
  async runChecks(context, testOptions) {
480
- var _a;
481
638
  context.log("info", "Running model checks...");
482
639
  const moduleR = await import(relativeToSourcePath(testOptions.currentBundlePath));
483
640
  const bundleR = moduleR.createBundle();
484
641
  const bundleNameR = testOptions.currentBundleName;
485
642
  let bundleL;
486
643
  let bundleNameL;
487
- if ((_a = this.options) == null ? void 0 : _a.baseline) {
644
+ if (this.options?.baseline) {
488
645
  const moduleL = await import(relativeToSourcePath(this.options.baseline.path));
489
646
  const rawBundleL = moduleL.createBundle();
490
647
  if (rawBundleL.version === bundleR.version) {
@@ -499,17 +656,21 @@ var CheckPlugin = class {
499
656
  };
500
657
  const configOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, configInitOptions);
501
658
  const checkConfig = await createConfig(configOptions);
502
- const result = await runTestSuite(context, checkConfig, false);
659
+ const result = await runTestSuite(
660
+ context,
661
+ checkConfig,
662
+ /*verbose=*/
663
+ false
664
+ );
503
665
  context.log("info", "Building model check report");
504
666
  const viteConfig = this.createViteConfigForReport(context.config, testOptions, result.suiteSummary);
505
667
  await build(viteConfig);
506
668
  return result.allChecksPassed;
507
669
  }
508
670
  resolveTestOptions(config) {
509
- var _a, _b;
510
671
  let currentBundleName;
511
672
  let currentBundlePath;
512
- if (((_a = this.options) == null ? void 0 : _a.current) === void 0) {
673
+ if (this.options?.current === void 0) {
513
674
  currentBundleName = "current";
514
675
  currentBundlePath = joinPath3(config.prepDir, "check-bundle.js");
515
676
  } else {
@@ -517,7 +678,7 @@ var CheckPlugin = class {
517
678
  currentBundlePath = this.options.current.path;
518
679
  }
519
680
  let testConfigPath;
520
- if (((_b = this.options) == null ? void 0 : _b.testConfigPath) === void 0) {
681
+ if (this.options?.testConfigPath === void 0) {
521
682
  testConfigPath = joinPath3(config.prepDir, "check-tests.js");
522
683
  } else {
523
684
  testConfigPath = this.options.testConfigPath;