@sdeverywhere/plugin-check 0.3.18 → 0.3.20

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 CHANGED
@@ -44,7 +44,7 @@ var import_path4 = require("path");
44
44
  var import_url4 = require("url");
45
45
  var import_vite = require("vite");
46
46
  var import_chokidar = __toESM(require("chokidar"), 1);
47
- var import_check_core2 = require("@sdeverywhere/check-core");
47
+ var import_check_core3 = require("@sdeverywhere/check-core");
48
48
 
49
49
  // src/run-suite.ts
50
50
  var import_perf_hooks = require("perf_hooks");
@@ -66,14 +66,15 @@ async function runTestSuite(context, config, verbose) {
66
66
  onComplete: (report) => {
67
67
  try {
68
68
  const t1 = import_perf_hooks.performance.now();
69
- const elapsed = ((t1 - t0) / 1e3).toFixed(1);
69
+ const elapsedMillis = t1 - t0;
70
+ const elapsedSeconds = (elapsedMillis / 1e3).toFixed(1);
70
71
  context.log("info", `
71
- Test suite completed in ${elapsed}s`);
72
+ Test suite completed in ${elapsedSeconds}s`);
72
73
  const allChecksPassed = printCheckSummary(context, report.checkReport, verbose);
73
74
  if (report.comparisonReport) {
74
75
  printPerfStats(context, config.comparison, report.comparisonReport);
75
76
  }
76
- const suiteSummary = (0, import_check_core.suiteSummaryFromReport)(report);
77
+ const suiteSummary = (0, import_check_core.suiteSummaryFromReport)(report, elapsedMillis);
77
78
  resolve({
78
79
  allChecksPassed,
79
80
  suiteSummary
@@ -105,6 +106,9 @@ function printCheckSummary(context, checkReport, verbose) {
105
106
  case "error":
106
107
  statusChar = "\u203C";
107
108
  break;
109
+ case "skipped":
110
+ statusChar = "\u2013";
111
+ break;
108
112
  default:
109
113
  statusChar = "";
110
114
  break;
@@ -166,9 +170,11 @@ var import_fs = require("fs");
166
170
  var import_path = require("path");
167
171
  var import_url = require("url");
168
172
  var import_plugin_node_resolve = require("@rollup/plugin-node-resolve");
173
+ var import_check_core2 = require("@sdeverywhere/check-core");
169
174
  var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
170
175
  var __dirname = (0, import_path.dirname)(__filename2);
171
176
  function injectModelSpec(context, modelSpec) {
177
+ const prepDir = context.config.prepDir;
172
178
  const inputSpecs = [];
173
179
  for (const modelInputSpec of modelSpec.inputs) {
174
180
  if (modelInputSpec.defaultValue === void 0 || modelInputSpec.minValue === void 0 || modelInputSpec.maxValue === void 0) {
@@ -194,8 +200,19 @@ function injectModelSpec(context, modelSpec) {
194
200
  ...o
195
201
  };
196
202
  });
203
+ function readJsonListing() {
204
+ const path = (0, import_path.join)(prepDir, "build", "processed.json");
205
+ if ((0, import_fs.existsSync)(path)) {
206
+ const json = (0, import_fs.readFileSync)(path, "utf8");
207
+ return JSON.parse(json);
208
+ } else {
209
+ return {};
210
+ }
211
+ }
212
+ const listing = readJsonListing();
213
+ const varInstances = listing.varInstances || {};
214
+ const encodedImplVars = (0, import_check_core2.encodeImplVars)(varInstances);
197
215
  function stagedFileSize(filename) {
198
- const prepDir = context.config.prepDir;
199
216
  const path = (0, import_path.join)(prepDir, "staged", "model", filename);
200
217
  if ((0, import_fs.existsSync)(path)) {
201
218
  return (0, import_fs.statSync)(path).size;
@@ -208,6 +225,7 @@ function injectModelSpec(context, modelSpec) {
208
225
  const moduleSrc = `
209
226
  export const inputSpecs = ${JSON.stringify(inputSpecs)};
210
227
  export const outputSpecs = ${JSON.stringify(outputSpecs)};
228
+ export const encodedImplVars = ${JSON.stringify(encodedImplVars)};
211
229
  export const modelSizeInBytes = ${modelSizeInBytes};
212
230
  export const dataSizeInBytes = ${dataSizeInBytes};
213
231
  `;
@@ -694,7 +712,7 @@ var CheckPlugin = class {
694
712
  bundleNameR
695
713
  };
696
714
  const configOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, configInitOptions);
697
- const checkConfig = await (0, import_check_core2.createConfig)(configOptions);
715
+ const checkConfig = await (0, import_check_core3.createConfig)(configOptions);
698
716
  const result = await runTestSuite(
699
717
  context,
700
718
  checkConfig,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/tsup@8.2.4_postcss@8.5.6_typescript@5.2.2/node_modules/tsup/assets/cjs_shims.js","../src/plugin.ts","../src/run-suite.ts","../src/vite-config-for-bundle.ts","../src/vite-config-for-report.ts","../src/vite-config-for-tests.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport type { CheckBundle, CheckPluginOptions } from './options'\nexport { checkPlugin } from './plugin'\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { copyFile, mkdir } from 'fs/promises'\nimport { dirname, join as joinPath, relative } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, ViteDevServer } from 'vite'\nimport { build, createServer } from 'vite'\n\nimport chokidar from 'chokidar'\n\nimport type { BuildContext, Plugin, ResolvedConfig, ResolvedModelSpec } from '@sdeverywhere/build'\n\nimport type { Bundle, ConfigInitOptions, SuiteSummary } from '@sdeverywhere/check-core'\nimport { createConfig } from '@sdeverywhere/check-core'\n\nimport type { CheckPluginOptions } from './options'\nimport { runTestSuite } from './run-suite'\nimport { createViteConfigForBundle } from './vite-config-for-bundle'\nimport { createViteConfigForReport } from './vite-config-for-report'\nimport { createViteConfigForTests } from './vite-config-for-tests'\n\nexport function checkPlugin(options?: CheckPluginOptions): Plugin {\n return new CheckPlugin(options)\n}\n\ninterface TestOptions {\n currentBundleName: string\n currentBundlePath: string\n testConfigPath: string\n}\n\nclass CheckPlugin implements Plugin {\n private firstBuild = true\n\n constructor(private readonly options?: CheckPluginOptions) {}\n\n async watch(config: ResolvedConfig): Promise<void> {\n if (this.options?.testConfigPath === undefined) {\n // Test config was not provided, so generate a default config in watch mode.\n // The test template uses import.meta.glob so that checks are re-run\n // automatically when the `{checks/comparisons}/*.yaml` files are changed.\n await this.genTestConfig(config, 'watch')\n }\n\n // For development mode, run Vite in dev mode so that it serves the\n // model-check report locally (with live reload enabled). When a model\n // test file is changed, the tests will be re-run in the browser.\n const testOptions = this.resolveTestOptions(config)\n const viteConfig = this.createViteConfigForReport(config, testOptions, undefined)\n const server: ViteDevServer = await createServer(viteConfig)\n await server.listen()\n\n // XXX: Currently Vite doesn't reload the page if a file is added/removed\n // in the baselines directory (Vite's import.meta.glob handling doesn't\n // seem to do this automatically), so as a workaround, watch the baselines\n // directory and restart the server if files are added/removed\n // TODO: The same problem also applies to the glob for `checks/*.yaml` and\n // `comparisons/*.yaml` files in the test config, so we should also reload\n // if files match/unmatch those glob patterns\n // TODO: Use the baselines directory from the config\n const baselinesDir = 'baselines'\n const watcher = chokidar.watch(baselinesDir, {\n // Watch paths are resolved relative to the project root directory\n cwd: config.rootDir,\n // Don't send initial \"file added\" events\n ignoreInitial: true,\n // XXX: Include a delay, otherwise on macOS we sometimes get multiple\n // change events when a file is saved just once\n awaitWriteFinish: {\n stabilityThreshold: 200\n }\n })\n watcher.on('add', () => server.restart())\n watcher.on('unlink', () => server.restart())\n }\n\n // TODO: Note that this plugin runs as a `postBuild` step because it currently\n // needs to run after other plugins, and those plugins need to run after the\n // staged files are copied to their final destination(s). We should probably\n // make it configurable so that it can either be run as a `postGenerate` or a\n // `postBuild` step.\n async postBuild(context: BuildContext, modelSpec: ResolvedModelSpec): Promise<boolean> {\n const firstBuild = this.firstBuild\n this.firstBuild = false\n\n // For both production builds and local development, generate default bundle\n // in this post-build step each time a source file is changed\n // TODO: We could potentially use watch mode for the bundle similar to\n // what we do for the test config, but the bundle depends on the ModelSpec,\n // which currently isn't made available to the `watch` function\n if (this.options?.current === undefined) {\n // Path to current bundle was not provided, so generate a default bundle\n if (context.config.mode === 'development') {\n // Copy the previous bundle to the `baselines` directory so that\n // we automatically have it available as a baseline for comparison\n await this.copyPreviousBundle(context.config)\n }\n context.log('info', 'Generating model check bundle...')\n await this.genCurrentBundle(context, modelSpec)\n }\n\n // For production builds (and for the initial build in local development mode),\n // generate default test config in this post-build step\n if (this.options?.testConfigPath === undefined) {\n if (context.config.mode === 'production' || firstBuild) {\n // Test config was not provided, so generate a default config\n context.log('info', 'Generating model check test configuration...')\n await this.genTestConfig(context.config, 'build')\n }\n }\n\n if (context.config.mode === 'production') {\n // For production builds, run the model checks/comparisons, and then\n // inject the results into the generated report\n const testOptions = this.resolveTestOptions(context.config)\n return this.runChecks(context, testOptions)\n } else {\n // Nothing to do here in dev mode; the dev server will refresh and\n // re-run the tests in the browser when changes are detected\n return true\n }\n }\n\n private async copyPreviousBundle(config: ResolvedConfig): Promise<void> {\n // Only copy if the current bundle exists\n const currentBundleFile = joinPath(config.prepDir, 'check-bundle.js')\n if (existsSync(currentBundleFile)) {\n // TODO: Use the baselines directory from the config (not yet available)\n const baselinesDir = joinPath(config.rootDir, 'baselines')\n if (!existsSync(baselinesDir)) {\n await mkdir(baselinesDir, { recursive: true })\n }\n const previousBundleFile = joinPath(baselinesDir, 'previous.js')\n await copyFile(currentBundleFile, previousBundleFile)\n }\n }\n\n private async genCurrentBundle(context: BuildContext, modelSpec: ResolvedModelSpec): Promise<void> {\n const viteConfig = await createViteConfigForBundle(context, modelSpec)\n await build(viteConfig)\n }\n\n private async genTestConfig(config: ResolvedConfig, mode: 'build' | 'watch'): Promise<void> {\n const rootDir = config.rootDir\n const prepDir = config.prepDir\n const viteConfig = createViteConfigForTests(rootDir, prepDir, mode)\n await build(viteConfig)\n }\n\n private async runChecks(context: BuildContext, testOptions: TestOptions): Promise<boolean> {\n context.log('info', 'Running model checks...')\n\n // Load the bundles used by the model check/compare configuration. We\n // always initialize the \"current\" bundle.\n const moduleR = await import(relativeToSourcePath(testOptions.currentBundlePath))\n const bundleR = moduleR.createBundle() as Bundle\n const bundleNameR = testOptions.currentBundleName\n\n // Only initialize the \"baseline\" bundle if it is defined and the version\n // is the same as the \"current\" one. If the baseline bundle has a different\n // version, we will skip the comparison tests and only run the checks on the\n // current bundle.\n let bundleL: Bundle\n let bundleNameL: string\n if (this.options?.baseline) {\n const moduleL = await import(relativeToSourcePath(this.options.baseline.path))\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const rawBundleL: any = moduleL.createBundle() as any\n if (rawBundleL.version === bundleR.version) {\n bundleL = rawBundleL as Bundle\n bundleNameL = this.options.baseline.name\n }\n }\n\n // Get the model check/comparison configuration\n const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath))\n const configInitOptions: ConfigInitOptions = {\n bundleNameL,\n bundleNameR\n }\n const configOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, configInitOptions)\n\n // Run the suite of checks and comparisons\n const checkConfig = await createConfig(configOptions)\n const result = await runTestSuite(context, checkConfig, /*verbose=*/ false)\n\n // Build the report (using Vite)\n context.log('info', 'Building model check report')\n const viteConfig = this.createViteConfigForReport(context.config, testOptions, result.suiteSummary)\n await build(viteConfig)\n\n // context.log('info', 'Done!')\n\n return result.allChecksPassed\n }\n\n private resolveTestOptions(config: ResolvedConfig): TestOptions {\n let currentBundleName: string\n let currentBundlePath: string\n if (this.options?.current === undefined) {\n // Path to current bundle was not provided, so use a generated bundle\n currentBundleName = 'current'\n currentBundlePath = joinPath(config.prepDir, 'check-bundle.js')\n } else {\n // Use the provided bundle\n currentBundleName = this.options.current.name\n currentBundlePath = this.options.current.path\n }\n\n let testConfigPath: string\n if (this.options?.testConfigPath === undefined) {\n // Test config was not provided, so use a generated config\n testConfigPath = joinPath(config.prepDir, 'check-tests.js')\n } else {\n // Use the provided test config\n testConfigPath = this.options.testConfigPath\n }\n\n return {\n currentBundleName,\n currentBundlePath,\n testConfigPath\n }\n }\n\n private createViteConfigForReport(\n config: ResolvedConfig,\n testOptions: TestOptions,\n suiteSummary: SuiteSummary | undefined\n ): InlineConfig {\n return createViteConfigForReport(\n this.options,\n config.rootDir,\n config.prepDir,\n testOptions.currentBundleName,\n testOptions.currentBundlePath,\n testOptions.testConfigPath,\n suiteSummary\n )\n }\n}\n\n/**\n * Return a Unix-style path (e.g. '../../foo.js') that is relative to the directory of\n * the current source file. This can be used to construct a path that is safe for\n * dynamic import on either Unix or Windows.\n *\n * @param filePath The path to make relative.\n */\nfunction relativeToSourcePath(filePath: string): string {\n const srcDir = dirname(fileURLToPath(import.meta.url))\n const relPath = relative(srcDir, filePath)\n return relPath.replaceAll('\\\\', '/')\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport { performance } from 'perf_hooks'\n\nimport pico from 'picocolors'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type {\n Config,\n ComparisonReport,\n PerfReport,\n RunSuiteCallbacks,\n CheckReport,\n CheckStatus,\n ComparisonConfig,\n CheckTestReport,\n SuiteSummary\n} from '@sdeverywhere/check-core'\nimport {\n datasetMessage,\n predicateMessage,\n runSuite,\n scenarioMessage,\n suiteSummaryFromReport\n} from '@sdeverywhere/check-core'\n\nexport interface RunTestSuiteResult {\n allChecksPassed: boolean\n suiteSummary: SuiteSummary\n}\n\n/**\n * Runs the test suite.\n */\nexport async function runTestSuite(\n context: BuildContext,\n config: Config,\n verbose: boolean\n): Promise<RunTestSuiteResult> {\n return new Promise((resolve, reject) => {\n const t0 = performance.now()\n let lastPctByInc: number\n const callbacks: RunSuiteCallbacks = {\n onProgress: progress => {\n const pct = Math.round(progress * 100)\n const pctByInc = Math.floor(pct / 5) * 5\n if (lastPctByInc === undefined || pctByInc > lastPctByInc) {\n lastPctByInc = pctByInc\n context.log('info', `${pctByInc}%`)\n }\n },\n onComplete: report => {\n try {\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `\\nTest suite completed in ${elapsed}s`)\n\n // Print check summary to the console\n const allChecksPassed = printCheckSummary(context, report.checkReport, verbose)\n\n if (report.comparisonReport) {\n // Print the perf stats to the console\n printPerfStats(context, config.comparison, report.comparisonReport)\n }\n\n // Convert check and compare reports to terse form that only includes\n // failed/errored checks or comparisons with differences\n // TODO: The terse form was originally used when we had to write the\n // results to a JSON file and then read them back in when building\n // the report, but we no longer use that intermediate file, so there's\n // less reason to use the terse form (since it requires the web app\n // code to reconstruct the results). But for now, we will continue\n // to use the terse form, and later we can update the app code.\n const suiteSummary = suiteSummaryFromReport(report)\n\n resolve({\n allChecksPassed,\n suiteSummary\n })\n } catch (e) {\n reject(e)\n }\n },\n onError: error => {\n reject(error)\n }\n }\n runSuite(config, callbacks)\n })\n}\n\nfunction printCheckSummary(context: BuildContext, checkReport: CheckReport, verbose: boolean): boolean {\n function printResult(indent: number, status: CheckStatus, text: string): void {\n if (!verbose && status === 'passed' && indent > 1) {\n return\n }\n let statusChar: string\n switch (status) {\n case 'passed':\n statusChar = '✓'\n break\n case 'failed':\n statusChar = '✗'\n break\n case 'error':\n statusChar = '‼'\n break\n default:\n statusChar = ''\n break\n }\n const msg = `${' '.repeat(indent)}${statusChar} ${text}`\n context.log('info', status === 'passed' ? pico.green(msg) : pico.red(msg))\n }\n\n function bold(s: string): string {\n return pico.bold(s)\n }\n\n function printTest(test: CheckTestReport): void {\n const msg = `${test.name}${verbose || test.status !== 'passed' ? ':' : ''}`\n printResult(1, test.status, msg)\n }\n\n let allPassed = true\n context.log('info', '\\nCheck results:')\n for (const group of checkReport.groups) {\n context.log('info', `\\n${group.name}`)\n\n for (const test of group.tests) {\n if (test.status !== 'passed') {\n allPassed = false\n }\n printTest(test)\n\n for (const scenario of test.scenarios) {\n printResult(3, scenario.status, scenarioMessage(scenario, bold))\n\n for (const dataset of scenario.datasets) {\n printResult(5, dataset.status, datasetMessage(dataset, bold))\n\n for (const predicate of dataset.predicates) {\n printResult(7, predicate.result.status, predicateMessage(predicate, bold))\n }\n }\n }\n }\n }\n context.log('info', '')\n\n return allPassed\n}\n\nfunction stat(label: string, n: number): string {\n return `${label}=${n.toFixed(1)}ms`\n}\n\nfunction printPerfReportLine(context: BuildContext, perfReport: PerfReport): void {\n const avg = stat('avg', perfReport.avgTime)\n const min = stat('min', perfReport.minTime)\n const max = stat('max', perfReport.maxTime)\n context.log('info', ` ${avg} ${min} ${max}`)\n}\n\nfunction printPerfStats(context: BuildContext, comparisonConfig: ComparisonConfig, report: ComparisonReport): void {\n context.log('info', '\\nPerformance stats:')\n context.log('info', ` ${comparisonConfig.bundleL.name}:`)\n printPerfReportLine(context, report.perfReportL)\n context.log('info', ` ${comparisonConfig.bundleR.name}:`)\n printPerfReportLine(context, report.perfReportR)\n context.log('info', '')\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, statSync } from 'fs'\nimport { basename, dirname, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, ResolvedConfig, Plugin as VitePlugin } from 'vite'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nimport type { BuildContext, ResolvedModelSpec } from '@sdeverywhere/build'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\n/**\n * This is a virtual module plugin used to inject model-specific configuration\n * values into the generated worker bundle.\n *\n * This follows the \"Virtual Modules Convention\" described here:\n * https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention\n *\n * TODO: This could be simplified by using `vite-plugin-virtual` but that\n * doesn't seem to be working correctly in an ESM setting\n */\nfunction injectModelSpec(context: BuildContext, modelSpec: ResolvedModelSpec): VitePlugin {\n // Include the SDE variable ID with each spec\n const inputSpecs = []\n for (const modelInputSpec of modelSpec.inputs) {\n // Note that the `InputSpec` interface in the `@sdeverywhere/build` package\n // allows the default/min/max values to be undefined, which can be the case\n // if the user doesn't return full `InputSpec` instances in the `ModelSpec`.\n // We will log a warning and skip the input if these values are not defined.\n if (\n modelInputSpec.defaultValue === undefined ||\n modelInputSpec.minValue === undefined ||\n modelInputSpec.maxValue === undefined\n ) {\n let msg = ''\n msg += `WARNING: The {defaultValue,minValue,maxValue} properties are required by plugin-check, `\n msg += `but are undefined in the InputSpec for '${modelInputSpec.varName}'. `\n msg += `This input variable will be excluded from the model-check bundle until those properties `\n msg += `are defined.`\n console.warn(msg)\n continue\n }\n\n // Use the `inputId` if defined for the `InputSpec`, otherwise use `varId`. The\n // latter is less resilient if the variable is renamed between two versions of\n // the model, but will be sufficient for now. Note that `plugin-config` defines\n // a stable `inputId` for each row in the `inputs.csv`, and that is the most\n // common way to configure a `ModelSpec`, so it will be uncommon for `inputId`\n // to be undefined here.\n const varId = context.canonicalVarId(modelInputSpec.varName)\n const inputId = modelInputSpec.inputId || varId\n inputSpecs.push({\n inputId,\n varId,\n ...modelInputSpec\n })\n }\n const outputSpecs = modelSpec.outputs.map(o => {\n return {\n varId: context.canonicalVarId(o.varName),\n ...o\n }\n })\n\n function stagedFileSize(filename: string): number {\n const prepDir = context.config.prepDir\n const path = joinPath(prepDir, 'staged', 'model', filename)\n if (existsSync(path)) {\n return statSync(path).size\n } else {\n return 0\n }\n }\n\n // The size (in bytes) of the `generated-model.js` file\n // TODO: Ideally we would measure the size of the raw Wasm binary, but currently\n // we inline it as a base64 blob inside the JS file, so we take the size of the\n // whole JS file as the second best option\n const modelSizeInBytes = stagedFileSize('generated-model.js')\n\n // The size (in bytes) of the `static-data.ts` file\n // TODO: Ideally we would measure the size of the minified JS file here, or\n // at least ignore things like whitespace\n const dataSizeInBytes = stagedFileSize('static-data.ts')\n\n const moduleSrc = `\nexport const inputSpecs = ${JSON.stringify(inputSpecs)};\nexport const outputSpecs = ${JSON.stringify(outputSpecs)};\nexport const modelSizeInBytes = ${modelSizeInBytes};\nexport const dataSizeInBytes = ${dataSizeInBytes};\n`\n\n const virtualModuleId = 'virtual:model-spec'\n const resolvedVirtualModuleId = '\\0' + virtualModuleId\n\n return {\n name: 'vite-plugin-virtual-custom',\n resolveId(id: string) {\n if (id === virtualModuleId) {\n return resolvedVirtualModuleId\n }\n },\n load(id: string) {\n if (id === resolvedVirtualModuleId) {\n return moduleSrc\n }\n }\n }\n}\n\n/**\n * XXX: This overrides the built-in `vite:resolve` plugin so that we can intercept `resolveId`\n * calls for the threads package.\n */\nfunction overrideViteResolvePlugin(viteConfig: ResolvedConfig) {\n const resolvePlugin = viteConfig.plugins.find(p => p.name === 'vite:resolve')\n if (resolvePlugin === undefined) {\n throw new Error('Failed to locate the built-in vite:resolve plugin')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const originalResolveId = resolvePlugin.resolveId as any\n resolvePlugin.resolveId = async function resolveId(id, importer, options) {\n if (id.startsWith('./implementation') && importer.includes('threads/dist-esm')) {\n // XXX: The default resolver behavior will look at the `browser` mappings in\n // `threads/package.json` and try to resolve `implementation.js` to\n // `implementation.browser.js` because it thinks we're in a browser-only context.\n // We don't want that. Instead we want to keep the generic implementation from\n // threads that chooses between the Node and browser implementations at runtime.\n //\n // If we get here, importer will be something like:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/index.js\n // And id will be:\n // ./implementation\n // So resolve the ID to:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.js\n //\n // Or, importer will be:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.js\n // And id will be one of:\n // ./implementation.browser\n // ./implementation.node\n // ./implementation.worker_threads\n // So resolve the ID to:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.{...}.js\n const idFileName = id.replace('./', '')\n const importerFileName = basename(importer)\n const resolvedId = importer.replace(importerFileName, `${idFileName}.js`)\n return {\n id: resolvedId,\n moduleSideEffects: false\n }\n }\n\n // For all other cases, fall back on the default resolver\n return await originalResolveId.handler.call(this, id, importer, options)\n }\n}\n\nexport async function createViteConfigForBundle(\n context: BuildContext,\n modelSpec: ResolvedModelSpec\n): Promise<InlineConfig> {\n // Use `template-bundle` as the root directory for the bundle project\n const root = resolvePath(__dirname, '..', 'template-bundle')\n\n // Calculate output directory relative to the template root\n // TODO: For now we write it to `prepDir`; make this configurable?\n const prepDir = context.config.prepDir\n const outDir = relative(root, prepDir)\n\n // Use the model worker from the staged directory\n // TODO: Make this configurable?\n const modelWorkerPath = joinPath(prepDir, 'staged', 'model', 'worker.js?raw')\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO: Disable vite output by default?\n // logLevel: 'silent',\n\n // Configure path aliases\n resolve: {\n alias: [\n // Inject the configured model worker\n {\n find: '@_model_worker_',\n replacement: modelWorkerPath\n },\n\n // XXX: Prevent Vite from using the `browser` section of `threads/package.json`\n // since we want to force the use of the general module (under dist-esm) that chooses\n // the correct implementation (Web Worker vs worker_threads) at runtime. Currently\n // Vite's library mode is browser focused and generally chooses the right imports,\n // except in the case of the threads package where we want to use the generic\n // `implementation.js` that chooses between Web Worker and worker_threads at runtime.\n // Note that we could in theory set `resolve.browserField` to false, but that would\n // make Vite not use the browser field for all other packages, and there is not\n // currently a way to tell Vite to use the browser field on a case-by-case basis.\n // So for now we need this workaround here to make it resolve to `dist-esm`, and then\n // a second workaround in `overrideViteResolvePlugin` to prevent the resolver from\n // using the browser field when resolving the threads package.\n {\n find: 'threads',\n replacement: 'threads',\n customResolver: async function (source, importer, options) {\n // Note that we need to use `resolveId.call` here in order to provide the\n // right `this` context, which provides Rollup plugin functionality\n const customResolver = nodeResolve({ browser: false })\n const resolved = await customResolver.resolveId.call(this, source, importer, options)\n // Force the use of the `dist-esm` variant of the threads.js package\n if (source === 'threads/worker') {\n return resolved.id.replace('worker.mjs', 'dist-esm/worker/index.js')\n } else {\n return resolved.id.replace('index.mjs', 'dist-esm/index.js')\n }\n }\n }\n ]\n },\n\n plugins: [\n // Use a virtual module plugin to inject the model spec values\n injectModelSpec(context, modelSpec),\n\n // XXX: Install a wrapper around the built-in `vite:resolve` plugin so that we can\n // override the default resolver behavior that tries to resolve the `browser` section\n // of the `package.json` for the threads package.\n {\n name: 'vite-plugin-override-resolve',\n configResolved(viteConfig) {\n overrideViteResolvePlugin(viteConfig)\n }\n }\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n emptyOutDir: false,\n\n // Uncomment for debugging purposes\n // minify: false,\n\n lib: {\n entry: './src/index.ts',\n formats: ['es'],\n fileName: () => 'check-bundle.js'\n },\n\n rollupOptions: {\n // Don't transform Node imports used by threads.js\n external: ['events', 'os', 'path', 'url'],\n\n // XXX: Insert custom code at the top of the generated bundle that defines\n // the special `__non_webpack_require__` function that is used by threads.js\n // in its Node implementation. This import ensures that threads.js uses\n // the native `worker_threads` implementation when using the bundle in a\n // Node environment. When importing the bundle for use in the browser,\n // Vite will transform this import into an empty module due to the empty\n // polyfill that is configured in `vite-config-for-report.ts`.\n output: {\n banner: `\nimport * as worker_threads from 'worker_threads'\nlet __non_webpack_require__ = () => {\n return worker_threads;\n};\n`\n },\n\n onwarn: (warning, warn) => {\n // XXX: Suppress \"Use of eval is strongly discouraged\" warnings that are\n // triggered by use of the following pattern in threads.js:\n // eval(\"require\")(\"worker_threads\")\n // It would be nice to avoid use of `eval` there, but it's not critical for\n // our use case so we will suppress the warnings for now\n if (warning.code !== 'EVAL') {\n warn(warning)\n }\n }\n }\n }\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, mkdirSync } from 'fs'\nimport { dirname, relative, join as joinPath, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { Alias, InlineConfig, PluginOption } from 'vite'\nimport replace from '@rollup/plugin-replace'\n\nimport type { SuiteSummary } from '@sdeverywhere/check-core'\n\nimport type { CheckPluginOptions } from './options'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nexport function createViteConfigForReport(\n options: CheckPluginOptions | undefined,\n projDir: string,\n prepDir: string,\n currentBundleName: string,\n currentBundlePath: string,\n testConfigPath: string,\n suiteSummary: SuiteSummary | undefined\n): InlineConfig {\n // Use `template-report` as the root directory for the report project\n const root = resolvePath(__dirname, '..', 'template-report')\n\n // Make sure the `baselines` directory exists, otherwise Vite's dependency scanner\n // may report errors when processing the `import.meta.glob` call\n const baselinesDir = resolvePath(projDir, 'baselines')\n if (!existsSync(baselinesDir)) {\n mkdirSync(baselinesDir, { recursive: true })\n }\n\n // Include `baselines/*.js` files under the configured project root directory. This\n // glob path apparently must be a relative path (relative to the `template-report/src`\n // directory where the glob is used).\n const templateSrcDir = resolvePath(root, 'src')\n const relProjDir = relative(templateSrcDir, projDir)\n // XXX: The glob pattern must use forward slashes only, so on Windows we need to\n // convert backslashes to slashes\n const relProjDirPath = relProjDir.replaceAll('\\\\', '/')\n // TODO: Use baselinesDir from options\n const baselinesPath = `${relProjDirPath}/baselines/*.js`\n\n // Calculate output directory relative to the template root\n let reportPath: string\n if (options?.reportPath) {\n reportPath = options.reportPath\n } else {\n reportPath = joinPath(prepDir, 'check-report')\n }\n const outDir = relative(root, reportPath)\n\n // Convert the suite summary to JSON, which is what the app currently expects\n const suiteSummaryJson = suiteSummary ? JSON.stringify(suiteSummary) : ''\n\n const alias = (find: string, replacement: string) => {\n return {\n find,\n replacement\n } as Alias\n }\n\n // XXX: This provides custom handling for Node built-ins such as 'events' that are\n // referenced by the check bundle (specifically in the Node implementation of\n // threads.js). These are not actually used in the browser, so we just need\n // to provide no-op polyfills for these.\n const noopPolyfillAlias = (find: string) => {\n return {\n find,\n replacement: '/polyfills/noop-polyfills.ts'\n } as Alias\n }\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Use `.` as the base directory (instead of the default `/`); this controls\n // how the path to the js/css files are generated in `index.html`\n base: '',\n\n // Use a custom cache directory under `prepDir`, as otherwise Vite will use\n // `packages/plugin-check/template-report/node_modules/.vite`, and we want to\n // avoid generating files in `template-report` (which should be read-only)\n cacheDir: joinPath(prepDir, '.vite-check-report'),\n\n // Load static files from `static` (instead of the default `public`)\n // publicDir: 'static',\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO\n // logLevel: 'silent',\n\n optimizeDeps: {\n // Prevent Vite from examining other html files when scanning entrypoints\n // for dependency optimization\n entries: ['index.html'],\n\n // XXX: When plugin-check is installed via pnpm, the Vite dev server seems\n // to have no trouble resolving other dependencies using the optimizeDeps\n // mechanism. However, this fails when the package is installed via yarn\n // or npm (probably due to the fact that the `template-report` directory\n // is located under the top-level `node_modules` directory); in the browser,\n // there will be \"import not found\" errors for the packages referenced below.\n // As a terrible workaround, explicitly include the direct dependencies so\n // that Vite optimizes them; this works for pnpm, yarn, and npm. We should\n // find a less fragile solution.\n include: [\n // from check-core\n '@sdeverywhere/check-core > assert-never',\n '@sdeverywhere/check-core > ajv',\n '@sdeverywhere/check-core > neverthrow',\n '@sdeverywhere/check-core > yaml',\n // from check-ui-shell\n '@sdeverywhere/check-ui-shell > fontfaceobserver',\n '@sdeverywhere/check-ui-shell > copy-text-to-clipboard',\n '@sdeverywhere/check-ui-shell > chart.js'\n ],\n\n exclude: [\n // XXX: The threads.js implementation references `tiny-worker` as an optional\n // dependency, but it doesn't get used at runtime, so we can just exclude it\n // so that Vite doesn't complain in dev mode\n 'tiny-worker'\n\n // XXX: Similarly, chart.js treats `moment` as an optional dependency, but we\n // don't use it at runtime; we need to exclude it here, otherwise Vite will\n // complain about missing dependencies in dev mode\n // 'moment'\n ]\n },\n\n // Configure path aliases\n resolve: {\n alias: [\n // Use the configured \"baseline\" bundle if defined, otherwise use the \"empty\" bundle\n // (which will cause comparison tests to be skipped)\n alias('@_baseline_bundle_', options?.baseline ? options.baseline.path : '/src/empty-bundle.ts'),\n\n // Use the configured \"current\" bundle\n alias('@_current_bundle_', currentBundlePath),\n\n // Use the configured test config file\n alias('@_test_config_', testConfigPath),\n\n // Make the overlay use the `messages.html` file that is written to the prep directory\n alias('@_prep_', prepDir),\n\n // XXX: Include no-op polyfills for these modules that are used in the Node-specific\n // implementation of threads.js; this allows us to use one bundle that works in both\n // Node and browser environments\n noopPolyfillAlias('events'),\n noopPolyfillAlias('fs'),\n noopPolyfillAlias('os'),\n noopPolyfillAlias('path'),\n noopPolyfillAlias('url'),\n noopPolyfillAlias('worker_threads')\n ]\n },\n\n // Inject special values into the generated JS\n define: {\n // Inject the summary JSON into the build\n __SUITE_SUMMARY_JSON__: JSON.stringify(suiteSummaryJson),\n\n // Inject the baseline branch name\n __BASELINE_NAME__: JSON.stringify(options?.baseline?.name || ''),\n\n // Inject the current branch name\n __CURRENT_NAME__: JSON.stringify(currentBundleName)\n },\n\n plugins: [\n // Inject special values into the generated JS\n // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's\n // built-in `define` feature because the latter does not seem to run before\n // the glob handler (which requires the glob to be injected as a literal)\n replace({\n preventAssignment: true,\n delimiters: ['', ''],\n values: {\n // Inject the path for baseline bundles\n // XXX: Note that we use './baselines/*.txt' instead of something special\n // like './__BASELINE_BUNDLES_PATH__' because sometimes Vite's dependency\n // scanner sees the latter (instead of the injected path) and reports\n // an error since the path does not exist. As a workaround, we use\n // './baselines/*.txt', which gets interpreted as the valid path\n // '.../template-report/src/baselines/*.txt' (see `baselines/unused.txt`).\n './baselines/*.txt': baselinesPath\n }\n }) as unknown as PluginOption\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n\n // Write js/css files to `public` (instead of the default `<outDir>/assets`)\n assetsDir: '',\n\n rollupOptions: {\n output: {\n // XXX: Prevent vite from creating a separate `vendor.js` file\n manualChunks: undefined\n },\n\n onwarn: (warning, warn) => {\n // XXX: Suppress \"Use of eval is strongly discouraged\" warnings that are\n // triggered by use of the following pattern in threads.js:\n // eval(\"require\")(\"worker_threads\")\n // It would be nice to avoid use of `eval` there, but it's not critical for\n // our use case so we will suppress the warnings for now\n if (warning.code !== 'EVAL') {\n warn(warning)\n }\n }\n }\n },\n\n server: {\n // Run the dev server at `localhost:8081` by default\n port: options?.serverPort || 8081,\n\n // Open the app in the browser by default\n open: '/index.html',\n\n // XXX: Add a small delay, otherwise on macOS we sometimes get multiple\n // change events when a file is saved just once. That is a relatively\n // harmless issue except that it causes redundant messages in the console\n // and can cause extra churn when refreshing the app.\n watch: {\n awaitWriteFinish: {\n stabilityThreshold: 100\n }\n }\n }\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { dirname, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, PluginOption } from 'vite'\nimport replace from '@rollup/plugin-replace'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nexport function createViteConfigForTests(projDir: string, prepDir: string, mode: 'build' | 'watch'): InlineConfig {\n // Use `template-tests` as the root directory for the tests project\n const root = resolvePath(__dirname, '..', 'template-tests')\n\n // Get the base glob path; apparently this must be a relative path (relative to\n // the `template-tests/src` directory where the glob is used)\n const templateSrcDir = resolvePath(root, 'src')\n const relProjDir = relative(templateSrcDir, projDir)\n // XXX: The glob pattern must use forward slashes only, so on Windows we need to\n // convert backslashes to slashes\n const relProjDirPath = relProjDir.replaceAll('\\\\', '/')\n\n // Include check test definitions in files matching `checks/*.yaml` under\n // the configured project root directory. We also include `*.check.yaml`,\n // which was the naming used in earlier versions of the create package and\n // related examples.\n // TODO: Use yaml path/pattern from options\n const yamlCheckGlobPatterns = `['${relProjDirPath}/**/checks/*.yaml', '${relProjDirPath}/**/*.check.yaml']`\n\n // Include comparison test definitions in files matching `comparisons/*.yaml`\n // under the configured project root directory\n // TODO: Use yaml path/pattern from options\n const yamlComparisonGlobPatterns = `['${relProjDirPath}/**/comparisons/*.yaml']`\n\n // Calculate output directory relative to the template root\n // TODO: For now we write it to `prepDir`; make this configurable?\n const outDir = relative(root, prepDir)\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO: Disable vite output by default?\n // logLevel: 'silent',\n\n plugins: [\n // Inject special values into the generated JS\n // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's\n // built-in `define` feature because the latter does not seem to run before\n // the glob handler (which requires the glob to be injected as a literal)\n replace({\n preventAssignment: true,\n delimiters: ['', ''],\n values: {\n // Inject the glob patterns for matching model check yaml files\n '\"./__YAML_CHECK_GLOB_PATTERNS__\"': yamlCheckGlobPatterns,\n // Inject the glob patterns for matching model comparison yaml files\n '\"./__YAML_COMPARISON_GLOB_PATTERNS__\"': yamlComparisonGlobPatterns\n }\n }) as unknown as PluginOption\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n emptyOutDir: false,\n\n lib: {\n entry: './src/index.ts',\n formats: ['es'],\n fileName: () => 'check-tests.js'\n },\n\n // Enable watch mode if requested\n watch: mode === 'watch' && {},\n\n rollupOptions: {\n // Prevent dependencies from being included in packaged library\n // TODO: For now we include check-core in the packaged library so that its\n // dependencies are correctly resolved at runtime. Ideally this would only\n // include a couple functions that are used for defining tests, but Vite 2.x\n // does not implement tree shaking for ES libraries, which means the generated\n // library is much larger than it needs to be. Once we upgrade to Vite 3.x,\n // the generated library should be smaller; see related fix:\n // https://github.com/vitejs/vite/pull/8737\n // external: Object.keys(pkg.dependencies)\n }\n }\n }\n}\n"],"mappings":"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","names":["import_fs","import_path","import_url","import_check_core","pico","__filename","joinPath","resolvePath","import_fs","import_path","import_url","__filename","__dirname","resolvePath","joinPath","replace","import_path","import_url","import_plugin_replace","__filename","__dirname","resolvePath","replace","chokidar","joinPath"]}
1
+ {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/tsup@8.2.4_postcss@8.5.6_typescript@5.2.2/node_modules/tsup/assets/cjs_shims.js","../src/plugin.ts","../src/run-suite.ts","../src/vite-config-for-bundle.ts","../src/vite-config-for-report.ts","../src/vite-config-for-tests.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport type { CheckBundle, CheckPluginOptions } from './options'\nexport { checkPlugin } from './plugin'\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { copyFile, mkdir } from 'fs/promises'\nimport { dirname, join as joinPath, relative } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, ViteDevServer } from 'vite'\nimport { build, createServer } from 'vite'\n\nimport chokidar from 'chokidar'\n\nimport type { BuildContext, Plugin, ResolvedConfig, ResolvedModelSpec } from '@sdeverywhere/build'\n\nimport type { Bundle, ConfigInitOptions, SuiteSummary } from '@sdeverywhere/check-core'\nimport { createConfig } from '@sdeverywhere/check-core'\n\nimport type { CheckPluginOptions } from './options'\nimport { runTestSuite } from './run-suite'\nimport { createViteConfigForBundle } from './vite-config-for-bundle'\nimport { createViteConfigForReport } from './vite-config-for-report'\nimport { createViteConfigForTests } from './vite-config-for-tests'\n\nexport function checkPlugin(options?: CheckPluginOptions): Plugin {\n return new CheckPlugin(options)\n}\n\ninterface TestOptions {\n currentBundleName: string\n currentBundlePath: string\n testConfigPath: string\n}\n\nclass CheckPlugin implements Plugin {\n private firstBuild = true\n\n constructor(private readonly options?: CheckPluginOptions) {}\n\n async watch(config: ResolvedConfig): Promise<void> {\n if (this.options?.testConfigPath === undefined) {\n // Test config was not provided, so generate a default config in watch mode.\n // The test template uses import.meta.glob so that checks are re-run\n // automatically when the `{checks/comparisons}/*.yaml` files are changed.\n await this.genTestConfig(config, 'watch')\n }\n\n // For development mode, run Vite in dev mode so that it serves the\n // model-check report locally (with live reload enabled). When a model\n // test file is changed, the tests will be re-run in the browser.\n const testOptions = this.resolveTestOptions(config)\n const viteConfig = this.createViteConfigForReport(config, testOptions, undefined)\n const server: ViteDevServer = await createServer(viteConfig)\n await server.listen()\n\n // XXX: Currently Vite doesn't reload the page if a file is added/removed\n // in the baselines directory (Vite's import.meta.glob handling doesn't\n // seem to do this automatically), so as a workaround, watch the baselines\n // directory and restart the server if files are added/removed\n // TODO: The same problem also applies to the glob for `checks/*.yaml` and\n // `comparisons/*.yaml` files in the test config, so we should also reload\n // if files match/unmatch those glob patterns\n // TODO: Use the baselines directory from the config\n const baselinesDir = 'baselines'\n const watcher = chokidar.watch(baselinesDir, {\n // Watch paths are resolved relative to the project root directory\n cwd: config.rootDir,\n // Don't send initial \"file added\" events\n ignoreInitial: true,\n // XXX: Include a delay, otherwise on macOS we sometimes get multiple\n // change events when a file is saved just once\n awaitWriteFinish: {\n stabilityThreshold: 200\n }\n })\n watcher.on('add', () => server.restart())\n watcher.on('unlink', () => server.restart())\n }\n\n // TODO: Note that this plugin runs as a `postBuild` step because it currently\n // needs to run after other plugins, and those plugins need to run after the\n // staged files are copied to their final destination(s). We should probably\n // make it configurable so that it can either be run as a `postGenerate` or a\n // `postBuild` step.\n async postBuild(context: BuildContext, modelSpec: ResolvedModelSpec): Promise<boolean> {\n const firstBuild = this.firstBuild\n this.firstBuild = false\n\n // For both production builds and local development, generate default bundle\n // in this post-build step each time a source file is changed\n // TODO: We could potentially use watch mode for the bundle similar to\n // what we do for the test config, but the bundle depends on the ModelSpec,\n // which currently isn't made available to the `watch` function\n if (this.options?.current === undefined) {\n // Path to current bundle was not provided, so generate a default bundle\n if (context.config.mode === 'development') {\n // Copy the previous bundle to the `baselines` directory so that\n // we automatically have it available as a baseline for comparison\n await this.copyPreviousBundle(context.config)\n }\n context.log('info', 'Generating model check bundle...')\n await this.genCurrentBundle(context, modelSpec)\n }\n\n // For production builds (and for the initial build in local development mode),\n // generate default test config in this post-build step\n if (this.options?.testConfigPath === undefined) {\n if (context.config.mode === 'production' || firstBuild) {\n // Test config was not provided, so generate a default config\n context.log('info', 'Generating model check test configuration...')\n await this.genTestConfig(context.config, 'build')\n }\n }\n\n if (context.config.mode === 'production') {\n // For production builds, run the model checks/comparisons, and then\n // inject the results into the generated report\n const testOptions = this.resolveTestOptions(context.config)\n return this.runChecks(context, testOptions)\n } else {\n // Nothing to do here in dev mode; the dev server will refresh and\n // re-run the tests in the browser when changes are detected\n return true\n }\n }\n\n private async copyPreviousBundle(config: ResolvedConfig): Promise<void> {\n // Only copy if the current bundle exists\n const currentBundleFile = joinPath(config.prepDir, 'check-bundle.js')\n if (existsSync(currentBundleFile)) {\n // TODO: Use the baselines directory from the config (not yet available)\n const baselinesDir = joinPath(config.rootDir, 'baselines')\n if (!existsSync(baselinesDir)) {\n await mkdir(baselinesDir, { recursive: true })\n }\n const previousBundleFile = joinPath(baselinesDir, 'previous.js')\n await copyFile(currentBundleFile, previousBundleFile)\n }\n }\n\n private async genCurrentBundle(context: BuildContext, modelSpec: ResolvedModelSpec): Promise<void> {\n const viteConfig = await createViteConfigForBundle(context, modelSpec)\n await build(viteConfig)\n }\n\n private async genTestConfig(config: ResolvedConfig, mode: 'build' | 'watch'): Promise<void> {\n const rootDir = config.rootDir\n const prepDir = config.prepDir\n const viteConfig = createViteConfigForTests(rootDir, prepDir, mode)\n await build(viteConfig)\n }\n\n private async runChecks(context: BuildContext, testOptions: TestOptions): Promise<boolean> {\n context.log('info', 'Running model checks...')\n\n // Load the bundles used by the model check/compare configuration. We\n // always initialize the \"current\" bundle.\n const moduleR = await import(relativeToSourcePath(testOptions.currentBundlePath))\n const bundleR = moduleR.createBundle() as Bundle\n const bundleNameR = testOptions.currentBundleName\n\n // Only initialize the \"baseline\" bundle if it is defined and the version\n // is the same as the \"current\" one. If the baseline bundle has a different\n // version, we will skip the comparison tests and only run the checks on the\n // current bundle.\n let bundleL: Bundle\n let bundleNameL: string\n if (this.options?.baseline) {\n const moduleL = await import(relativeToSourcePath(this.options.baseline.path))\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const rawBundleL: any = moduleL.createBundle() as any\n if (rawBundleL.version === bundleR.version) {\n bundleL = rawBundleL as Bundle\n bundleNameL = this.options.baseline.name\n }\n }\n\n // Get the model check/comparison configuration\n const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath))\n const configInitOptions: ConfigInitOptions = {\n bundleNameL,\n bundleNameR\n }\n const configOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, configInitOptions)\n\n // Run the suite of checks and comparisons\n const checkConfig = await createConfig(configOptions)\n const result = await runTestSuite(context, checkConfig, /*verbose=*/ false)\n\n // Build the report (using Vite)\n context.log('info', 'Building model check report')\n const viteConfig = this.createViteConfigForReport(context.config, testOptions, result.suiteSummary)\n await build(viteConfig)\n\n // context.log('info', 'Done!')\n\n return result.allChecksPassed\n }\n\n private resolveTestOptions(config: ResolvedConfig): TestOptions {\n let currentBundleName: string\n let currentBundlePath: string\n if (this.options?.current === undefined) {\n // Path to current bundle was not provided, so use a generated bundle\n currentBundleName = 'current'\n currentBundlePath = joinPath(config.prepDir, 'check-bundle.js')\n } else {\n // Use the provided bundle\n currentBundleName = this.options.current.name\n currentBundlePath = this.options.current.path\n }\n\n let testConfigPath: string\n if (this.options?.testConfigPath === undefined) {\n // Test config was not provided, so use a generated config\n testConfigPath = joinPath(config.prepDir, 'check-tests.js')\n } else {\n // Use the provided test config\n testConfigPath = this.options.testConfigPath\n }\n\n return {\n currentBundleName,\n currentBundlePath,\n testConfigPath\n }\n }\n\n private createViteConfigForReport(\n config: ResolvedConfig,\n testOptions: TestOptions,\n suiteSummary: SuiteSummary | undefined\n ): InlineConfig {\n return createViteConfigForReport(\n this.options,\n config.rootDir,\n config.prepDir,\n testOptions.currentBundleName,\n testOptions.currentBundlePath,\n testOptions.testConfigPath,\n suiteSummary\n )\n }\n}\n\n/**\n * Return a Unix-style path (e.g. '../../foo.js') that is relative to the directory of\n * the current source file. This can be used to construct a path that is safe for\n * dynamic import on either Unix or Windows.\n *\n * @param filePath The path to make relative.\n */\nfunction relativeToSourcePath(filePath: string): string {\n const srcDir = dirname(fileURLToPath(import.meta.url))\n const relPath = relative(srcDir, filePath)\n return relPath.replaceAll('\\\\', '/')\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport { performance } from 'perf_hooks'\n\nimport pico from 'picocolors'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type {\n Config,\n ComparisonReport,\n PerfReport,\n RunSuiteCallbacks,\n CheckReport,\n CheckStatus,\n ComparisonConfig,\n CheckTestReport,\n SuiteSummary\n} from '@sdeverywhere/check-core'\nimport {\n datasetMessage,\n predicateMessage,\n runSuite,\n scenarioMessage,\n suiteSummaryFromReport\n} from '@sdeverywhere/check-core'\n\nexport interface RunTestSuiteResult {\n allChecksPassed: boolean\n suiteSummary: SuiteSummary\n}\n\n/**\n * Runs the test suite.\n */\nexport async function runTestSuite(\n context: BuildContext,\n config: Config,\n verbose: boolean\n): Promise<RunTestSuiteResult> {\n return new Promise((resolve, reject) => {\n const t0 = performance.now()\n let lastPctByInc: number\n const callbacks: RunSuiteCallbacks = {\n onProgress: progress => {\n const pct = Math.round(progress * 100)\n const pctByInc = Math.floor(pct / 5) * 5\n if (lastPctByInc === undefined || pctByInc > lastPctByInc) {\n lastPctByInc = pctByInc\n context.log('info', `${pctByInc}%`)\n }\n },\n onComplete: report => {\n try {\n const t1 = performance.now()\n const elapsedMillis = t1 - t0\n const elapsedSeconds = (elapsedMillis / 1000).toFixed(1)\n context.log('info', `\\nTest suite completed in ${elapsedSeconds}s`)\n\n // Print check summary to the console\n const allChecksPassed = printCheckSummary(context, report.checkReport, verbose)\n\n if (report.comparisonReport) {\n // Print the perf stats to the console\n printPerfStats(context, config.comparison, report.comparisonReport)\n }\n\n // Convert check and compare reports to terse form that only includes\n // failed/errored checks or comparisons with differences\n // TODO: The terse form was originally used when we had to write the\n // results to a JSON file and then read them back in when building\n // the report, but we no longer use that intermediate file, so there's\n // less reason to use the terse form (since it requires the web app\n // code to reconstruct the results). But for now, we will continue\n // to use the terse form, and later we can update the app code.\n const suiteSummary = suiteSummaryFromReport(report, elapsedMillis)\n\n resolve({\n allChecksPassed,\n suiteSummary\n })\n } catch (e) {\n reject(e)\n }\n },\n onError: error => {\n reject(error)\n }\n }\n runSuite(config, callbacks)\n })\n}\n\nfunction printCheckSummary(context: BuildContext, checkReport: CheckReport, verbose: boolean): boolean {\n function printResult(indent: number, status: CheckStatus, text: string): void {\n if (!verbose && status === 'passed' && indent > 1) {\n return\n }\n let statusChar: string\n switch (status) {\n case 'passed':\n statusChar = '✓'\n break\n case 'failed':\n statusChar = '✗'\n break\n case 'error':\n statusChar = '‼'\n break\n case 'skipped':\n statusChar = '–'\n break\n default:\n statusChar = ''\n break\n }\n const msg = `${' '.repeat(indent)}${statusChar} ${text}`\n context.log('info', status === 'passed' ? pico.green(msg) : pico.red(msg))\n }\n\n function bold(s: string): string {\n return pico.bold(s)\n }\n\n function printTest(test: CheckTestReport): void {\n const msg = `${test.name}${verbose || test.status !== 'passed' ? ':' : ''}`\n printResult(1, test.status, msg)\n }\n\n let allPassed = true\n context.log('info', '\\nCheck results:')\n for (const group of checkReport.groups) {\n context.log('info', `\\n${group.name}`)\n\n for (const test of group.tests) {\n if (test.status !== 'passed') {\n allPassed = false\n }\n printTest(test)\n\n for (const scenario of test.scenarios) {\n printResult(3, scenario.status, scenarioMessage(scenario, bold))\n\n for (const dataset of scenario.datasets) {\n printResult(5, dataset.status, datasetMessage(dataset, bold))\n\n for (const predicate of dataset.predicates) {\n printResult(7, predicate.result.status, predicateMessage(predicate, bold))\n }\n }\n }\n }\n }\n context.log('info', '')\n\n return allPassed\n}\n\nfunction stat(label: string, n: number): string {\n return `${label}=${n.toFixed(1)}ms`\n}\n\nfunction printPerfReportLine(context: BuildContext, perfReport: PerfReport): void {\n const avg = stat('avg', perfReport.avgTime)\n const min = stat('min', perfReport.minTime)\n const max = stat('max', perfReport.maxTime)\n context.log('info', ` ${avg} ${min} ${max}`)\n}\n\nfunction printPerfStats(context: BuildContext, comparisonConfig: ComparisonConfig, report: ComparisonReport): void {\n context.log('info', '\\nPerformance stats:')\n context.log('info', ` ${comparisonConfig.bundleL.name}:`)\n printPerfReportLine(context, report.perfReportL)\n context.log('info', ` ${comparisonConfig.bundleR.name}:`)\n printPerfReportLine(context, report.perfReportR)\n context.log('info', '')\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, readFileSync, statSync } from 'fs'\nimport { basename, dirname, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, ResolvedConfig, Plugin as VitePlugin } from 'vite'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nimport type { BuildContext, ResolvedModelSpec } from '@sdeverywhere/build'\nimport { encodeImplVars } from '@sdeverywhere/check-core'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\n/**\n * This is a virtual module plugin used to inject model-specific configuration\n * values into the generated worker bundle.\n *\n * This follows the \"Virtual Modules Convention\" described here:\n * https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention\n *\n * TODO: This could be simplified by using `vite-plugin-virtual` but that\n * doesn't seem to be working correctly in an ESM setting\n */\nfunction injectModelSpec(context: BuildContext, modelSpec: ResolvedModelSpec): VitePlugin {\n const prepDir = context.config.prepDir\n\n // Include the SDE variable ID with each spec\n const inputSpecs = []\n for (const modelInputSpec of modelSpec.inputs) {\n // Note that the `InputSpec` interface in the `@sdeverywhere/build` package\n // allows the default/min/max values to be undefined, which can be the case\n // if the user doesn't return full `InputSpec` instances in the `ModelSpec`.\n // We will log a warning and skip the input if these values are not defined.\n if (\n modelInputSpec.defaultValue === undefined ||\n modelInputSpec.minValue === undefined ||\n modelInputSpec.maxValue === undefined\n ) {\n let msg = ''\n msg += `WARNING: The {defaultValue,minValue,maxValue} properties are required by plugin-check, `\n msg += `but are undefined in the InputSpec for '${modelInputSpec.varName}'. `\n msg += `This input variable will be excluded from the model-check bundle until those properties `\n msg += `are defined.`\n console.warn(msg)\n continue\n }\n\n // Use the `inputId` if defined for the `InputSpec`, otherwise use `varId`. The\n // latter is less resilient if the variable is renamed between two versions of\n // the model, but will be sufficient for now. Note that `plugin-config` defines\n // a stable `inputId` for each row in the `inputs.csv`, and that is the most\n // common way to configure a `ModelSpec`, so it will be uncommon for `inputId`\n // to be undefined here.\n const varId = context.canonicalVarId(modelInputSpec.varName)\n const inputId = modelInputSpec.inputId || varId\n inputSpecs.push({\n inputId,\n varId,\n ...modelInputSpec\n })\n }\n\n // Include the SDE variable ID with each output variable spec\n const outputSpecs = modelSpec.outputs.map(o => {\n return {\n varId: context.canonicalVarId(o.varName),\n ...o\n }\n })\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function readJsonListing(): any {\n const path = joinPath(prepDir, 'build', 'processed.json')\n if (existsSync(path)) {\n const json = readFileSync(path, 'utf8')\n return JSON.parse(json)\n } else {\n return {}\n }\n }\n\n // Read the JSON model listing\n const listing = readJsonListing()\n\n // Extract the `varInstances` object from the model listing\n const varInstances = listing.varInstances || {}\n\n // Encode the `varInstances` object into a more efficient format to reduce the bundle size\n const encodedImplVars = encodeImplVars(varInstances)\n\n function stagedFileSize(filename: string): number {\n const path = joinPath(prepDir, 'staged', 'model', filename)\n if (existsSync(path)) {\n return statSync(path).size\n } else {\n return 0\n }\n }\n\n // The size (in bytes) of the `generated-model.js` file\n // TODO: Ideally we would measure the size of the raw Wasm binary, but currently\n // we inline it as a base64 blob inside the JS file, so we take the size of the\n // whole JS file as the second best option\n const modelSizeInBytes = stagedFileSize('generated-model.js')\n\n // The size (in bytes) of the `static-data.ts` file\n // TODO: Ideally we would measure the size of the minified JS file here, or\n // at least ignore things like whitespace\n const dataSizeInBytes = stagedFileSize('static-data.ts')\n\n const moduleSrc = `\nexport const inputSpecs = ${JSON.stringify(inputSpecs)};\nexport const outputSpecs = ${JSON.stringify(outputSpecs)};\nexport const encodedImplVars = ${JSON.stringify(encodedImplVars)};\nexport const modelSizeInBytes = ${modelSizeInBytes};\nexport const dataSizeInBytes = ${dataSizeInBytes};\n`\n\n const virtualModuleId = 'virtual:model-spec'\n const resolvedVirtualModuleId = '\\0' + virtualModuleId\n\n return {\n name: 'vite-plugin-virtual-custom',\n resolveId(id: string) {\n if (id === virtualModuleId) {\n return resolvedVirtualModuleId\n }\n },\n load(id: string) {\n if (id === resolvedVirtualModuleId) {\n return moduleSrc\n }\n }\n }\n}\n\n/**\n * XXX: This overrides the built-in `vite:resolve` plugin so that we can intercept `resolveId`\n * calls for the threads package.\n */\nfunction overrideViteResolvePlugin(viteConfig: ResolvedConfig) {\n const resolvePlugin = viteConfig.plugins.find(p => p.name === 'vite:resolve')\n if (resolvePlugin === undefined) {\n throw new Error('Failed to locate the built-in vite:resolve plugin')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const originalResolveId = resolvePlugin.resolveId as any\n resolvePlugin.resolveId = async function resolveId(id, importer, options) {\n if (id.startsWith('./implementation') && importer.includes('threads/dist-esm')) {\n // XXX: The default resolver behavior will look at the `browser` mappings in\n // `threads/package.json` and try to resolve `implementation.js` to\n // `implementation.browser.js` because it thinks we're in a browser-only context.\n // We don't want that. Instead we want to keep the generic implementation from\n // threads that chooses between the Node and browser implementations at runtime.\n //\n // If we get here, importer will be something like:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/index.js\n // And id will be:\n // ./implementation\n // So resolve the ID to:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.js\n //\n // Or, importer will be:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.js\n // And id will be one of:\n // ./implementation.browser\n // ./implementation.node\n // ./implementation.worker_threads\n // So resolve the ID to:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.{...}.js\n const idFileName = id.replace('./', '')\n const importerFileName = basename(importer)\n const resolvedId = importer.replace(importerFileName, `${idFileName}.js`)\n return {\n id: resolvedId,\n moduleSideEffects: false\n }\n }\n\n // For all other cases, fall back on the default resolver\n return await originalResolveId.handler.call(this, id, importer, options)\n }\n}\n\nexport async function createViteConfigForBundle(\n context: BuildContext,\n modelSpec: ResolvedModelSpec\n): Promise<InlineConfig> {\n // Use `template-bundle` as the root directory for the bundle project\n const root = resolvePath(__dirname, '..', 'template-bundle')\n\n // Calculate output directory relative to the template root\n // TODO: For now we write it to `prepDir`; make this configurable?\n const prepDir = context.config.prepDir\n const outDir = relative(root, prepDir)\n\n // Use the model worker from the staged directory\n // TODO: Make this configurable?\n const modelWorkerPath = joinPath(prepDir, 'staged', 'model', 'worker.js?raw')\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO: Disable vite output by default?\n // logLevel: 'silent',\n\n // Configure path aliases\n resolve: {\n alias: [\n // Inject the configured model worker\n {\n find: '@_model_worker_',\n replacement: modelWorkerPath\n },\n\n // XXX: Prevent Vite from using the `browser` section of `threads/package.json`\n // since we want to force the use of the general module (under dist-esm) that chooses\n // the correct implementation (Web Worker vs worker_threads) at runtime. Currently\n // Vite's library mode is browser focused and generally chooses the right imports,\n // except in the case of the threads package where we want to use the generic\n // `implementation.js` that chooses between Web Worker and worker_threads at runtime.\n // Note that we could in theory set `resolve.browserField` to false, but that would\n // make Vite not use the browser field for all other packages, and there is not\n // currently a way to tell Vite to use the browser field on a case-by-case basis.\n // So for now we need this workaround here to make it resolve to `dist-esm`, and then\n // a second workaround in `overrideViteResolvePlugin` to prevent the resolver from\n // using the browser field when resolving the threads package.\n {\n find: 'threads',\n replacement: 'threads',\n customResolver: async function (source, importer, options) {\n // Note that we need to use `resolveId.call` here in order to provide the\n // right `this` context, which provides Rollup plugin functionality\n const customResolver = nodeResolve({ browser: false })\n const resolved = await customResolver.resolveId.call(this, source, importer, options)\n // Force the use of the `dist-esm` variant of the threads.js package\n if (source === 'threads/worker') {\n return resolved.id.replace('worker.mjs', 'dist-esm/worker/index.js')\n } else {\n return resolved.id.replace('index.mjs', 'dist-esm/index.js')\n }\n }\n }\n ]\n },\n\n plugins: [\n // Use a virtual module plugin to inject the model spec values\n injectModelSpec(context, modelSpec),\n\n // XXX: Install a wrapper around the built-in `vite:resolve` plugin so that we can\n // override the default resolver behavior that tries to resolve the `browser` section\n // of the `package.json` for the threads package.\n {\n name: 'vite-plugin-override-resolve',\n configResolved(viteConfig) {\n overrideViteResolvePlugin(viteConfig)\n }\n }\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n emptyOutDir: false,\n\n // Uncomment for debugging purposes\n // minify: false,\n\n lib: {\n entry: './src/index.ts',\n formats: ['es'],\n fileName: () => 'check-bundle.js'\n },\n\n rollupOptions: {\n // Don't transform Node imports used by threads.js\n external: ['events', 'os', 'path', 'url'],\n\n // XXX: Insert custom code at the top of the generated bundle that defines\n // the special `__non_webpack_require__` function that is used by threads.js\n // in its Node implementation. This import ensures that threads.js uses\n // the native `worker_threads` implementation when using the bundle in a\n // Node environment. When importing the bundle for use in the browser,\n // Vite will transform this import into an empty module due to the empty\n // polyfill that is configured in `vite-config-for-report.ts`.\n output: {\n banner: `\nimport * as worker_threads from 'worker_threads'\nlet __non_webpack_require__ = () => {\n return worker_threads;\n};\n`\n },\n\n onwarn: (warning, warn) => {\n // XXX: Suppress \"Use of eval is strongly discouraged\" warnings that are\n // triggered by use of the following pattern in threads.js:\n // eval(\"require\")(\"worker_threads\")\n // It would be nice to avoid use of `eval` there, but it's not critical for\n // our use case so we will suppress the warnings for now\n if (warning.code !== 'EVAL') {\n warn(warning)\n }\n }\n }\n }\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, mkdirSync } from 'fs'\nimport { dirname, relative, join as joinPath, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { Alias, InlineConfig, PluginOption } from 'vite'\nimport replace from '@rollup/plugin-replace'\n\nimport type { SuiteSummary } from '@sdeverywhere/check-core'\n\nimport type { CheckPluginOptions } from './options'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nexport function createViteConfigForReport(\n options: CheckPluginOptions | undefined,\n projDir: string,\n prepDir: string,\n currentBundleName: string,\n currentBundlePath: string,\n testConfigPath: string,\n suiteSummary: SuiteSummary | undefined\n): InlineConfig {\n // Use `template-report` as the root directory for the report project\n const root = resolvePath(__dirname, '..', 'template-report')\n\n // Make sure the `baselines` directory exists, otherwise Vite's dependency scanner\n // may report errors when processing the `import.meta.glob` call\n const baselinesDir = resolvePath(projDir, 'baselines')\n if (!existsSync(baselinesDir)) {\n mkdirSync(baselinesDir, { recursive: true })\n }\n\n // Include `baselines/*.js` files under the configured project root directory. This\n // glob path apparently must be a relative path (relative to the `template-report/src`\n // directory where the glob is used).\n const templateSrcDir = resolvePath(root, 'src')\n const relProjDir = relative(templateSrcDir, projDir)\n // XXX: The glob pattern must use forward slashes only, so on Windows we need to\n // convert backslashes to slashes\n const relProjDirPath = relProjDir.replaceAll('\\\\', '/')\n // TODO: Use baselinesDir from options\n const baselinesPath = `${relProjDirPath}/baselines/*.js`\n\n // Calculate output directory relative to the template root\n let reportPath: string\n if (options?.reportPath) {\n reportPath = options.reportPath\n } else {\n reportPath = joinPath(prepDir, 'check-report')\n }\n const outDir = relative(root, reportPath)\n\n // Convert the suite summary to JSON, which is what the app currently expects\n const suiteSummaryJson = suiteSummary ? JSON.stringify(suiteSummary) : ''\n\n const alias = (find: string, replacement: string) => {\n return {\n find,\n replacement\n } as Alias\n }\n\n // XXX: This provides custom handling for Node built-ins such as 'events' that are\n // referenced by the check bundle (specifically in the Node implementation of\n // threads.js). These are not actually used in the browser, so we just need\n // to provide no-op polyfills for these.\n const noopPolyfillAlias = (find: string) => {\n return {\n find,\n replacement: '/polyfills/noop-polyfills.ts'\n } as Alias\n }\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Use `.` as the base directory (instead of the default `/`); this controls\n // how the path to the js/css files are generated in `index.html`\n base: '',\n\n // Use a custom cache directory under `prepDir`, as otherwise Vite will use\n // `packages/plugin-check/template-report/node_modules/.vite`, and we want to\n // avoid generating files in `template-report` (which should be read-only)\n cacheDir: joinPath(prepDir, '.vite-check-report'),\n\n // Load static files from `static` (instead of the default `public`)\n // publicDir: 'static',\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO\n // logLevel: 'silent',\n\n optimizeDeps: {\n // Prevent Vite from examining other html files when scanning entrypoints\n // for dependency optimization\n entries: ['index.html'],\n\n // XXX: When plugin-check is installed via pnpm, the Vite dev server seems\n // to have no trouble resolving other dependencies using the optimizeDeps\n // mechanism. However, this fails when the package is installed via yarn\n // or npm (probably due to the fact that the `template-report` directory\n // is located under the top-level `node_modules` directory); in the browser,\n // there will be \"import not found\" errors for the packages referenced below.\n // As a terrible workaround, explicitly include the direct dependencies so\n // that Vite optimizes them; this works for pnpm, yarn, and npm. We should\n // find a less fragile solution.\n include: [\n // from check-core\n '@sdeverywhere/check-core > assert-never',\n '@sdeverywhere/check-core > ajv',\n '@sdeverywhere/check-core > neverthrow',\n '@sdeverywhere/check-core > yaml',\n // from check-ui-shell\n '@sdeverywhere/check-ui-shell > fontfaceobserver',\n '@sdeverywhere/check-ui-shell > copy-text-to-clipboard',\n '@sdeverywhere/check-ui-shell > chart.js'\n ],\n\n exclude: [\n // XXX: The threads.js implementation references `tiny-worker` as an optional\n // dependency, but it doesn't get used at runtime, so we can just exclude it\n // so that Vite doesn't complain in dev mode\n 'tiny-worker'\n\n // XXX: Similarly, chart.js treats `moment` as an optional dependency, but we\n // don't use it at runtime; we need to exclude it here, otherwise Vite will\n // complain about missing dependencies in dev mode\n // 'moment'\n ]\n },\n\n // Configure path aliases\n resolve: {\n alias: [\n // Use the configured \"baseline\" bundle if defined, otherwise use the \"empty\" bundle\n // (which will cause comparison tests to be skipped)\n alias('@_baseline_bundle_', options?.baseline ? options.baseline.path : '/src/empty-bundle.ts'),\n\n // Use the configured \"current\" bundle\n alias('@_current_bundle_', currentBundlePath),\n\n // Use the configured test config file\n alias('@_test_config_', testConfigPath),\n\n // Make the overlay use the `messages.html` file that is written to the prep directory\n alias('@_prep_', prepDir),\n\n // XXX: Include no-op polyfills for these modules that are used in the Node-specific\n // implementation of threads.js; this allows us to use one bundle that works in both\n // Node and browser environments\n noopPolyfillAlias('events'),\n noopPolyfillAlias('fs'),\n noopPolyfillAlias('os'),\n noopPolyfillAlias('path'),\n noopPolyfillAlias('url'),\n noopPolyfillAlias('worker_threads')\n ]\n },\n\n // Inject special values into the generated JS\n define: {\n // Inject the summary JSON into the build\n __SUITE_SUMMARY_JSON__: JSON.stringify(suiteSummaryJson),\n\n // Inject the baseline branch name\n __BASELINE_NAME__: JSON.stringify(options?.baseline?.name || ''),\n\n // Inject the current branch name\n __CURRENT_NAME__: JSON.stringify(currentBundleName)\n },\n\n plugins: [\n // Inject special values into the generated JS\n // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's\n // built-in `define` feature because the latter does not seem to run before\n // the glob handler (which requires the glob to be injected as a literal)\n replace({\n preventAssignment: true,\n delimiters: ['', ''],\n values: {\n // Inject the path for baseline bundles\n // XXX: Note that we use './baselines/*.txt' instead of something special\n // like './__BASELINE_BUNDLES_PATH__' because sometimes Vite's dependency\n // scanner sees the latter (instead of the injected path) and reports\n // an error since the path does not exist. As a workaround, we use\n // './baselines/*.txt', which gets interpreted as the valid path\n // '.../template-report/src/baselines/*.txt' (see `baselines/unused.txt`).\n './baselines/*.txt': baselinesPath\n }\n }) as unknown as PluginOption\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n\n // Write js/css files to `public` (instead of the default `<outDir>/assets`)\n assetsDir: '',\n\n rollupOptions: {\n output: {\n // XXX: Prevent vite from creating a separate `vendor.js` file\n manualChunks: undefined\n },\n\n onwarn: (warning, warn) => {\n // XXX: Suppress \"Use of eval is strongly discouraged\" warnings that are\n // triggered by use of the following pattern in threads.js:\n // eval(\"require\")(\"worker_threads\")\n // It would be nice to avoid use of `eval` there, but it's not critical for\n // our use case so we will suppress the warnings for now\n if (warning.code !== 'EVAL') {\n warn(warning)\n }\n }\n }\n },\n\n server: {\n // Run the dev server at `localhost:8081` by default\n port: options?.serverPort || 8081,\n\n // Open the app in the browser by default\n open: '/index.html',\n\n // XXX: Add a small delay, otherwise on macOS we sometimes get multiple\n // change events when a file is saved just once. That is a relatively\n // harmless issue except that it causes redundant messages in the console\n // and can cause extra churn when refreshing the app.\n watch: {\n awaitWriteFinish: {\n stabilityThreshold: 100\n }\n }\n }\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { dirname, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, PluginOption } from 'vite'\nimport replace from '@rollup/plugin-replace'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nexport function createViteConfigForTests(projDir: string, prepDir: string, mode: 'build' | 'watch'): InlineConfig {\n // Use `template-tests` as the root directory for the tests project\n const root = resolvePath(__dirname, '..', 'template-tests')\n\n // Get the base glob path; apparently this must be a relative path (relative to\n // the `template-tests/src` directory where the glob is used)\n const templateSrcDir = resolvePath(root, 'src')\n const relProjDir = relative(templateSrcDir, projDir)\n // XXX: The glob pattern must use forward slashes only, so on Windows we need to\n // convert backslashes to slashes\n const relProjDirPath = relProjDir.replaceAll('\\\\', '/')\n\n // Include check test definitions in files matching `checks/*.yaml` under\n // the configured project root directory. We also include `*.check.yaml`,\n // which was the naming used in earlier versions of the create package and\n // related examples.\n // TODO: Use yaml path/pattern from options\n const yamlCheckGlobPatterns = `['${relProjDirPath}/**/checks/*.yaml', '${relProjDirPath}/**/*.check.yaml']`\n\n // Include comparison test definitions in files matching `comparisons/*.yaml`\n // under the configured project root directory\n // TODO: Use yaml path/pattern from options\n const yamlComparisonGlobPatterns = `['${relProjDirPath}/**/comparisons/*.yaml']`\n\n // Calculate output directory relative to the template root\n // TODO: For now we write it to `prepDir`; make this configurable?\n const outDir = relative(root, prepDir)\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO: Disable vite output by default?\n // logLevel: 'silent',\n\n plugins: [\n // Inject special values into the generated JS\n // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's\n // built-in `define` feature because the latter does not seem to run before\n // the glob handler (which requires the glob to be injected as a literal)\n replace({\n preventAssignment: true,\n delimiters: ['', ''],\n values: {\n // Inject the glob patterns for matching model check yaml files\n '\"./__YAML_CHECK_GLOB_PATTERNS__\"': yamlCheckGlobPatterns,\n // Inject the glob patterns for matching model comparison yaml files\n '\"./__YAML_COMPARISON_GLOB_PATTERNS__\"': yamlComparisonGlobPatterns\n }\n }) as unknown as PluginOption\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n emptyOutDir: false,\n\n lib: {\n entry: './src/index.ts',\n formats: ['es'],\n fileName: () => 'check-tests.js'\n },\n\n // Enable watch mode if requested\n watch: mode === 'watch' && {},\n\n rollupOptions: {\n // Prevent dependencies from being included in packaged library\n // TODO: For now we include check-core in the packaged library so that its\n // dependencies are correctly resolved at runtime. Ideally this would only\n // include a couple functions that are used for defining tests, but Vite 2.x\n // does not implement tree shaking for ES libraries, which means the generated\n // library is much larger than it needs to be. Once we upgrade to Vite 3.x,\n // the generated library should be smaller; see related fix:\n // https://github.com/vitejs/vite/pull/8737\n // external: Object.keys(pkg.dependencies)\n }\n }\n }\n}\n"],"mappings":"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","names":["import_fs","import_path","import_url","import_check_core","pico","import_check_core","__filename","joinPath","resolvePath","import_fs","import_path","import_url","__filename","__dirname","resolvePath","joinPath","replace","import_path","import_url","import_plugin_replace","__filename","__dirname","resolvePath","replace","chokidar","joinPath"]}
package/dist/index.js CHANGED
@@ -33,14 +33,15 @@ async function runTestSuite(context, config, verbose) {
33
33
  onComplete: (report) => {
34
34
  try {
35
35
  const t1 = performance.now();
36
- const elapsed = ((t1 - t0) / 1e3).toFixed(1);
36
+ const elapsedMillis = t1 - t0;
37
+ const elapsedSeconds = (elapsedMillis / 1e3).toFixed(1);
37
38
  context.log("info", `
38
- Test suite completed in ${elapsed}s`);
39
+ Test suite completed in ${elapsedSeconds}s`);
39
40
  const allChecksPassed = printCheckSummary(context, report.checkReport, verbose);
40
41
  if (report.comparisonReport) {
41
42
  printPerfStats(context, config.comparison, report.comparisonReport);
42
43
  }
43
- const suiteSummary = suiteSummaryFromReport(report);
44
+ const suiteSummary = suiteSummaryFromReport(report, elapsedMillis);
44
45
  resolve({
45
46
  allChecksPassed,
46
47
  suiteSummary
@@ -72,6 +73,9 @@ function printCheckSummary(context, checkReport, verbose) {
72
73
  case "error":
73
74
  statusChar = "\u203C";
74
75
  break;
76
+ case "skipped":
77
+ statusChar = "\u2013";
78
+ break;
75
79
  default:
76
80
  statusChar = "";
77
81
  break;
@@ -129,13 +133,15 @@ function printPerfStats(context, comparisonConfig, report) {
129
133
  }
130
134
 
131
135
  // src/vite-config-for-bundle.ts
132
- import { existsSync, statSync } from "fs";
136
+ import { existsSync, readFileSync, statSync } from "fs";
133
137
  import { basename, dirname, join as joinPath, relative, resolve as resolvePath } from "path";
134
138
  import { fileURLToPath } from "url";
135
139
  import { nodeResolve } from "@rollup/plugin-node-resolve";
140
+ import { encodeImplVars } from "@sdeverywhere/check-core";
136
141
  var __filename2 = fileURLToPath(import.meta.url);
137
142
  var __dirname2 = dirname(__filename2);
138
143
  function injectModelSpec(context, modelSpec) {
144
+ const prepDir = context.config.prepDir;
139
145
  const inputSpecs = [];
140
146
  for (const modelInputSpec of modelSpec.inputs) {
141
147
  if (modelInputSpec.defaultValue === void 0 || modelInputSpec.minValue === void 0 || modelInputSpec.maxValue === void 0) {
@@ -161,8 +167,19 @@ function injectModelSpec(context, modelSpec) {
161
167
  ...o
162
168
  };
163
169
  });
170
+ function readJsonListing() {
171
+ const path = joinPath(prepDir, "build", "processed.json");
172
+ if (existsSync(path)) {
173
+ const json = readFileSync(path, "utf8");
174
+ return JSON.parse(json);
175
+ } else {
176
+ return {};
177
+ }
178
+ }
179
+ const listing = readJsonListing();
180
+ const varInstances = listing.varInstances || {};
181
+ const encodedImplVars = encodeImplVars(varInstances);
164
182
  function stagedFileSize(filename) {
165
- const prepDir = context.config.prepDir;
166
183
  const path = joinPath(prepDir, "staged", "model", filename);
167
184
  if (existsSync(path)) {
168
185
  return statSync(path).size;
@@ -175,6 +192,7 @@ function injectModelSpec(context, modelSpec) {
175
192
  const moduleSrc = `
176
193
  export const inputSpecs = ${JSON.stringify(inputSpecs)};
177
194
  export const outputSpecs = ${JSON.stringify(outputSpecs)};
195
+ export const encodedImplVars = ${JSON.stringify(encodedImplVars)};
178
196
  export const modelSizeInBytes = ${modelSizeInBytes};
179
197
  export const dataSizeInBytes = ${dataSizeInBytes};
180
198
  `;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts","../src/run-suite.ts","../src/vite-config-for-bundle.ts","../src/vite-config-for-report.ts","../src/vite-config-for-tests.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { copyFile, mkdir } from 'fs/promises'\nimport { dirname, join as joinPath, relative } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, ViteDevServer } from 'vite'\nimport { build, createServer } from 'vite'\n\nimport chokidar from 'chokidar'\n\nimport type { BuildContext, Plugin, ResolvedConfig, ResolvedModelSpec } from '@sdeverywhere/build'\n\nimport type { Bundle, ConfigInitOptions, SuiteSummary } from '@sdeverywhere/check-core'\nimport { createConfig } from '@sdeverywhere/check-core'\n\nimport type { CheckPluginOptions } from './options'\nimport { runTestSuite } from './run-suite'\nimport { createViteConfigForBundle } from './vite-config-for-bundle'\nimport { createViteConfigForReport } from './vite-config-for-report'\nimport { createViteConfigForTests } from './vite-config-for-tests'\n\nexport function checkPlugin(options?: CheckPluginOptions): Plugin {\n return new CheckPlugin(options)\n}\n\ninterface TestOptions {\n currentBundleName: string\n currentBundlePath: string\n testConfigPath: string\n}\n\nclass CheckPlugin implements Plugin {\n private firstBuild = true\n\n constructor(private readonly options?: CheckPluginOptions) {}\n\n async watch(config: ResolvedConfig): Promise<void> {\n if (this.options?.testConfigPath === undefined) {\n // Test config was not provided, so generate a default config in watch mode.\n // The test template uses import.meta.glob so that checks are re-run\n // automatically when the `{checks/comparisons}/*.yaml` files are changed.\n await this.genTestConfig(config, 'watch')\n }\n\n // For development mode, run Vite in dev mode so that it serves the\n // model-check report locally (with live reload enabled). When a model\n // test file is changed, the tests will be re-run in the browser.\n const testOptions = this.resolveTestOptions(config)\n const viteConfig = this.createViteConfigForReport(config, testOptions, undefined)\n const server: ViteDevServer = await createServer(viteConfig)\n await server.listen()\n\n // XXX: Currently Vite doesn't reload the page if a file is added/removed\n // in the baselines directory (Vite's import.meta.glob handling doesn't\n // seem to do this automatically), so as a workaround, watch the baselines\n // directory and restart the server if files are added/removed\n // TODO: The same problem also applies to the glob for `checks/*.yaml` and\n // `comparisons/*.yaml` files in the test config, so we should also reload\n // if files match/unmatch those glob patterns\n // TODO: Use the baselines directory from the config\n const baselinesDir = 'baselines'\n const watcher = chokidar.watch(baselinesDir, {\n // Watch paths are resolved relative to the project root directory\n cwd: config.rootDir,\n // Don't send initial \"file added\" events\n ignoreInitial: true,\n // XXX: Include a delay, otherwise on macOS we sometimes get multiple\n // change events when a file is saved just once\n awaitWriteFinish: {\n stabilityThreshold: 200\n }\n })\n watcher.on('add', () => server.restart())\n watcher.on('unlink', () => server.restart())\n }\n\n // TODO: Note that this plugin runs as a `postBuild` step because it currently\n // needs to run after other plugins, and those plugins need to run after the\n // staged files are copied to their final destination(s). We should probably\n // make it configurable so that it can either be run as a `postGenerate` or a\n // `postBuild` step.\n async postBuild(context: BuildContext, modelSpec: ResolvedModelSpec): Promise<boolean> {\n const firstBuild = this.firstBuild\n this.firstBuild = false\n\n // For both production builds and local development, generate default bundle\n // in this post-build step each time a source file is changed\n // TODO: We could potentially use watch mode for the bundle similar to\n // what we do for the test config, but the bundle depends on the ModelSpec,\n // which currently isn't made available to the `watch` function\n if (this.options?.current === undefined) {\n // Path to current bundle was not provided, so generate a default bundle\n if (context.config.mode === 'development') {\n // Copy the previous bundle to the `baselines` directory so that\n // we automatically have it available as a baseline for comparison\n await this.copyPreviousBundle(context.config)\n }\n context.log('info', 'Generating model check bundle...')\n await this.genCurrentBundle(context, modelSpec)\n }\n\n // For production builds (and for the initial build in local development mode),\n // generate default test config in this post-build step\n if (this.options?.testConfigPath === undefined) {\n if (context.config.mode === 'production' || firstBuild) {\n // Test config was not provided, so generate a default config\n context.log('info', 'Generating model check test configuration...')\n await this.genTestConfig(context.config, 'build')\n }\n }\n\n if (context.config.mode === 'production') {\n // For production builds, run the model checks/comparisons, and then\n // inject the results into the generated report\n const testOptions = this.resolveTestOptions(context.config)\n return this.runChecks(context, testOptions)\n } else {\n // Nothing to do here in dev mode; the dev server will refresh and\n // re-run the tests in the browser when changes are detected\n return true\n }\n }\n\n private async copyPreviousBundle(config: ResolvedConfig): Promise<void> {\n // Only copy if the current bundle exists\n const currentBundleFile = joinPath(config.prepDir, 'check-bundle.js')\n if (existsSync(currentBundleFile)) {\n // TODO: Use the baselines directory from the config (not yet available)\n const baselinesDir = joinPath(config.rootDir, 'baselines')\n if (!existsSync(baselinesDir)) {\n await mkdir(baselinesDir, { recursive: true })\n }\n const previousBundleFile = joinPath(baselinesDir, 'previous.js')\n await copyFile(currentBundleFile, previousBundleFile)\n }\n }\n\n private async genCurrentBundle(context: BuildContext, modelSpec: ResolvedModelSpec): Promise<void> {\n const viteConfig = await createViteConfigForBundle(context, modelSpec)\n await build(viteConfig)\n }\n\n private async genTestConfig(config: ResolvedConfig, mode: 'build' | 'watch'): Promise<void> {\n const rootDir = config.rootDir\n const prepDir = config.prepDir\n const viteConfig = createViteConfigForTests(rootDir, prepDir, mode)\n await build(viteConfig)\n }\n\n private async runChecks(context: BuildContext, testOptions: TestOptions): Promise<boolean> {\n context.log('info', 'Running model checks...')\n\n // Load the bundles used by the model check/compare configuration. We\n // always initialize the \"current\" bundle.\n const moduleR = await import(relativeToSourcePath(testOptions.currentBundlePath))\n const bundleR = moduleR.createBundle() as Bundle\n const bundleNameR = testOptions.currentBundleName\n\n // Only initialize the \"baseline\" bundle if it is defined and the version\n // is the same as the \"current\" one. If the baseline bundle has a different\n // version, we will skip the comparison tests and only run the checks on the\n // current bundle.\n let bundleL: Bundle\n let bundleNameL: string\n if (this.options?.baseline) {\n const moduleL = await import(relativeToSourcePath(this.options.baseline.path))\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const rawBundleL: any = moduleL.createBundle() as any\n if (rawBundleL.version === bundleR.version) {\n bundleL = rawBundleL as Bundle\n bundleNameL = this.options.baseline.name\n }\n }\n\n // Get the model check/comparison configuration\n const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath))\n const configInitOptions: ConfigInitOptions = {\n bundleNameL,\n bundleNameR\n }\n const configOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, configInitOptions)\n\n // Run the suite of checks and comparisons\n const checkConfig = await createConfig(configOptions)\n const result = await runTestSuite(context, checkConfig, /*verbose=*/ false)\n\n // Build the report (using Vite)\n context.log('info', 'Building model check report')\n const viteConfig = this.createViteConfigForReport(context.config, testOptions, result.suiteSummary)\n await build(viteConfig)\n\n // context.log('info', 'Done!')\n\n return result.allChecksPassed\n }\n\n private resolveTestOptions(config: ResolvedConfig): TestOptions {\n let currentBundleName: string\n let currentBundlePath: string\n if (this.options?.current === undefined) {\n // Path to current bundle was not provided, so use a generated bundle\n currentBundleName = 'current'\n currentBundlePath = joinPath(config.prepDir, 'check-bundle.js')\n } else {\n // Use the provided bundle\n currentBundleName = this.options.current.name\n currentBundlePath = this.options.current.path\n }\n\n let testConfigPath: string\n if (this.options?.testConfigPath === undefined) {\n // Test config was not provided, so use a generated config\n testConfigPath = joinPath(config.prepDir, 'check-tests.js')\n } else {\n // Use the provided test config\n testConfigPath = this.options.testConfigPath\n }\n\n return {\n currentBundleName,\n currentBundlePath,\n testConfigPath\n }\n }\n\n private createViteConfigForReport(\n config: ResolvedConfig,\n testOptions: TestOptions,\n suiteSummary: SuiteSummary | undefined\n ): InlineConfig {\n return createViteConfigForReport(\n this.options,\n config.rootDir,\n config.prepDir,\n testOptions.currentBundleName,\n testOptions.currentBundlePath,\n testOptions.testConfigPath,\n suiteSummary\n )\n }\n}\n\n/**\n * Return a Unix-style path (e.g. '../../foo.js') that is relative to the directory of\n * the current source file. This can be used to construct a path that is safe for\n * dynamic import on either Unix or Windows.\n *\n * @param filePath The path to make relative.\n */\nfunction relativeToSourcePath(filePath: string): string {\n const srcDir = dirname(fileURLToPath(import.meta.url))\n const relPath = relative(srcDir, filePath)\n return relPath.replaceAll('\\\\', '/')\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport { performance } from 'perf_hooks'\n\nimport pico from 'picocolors'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type {\n Config,\n ComparisonReport,\n PerfReport,\n RunSuiteCallbacks,\n CheckReport,\n CheckStatus,\n ComparisonConfig,\n CheckTestReport,\n SuiteSummary\n} from '@sdeverywhere/check-core'\nimport {\n datasetMessage,\n predicateMessage,\n runSuite,\n scenarioMessage,\n suiteSummaryFromReport\n} from '@sdeverywhere/check-core'\n\nexport interface RunTestSuiteResult {\n allChecksPassed: boolean\n suiteSummary: SuiteSummary\n}\n\n/**\n * Runs the test suite.\n */\nexport async function runTestSuite(\n context: BuildContext,\n config: Config,\n verbose: boolean\n): Promise<RunTestSuiteResult> {\n return new Promise((resolve, reject) => {\n const t0 = performance.now()\n let lastPctByInc: number\n const callbacks: RunSuiteCallbacks = {\n onProgress: progress => {\n const pct = Math.round(progress * 100)\n const pctByInc = Math.floor(pct / 5) * 5\n if (lastPctByInc === undefined || pctByInc > lastPctByInc) {\n lastPctByInc = pctByInc\n context.log('info', `${pctByInc}%`)\n }\n },\n onComplete: report => {\n try {\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `\\nTest suite completed in ${elapsed}s`)\n\n // Print check summary to the console\n const allChecksPassed = printCheckSummary(context, report.checkReport, verbose)\n\n if (report.comparisonReport) {\n // Print the perf stats to the console\n printPerfStats(context, config.comparison, report.comparisonReport)\n }\n\n // Convert check and compare reports to terse form that only includes\n // failed/errored checks or comparisons with differences\n // TODO: The terse form was originally used when we had to write the\n // results to a JSON file and then read them back in when building\n // the report, but we no longer use that intermediate file, so there's\n // less reason to use the terse form (since it requires the web app\n // code to reconstruct the results). But for now, we will continue\n // to use the terse form, and later we can update the app code.\n const suiteSummary = suiteSummaryFromReport(report)\n\n resolve({\n allChecksPassed,\n suiteSummary\n })\n } catch (e) {\n reject(e)\n }\n },\n onError: error => {\n reject(error)\n }\n }\n runSuite(config, callbacks)\n })\n}\n\nfunction printCheckSummary(context: BuildContext, checkReport: CheckReport, verbose: boolean): boolean {\n function printResult(indent: number, status: CheckStatus, text: string): void {\n if (!verbose && status === 'passed' && indent > 1) {\n return\n }\n let statusChar: string\n switch (status) {\n case 'passed':\n statusChar = '✓'\n break\n case 'failed':\n statusChar = '✗'\n break\n case 'error':\n statusChar = '‼'\n break\n default:\n statusChar = ''\n break\n }\n const msg = `${' '.repeat(indent)}${statusChar} ${text}`\n context.log('info', status === 'passed' ? pico.green(msg) : pico.red(msg))\n }\n\n function bold(s: string): string {\n return pico.bold(s)\n }\n\n function printTest(test: CheckTestReport): void {\n const msg = `${test.name}${verbose || test.status !== 'passed' ? ':' : ''}`\n printResult(1, test.status, msg)\n }\n\n let allPassed = true\n context.log('info', '\\nCheck results:')\n for (const group of checkReport.groups) {\n context.log('info', `\\n${group.name}`)\n\n for (const test of group.tests) {\n if (test.status !== 'passed') {\n allPassed = false\n }\n printTest(test)\n\n for (const scenario of test.scenarios) {\n printResult(3, scenario.status, scenarioMessage(scenario, bold))\n\n for (const dataset of scenario.datasets) {\n printResult(5, dataset.status, datasetMessage(dataset, bold))\n\n for (const predicate of dataset.predicates) {\n printResult(7, predicate.result.status, predicateMessage(predicate, bold))\n }\n }\n }\n }\n }\n context.log('info', '')\n\n return allPassed\n}\n\nfunction stat(label: string, n: number): string {\n return `${label}=${n.toFixed(1)}ms`\n}\n\nfunction printPerfReportLine(context: BuildContext, perfReport: PerfReport): void {\n const avg = stat('avg', perfReport.avgTime)\n const min = stat('min', perfReport.minTime)\n const max = stat('max', perfReport.maxTime)\n context.log('info', ` ${avg} ${min} ${max}`)\n}\n\nfunction printPerfStats(context: BuildContext, comparisonConfig: ComparisonConfig, report: ComparisonReport): void {\n context.log('info', '\\nPerformance stats:')\n context.log('info', ` ${comparisonConfig.bundleL.name}:`)\n printPerfReportLine(context, report.perfReportL)\n context.log('info', ` ${comparisonConfig.bundleR.name}:`)\n printPerfReportLine(context, report.perfReportR)\n context.log('info', '')\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, statSync } from 'fs'\nimport { basename, dirname, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, ResolvedConfig, Plugin as VitePlugin } from 'vite'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nimport type { BuildContext, ResolvedModelSpec } from '@sdeverywhere/build'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\n/**\n * This is a virtual module plugin used to inject model-specific configuration\n * values into the generated worker bundle.\n *\n * This follows the \"Virtual Modules Convention\" described here:\n * https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention\n *\n * TODO: This could be simplified by using `vite-plugin-virtual` but that\n * doesn't seem to be working correctly in an ESM setting\n */\nfunction injectModelSpec(context: BuildContext, modelSpec: ResolvedModelSpec): VitePlugin {\n // Include the SDE variable ID with each spec\n const inputSpecs = []\n for (const modelInputSpec of modelSpec.inputs) {\n // Note that the `InputSpec` interface in the `@sdeverywhere/build` package\n // allows the default/min/max values to be undefined, which can be the case\n // if the user doesn't return full `InputSpec` instances in the `ModelSpec`.\n // We will log a warning and skip the input if these values are not defined.\n if (\n modelInputSpec.defaultValue === undefined ||\n modelInputSpec.minValue === undefined ||\n modelInputSpec.maxValue === undefined\n ) {\n let msg = ''\n msg += `WARNING: The {defaultValue,minValue,maxValue} properties are required by plugin-check, `\n msg += `but are undefined in the InputSpec for '${modelInputSpec.varName}'. `\n msg += `This input variable will be excluded from the model-check bundle until those properties `\n msg += `are defined.`\n console.warn(msg)\n continue\n }\n\n // Use the `inputId` if defined for the `InputSpec`, otherwise use `varId`. The\n // latter is less resilient if the variable is renamed between two versions of\n // the model, but will be sufficient for now. Note that `plugin-config` defines\n // a stable `inputId` for each row in the `inputs.csv`, and that is the most\n // common way to configure a `ModelSpec`, so it will be uncommon for `inputId`\n // to be undefined here.\n const varId = context.canonicalVarId(modelInputSpec.varName)\n const inputId = modelInputSpec.inputId || varId\n inputSpecs.push({\n inputId,\n varId,\n ...modelInputSpec\n })\n }\n const outputSpecs = modelSpec.outputs.map(o => {\n return {\n varId: context.canonicalVarId(o.varName),\n ...o\n }\n })\n\n function stagedFileSize(filename: string): number {\n const prepDir = context.config.prepDir\n const path = joinPath(prepDir, 'staged', 'model', filename)\n if (existsSync(path)) {\n return statSync(path).size\n } else {\n return 0\n }\n }\n\n // The size (in bytes) of the `generated-model.js` file\n // TODO: Ideally we would measure the size of the raw Wasm binary, but currently\n // we inline it as a base64 blob inside the JS file, so we take the size of the\n // whole JS file as the second best option\n const modelSizeInBytes = stagedFileSize('generated-model.js')\n\n // The size (in bytes) of the `static-data.ts` file\n // TODO: Ideally we would measure the size of the minified JS file here, or\n // at least ignore things like whitespace\n const dataSizeInBytes = stagedFileSize('static-data.ts')\n\n const moduleSrc = `\nexport const inputSpecs = ${JSON.stringify(inputSpecs)};\nexport const outputSpecs = ${JSON.stringify(outputSpecs)};\nexport const modelSizeInBytes = ${modelSizeInBytes};\nexport const dataSizeInBytes = ${dataSizeInBytes};\n`\n\n const virtualModuleId = 'virtual:model-spec'\n const resolvedVirtualModuleId = '\\0' + virtualModuleId\n\n return {\n name: 'vite-plugin-virtual-custom',\n resolveId(id: string) {\n if (id === virtualModuleId) {\n return resolvedVirtualModuleId\n }\n },\n load(id: string) {\n if (id === resolvedVirtualModuleId) {\n return moduleSrc\n }\n }\n }\n}\n\n/**\n * XXX: This overrides the built-in `vite:resolve` plugin so that we can intercept `resolveId`\n * calls for the threads package.\n */\nfunction overrideViteResolvePlugin(viteConfig: ResolvedConfig) {\n const resolvePlugin = viteConfig.plugins.find(p => p.name === 'vite:resolve')\n if (resolvePlugin === undefined) {\n throw new Error('Failed to locate the built-in vite:resolve plugin')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const originalResolveId = resolvePlugin.resolveId as any\n resolvePlugin.resolveId = async function resolveId(id, importer, options) {\n if (id.startsWith('./implementation') && importer.includes('threads/dist-esm')) {\n // XXX: The default resolver behavior will look at the `browser` mappings in\n // `threads/package.json` and try to resolve `implementation.js` to\n // `implementation.browser.js` because it thinks we're in a browser-only context.\n // We don't want that. Instead we want to keep the generic implementation from\n // threads that chooses between the Node and browser implementations at runtime.\n //\n // If we get here, importer will be something like:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/index.js\n // And id will be:\n // ./implementation\n // So resolve the ID to:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.js\n //\n // Or, importer will be:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.js\n // And id will be one of:\n // ./implementation.browser\n // ./implementation.node\n // ./implementation.worker_threads\n // So resolve the ID to:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.{...}.js\n const idFileName = id.replace('./', '')\n const importerFileName = basename(importer)\n const resolvedId = importer.replace(importerFileName, `${idFileName}.js`)\n return {\n id: resolvedId,\n moduleSideEffects: false\n }\n }\n\n // For all other cases, fall back on the default resolver\n return await originalResolveId.handler.call(this, id, importer, options)\n }\n}\n\nexport async function createViteConfigForBundle(\n context: BuildContext,\n modelSpec: ResolvedModelSpec\n): Promise<InlineConfig> {\n // Use `template-bundle` as the root directory for the bundle project\n const root = resolvePath(__dirname, '..', 'template-bundle')\n\n // Calculate output directory relative to the template root\n // TODO: For now we write it to `prepDir`; make this configurable?\n const prepDir = context.config.prepDir\n const outDir = relative(root, prepDir)\n\n // Use the model worker from the staged directory\n // TODO: Make this configurable?\n const modelWorkerPath = joinPath(prepDir, 'staged', 'model', 'worker.js?raw')\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO: Disable vite output by default?\n // logLevel: 'silent',\n\n // Configure path aliases\n resolve: {\n alias: [\n // Inject the configured model worker\n {\n find: '@_model_worker_',\n replacement: modelWorkerPath\n },\n\n // XXX: Prevent Vite from using the `browser` section of `threads/package.json`\n // since we want to force the use of the general module (under dist-esm) that chooses\n // the correct implementation (Web Worker vs worker_threads) at runtime. Currently\n // Vite's library mode is browser focused and generally chooses the right imports,\n // except in the case of the threads package where we want to use the generic\n // `implementation.js` that chooses between Web Worker and worker_threads at runtime.\n // Note that we could in theory set `resolve.browserField` to false, but that would\n // make Vite not use the browser field for all other packages, and there is not\n // currently a way to tell Vite to use the browser field on a case-by-case basis.\n // So for now we need this workaround here to make it resolve to `dist-esm`, and then\n // a second workaround in `overrideViteResolvePlugin` to prevent the resolver from\n // using the browser field when resolving the threads package.\n {\n find: 'threads',\n replacement: 'threads',\n customResolver: async function (source, importer, options) {\n // Note that we need to use `resolveId.call` here in order to provide the\n // right `this` context, which provides Rollup plugin functionality\n const customResolver = nodeResolve({ browser: false })\n const resolved = await customResolver.resolveId.call(this, source, importer, options)\n // Force the use of the `dist-esm` variant of the threads.js package\n if (source === 'threads/worker') {\n return resolved.id.replace('worker.mjs', 'dist-esm/worker/index.js')\n } else {\n return resolved.id.replace('index.mjs', 'dist-esm/index.js')\n }\n }\n }\n ]\n },\n\n plugins: [\n // Use a virtual module plugin to inject the model spec values\n injectModelSpec(context, modelSpec),\n\n // XXX: Install a wrapper around the built-in `vite:resolve` plugin so that we can\n // override the default resolver behavior that tries to resolve the `browser` section\n // of the `package.json` for the threads package.\n {\n name: 'vite-plugin-override-resolve',\n configResolved(viteConfig) {\n overrideViteResolvePlugin(viteConfig)\n }\n }\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n emptyOutDir: false,\n\n // Uncomment for debugging purposes\n // minify: false,\n\n lib: {\n entry: './src/index.ts',\n formats: ['es'],\n fileName: () => 'check-bundle.js'\n },\n\n rollupOptions: {\n // Don't transform Node imports used by threads.js\n external: ['events', 'os', 'path', 'url'],\n\n // XXX: Insert custom code at the top of the generated bundle that defines\n // the special `__non_webpack_require__` function that is used by threads.js\n // in its Node implementation. This import ensures that threads.js uses\n // the native `worker_threads` implementation when using the bundle in a\n // Node environment. When importing the bundle for use in the browser,\n // Vite will transform this import into an empty module due to the empty\n // polyfill that is configured in `vite-config-for-report.ts`.\n output: {\n banner: `\nimport * as worker_threads from 'worker_threads'\nlet __non_webpack_require__ = () => {\n return worker_threads;\n};\n`\n },\n\n onwarn: (warning, warn) => {\n // XXX: Suppress \"Use of eval is strongly discouraged\" warnings that are\n // triggered by use of the following pattern in threads.js:\n // eval(\"require\")(\"worker_threads\")\n // It would be nice to avoid use of `eval` there, but it's not critical for\n // our use case so we will suppress the warnings for now\n if (warning.code !== 'EVAL') {\n warn(warning)\n }\n }\n }\n }\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, mkdirSync } from 'fs'\nimport { dirname, relative, join as joinPath, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { Alias, InlineConfig, PluginOption } from 'vite'\nimport replace from '@rollup/plugin-replace'\n\nimport type { SuiteSummary } from '@sdeverywhere/check-core'\n\nimport type { CheckPluginOptions } from './options'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nexport function createViteConfigForReport(\n options: CheckPluginOptions | undefined,\n projDir: string,\n prepDir: string,\n currentBundleName: string,\n currentBundlePath: string,\n testConfigPath: string,\n suiteSummary: SuiteSummary | undefined\n): InlineConfig {\n // Use `template-report` as the root directory for the report project\n const root = resolvePath(__dirname, '..', 'template-report')\n\n // Make sure the `baselines` directory exists, otherwise Vite's dependency scanner\n // may report errors when processing the `import.meta.glob` call\n const baselinesDir = resolvePath(projDir, 'baselines')\n if (!existsSync(baselinesDir)) {\n mkdirSync(baselinesDir, { recursive: true })\n }\n\n // Include `baselines/*.js` files under the configured project root directory. This\n // glob path apparently must be a relative path (relative to the `template-report/src`\n // directory where the glob is used).\n const templateSrcDir = resolvePath(root, 'src')\n const relProjDir = relative(templateSrcDir, projDir)\n // XXX: The glob pattern must use forward slashes only, so on Windows we need to\n // convert backslashes to slashes\n const relProjDirPath = relProjDir.replaceAll('\\\\', '/')\n // TODO: Use baselinesDir from options\n const baselinesPath = `${relProjDirPath}/baselines/*.js`\n\n // Calculate output directory relative to the template root\n let reportPath: string\n if (options?.reportPath) {\n reportPath = options.reportPath\n } else {\n reportPath = joinPath(prepDir, 'check-report')\n }\n const outDir = relative(root, reportPath)\n\n // Convert the suite summary to JSON, which is what the app currently expects\n const suiteSummaryJson = suiteSummary ? JSON.stringify(suiteSummary) : ''\n\n const alias = (find: string, replacement: string) => {\n return {\n find,\n replacement\n } as Alias\n }\n\n // XXX: This provides custom handling for Node built-ins such as 'events' that are\n // referenced by the check bundle (specifically in the Node implementation of\n // threads.js). These are not actually used in the browser, so we just need\n // to provide no-op polyfills for these.\n const noopPolyfillAlias = (find: string) => {\n return {\n find,\n replacement: '/polyfills/noop-polyfills.ts'\n } as Alias\n }\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Use `.` as the base directory (instead of the default `/`); this controls\n // how the path to the js/css files are generated in `index.html`\n base: '',\n\n // Use a custom cache directory under `prepDir`, as otherwise Vite will use\n // `packages/plugin-check/template-report/node_modules/.vite`, and we want to\n // avoid generating files in `template-report` (which should be read-only)\n cacheDir: joinPath(prepDir, '.vite-check-report'),\n\n // Load static files from `static` (instead of the default `public`)\n // publicDir: 'static',\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO\n // logLevel: 'silent',\n\n optimizeDeps: {\n // Prevent Vite from examining other html files when scanning entrypoints\n // for dependency optimization\n entries: ['index.html'],\n\n // XXX: When plugin-check is installed via pnpm, the Vite dev server seems\n // to have no trouble resolving other dependencies using the optimizeDeps\n // mechanism. However, this fails when the package is installed via yarn\n // or npm (probably due to the fact that the `template-report` directory\n // is located under the top-level `node_modules` directory); in the browser,\n // there will be \"import not found\" errors for the packages referenced below.\n // As a terrible workaround, explicitly include the direct dependencies so\n // that Vite optimizes them; this works for pnpm, yarn, and npm. We should\n // find a less fragile solution.\n include: [\n // from check-core\n '@sdeverywhere/check-core > assert-never',\n '@sdeverywhere/check-core > ajv',\n '@sdeverywhere/check-core > neverthrow',\n '@sdeverywhere/check-core > yaml',\n // from check-ui-shell\n '@sdeverywhere/check-ui-shell > fontfaceobserver',\n '@sdeverywhere/check-ui-shell > copy-text-to-clipboard',\n '@sdeverywhere/check-ui-shell > chart.js'\n ],\n\n exclude: [\n // XXX: The threads.js implementation references `tiny-worker` as an optional\n // dependency, but it doesn't get used at runtime, so we can just exclude it\n // so that Vite doesn't complain in dev mode\n 'tiny-worker'\n\n // XXX: Similarly, chart.js treats `moment` as an optional dependency, but we\n // don't use it at runtime; we need to exclude it here, otherwise Vite will\n // complain about missing dependencies in dev mode\n // 'moment'\n ]\n },\n\n // Configure path aliases\n resolve: {\n alias: [\n // Use the configured \"baseline\" bundle if defined, otherwise use the \"empty\" bundle\n // (which will cause comparison tests to be skipped)\n alias('@_baseline_bundle_', options?.baseline ? options.baseline.path : '/src/empty-bundle.ts'),\n\n // Use the configured \"current\" bundle\n alias('@_current_bundle_', currentBundlePath),\n\n // Use the configured test config file\n alias('@_test_config_', testConfigPath),\n\n // Make the overlay use the `messages.html` file that is written to the prep directory\n alias('@_prep_', prepDir),\n\n // XXX: Include no-op polyfills for these modules that are used in the Node-specific\n // implementation of threads.js; this allows us to use one bundle that works in both\n // Node and browser environments\n noopPolyfillAlias('events'),\n noopPolyfillAlias('fs'),\n noopPolyfillAlias('os'),\n noopPolyfillAlias('path'),\n noopPolyfillAlias('url'),\n noopPolyfillAlias('worker_threads')\n ]\n },\n\n // Inject special values into the generated JS\n define: {\n // Inject the summary JSON into the build\n __SUITE_SUMMARY_JSON__: JSON.stringify(suiteSummaryJson),\n\n // Inject the baseline branch name\n __BASELINE_NAME__: JSON.stringify(options?.baseline?.name || ''),\n\n // Inject the current branch name\n __CURRENT_NAME__: JSON.stringify(currentBundleName)\n },\n\n plugins: [\n // Inject special values into the generated JS\n // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's\n // built-in `define` feature because the latter does not seem to run before\n // the glob handler (which requires the glob to be injected as a literal)\n replace({\n preventAssignment: true,\n delimiters: ['', ''],\n values: {\n // Inject the path for baseline bundles\n // XXX: Note that we use './baselines/*.txt' instead of something special\n // like './__BASELINE_BUNDLES_PATH__' because sometimes Vite's dependency\n // scanner sees the latter (instead of the injected path) and reports\n // an error since the path does not exist. As a workaround, we use\n // './baselines/*.txt', which gets interpreted as the valid path\n // '.../template-report/src/baselines/*.txt' (see `baselines/unused.txt`).\n './baselines/*.txt': baselinesPath\n }\n }) as unknown as PluginOption\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n\n // Write js/css files to `public` (instead of the default `<outDir>/assets`)\n assetsDir: '',\n\n rollupOptions: {\n output: {\n // XXX: Prevent vite from creating a separate `vendor.js` file\n manualChunks: undefined\n },\n\n onwarn: (warning, warn) => {\n // XXX: Suppress \"Use of eval is strongly discouraged\" warnings that are\n // triggered by use of the following pattern in threads.js:\n // eval(\"require\")(\"worker_threads\")\n // It would be nice to avoid use of `eval` there, but it's not critical for\n // our use case so we will suppress the warnings for now\n if (warning.code !== 'EVAL') {\n warn(warning)\n }\n }\n }\n },\n\n server: {\n // Run the dev server at `localhost:8081` by default\n port: options?.serverPort || 8081,\n\n // Open the app in the browser by default\n open: '/index.html',\n\n // XXX: Add a small delay, otherwise on macOS we sometimes get multiple\n // change events when a file is saved just once. That is a relatively\n // harmless issue except that it causes redundant messages in the console\n // and can cause extra churn when refreshing the app.\n watch: {\n awaitWriteFinish: {\n stabilityThreshold: 100\n }\n }\n }\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { dirname, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, PluginOption } from 'vite'\nimport replace from '@rollup/plugin-replace'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nexport function createViteConfigForTests(projDir: string, prepDir: string, mode: 'build' | 'watch'): InlineConfig {\n // Use `template-tests` as the root directory for the tests project\n const root = resolvePath(__dirname, '..', 'template-tests')\n\n // Get the base glob path; apparently this must be a relative path (relative to\n // the `template-tests/src` directory where the glob is used)\n const templateSrcDir = resolvePath(root, 'src')\n const relProjDir = relative(templateSrcDir, projDir)\n // XXX: The glob pattern must use forward slashes only, so on Windows we need to\n // convert backslashes to slashes\n const relProjDirPath = relProjDir.replaceAll('\\\\', '/')\n\n // Include check test definitions in files matching `checks/*.yaml` under\n // the configured project root directory. We also include `*.check.yaml`,\n // which was the naming used in earlier versions of the create package and\n // related examples.\n // TODO: Use yaml path/pattern from options\n const yamlCheckGlobPatterns = `['${relProjDirPath}/**/checks/*.yaml', '${relProjDirPath}/**/*.check.yaml']`\n\n // Include comparison test definitions in files matching `comparisons/*.yaml`\n // under the configured project root directory\n // TODO: Use yaml path/pattern from options\n const yamlComparisonGlobPatterns = `['${relProjDirPath}/**/comparisons/*.yaml']`\n\n // Calculate output directory relative to the template root\n // TODO: For now we write it to `prepDir`; make this configurable?\n const outDir = relative(root, prepDir)\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO: Disable vite output by default?\n // logLevel: 'silent',\n\n plugins: [\n // Inject special values into the generated JS\n // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's\n // built-in `define` feature because the latter does not seem to run before\n // the glob handler (which requires the glob to be injected as a literal)\n replace({\n preventAssignment: true,\n delimiters: ['', ''],\n values: {\n // Inject the glob patterns for matching model check yaml files\n '\"./__YAML_CHECK_GLOB_PATTERNS__\"': yamlCheckGlobPatterns,\n // Inject the glob patterns for matching model comparison yaml files\n '\"./__YAML_COMPARISON_GLOB_PATTERNS__\"': yamlComparisonGlobPatterns\n }\n }) as unknown as PluginOption\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n emptyOutDir: false,\n\n lib: {\n entry: './src/index.ts',\n formats: ['es'],\n fileName: () => 'check-tests.js'\n },\n\n // Enable watch mode if requested\n watch: mode === 'watch' && {},\n\n rollupOptions: {\n // Prevent dependencies from being included in packaged library\n // TODO: For now we include check-core in the packaged library so that its\n // dependencies are correctly resolved at runtime. Ideally this would only\n // include a couple functions that are used for defining tests, but Vite 2.x\n // does not implement tree shaking for ES libraries, which means the generated\n // library is much larger than it needs to be. Once we upgrade to Vite 3.x,\n // the generated library should be smaller; see related fix:\n // https://github.com/vitejs/vite/pull/8737\n // external: Object.keys(pkg.dependencies)\n }\n }\n }\n}\n"],"mappings":"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","names":["existsSync","dirname","joinPath","relative","fileURLToPath","__filename","__dirname","existsSync","dirname","relative","joinPath","resolvePath","fileURLToPath","__filename","__dirname","dirname","relative","resolvePath","fileURLToPath","replace","__filename","__dirname","joinPath","existsSync","dirname","fileURLToPath","relative"]}
1
+ {"version":3,"sources":["../src/plugin.ts","../src/run-suite.ts","../src/vite-config-for-bundle.ts","../src/vite-config-for-report.ts","../src/vite-config-for-tests.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { copyFile, mkdir } from 'fs/promises'\nimport { dirname, join as joinPath, relative } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, ViteDevServer } from 'vite'\nimport { build, createServer } from 'vite'\n\nimport chokidar from 'chokidar'\n\nimport type { BuildContext, Plugin, ResolvedConfig, ResolvedModelSpec } from '@sdeverywhere/build'\n\nimport type { Bundle, ConfigInitOptions, SuiteSummary } from '@sdeverywhere/check-core'\nimport { createConfig } from '@sdeverywhere/check-core'\n\nimport type { CheckPluginOptions } from './options'\nimport { runTestSuite } from './run-suite'\nimport { createViteConfigForBundle } from './vite-config-for-bundle'\nimport { createViteConfigForReport } from './vite-config-for-report'\nimport { createViteConfigForTests } from './vite-config-for-tests'\n\nexport function checkPlugin(options?: CheckPluginOptions): Plugin {\n return new CheckPlugin(options)\n}\n\ninterface TestOptions {\n currentBundleName: string\n currentBundlePath: string\n testConfigPath: string\n}\n\nclass CheckPlugin implements Plugin {\n private firstBuild = true\n\n constructor(private readonly options?: CheckPluginOptions) {}\n\n async watch(config: ResolvedConfig): Promise<void> {\n if (this.options?.testConfigPath === undefined) {\n // Test config was not provided, so generate a default config in watch mode.\n // The test template uses import.meta.glob so that checks are re-run\n // automatically when the `{checks/comparisons}/*.yaml` files are changed.\n await this.genTestConfig(config, 'watch')\n }\n\n // For development mode, run Vite in dev mode so that it serves the\n // model-check report locally (with live reload enabled). When a model\n // test file is changed, the tests will be re-run in the browser.\n const testOptions = this.resolveTestOptions(config)\n const viteConfig = this.createViteConfigForReport(config, testOptions, undefined)\n const server: ViteDevServer = await createServer(viteConfig)\n await server.listen()\n\n // XXX: Currently Vite doesn't reload the page if a file is added/removed\n // in the baselines directory (Vite's import.meta.glob handling doesn't\n // seem to do this automatically), so as a workaround, watch the baselines\n // directory and restart the server if files are added/removed\n // TODO: The same problem also applies to the glob for `checks/*.yaml` and\n // `comparisons/*.yaml` files in the test config, so we should also reload\n // if files match/unmatch those glob patterns\n // TODO: Use the baselines directory from the config\n const baselinesDir = 'baselines'\n const watcher = chokidar.watch(baselinesDir, {\n // Watch paths are resolved relative to the project root directory\n cwd: config.rootDir,\n // Don't send initial \"file added\" events\n ignoreInitial: true,\n // XXX: Include a delay, otherwise on macOS we sometimes get multiple\n // change events when a file is saved just once\n awaitWriteFinish: {\n stabilityThreshold: 200\n }\n })\n watcher.on('add', () => server.restart())\n watcher.on('unlink', () => server.restart())\n }\n\n // TODO: Note that this plugin runs as a `postBuild` step because it currently\n // needs to run after other plugins, and those plugins need to run after the\n // staged files are copied to their final destination(s). We should probably\n // make it configurable so that it can either be run as a `postGenerate` or a\n // `postBuild` step.\n async postBuild(context: BuildContext, modelSpec: ResolvedModelSpec): Promise<boolean> {\n const firstBuild = this.firstBuild\n this.firstBuild = false\n\n // For both production builds and local development, generate default bundle\n // in this post-build step each time a source file is changed\n // TODO: We could potentially use watch mode for the bundle similar to\n // what we do for the test config, but the bundle depends on the ModelSpec,\n // which currently isn't made available to the `watch` function\n if (this.options?.current === undefined) {\n // Path to current bundle was not provided, so generate a default bundle\n if (context.config.mode === 'development') {\n // Copy the previous bundle to the `baselines` directory so that\n // we automatically have it available as a baseline for comparison\n await this.copyPreviousBundle(context.config)\n }\n context.log('info', 'Generating model check bundle...')\n await this.genCurrentBundle(context, modelSpec)\n }\n\n // For production builds (and for the initial build in local development mode),\n // generate default test config in this post-build step\n if (this.options?.testConfigPath === undefined) {\n if (context.config.mode === 'production' || firstBuild) {\n // Test config was not provided, so generate a default config\n context.log('info', 'Generating model check test configuration...')\n await this.genTestConfig(context.config, 'build')\n }\n }\n\n if (context.config.mode === 'production') {\n // For production builds, run the model checks/comparisons, and then\n // inject the results into the generated report\n const testOptions = this.resolveTestOptions(context.config)\n return this.runChecks(context, testOptions)\n } else {\n // Nothing to do here in dev mode; the dev server will refresh and\n // re-run the tests in the browser when changes are detected\n return true\n }\n }\n\n private async copyPreviousBundle(config: ResolvedConfig): Promise<void> {\n // Only copy if the current bundle exists\n const currentBundleFile = joinPath(config.prepDir, 'check-bundle.js')\n if (existsSync(currentBundleFile)) {\n // TODO: Use the baselines directory from the config (not yet available)\n const baselinesDir = joinPath(config.rootDir, 'baselines')\n if (!existsSync(baselinesDir)) {\n await mkdir(baselinesDir, { recursive: true })\n }\n const previousBundleFile = joinPath(baselinesDir, 'previous.js')\n await copyFile(currentBundleFile, previousBundleFile)\n }\n }\n\n private async genCurrentBundle(context: BuildContext, modelSpec: ResolvedModelSpec): Promise<void> {\n const viteConfig = await createViteConfigForBundle(context, modelSpec)\n await build(viteConfig)\n }\n\n private async genTestConfig(config: ResolvedConfig, mode: 'build' | 'watch'): Promise<void> {\n const rootDir = config.rootDir\n const prepDir = config.prepDir\n const viteConfig = createViteConfigForTests(rootDir, prepDir, mode)\n await build(viteConfig)\n }\n\n private async runChecks(context: BuildContext, testOptions: TestOptions): Promise<boolean> {\n context.log('info', 'Running model checks...')\n\n // Load the bundles used by the model check/compare configuration. We\n // always initialize the \"current\" bundle.\n const moduleR = await import(relativeToSourcePath(testOptions.currentBundlePath))\n const bundleR = moduleR.createBundle() as Bundle\n const bundleNameR = testOptions.currentBundleName\n\n // Only initialize the \"baseline\" bundle if it is defined and the version\n // is the same as the \"current\" one. If the baseline bundle has a different\n // version, we will skip the comparison tests and only run the checks on the\n // current bundle.\n let bundleL: Bundle\n let bundleNameL: string\n if (this.options?.baseline) {\n const moduleL = await import(relativeToSourcePath(this.options.baseline.path))\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const rawBundleL: any = moduleL.createBundle() as any\n if (rawBundleL.version === bundleR.version) {\n bundleL = rawBundleL as Bundle\n bundleNameL = this.options.baseline.name\n }\n }\n\n // Get the model check/comparison configuration\n const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath))\n const configInitOptions: ConfigInitOptions = {\n bundleNameL,\n bundleNameR\n }\n const configOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, configInitOptions)\n\n // Run the suite of checks and comparisons\n const checkConfig = await createConfig(configOptions)\n const result = await runTestSuite(context, checkConfig, /*verbose=*/ false)\n\n // Build the report (using Vite)\n context.log('info', 'Building model check report')\n const viteConfig = this.createViteConfigForReport(context.config, testOptions, result.suiteSummary)\n await build(viteConfig)\n\n // context.log('info', 'Done!')\n\n return result.allChecksPassed\n }\n\n private resolveTestOptions(config: ResolvedConfig): TestOptions {\n let currentBundleName: string\n let currentBundlePath: string\n if (this.options?.current === undefined) {\n // Path to current bundle was not provided, so use a generated bundle\n currentBundleName = 'current'\n currentBundlePath = joinPath(config.prepDir, 'check-bundle.js')\n } else {\n // Use the provided bundle\n currentBundleName = this.options.current.name\n currentBundlePath = this.options.current.path\n }\n\n let testConfigPath: string\n if (this.options?.testConfigPath === undefined) {\n // Test config was not provided, so use a generated config\n testConfigPath = joinPath(config.prepDir, 'check-tests.js')\n } else {\n // Use the provided test config\n testConfigPath = this.options.testConfigPath\n }\n\n return {\n currentBundleName,\n currentBundlePath,\n testConfigPath\n }\n }\n\n private createViteConfigForReport(\n config: ResolvedConfig,\n testOptions: TestOptions,\n suiteSummary: SuiteSummary | undefined\n ): InlineConfig {\n return createViteConfigForReport(\n this.options,\n config.rootDir,\n config.prepDir,\n testOptions.currentBundleName,\n testOptions.currentBundlePath,\n testOptions.testConfigPath,\n suiteSummary\n )\n }\n}\n\n/**\n * Return a Unix-style path (e.g. '../../foo.js') that is relative to the directory of\n * the current source file. This can be used to construct a path that is safe for\n * dynamic import on either Unix or Windows.\n *\n * @param filePath The path to make relative.\n */\nfunction relativeToSourcePath(filePath: string): string {\n const srcDir = dirname(fileURLToPath(import.meta.url))\n const relPath = relative(srcDir, filePath)\n return relPath.replaceAll('\\\\', '/')\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport { performance } from 'perf_hooks'\n\nimport pico from 'picocolors'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type {\n Config,\n ComparisonReport,\n PerfReport,\n RunSuiteCallbacks,\n CheckReport,\n CheckStatus,\n ComparisonConfig,\n CheckTestReport,\n SuiteSummary\n} from '@sdeverywhere/check-core'\nimport {\n datasetMessage,\n predicateMessage,\n runSuite,\n scenarioMessage,\n suiteSummaryFromReport\n} from '@sdeverywhere/check-core'\n\nexport interface RunTestSuiteResult {\n allChecksPassed: boolean\n suiteSummary: SuiteSummary\n}\n\n/**\n * Runs the test suite.\n */\nexport async function runTestSuite(\n context: BuildContext,\n config: Config,\n verbose: boolean\n): Promise<RunTestSuiteResult> {\n return new Promise((resolve, reject) => {\n const t0 = performance.now()\n let lastPctByInc: number\n const callbacks: RunSuiteCallbacks = {\n onProgress: progress => {\n const pct = Math.round(progress * 100)\n const pctByInc = Math.floor(pct / 5) * 5\n if (lastPctByInc === undefined || pctByInc > lastPctByInc) {\n lastPctByInc = pctByInc\n context.log('info', `${pctByInc}%`)\n }\n },\n onComplete: report => {\n try {\n const t1 = performance.now()\n const elapsedMillis = t1 - t0\n const elapsedSeconds = (elapsedMillis / 1000).toFixed(1)\n context.log('info', `\\nTest suite completed in ${elapsedSeconds}s`)\n\n // Print check summary to the console\n const allChecksPassed = printCheckSummary(context, report.checkReport, verbose)\n\n if (report.comparisonReport) {\n // Print the perf stats to the console\n printPerfStats(context, config.comparison, report.comparisonReport)\n }\n\n // Convert check and compare reports to terse form that only includes\n // failed/errored checks or comparisons with differences\n // TODO: The terse form was originally used when we had to write the\n // results to a JSON file and then read them back in when building\n // the report, but we no longer use that intermediate file, so there's\n // less reason to use the terse form (since it requires the web app\n // code to reconstruct the results). But for now, we will continue\n // to use the terse form, and later we can update the app code.\n const suiteSummary = suiteSummaryFromReport(report, elapsedMillis)\n\n resolve({\n allChecksPassed,\n suiteSummary\n })\n } catch (e) {\n reject(e)\n }\n },\n onError: error => {\n reject(error)\n }\n }\n runSuite(config, callbacks)\n })\n}\n\nfunction printCheckSummary(context: BuildContext, checkReport: CheckReport, verbose: boolean): boolean {\n function printResult(indent: number, status: CheckStatus, text: string): void {\n if (!verbose && status === 'passed' && indent > 1) {\n return\n }\n let statusChar: string\n switch (status) {\n case 'passed':\n statusChar = '✓'\n break\n case 'failed':\n statusChar = '✗'\n break\n case 'error':\n statusChar = '‼'\n break\n case 'skipped':\n statusChar = '–'\n break\n default:\n statusChar = ''\n break\n }\n const msg = `${' '.repeat(indent)}${statusChar} ${text}`\n context.log('info', status === 'passed' ? pico.green(msg) : pico.red(msg))\n }\n\n function bold(s: string): string {\n return pico.bold(s)\n }\n\n function printTest(test: CheckTestReport): void {\n const msg = `${test.name}${verbose || test.status !== 'passed' ? ':' : ''}`\n printResult(1, test.status, msg)\n }\n\n let allPassed = true\n context.log('info', '\\nCheck results:')\n for (const group of checkReport.groups) {\n context.log('info', `\\n${group.name}`)\n\n for (const test of group.tests) {\n if (test.status !== 'passed') {\n allPassed = false\n }\n printTest(test)\n\n for (const scenario of test.scenarios) {\n printResult(3, scenario.status, scenarioMessage(scenario, bold))\n\n for (const dataset of scenario.datasets) {\n printResult(5, dataset.status, datasetMessage(dataset, bold))\n\n for (const predicate of dataset.predicates) {\n printResult(7, predicate.result.status, predicateMessage(predicate, bold))\n }\n }\n }\n }\n }\n context.log('info', '')\n\n return allPassed\n}\n\nfunction stat(label: string, n: number): string {\n return `${label}=${n.toFixed(1)}ms`\n}\n\nfunction printPerfReportLine(context: BuildContext, perfReport: PerfReport): void {\n const avg = stat('avg', perfReport.avgTime)\n const min = stat('min', perfReport.minTime)\n const max = stat('max', perfReport.maxTime)\n context.log('info', ` ${avg} ${min} ${max}`)\n}\n\nfunction printPerfStats(context: BuildContext, comparisonConfig: ComparisonConfig, report: ComparisonReport): void {\n context.log('info', '\\nPerformance stats:')\n context.log('info', ` ${comparisonConfig.bundleL.name}:`)\n printPerfReportLine(context, report.perfReportL)\n context.log('info', ` ${comparisonConfig.bundleR.name}:`)\n printPerfReportLine(context, report.perfReportR)\n context.log('info', '')\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, readFileSync, statSync } from 'fs'\nimport { basename, dirname, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, ResolvedConfig, Plugin as VitePlugin } from 'vite'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nimport type { BuildContext, ResolvedModelSpec } from '@sdeverywhere/build'\nimport { encodeImplVars } from '@sdeverywhere/check-core'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\n/**\n * This is a virtual module plugin used to inject model-specific configuration\n * values into the generated worker bundle.\n *\n * This follows the \"Virtual Modules Convention\" described here:\n * https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention\n *\n * TODO: This could be simplified by using `vite-plugin-virtual` but that\n * doesn't seem to be working correctly in an ESM setting\n */\nfunction injectModelSpec(context: BuildContext, modelSpec: ResolvedModelSpec): VitePlugin {\n const prepDir = context.config.prepDir\n\n // Include the SDE variable ID with each spec\n const inputSpecs = []\n for (const modelInputSpec of modelSpec.inputs) {\n // Note that the `InputSpec` interface in the `@sdeverywhere/build` package\n // allows the default/min/max values to be undefined, which can be the case\n // if the user doesn't return full `InputSpec` instances in the `ModelSpec`.\n // We will log a warning and skip the input if these values are not defined.\n if (\n modelInputSpec.defaultValue === undefined ||\n modelInputSpec.minValue === undefined ||\n modelInputSpec.maxValue === undefined\n ) {\n let msg = ''\n msg += `WARNING: The {defaultValue,minValue,maxValue} properties are required by plugin-check, `\n msg += `but are undefined in the InputSpec for '${modelInputSpec.varName}'. `\n msg += `This input variable will be excluded from the model-check bundle until those properties `\n msg += `are defined.`\n console.warn(msg)\n continue\n }\n\n // Use the `inputId` if defined for the `InputSpec`, otherwise use `varId`. The\n // latter is less resilient if the variable is renamed between two versions of\n // the model, but will be sufficient for now. Note that `plugin-config` defines\n // a stable `inputId` for each row in the `inputs.csv`, and that is the most\n // common way to configure a `ModelSpec`, so it will be uncommon for `inputId`\n // to be undefined here.\n const varId = context.canonicalVarId(modelInputSpec.varName)\n const inputId = modelInputSpec.inputId || varId\n inputSpecs.push({\n inputId,\n varId,\n ...modelInputSpec\n })\n }\n\n // Include the SDE variable ID with each output variable spec\n const outputSpecs = modelSpec.outputs.map(o => {\n return {\n varId: context.canonicalVarId(o.varName),\n ...o\n }\n })\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function readJsonListing(): any {\n const path = joinPath(prepDir, 'build', 'processed.json')\n if (existsSync(path)) {\n const json = readFileSync(path, 'utf8')\n return JSON.parse(json)\n } else {\n return {}\n }\n }\n\n // Read the JSON model listing\n const listing = readJsonListing()\n\n // Extract the `varInstances` object from the model listing\n const varInstances = listing.varInstances || {}\n\n // Encode the `varInstances` object into a more efficient format to reduce the bundle size\n const encodedImplVars = encodeImplVars(varInstances)\n\n function stagedFileSize(filename: string): number {\n const path = joinPath(prepDir, 'staged', 'model', filename)\n if (existsSync(path)) {\n return statSync(path).size\n } else {\n return 0\n }\n }\n\n // The size (in bytes) of the `generated-model.js` file\n // TODO: Ideally we would measure the size of the raw Wasm binary, but currently\n // we inline it as a base64 blob inside the JS file, so we take the size of the\n // whole JS file as the second best option\n const modelSizeInBytes = stagedFileSize('generated-model.js')\n\n // The size (in bytes) of the `static-data.ts` file\n // TODO: Ideally we would measure the size of the minified JS file here, or\n // at least ignore things like whitespace\n const dataSizeInBytes = stagedFileSize('static-data.ts')\n\n const moduleSrc = `\nexport const inputSpecs = ${JSON.stringify(inputSpecs)};\nexport const outputSpecs = ${JSON.stringify(outputSpecs)};\nexport const encodedImplVars = ${JSON.stringify(encodedImplVars)};\nexport const modelSizeInBytes = ${modelSizeInBytes};\nexport const dataSizeInBytes = ${dataSizeInBytes};\n`\n\n const virtualModuleId = 'virtual:model-spec'\n const resolvedVirtualModuleId = '\\0' + virtualModuleId\n\n return {\n name: 'vite-plugin-virtual-custom',\n resolveId(id: string) {\n if (id === virtualModuleId) {\n return resolvedVirtualModuleId\n }\n },\n load(id: string) {\n if (id === resolvedVirtualModuleId) {\n return moduleSrc\n }\n }\n }\n}\n\n/**\n * XXX: This overrides the built-in `vite:resolve` plugin so that we can intercept `resolveId`\n * calls for the threads package.\n */\nfunction overrideViteResolvePlugin(viteConfig: ResolvedConfig) {\n const resolvePlugin = viteConfig.plugins.find(p => p.name === 'vite:resolve')\n if (resolvePlugin === undefined) {\n throw new Error('Failed to locate the built-in vite:resolve plugin')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const originalResolveId = resolvePlugin.resolveId as any\n resolvePlugin.resolveId = async function resolveId(id, importer, options) {\n if (id.startsWith('./implementation') && importer.includes('threads/dist-esm')) {\n // XXX: The default resolver behavior will look at the `browser` mappings in\n // `threads/package.json` and try to resolve `implementation.js` to\n // `implementation.browser.js` because it thinks we're in a browser-only context.\n // We don't want that. Instead we want to keep the generic implementation from\n // threads that chooses between the Node and browser implementations at runtime.\n //\n // If we get here, importer will be something like:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/index.js\n // And id will be:\n // ./implementation\n // So resolve the ID to:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.js\n //\n // Or, importer will be:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.js\n // And id will be one of:\n // ./implementation.browser\n // ./implementation.node\n // ./implementation.worker_threads\n // So resolve the ID to:\n // /.../node_modules/.pnpm/threads@1.7.0/node_modules/threads/dist-esm/{worker,master}/implementation.{...}.js\n const idFileName = id.replace('./', '')\n const importerFileName = basename(importer)\n const resolvedId = importer.replace(importerFileName, `${idFileName}.js`)\n return {\n id: resolvedId,\n moduleSideEffects: false\n }\n }\n\n // For all other cases, fall back on the default resolver\n return await originalResolveId.handler.call(this, id, importer, options)\n }\n}\n\nexport async function createViteConfigForBundle(\n context: BuildContext,\n modelSpec: ResolvedModelSpec\n): Promise<InlineConfig> {\n // Use `template-bundle` as the root directory for the bundle project\n const root = resolvePath(__dirname, '..', 'template-bundle')\n\n // Calculate output directory relative to the template root\n // TODO: For now we write it to `prepDir`; make this configurable?\n const prepDir = context.config.prepDir\n const outDir = relative(root, prepDir)\n\n // Use the model worker from the staged directory\n // TODO: Make this configurable?\n const modelWorkerPath = joinPath(prepDir, 'staged', 'model', 'worker.js?raw')\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO: Disable vite output by default?\n // logLevel: 'silent',\n\n // Configure path aliases\n resolve: {\n alias: [\n // Inject the configured model worker\n {\n find: '@_model_worker_',\n replacement: modelWorkerPath\n },\n\n // XXX: Prevent Vite from using the `browser` section of `threads/package.json`\n // since we want to force the use of the general module (under dist-esm) that chooses\n // the correct implementation (Web Worker vs worker_threads) at runtime. Currently\n // Vite's library mode is browser focused and generally chooses the right imports,\n // except in the case of the threads package where we want to use the generic\n // `implementation.js` that chooses between Web Worker and worker_threads at runtime.\n // Note that we could in theory set `resolve.browserField` to false, but that would\n // make Vite not use the browser field for all other packages, and there is not\n // currently a way to tell Vite to use the browser field on a case-by-case basis.\n // So for now we need this workaround here to make it resolve to `dist-esm`, and then\n // a second workaround in `overrideViteResolvePlugin` to prevent the resolver from\n // using the browser field when resolving the threads package.\n {\n find: 'threads',\n replacement: 'threads',\n customResolver: async function (source, importer, options) {\n // Note that we need to use `resolveId.call` here in order to provide the\n // right `this` context, which provides Rollup plugin functionality\n const customResolver = nodeResolve({ browser: false })\n const resolved = await customResolver.resolveId.call(this, source, importer, options)\n // Force the use of the `dist-esm` variant of the threads.js package\n if (source === 'threads/worker') {\n return resolved.id.replace('worker.mjs', 'dist-esm/worker/index.js')\n } else {\n return resolved.id.replace('index.mjs', 'dist-esm/index.js')\n }\n }\n }\n ]\n },\n\n plugins: [\n // Use a virtual module plugin to inject the model spec values\n injectModelSpec(context, modelSpec),\n\n // XXX: Install a wrapper around the built-in `vite:resolve` plugin so that we can\n // override the default resolver behavior that tries to resolve the `browser` section\n // of the `package.json` for the threads package.\n {\n name: 'vite-plugin-override-resolve',\n configResolved(viteConfig) {\n overrideViteResolvePlugin(viteConfig)\n }\n }\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n emptyOutDir: false,\n\n // Uncomment for debugging purposes\n // minify: false,\n\n lib: {\n entry: './src/index.ts',\n formats: ['es'],\n fileName: () => 'check-bundle.js'\n },\n\n rollupOptions: {\n // Don't transform Node imports used by threads.js\n external: ['events', 'os', 'path', 'url'],\n\n // XXX: Insert custom code at the top of the generated bundle that defines\n // the special `__non_webpack_require__` function that is used by threads.js\n // in its Node implementation. This import ensures that threads.js uses\n // the native `worker_threads` implementation when using the bundle in a\n // Node environment. When importing the bundle for use in the browser,\n // Vite will transform this import into an empty module due to the empty\n // polyfill that is configured in `vite-config-for-report.ts`.\n output: {\n banner: `\nimport * as worker_threads from 'worker_threads'\nlet __non_webpack_require__ = () => {\n return worker_threads;\n};\n`\n },\n\n onwarn: (warning, warn) => {\n // XXX: Suppress \"Use of eval is strongly discouraged\" warnings that are\n // triggered by use of the following pattern in threads.js:\n // eval(\"require\")(\"worker_threads\")\n // It would be nice to avoid use of `eval` there, but it's not critical for\n // our use case so we will suppress the warnings for now\n if (warning.code !== 'EVAL') {\n warn(warning)\n }\n }\n }\n }\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, mkdirSync } from 'fs'\nimport { dirname, relative, join as joinPath, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { Alias, InlineConfig, PluginOption } from 'vite'\nimport replace from '@rollup/plugin-replace'\n\nimport type { SuiteSummary } from '@sdeverywhere/check-core'\n\nimport type { CheckPluginOptions } from './options'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nexport function createViteConfigForReport(\n options: CheckPluginOptions | undefined,\n projDir: string,\n prepDir: string,\n currentBundleName: string,\n currentBundlePath: string,\n testConfigPath: string,\n suiteSummary: SuiteSummary | undefined\n): InlineConfig {\n // Use `template-report` as the root directory for the report project\n const root = resolvePath(__dirname, '..', 'template-report')\n\n // Make sure the `baselines` directory exists, otherwise Vite's dependency scanner\n // may report errors when processing the `import.meta.glob` call\n const baselinesDir = resolvePath(projDir, 'baselines')\n if (!existsSync(baselinesDir)) {\n mkdirSync(baselinesDir, { recursive: true })\n }\n\n // Include `baselines/*.js` files under the configured project root directory. This\n // glob path apparently must be a relative path (relative to the `template-report/src`\n // directory where the glob is used).\n const templateSrcDir = resolvePath(root, 'src')\n const relProjDir = relative(templateSrcDir, projDir)\n // XXX: The glob pattern must use forward slashes only, so on Windows we need to\n // convert backslashes to slashes\n const relProjDirPath = relProjDir.replaceAll('\\\\', '/')\n // TODO: Use baselinesDir from options\n const baselinesPath = `${relProjDirPath}/baselines/*.js`\n\n // Calculate output directory relative to the template root\n let reportPath: string\n if (options?.reportPath) {\n reportPath = options.reportPath\n } else {\n reportPath = joinPath(prepDir, 'check-report')\n }\n const outDir = relative(root, reportPath)\n\n // Convert the suite summary to JSON, which is what the app currently expects\n const suiteSummaryJson = suiteSummary ? JSON.stringify(suiteSummary) : ''\n\n const alias = (find: string, replacement: string) => {\n return {\n find,\n replacement\n } as Alias\n }\n\n // XXX: This provides custom handling for Node built-ins such as 'events' that are\n // referenced by the check bundle (specifically in the Node implementation of\n // threads.js). These are not actually used in the browser, so we just need\n // to provide no-op polyfills for these.\n const noopPolyfillAlias = (find: string) => {\n return {\n find,\n replacement: '/polyfills/noop-polyfills.ts'\n } as Alias\n }\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Use `.` as the base directory (instead of the default `/`); this controls\n // how the path to the js/css files are generated in `index.html`\n base: '',\n\n // Use a custom cache directory under `prepDir`, as otherwise Vite will use\n // `packages/plugin-check/template-report/node_modules/.vite`, and we want to\n // avoid generating files in `template-report` (which should be read-only)\n cacheDir: joinPath(prepDir, '.vite-check-report'),\n\n // Load static files from `static` (instead of the default `public`)\n // publicDir: 'static',\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO\n // logLevel: 'silent',\n\n optimizeDeps: {\n // Prevent Vite from examining other html files when scanning entrypoints\n // for dependency optimization\n entries: ['index.html'],\n\n // XXX: When plugin-check is installed via pnpm, the Vite dev server seems\n // to have no trouble resolving other dependencies using the optimizeDeps\n // mechanism. However, this fails when the package is installed via yarn\n // or npm (probably due to the fact that the `template-report` directory\n // is located under the top-level `node_modules` directory); in the browser,\n // there will be \"import not found\" errors for the packages referenced below.\n // As a terrible workaround, explicitly include the direct dependencies so\n // that Vite optimizes them; this works for pnpm, yarn, and npm. We should\n // find a less fragile solution.\n include: [\n // from check-core\n '@sdeverywhere/check-core > assert-never',\n '@sdeverywhere/check-core > ajv',\n '@sdeverywhere/check-core > neverthrow',\n '@sdeverywhere/check-core > yaml',\n // from check-ui-shell\n '@sdeverywhere/check-ui-shell > fontfaceobserver',\n '@sdeverywhere/check-ui-shell > copy-text-to-clipboard',\n '@sdeverywhere/check-ui-shell > chart.js'\n ],\n\n exclude: [\n // XXX: The threads.js implementation references `tiny-worker` as an optional\n // dependency, but it doesn't get used at runtime, so we can just exclude it\n // so that Vite doesn't complain in dev mode\n 'tiny-worker'\n\n // XXX: Similarly, chart.js treats `moment` as an optional dependency, but we\n // don't use it at runtime; we need to exclude it here, otherwise Vite will\n // complain about missing dependencies in dev mode\n // 'moment'\n ]\n },\n\n // Configure path aliases\n resolve: {\n alias: [\n // Use the configured \"baseline\" bundle if defined, otherwise use the \"empty\" bundle\n // (which will cause comparison tests to be skipped)\n alias('@_baseline_bundle_', options?.baseline ? options.baseline.path : '/src/empty-bundle.ts'),\n\n // Use the configured \"current\" bundle\n alias('@_current_bundle_', currentBundlePath),\n\n // Use the configured test config file\n alias('@_test_config_', testConfigPath),\n\n // Make the overlay use the `messages.html` file that is written to the prep directory\n alias('@_prep_', prepDir),\n\n // XXX: Include no-op polyfills for these modules that are used in the Node-specific\n // implementation of threads.js; this allows us to use one bundle that works in both\n // Node and browser environments\n noopPolyfillAlias('events'),\n noopPolyfillAlias('fs'),\n noopPolyfillAlias('os'),\n noopPolyfillAlias('path'),\n noopPolyfillAlias('url'),\n noopPolyfillAlias('worker_threads')\n ]\n },\n\n // Inject special values into the generated JS\n define: {\n // Inject the summary JSON into the build\n __SUITE_SUMMARY_JSON__: JSON.stringify(suiteSummaryJson),\n\n // Inject the baseline branch name\n __BASELINE_NAME__: JSON.stringify(options?.baseline?.name || ''),\n\n // Inject the current branch name\n __CURRENT_NAME__: JSON.stringify(currentBundleName)\n },\n\n plugins: [\n // Inject special values into the generated JS\n // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's\n // built-in `define` feature because the latter does not seem to run before\n // the glob handler (which requires the glob to be injected as a literal)\n replace({\n preventAssignment: true,\n delimiters: ['', ''],\n values: {\n // Inject the path for baseline bundles\n // XXX: Note that we use './baselines/*.txt' instead of something special\n // like './__BASELINE_BUNDLES_PATH__' because sometimes Vite's dependency\n // scanner sees the latter (instead of the injected path) and reports\n // an error since the path does not exist. As a workaround, we use\n // './baselines/*.txt', which gets interpreted as the valid path\n // '.../template-report/src/baselines/*.txt' (see `baselines/unused.txt`).\n './baselines/*.txt': baselinesPath\n }\n }) as unknown as PluginOption\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n\n // Write js/css files to `public` (instead of the default `<outDir>/assets`)\n assetsDir: '',\n\n rollupOptions: {\n output: {\n // XXX: Prevent vite from creating a separate `vendor.js` file\n manualChunks: undefined\n },\n\n onwarn: (warning, warn) => {\n // XXX: Suppress \"Use of eval is strongly discouraged\" warnings that are\n // triggered by use of the following pattern in threads.js:\n // eval(\"require\")(\"worker_threads\")\n // It would be nice to avoid use of `eval` there, but it's not critical for\n // our use case so we will suppress the warnings for now\n if (warning.code !== 'EVAL') {\n warn(warning)\n }\n }\n }\n },\n\n server: {\n // Run the dev server at `localhost:8081` by default\n port: options?.serverPort || 8081,\n\n // Open the app in the browser by default\n open: '/index.html',\n\n // XXX: Add a small delay, otherwise on macOS we sometimes get multiple\n // change events when a file is saved just once. That is a relatively\n // harmless issue except that it causes redundant messages in the console\n // and can cause extra churn when refreshing the app.\n watch: {\n awaitWriteFinish: {\n stabilityThreshold: 100\n }\n }\n }\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { dirname, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, PluginOption } from 'vite'\nimport replace from '@rollup/plugin-replace'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nexport function createViteConfigForTests(projDir: string, prepDir: string, mode: 'build' | 'watch'): InlineConfig {\n // Use `template-tests` as the root directory for the tests project\n const root = resolvePath(__dirname, '..', 'template-tests')\n\n // Get the base glob path; apparently this must be a relative path (relative to\n // the `template-tests/src` directory where the glob is used)\n const templateSrcDir = resolvePath(root, 'src')\n const relProjDir = relative(templateSrcDir, projDir)\n // XXX: The glob pattern must use forward slashes only, so on Windows we need to\n // convert backslashes to slashes\n const relProjDirPath = relProjDir.replaceAll('\\\\', '/')\n\n // Include check test definitions in files matching `checks/*.yaml` under\n // the configured project root directory. We also include `*.check.yaml`,\n // which was the naming used in earlier versions of the create package and\n // related examples.\n // TODO: Use yaml path/pattern from options\n const yamlCheckGlobPatterns = `['${relProjDirPath}/**/checks/*.yaml', '${relProjDirPath}/**/*.check.yaml']`\n\n // Include comparison test definitions in files matching `comparisons/*.yaml`\n // under the configured project root directory\n // TODO: Use yaml path/pattern from options\n const yamlComparisonGlobPatterns = `['${relProjDirPath}/**/comparisons/*.yaml']`\n\n // Calculate output directory relative to the template root\n // TODO: For now we write it to `prepDir`; make this configurable?\n const outDir = relative(root, prepDir)\n\n return {\n // Don't use an external config file\n configFile: false,\n\n // Use the root directory configured above\n root,\n\n // Don't clear the screen in dev mode so that we can see builder output\n clearScreen: false,\n\n // TODO: Disable vite output by default?\n // logLevel: 'silent',\n\n plugins: [\n // Inject special values into the generated JS\n // TODO: We currently have to use `@rollup/plugin-replace` instead of Vite's\n // built-in `define` feature because the latter does not seem to run before\n // the glob handler (which requires the glob to be injected as a literal)\n replace({\n preventAssignment: true,\n delimiters: ['', ''],\n values: {\n // Inject the glob patterns for matching model check yaml files\n '\"./__YAML_CHECK_GLOB_PATTERNS__\"': yamlCheckGlobPatterns,\n // Inject the glob patterns for matching model comparison yaml files\n '\"./__YAML_COMPARISON_GLOB_PATTERNS__\"': yamlComparisonGlobPatterns\n }\n }) as unknown as PluginOption\n ],\n\n build: {\n // Write output files to the configured directory (instead of the default `dist`);\n // note that this must be relative to the project `root`\n outDir,\n emptyOutDir: false,\n\n lib: {\n entry: './src/index.ts',\n formats: ['es'],\n fileName: () => 'check-tests.js'\n },\n\n // Enable watch mode if requested\n watch: mode === 'watch' && {},\n\n rollupOptions: {\n // Prevent dependencies from being included in packaged library\n // TODO: For now we include check-core in the packaged library so that its\n // dependencies are correctly resolved at runtime. Ideally this would only\n // include a couple functions that are used for defining tests, but Vite 2.x\n // does not implement tree shaking for ES libraries, which means the generated\n // library is much larger than it needs to be. Once we upgrade to Vite 3.x,\n // the generated library should be smaller; see related fix:\n // https://github.com/vitejs/vite/pull/8737\n // external: Object.keys(pkg.dependencies)\n }\n }\n }\n}\n"],"mappings":"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","names":["existsSync","dirname","joinPath","relative","fileURLToPath","__filename","__dirname","existsSync","dirname","relative","joinPath","resolvePath","fileURLToPath","__filename","__dirname","dirname","relative","resolvePath","fileURLToPath","replace","__filename","__dirname","joinPath","existsSync","dirname","fileURLToPath","relative"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/plugin-check",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "files": [
5
5
  "bin/**",
6
6
  "dist/**",
@@ -25,8 +25,8 @@
25
25
  "dependencies": {
26
26
  "@rollup/plugin-node-resolve": "^13.3.0",
27
27
  "@rollup/plugin-replace": "^6.0.2",
28
- "@sdeverywhere/check-core": "^0.1.5",
29
- "@sdeverywhere/check-ui-shell": "^0.2.10",
28
+ "@sdeverywhere/check-core": "^0.1.6",
29
+ "@sdeverywhere/check-ui-shell": "^0.2.12",
30
30
  "@sdeverywhere/runtime": "^0.2.7",
31
31
  "@sdeverywhere/runtime-async": "^0.2.7",
32
32
  "assert-never": "^1.2.1",
@@ -0,0 +1,159 @@
1
+ // Copyright (c) 2024 Climate Interactive / New Venture Fund
2
+
3
+ import type {
4
+ Dataset,
5
+ DatasetKey,
6
+ DatasetMap,
7
+ DatasetsResult,
8
+ ImplVar,
9
+ ModelSpec,
10
+ ScenarioSpec
11
+ } from '@sdeverywhere/check-core'
12
+
13
+ import {
14
+ Outputs,
15
+ type InputValue,
16
+ type InputVarId,
17
+ type ModelRunner,
18
+ type OutputVarId,
19
+ type Point,
20
+ type VarSpec
21
+ } from '@sdeverywhere/runtime'
22
+
23
+ import { setInputsForScenario, type Input } from './inputs'
24
+
25
+ export class BundleModelRunner {
26
+ private readonly inputs: InputValue[]
27
+ private outputs: Outputs
28
+
29
+ /**
30
+ * @param modelSpec The spec for the bundled model.
31
+ * @param inputMap The model inputs.
32
+ * @param modelRunner The model runner.
33
+ */
34
+ constructor(
35
+ public readonly modelSpec: ModelSpec,
36
+ private readonly inputMap: Map<InputVarId, Input>,
37
+ private readonly modelRunner: ModelRunner
38
+ ) {
39
+ // Derive an array of `InputValue` instances that can be passed to the runner
40
+ this.inputs = [...inputMap.values()].map(input => input.value)
41
+
42
+ // Create an `Outputs` instance that is initialized to hold output data
43
+ // produced by the Wasm model
44
+ this.outputs = modelRunner.createOutputs()
45
+ }
46
+
47
+ async runModelForScenario(scenario: ScenarioSpec, datasetKeys: DatasetKey[]): Promise<DatasetsResult> {
48
+ // Set the input values according to the given scenario
49
+ setInputsForScenario(this.inputMap, scenario)
50
+
51
+ if (datasetKeys[0]?.startsWith('ModelImpl')) {
52
+ // This is a batch of dataset keys corresponding to impl variables
53
+ return this.runModelWithImplOutputs(datasetKeys)
54
+ } else {
55
+ // This is a batch of dataset keys corresponding to normal model outputs
56
+ return this.runModelWithNormalOutputs(datasetKeys)
57
+ }
58
+ }
59
+
60
+ private async runModelWithNormalOutputs(datasetKeys: DatasetKey[]): Promise<DatasetsResult> {
61
+ // Run the JS model
62
+ this.outputs = await this.modelRunner.runModel(this.inputs, this.outputs)
63
+ const modelRunTime = this.outputs.runTimeInMillis
64
+
65
+ // Extract the data for each requested output variable and put it into a map
66
+ const datasetMap: DatasetMap = new Map()
67
+ for (const datasetKey of datasetKeys) {
68
+ // Get the output variable for the given dataset key; if the variable doesn't
69
+ // exist in this version of the model/bundle, just skip it
70
+ const outputVar = this.modelSpec.outputVars.get(datasetKey)
71
+ if (!outputVar) {
72
+ continue
73
+ }
74
+
75
+ if (outputVar.sourceName === undefined) {
76
+ // See if we have data for the requested model output variable
77
+ const series = this.outputs.getSeriesForVar(outputVar.varId)
78
+ if (series) {
79
+ datasetMap.set(datasetKey, datasetFromPoints(series.points))
80
+ }
81
+ } else {
82
+ console.error('Static data sources not yet handled in default model check bundle')
83
+ }
84
+ }
85
+
86
+ return {
87
+ datasetMap,
88
+ modelRunTime
89
+ }
90
+ }
91
+
92
+ private async runModelWithImplOutputs(datasetKeys: DatasetKey[]): Promise<DatasetsResult> {
93
+ // Get the `ImplVar` instances for the requested dataset keys
94
+ const implVars: ImplVar[] = []
95
+ for (const datasetKey of datasetKeys) {
96
+ const implVar = this.modelSpec.implVars.get(datasetKey)
97
+ if (implVar) {
98
+ implVars.push(implVar)
99
+ }
100
+ }
101
+
102
+ // Run the model and capture the data for the impl vars
103
+ const startTime = this.outputs.startTime
104
+ const endTime = this.outputs.endTime
105
+ const saveFreq = this.outputs.saveFreq
106
+ let implOutputs = createImplOutputs(implVars, startTime, endTime, saveFreq)
107
+ implOutputs = await this.modelRunner.runModel(this.inputs, implOutputs)
108
+ const modelRunTime = implOutputs.runTimeInMillis
109
+
110
+ // Copy the data into a map
111
+ const datasetMap: DatasetMap = new Map()
112
+ for (const datasetKey of datasetKeys) {
113
+ const implVar = this.modelSpec.implVars.get(datasetKey)
114
+ const series = implOutputs.getSeriesForVar(implVar.varId)
115
+ if (series) {
116
+ datasetMap.set(datasetKey, datasetFromPoints(series.points))
117
+ }
118
+ }
119
+
120
+ return {
121
+ datasetMap,
122
+ modelRunTime
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Create a `Dataset` containing the given data points from the model.
129
+ */
130
+ function datasetFromPoints(points: Point[]): Dataset {
131
+ const dataMap = new Map()
132
+ for (const point of points) {
133
+ // We omit points that have an undefined value. SDE represents `:NA:` values as
134
+ // a special value (`-DBL_MAX`); the `runtime` package detects these and converts
135
+ // them to undefined instead. We don't need them to appear in graphs, so omit them.
136
+ if (point.y !== undefined) {
137
+ dataMap.set(point.x, point.y)
138
+ }
139
+ }
140
+ return dataMap
141
+ }
142
+
143
+ function createImplOutputs(implVars: ImplVar[], startTime: number, endTime: number, saveFreq?: number): Outputs {
144
+ // Create a `VarSpec` for each impl variable
145
+ const varIds: OutputVarId[] = []
146
+ const varSpecs: VarSpec[] = []
147
+ for (const implVar of implVars) {
148
+ varIds.push(implVar.varId)
149
+ varSpecs.push({
150
+ varIndex: implVar.varIndex,
151
+ subscriptIndices: implVar.subscriptIndices
152
+ })
153
+ }
154
+
155
+ // Create a new `Outputs` instance that accepts the impl variables
156
+ const newOutputs = new Outputs(varIds, startTime, endTime, saveFreq)
157
+ newOutputs.varSpecs = varSpecs
158
+ return newOutputs
159
+ }
@@ -4,25 +4,24 @@ import type {
4
4
  Bundle,
5
5
  BundleGraphData,
6
6
  BundleModel as CheckBundleModel,
7
- Dataset,
8
7
  DatasetKey,
9
- DatasetMap,
10
8
  DatasetsResult,
11
9
  LinkItem,
12
10
  ModelSpec,
13
11
  ScenarioSpec
14
12
  } from '@sdeverywhere/check-core'
15
13
 
16
- import type { InputValue, InputVarId, ModelRunner, Outputs, Point } from '@sdeverywhere/runtime'
14
+ import type { InputVarId } from '@sdeverywhere/runtime'
17
15
  import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async'
18
16
 
19
- import type { Input } from './inputs'
20
- import { getInputVars, setInputsForScenario } from './inputs'
17
+ import { getImplVars } from './impl-vars'
18
+ import { type Input, getInputVars } from './inputs'
21
19
  import { getOutputVars } from './outputs'
22
20
 
23
- import { inputSpecs, outputSpecs, modelSizeInBytes, dataSizeInBytes } from 'virtual:model-spec'
21
+ import { encodedImplVars, inputSpecs, outputSpecs, modelSizeInBytes, dataSizeInBytes } from 'virtual:model-spec'
24
22
 
25
23
  import modelWorkerJs from '@_model_worker_/worker.js?raw'
24
+ import { BundleModelRunner } from './bundle-model-runner'
26
25
 
27
26
  // The current version of the check bundle format. This should be
28
27
  // incremented when there is an incompatible change to the bundle format.
@@ -31,62 +30,18 @@ import modelWorkerJs from '@_model_worker_/worker.js?raw'
31
30
  const VERSION = 1
32
31
 
33
32
  export class BundleModel implements CheckBundleModel {
34
- private readonly inputs: InputValue[]
35
- private outputs: Outputs
36
-
37
33
  /**
38
34
  * @param modelSpec The spec for the bundled model.
39
- * @param inputMap The model inputs.
40
- * @param modelRunner The model runner.
35
+ * @param bundleModelRunner The bundle model runner.
41
36
  */
42
37
  constructor(
43
38
  public readonly modelSpec: ModelSpec,
44
- private readonly inputMap: Map<InputVarId, Input>,
45
- private readonly modelRunner: ModelRunner
46
- ) {
47
- // Derive an array of `InputValue` instances that can be passed to the runner
48
- this.inputs = [...inputMap.values()].map(input => input.value)
49
-
50
- // Create an `Outputs` instance that is initialized to hold output data
51
- // produced by the Wasm model
52
- this.outputs = modelRunner.createOutputs()
53
- }
39
+ private readonly bundleModelRunner: BundleModelRunner
40
+ ) {}
54
41
 
55
42
  // from CheckBundleModel interface
56
43
  async getDatasetsForScenario(scenario: ScenarioSpec, datasetKeys: DatasetKey[]): Promise<DatasetsResult> {
57
- const datasetMap: DatasetMap = new Map()
58
-
59
- // Set the input values according to the given scenario
60
- setInputsForScenario(this.inputMap, scenario)
61
-
62
- // Run the JS model
63
- this.outputs = await this.modelRunner.runModel(this.inputs, this.outputs)
64
- const modelRunTime = this.outputs.runTimeInMillis
65
-
66
- // Extract the data for each requested output variable and put it into a map
67
- for (const datasetKey of datasetKeys) {
68
- // Get the output variable for the given dataset key; if the variable doesn't
69
- // exist in this version of the model/bundle, just skip it
70
- const outputVar = this.modelSpec.outputVars.get(datasetKey)
71
- if (!outputVar) {
72
- continue
73
- }
74
-
75
- if (outputVar.sourceName === undefined) {
76
- // See if we have data for the requested model output variable
77
- const series = this.outputs.getSeriesForVar(outputVar.varId)
78
- if (series) {
79
- datasetMap.set(datasetKey, datasetFromPoints(series.points))
80
- }
81
- } else {
82
- console.error('Static data sources not yet handled in default model check bundle')
83
- }
84
- }
85
-
86
- return {
87
- datasetMap,
88
- modelRunTime
89
- }
44
+ return this.bundleModelRunner.runModelForScenario(scenario, datasetKeys)
90
45
  }
91
46
 
92
47
  // from CheckBundleModel interface
@@ -107,28 +62,16 @@ export class BundleModel implements CheckBundleModel {
107
62
  * under different scenarios.
108
63
  */
109
64
  async function initBundleModel(modelSpec: ModelSpec, inputMap: Map<InputVarId, Input>): Promise<BundleModel> {
110
- // Initialize the Wasm model asynchronously. We inline the worker code in the
65
+ // Initialize the JS/Wasm model asynchronously. We inline the worker code in the
111
66
  // rolled-up bundle so that we don't have to fetch a separate `worker.js` file
112
67
  const modelRunner = await spawnAsyncModelRunner({ source: modelWorkerJs })
113
68
 
114
- // Return a `BundleModel` that wraps the underlying config and Wasm model
115
- return new BundleModel(modelSpec, inputMap, modelRunner)
116
- }
69
+ // Create a `BundleModelRunner` that wraps the `ModelRunner` instance and takes
70
+ // care of translating `runtime` types to the types expected by `check-core`
71
+ const bundleModelRunner = new BundleModelRunner(modelSpec, inputMap, modelRunner)
117
72
 
118
- /**
119
- * Create a `Dataset` containing the given data points from the model.
120
- */
121
- export function datasetFromPoints(points: Point[]): Dataset {
122
- const dataMap = new Map()
123
- for (const point of points) {
124
- // We omit points that have an undefined value. SDE represents `:NA:` values as
125
- // a special value (`-DBL_MAX`); the `runtime` package detects these and converts
126
- // them to undefined instead. We don't need them to appear in graphs, so omit them.
127
- if (point.y !== undefined) {
128
- dataMap.set(point.x, point.y)
129
- }
130
- }
131
- return dataMap
73
+ // Return a `BundleModel` that wraps the underlying config and Wasm model
74
+ return new BundleModel(modelSpec, bundleModelRunner)
132
75
  }
133
76
 
134
77
  /**
@@ -140,12 +83,16 @@ export function createBundle(): Bundle {
140
83
  const inputVars = getInputVars(inputSpecs)
141
84
  const outputVars = getOutputVars(outputSpecs)
142
85
 
86
+ // Gather information about internal/implementation variables
87
+ const { implVars, implVarGroups } = getImplVars(encodedImplVars)
88
+
143
89
  const modelSpec: ModelSpec = {
144
90
  modelSizeInBytes,
145
91
  dataSizeInBytes,
146
92
  inputVars,
147
93
  outputVars,
148
- implVars: new Map()
94
+ implVars,
95
+ implVarGroups
149
96
  // TODO: startTime and endTime are optional; the comparison graphs work OK if
150
97
  // they are undefined. The main benefit of using these is to set a specific
151
98
  // range for the x-axis on the comparison graphs, so maybe we should find
@@ -6,10 +6,14 @@
6
6
  * up using aliases in the Vite config file.)
7
7
  */
8
8
 
9
+ import type { EncodedImplVars } from '@sdeverywhere/check-core'
10
+
9
11
  import type { InputSpec } from './inputs'
10
12
  import type { OutputSpec } from './outputs'
11
13
 
12
14
  export const inputSpecs: InputSpec[] = []
13
15
  export const outputSpecs: OutputSpec[] = []
16
+ export const encodedImplVars: EncodedImplVars = undefined
17
+
14
18
  export const modelSizeInBytes = 0
15
19
  export const dataSizeInBytes = 0
@@ -0,0 +1,59 @@
1
+ // Copyright (c) 2022 Climate Interactive / New Venture Fund
2
+
3
+ import type { DatasetKey, EncodedImplVars, ImplVar, ImplVarGroup } from '@sdeverywhere/check-core'
4
+ import { decodeImplVars } from '@sdeverywhere/check-core'
5
+
6
+ /** The properties related to impl variables that will be included in the bundle model spec. */
7
+ export interface ImplSpec {
8
+ /** The map of all variables (both internal and exported) in this version of the model. */
9
+ implVars: Map<DatasetKey, ImplVar>
10
+ /** The groupings of internal/implementation variables in this version of the model. */
11
+ implVarGroups?: ImplVarGroup[]
12
+ }
13
+
14
+ /**
15
+ * Gather the set of internal/implementation variables used in this version of the model.
16
+ */
17
+ export function getImplVars(encodedImplVars: EncodedImplVars): ImplSpec {
18
+ // Decode the encoded impl variables into the original `ImplVar` objects
19
+ const decodedImplVars = decodeImplVars(encodedImplVars)
20
+
21
+ const implVars: Map<DatasetKey, ImplVar> = new Map()
22
+ const implVarGroups: ImplVarGroup[] = []
23
+
24
+ function addGroup(fn: string, implVarInstances: ImplVar[]): void {
25
+ // Add the var instances for this group to the map
26
+ const datasetKeysForGroup: DatasetKey[] = []
27
+ for (const implVarInstance of implVarInstances) {
28
+ // TODO: For now, skip lookup and data variables because we have no way to
29
+ // access them in the model
30
+ if (implVarInstance.varType === 'lookup' || implVarInstance.varType === 'data') {
31
+ continue
32
+ }
33
+ const varId = implVarInstance.varId
34
+ const datasetKey = `ModelImpl_${varId}`
35
+ implVars.set(datasetKey, implVarInstance)
36
+ datasetKeysForGroup.push(datasetKey)
37
+ }
38
+
39
+ // Add the group info
40
+ implVarGroups.push({
41
+ title: fn,
42
+ fn,
43
+ datasetKeys: datasetKeysForGroup
44
+ })
45
+ }
46
+
47
+ addGroup('initConstants', decodedImplVars.constants || [])
48
+ // TODO: Include lookups and data variables
49
+ // addGroup('initLookups', decodedImplVars.lookupVars || [])
50
+ // addGroup('initData', decodedImplVars.dataVars || [])
51
+ addGroup('initLevels', decodedImplVars.initVars || [])
52
+ addGroup('evalLevels', decodedImplVars.levelVars || [])
53
+ addGroup('evalAux', decodedImplVars.auxVars || [])
54
+
55
+ return {
56
+ implVars,
57
+ implVarGroups
58
+ }
59
+ }
@@ -14,14 +14,6 @@ import { createBundle as createBaselineBundle } from '@_baseline_bundle_'
14
14
  import { createBundle as createCurrentBundle } from '@_current_bundle_'
15
15
  import { getConfigOptions } from '@_test_config_'
16
16
 
17
- function loadSimplifyScenariosFlag(): boolean {
18
- if (import.meta.hot) {
19
- return localStorage.getItem('sde-check-simplify-scenarios') === '1'
20
- } else {
21
- return false
22
- }
23
- }
24
-
25
17
  function loadBundleName(key: string): string | undefined {
26
18
  if (import.meta.hot) {
27
19
  return localStorage.getItem(`sde-check-selected-bundle-${key}`)
@@ -149,11 +141,19 @@ async function initForLocal(): Promise<void> {
149
141
  // Prepare the model check/comparison configuration
150
142
  const configInitOptions: ConfigInitOptions = {
151
143
  bundleNameL,
152
- bundleNameR,
153
- simplifyScenarios: loadSimplifyScenariosFlag()
144
+ bundleNameR
154
145
  }
155
146
  const configOptions = await getConfigOptions(bundleL, bundleR, configInitOptions)
156
147
 
148
+ // Override the concurrency setting using the value from LocalStorage
149
+ const concurrencyValue = localStorage.getItem('sde-check-concurrency')
150
+ if (concurrencyValue !== null) {
151
+ const concurrency = parseInt(concurrencyValue)
152
+ configOptions.concurrency = !isNaN(concurrency) ? concurrency : 1
153
+ } else {
154
+ configOptions.concurrency = 1
155
+ }
156
+
157
157
  // Initialize the root Svelte component
158
158
  initAppShell(configOptions, {
159
159
  bundleNames
@@ -200,9 +200,10 @@ if (import.meta.hot) {
200
200
  initBundlesAndUI()
201
201
  })
202
202
 
203
- // Reload everything when the user toggles the "Simplify Scenarios" checkbox
204
- document.addEventListener('sde-check-simplify-scenarios-toggled', () => {
205
- // Reinitialize using the new state
203
+ // Reload everything when the user applies updated configuration (e.g., updated filters or
204
+ // concurrency setting)
205
+ document.addEventListener('sde-check-config-changed', () => {
206
+ // Reinitialize using the new configuration
206
207
  initBundlesAndUI()
207
208
  })
208
209
  }