@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 +24 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +23 -5
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/template-bundle/src/bundle-model-runner.ts +159 -0
- package/template-bundle/src/bundle.ts +20 -73
- package/template-bundle/src/empty-model-spec.ts +4 -0
- package/template-bundle/src/impl-vars.ts +59 -0
- package/template-report/src/index.ts +14 -13
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
|
|
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
|
|
69
|
+
const elapsedMillis = t1 - t0;
|
|
70
|
+
const elapsedSeconds = (elapsedMillis / 1e3).toFixed(1);
|
|
70
71
|
context.log("info", `
|
|
71
|
-
Test suite completed in ${
|
|
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,
|
|
715
|
+
const checkConfig = await (0, import_check_core3.createConfig)(configOptions);
|
|
698
716
|
const result = await runTestSuite(
|
|
699
717
|
context,
|
|
700
718
|
checkConfig,
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
|
36
|
+
const elapsedMillis = t1 - t0;
|
|
37
|
+
const elapsedSeconds = (elapsedMillis / 1e3).toFixed(1);
|
|
37
38
|
context.log("info", `
|
|
38
|
-
Test suite completed in ${
|
|
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":";AAEA,SAAS,cAAAA,mBAAkB;AAC3B,SAAS,UAAU,aAAa;AAChC,SAAS,WAAAC,UAAS,QAAQC,WAAU,YAAAC,iBAAgB;AACpD,SAAS,iBAAAC,sBAAqB;AAG9B,SAAS,OAAO,oBAAoB;AAEpC,OAAO,cAAc;AAKrB,SAAS,oBAAoB;;;ACb7B,SAAS,mBAAmB;AAE5B,OAAO,UAAU;AAejB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,eAAsB,aACpB,SACA,QACA,SAC6B;AAC7B,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,KAAK,YAAY,IAAI;AAC3B,QAAI;AACJ,UAAM,YAA+B;AAAA,MACnC,YAAY,cAAY;AACtB,cAAM,MAAM,KAAK,MAAM,WAAW,GAAG;AACrC,cAAM,WAAW,KAAK,MAAM,MAAM,CAAC,IAAI;AACvC,YAAI,iBAAiB,UAAa,WAAW,cAAc;AACzD,yBAAe;AACf,kBAAQ,IAAI,QAAQ,GAAG,QAAQ,GAAG;AAAA,QACpC;AAAA,MACF;AAAA,MACA,YAAY,YAAU;AACpB,YAAI;AACF,gBAAM,KAAK,YAAY,IAAI;AAC3B,gBAAM,YAAY,KAAK,MAAM,KAAM,QAAQ,CAAC;AAC5C,kBAAQ,IAAI,QAAQ;AAAA,0BAA6B,OAAO,GAAG;AAG3D,gBAAM,kBAAkB,kBAAkB,SAAS,OAAO,aAAa,OAAO;AAE9E,cAAI,OAAO,kBAAkB;AAE3B,2BAAe,SAAS,OAAO,YAAY,OAAO,gBAAgB;AAAA,UACpE;AAUA,gBAAM,eAAe,uBAAuB,MAAM;AAElD,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,GAAG;AACV,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,MACA,SAAS,WAAS;AAChB,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AACA,aAAS,QAAQ,SAAS;AAAA,EAC5B,CAAC;AACH;AAEA,SAAS,kBAAkB,SAAuB,aAA0B,SAA2B;AACrG,WAAS,YAAY,QAAgB,QAAqB,MAAoB;AAC5E,QAAI,CAAC,WAAW,WAAW,YAAY,SAAS,GAAG;AACjD;AAAA,IACF;AACA,QAAI;AACJ,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,qBAAa;AACb;AAAA,MACF,KAAK;AACH,qBAAa;AACb;AAAA,MACF,KAAK;AACH,qBAAa;AACb;AAAA,MACF;AACE,qBAAa;AACb;AAAA,IACJ;AACA,UAAM,MAAM,GAAG,KAAK,OAAO,MAAM,CAAC,GAAG,UAAU,IAAI,IAAI;AACvD,YAAQ,IAAI,QAAQ,WAAW,WAAW,KAAK,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,CAAC;AAAA,EAC3E;AAEA,WAAS,KAAK,GAAmB;AAC/B,WAAO,KAAK,KAAK,CAAC;AAAA,EACpB;AAEA,WAAS,UAAU,MAA6B;AAC9C,UAAM,MAAM,GAAG,KAAK,IAAI,GAAG,WAAW,KAAK,WAAW,WAAW,MAAM,EAAE;AACzE,gBAAY,GAAG,KAAK,QAAQ,GAAG;AAAA,EACjC;AAEA,MAAI,YAAY;AAChB,UAAQ,IAAI,QAAQ,kBAAkB;AACtC,aAAW,SAAS,YAAY,QAAQ;AACtC,YAAQ,IAAI,QAAQ;AAAA,EAAK,MAAM,IAAI,EAAE;AAErC,eAAW,QAAQ,MAAM,OAAO;AAC9B,UAAI,KAAK,WAAW,UAAU;AAC5B,oBAAY;AAAA,MACd;AACA,gBAAU,IAAI;AAEd,iBAAW,YAAY,KAAK,WAAW;AACrC,oBAAY,GAAG,SAAS,QAAQ,gBAAgB,UAAU,IAAI,CAAC;AAE/D,mBAAW,WAAW,SAAS,UAAU;AACvC,sBAAY,GAAG,QAAQ,QAAQ,eAAe,SAAS,IAAI,CAAC;AAE5D,qBAAW,aAAa,QAAQ,YAAY;AAC1C,wBAAY,GAAG,UAAU,OAAO,QAAQ,iBAAiB,WAAW,IAAI,CAAC;AAAA,UAC3E;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,UAAQ,IAAI,QAAQ,EAAE;AAEtB,SAAO;AACT;AAEA,SAAS,KAAK,OAAe,GAAmB;AAC9C,SAAO,GAAG,KAAK,IAAI,EAAE,QAAQ,CAAC,CAAC;AACjC;AAEA,SAAS,oBAAoB,SAAuB,YAA8B;AAChF,QAAM,MAAM,KAAK,OAAO,WAAW,OAAO;AAC1C,QAAM,MAAM,KAAK,OAAO,WAAW,OAAO;AAC1C,QAAM,MAAM,KAAK,OAAO,WAAW,OAAO;AAC1C,UAAQ,IAAI,QAAQ,OAAO,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE;AAChD;AAEA,SAAS,eAAe,SAAuB,kBAAoC,QAAgC;AACjH,UAAQ,IAAI,QAAQ,sBAAsB;AAC1C,UAAQ,IAAI,QAAQ,KAAK,iBAAiB,QAAQ,IAAI,GAAG;AACzD,sBAAoB,SAAS,OAAO,WAAW;AAC/C,UAAQ,IAAI,QAAQ,KAAK,iBAAiB,QAAQ,IAAI,GAAG;AACzD,sBAAoB,SAAS,OAAO,WAAW;AAC/C,UAAQ,IAAI,QAAQ,EAAE;AACxB;;;AC1KA,SAAS,YAAY,gBAAgB;AACrC,SAAS,UAAU,SAAS,QAAQ,UAAU,UAAU,WAAW,mBAAmB;AACtF,SAAS,qBAAqB;AAG9B,SAAS,mBAAmB;AAI5B,IAAMC,cAAa,cAAc,YAAY,GAAG;AAChD,IAAMC,aAAY,QAAQD,WAAU;AAYpC,SAAS,gBAAgB,SAAuB,WAA0C;AAExF,QAAM,aAAa,CAAC;AACpB,aAAW,kBAAkB,UAAU,QAAQ;AAK7C,QACE,eAAe,iBAAiB,UAChC,eAAe,aAAa,UAC5B,eAAe,aAAa,QAC5B;AACA,UAAI,MAAM;AACV,aAAO;AACP,aAAO,2CAA2C,eAAe,OAAO;AACxE,aAAO;AACP,aAAO;AACP,cAAQ,KAAK,GAAG;AAChB;AAAA,IACF;AAQA,UAAM,QAAQ,QAAQ,eAAe,eAAe,OAAO;AAC3D,UAAM,UAAU,eAAe,WAAW;AAC1C,eAAW,KAAK;AAAA,MACd;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACA,QAAM,cAAc,UAAU,QAAQ,IAAI,OAAK;AAC7C,WAAO;AAAA,MACL,OAAO,QAAQ,eAAe,EAAE,OAAO;AAAA,MACvC,GAAG;AAAA,IACL;AAAA,EACF,CAAC;AAED,WAAS,eAAe,UAA0B;AAChD,UAAM,UAAU,QAAQ,OAAO;AAC/B,UAAM,OAAO,SAAS,SAAS,UAAU,SAAS,QAAQ;AAC1D,QAAI,WAAW,IAAI,GAAG;AACpB,aAAO,SAAS,IAAI,EAAE;AAAA,IACxB,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AAMA,QAAM,mBAAmB,eAAe,oBAAoB;AAK5D,QAAM,kBAAkB,eAAe,gBAAgB;AAEvD,QAAM,YAAY;AAAA,4BACQ,KAAK,UAAU,UAAU,CAAC;AAAA,6BACzB,KAAK,UAAU,WAAW,CAAC;AAAA,kCACtB,gBAAgB;AAAA,iCACjB,eAAe;AAAA;AAG9C,QAAM,kBAAkB;AACxB,QAAM,0BAA0B,OAAO;AAEvC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,IAAY;AACpB,UAAI,OAAO,iBAAiB;AAC1B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,KAAK,IAAY;AACf,UAAI,OAAO,yBAAyB;AAClC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;AAMA,SAAS,0BAA0B,YAA4B;AAC7D,QAAM,gBAAgB,WAAW,QAAQ,KAAK,OAAK,EAAE,SAAS,cAAc;AAC5E,MAAI,kBAAkB,QAAW;AAC/B,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAGA,QAAM,oBAAoB,cAAc;AACxC,gBAAc,YAAY,eAAe,UAAU,IAAI,UAAU,SAAS;AACxE,QAAI,GAAG,WAAW,kBAAkB,KAAK,SAAS,SAAS,kBAAkB,GAAG;AAsB9E,YAAM,aAAa,GAAG,QAAQ,MAAM,EAAE;AACtC,YAAM,mBAAmB,SAAS,QAAQ;AAC1C,YAAM,aAAa,SAAS,QAAQ,kBAAkB,GAAG,UAAU,KAAK;AACxE,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,mBAAmB;AAAA,MACrB;AAAA,IACF;AAGA,WAAO,MAAM,kBAAkB,QAAQ,KAAK,MAAM,IAAI,UAAU,OAAO;AAAA,EACzE;AACF;AAEA,eAAsB,0BACpB,SACA,WACuB;AAEvB,QAAM,OAAO,YAAYC,YAAW,MAAM,iBAAiB;AAI3D,QAAM,UAAU,QAAQ,OAAO;AAC/B,QAAM,SAAS,SAAS,MAAM,OAAO;AAIrC,QAAM,kBAAkB,SAAS,SAAS,UAAU,SAAS,eAAe;AAE5E,SAAO;AAAA;AAAA,IAEL,YAAY;AAAA;AAAA,IAGZ;AAAA;AAAA,IAGA,aAAa;AAAA;AAAA;AAAA;AAAA,IAMb,SAAS;AAAA,MACP,OAAO;AAAA;AAAA,QAEL;AAAA,UACE,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcA;AAAA,UACE,MAAM;AAAA,UACN,aAAa;AAAA,UACb,gBAAgB,eAAgB,QAAQ,UAAU,SAAS;AAGzD,kBAAM,iBAAiB,YAAY,EAAE,SAAS,MAAM,CAAC;AACrD,kBAAM,WAAW,MAAM,eAAe,UAAU,KAAK,MAAM,QAAQ,UAAU,OAAO;AAEpF,gBAAI,WAAW,kBAAkB;AAC/B,qBAAO,SAAS,GAAG,QAAQ,cAAc,0BAA0B;AAAA,YACrE,OAAO;AACL,qBAAO,SAAS,GAAG,QAAQ,aAAa,mBAAmB;AAAA,YAC7D;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,SAAS;AAAA;AAAA,MAEP,gBAAgB,SAAS,SAAS;AAAA;AAAA;AAAA;AAAA,MAKlC;AAAA,QACE,MAAM;AAAA,QACN,eAAe,YAAY;AACzB,oCAA0B,UAAU;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,IAEA,OAAO;AAAA;AAAA;AAAA,MAGL;AAAA,MACA,aAAa;AAAA;AAAA;AAAA,MAKb,KAAK;AAAA,QACH,OAAO;AAAA,QACP,SAAS,CAAC,IAAI;AAAA,QACd,UAAU,MAAM;AAAA,MAClB;AAAA,MAEA,eAAe;AAAA;AAAA,QAEb,UAAU,CAAC,UAAU,MAAM,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASxC,QAAQ;AAAA,UACN,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMV;AAAA,QAEA,QAAQ,CAAC,SAAS,SAAS;AAMzB,cAAI,QAAQ,SAAS,QAAQ;AAC3B,iBAAK,OAAO;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACpSA,SAAS,cAAAC,aAAY,iBAAiB;AACtC,SAAS,WAAAC,UAAS,YAAAC,WAAU,QAAQC,WAAU,WAAWC,oBAAmB;AAC5E,SAAS,iBAAAC,sBAAqB;AAG9B,OAAO,aAAa;AAMpB,IAAMC,cAAaD,eAAc,YAAY,GAAG;AAChD,IAAME,aAAYN,SAAQK,WAAU;AAE7B,SAAS,0BACd,SACA,SACA,SACA,mBACA,mBACA,gBACA,cACc;AAEd,QAAM,OAAOF,aAAYG,YAAW,MAAM,iBAAiB;AAI3D,QAAM,eAAeH,aAAY,SAAS,WAAW;AACrD,MAAI,CAACJ,YAAW,YAAY,GAAG;AAC7B,cAAU,cAAc,EAAE,WAAW,KAAK,CAAC;AAAA,EAC7C;AAKA,QAAM,iBAAiBI,aAAY,MAAM,KAAK;AAC9C,QAAM,aAAaF,UAAS,gBAAgB,OAAO;AAGnD,QAAM,iBAAiB,WAAW,WAAW,MAAM,GAAG;AAEtD,QAAM,gBAAgB,GAAG,cAAc;AAGvC,MAAI;AACJ,MAAI,SAAS,YAAY;AACvB,iBAAa,QAAQ;AAAA,EACvB,OAAO;AACL,iBAAaC,UAAS,SAAS,cAAc;AAAA,EAC/C;AACA,QAAM,SAASD,UAAS,MAAM,UAAU;AAGxC,QAAM,mBAAmB,eAAe,KAAK,UAAU,YAAY,IAAI;AAEvE,QAAM,QAAQ,CAAC,MAAc,gBAAwB;AACnD,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAMA,QAAM,oBAAoB,CAAC,SAAiB;AAC1C,WAAO;AAAA,MACL;AAAA,MACA,aAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AAAA;AAAA,IAEL,YAAY;AAAA;AAAA,IAGZ;AAAA;AAAA;AAAA,IAIA,MAAM;AAAA;AAAA;AAAA;AAAA,IAKN,UAAUC,UAAS,SAAS,oBAAoB;AAAA;AAAA;AAAA;AAAA,IAMhD,aAAa;AAAA;AAAA;AAAA,IAKb,cAAc;AAAA;AAAA;AAAA,MAGZ,SAAS,CAAC,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWtB,SAAS;AAAA;AAAA,QAEP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAEA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEA,SAAS;AAAA;AAAA;AAAA;AAAA,QAIP;AAAA;AAAA;AAAA;AAAA;AAAA,MAMF;AAAA,IACF;AAAA;AAAA,IAGA,SAAS;AAAA,MACP,OAAO;AAAA;AAAA;AAAA,QAGL,MAAM,sBAAsB,SAAS,WAAW,QAAQ,SAAS,OAAO,sBAAsB;AAAA;AAAA,QAG9F,MAAM,qBAAqB,iBAAiB;AAAA;AAAA,QAG5C,MAAM,kBAAkB,cAAc;AAAA;AAAA,QAGtC,MAAM,WAAW,OAAO;AAAA;AAAA;AAAA;AAAA,QAKxB,kBAAkB,QAAQ;AAAA,QAC1B,kBAAkB,IAAI;AAAA,QACtB,kBAAkB,IAAI;AAAA,QACtB,kBAAkB,MAAM;AAAA,QACxB,kBAAkB,KAAK;AAAA,QACvB,kBAAkB,gBAAgB;AAAA,MACpC;AAAA,IACF;AAAA;AAAA,IAGA,QAAQ;AAAA;AAAA,MAEN,wBAAwB,KAAK,UAAU,gBAAgB;AAAA;AAAA,MAGvD,mBAAmB,KAAK,UAAU,SAAS,UAAU,QAAQ,EAAE;AAAA;AAAA,MAG/D,kBAAkB,KAAK,UAAU,iBAAiB;AAAA,IACpD;AAAA,IAEA,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,MAKP,QAAQ;AAAA,QACN,mBAAmB;AAAA,QACnB,YAAY,CAAC,IAAI,EAAE;AAAA,QACnB,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAQN,qBAAqB;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,OAAO;AAAA;AAAA;AAAA,MAGL;AAAA;AAAA,MAGA,WAAW;AAAA,MAEX,eAAe;AAAA,QACb,QAAQ;AAAA;AAAA,UAEN,cAAc;AAAA,QAChB;AAAA,QAEA,QAAQ,CAAC,SAAS,SAAS;AAMzB,cAAI,QAAQ,SAAS,QAAQ;AAC3B,iBAAK,OAAO;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,QAAQ;AAAA;AAAA,MAEN,MAAM,SAAS,cAAc;AAAA;AAAA,MAG7B,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,MAMN,OAAO;AAAA,QACL,kBAAkB;AAAA,UAChB,oBAAoB;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACpPA,SAAS,WAAAK,UAAS,YAAAC,WAAU,WAAWC,oBAAmB;AAC1D,SAAS,iBAAAC,sBAAqB;AAG9B,OAAOC,cAAa;AAEpB,IAAMC,cAAaF,eAAc,YAAY,GAAG;AAChD,IAAMG,aAAYN,SAAQK,WAAU;AAE7B,SAAS,yBAAyB,SAAiB,SAAiB,MAAuC;AAEhH,QAAM,OAAOH,aAAYI,YAAW,MAAM,gBAAgB;AAI1D,QAAM,iBAAiBJ,aAAY,MAAM,KAAK;AAC9C,QAAM,aAAaD,UAAS,gBAAgB,OAAO;AAGnD,QAAM,iBAAiB,WAAW,WAAW,MAAM,GAAG;AAOtD,QAAM,wBAAwB,KAAK,cAAc,wBAAwB,cAAc;AAKvF,QAAM,6BAA6B,KAAK,cAAc;AAItD,QAAM,SAASA,UAAS,MAAM,OAAO;AAErC,SAAO;AAAA;AAAA,IAEL,YAAY;AAAA;AAAA,IAGZ;AAAA;AAAA,IAGA,aAAa;AAAA;AAAA;AAAA,IAKb,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,MAKPG,SAAQ;AAAA,QACN,mBAAmB;AAAA,QACnB,YAAY,CAAC,IAAI,EAAE;AAAA,QACnB,QAAQ;AAAA;AAAA,UAEN,oCAAoC;AAAA;AAAA,UAEpC,yCAAyC;AAAA,QAC3C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,OAAO;AAAA;AAAA;AAAA,MAGL;AAAA,MACA,aAAa;AAAA,MAEb,KAAK;AAAA,QACH,OAAO;AAAA,QACP,SAAS,CAAC,IAAI;AAAA,QACd,UAAU,MAAM;AAAA,MAClB;AAAA;AAAA,MAGA,OAAO,SAAS,WAAW,CAAC;AAAA,MAE5B,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUf;AAAA,IACF;AAAA,EACF;AACF;;;AJ1EO,SAAS,YAAY,SAAsC;AAChE,SAAO,IAAI,YAAY,OAAO;AAChC;AAQA,IAAM,cAAN,MAAoC;AAAA,EAGlC,YAA6B,SAA8B;AAA9B;AAF7B,SAAQ,aAAa;AAAA,EAEuC;AAAA,EAE5D,MAAM,MAAM,QAAuC;AACjD,QAAI,KAAK,SAAS,mBAAmB,QAAW;AAI9C,YAAM,KAAK,cAAc,QAAQ,OAAO;AAAA,IAC1C;AAKA,UAAM,cAAc,KAAK,mBAAmB,MAAM;AAClD,UAAM,aAAa,KAAK,0BAA0B,QAAQ,aAAa,MAAS;AAChF,UAAM,SAAwB,MAAM,aAAa,UAAU;AAC3D,UAAM,OAAO,OAAO;AAUpB,UAAM,eAAe;AACrB,UAAM,UAAU,SAAS,MAAM,cAAc;AAAA;AAAA,MAE3C,KAAK,OAAO;AAAA;AAAA,MAEZ,eAAe;AAAA;AAAA;AAAA,MAGf,kBAAkB;AAAA,QAChB,oBAAoB;AAAA,MACtB;AAAA,IACF,CAAC;AACD,YAAQ,GAAG,OAAO,MAAM,OAAO,QAAQ,CAAC;AACxC,YAAQ,GAAG,UAAU,MAAM,OAAO,QAAQ,CAAC;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,SAAuB,WAAgD;AACrF,UAAM,aAAa,KAAK;AACxB,SAAK,aAAa;AAOlB,QAAI,KAAK,SAAS,YAAY,QAAW;AAEvC,UAAI,QAAQ,OAAO,SAAS,eAAe;AAGzC,cAAM,KAAK,mBAAmB,QAAQ,MAAM;AAAA,MAC9C;AACA,cAAQ,IAAI,QAAQ,kCAAkC;AACtD,YAAM,KAAK,iBAAiB,SAAS,SAAS;AAAA,IAChD;AAIA,QAAI,KAAK,SAAS,mBAAmB,QAAW;AAC9C,UAAI,QAAQ,OAAO,SAAS,gBAAgB,YAAY;AAEtD,gBAAQ,IAAI,QAAQ,8CAA8C;AAClE,cAAM,KAAK,cAAc,QAAQ,QAAQ,OAAO;AAAA,MAClD;AAAA,IACF;AAEA,QAAI,QAAQ,OAAO,SAAS,cAAc;AAGxC,YAAM,cAAc,KAAK,mBAAmB,QAAQ,MAAM;AAC1D,aAAO,KAAK,UAAU,SAAS,WAAW;AAAA,IAC5C,OAAO;AAGL,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,mBAAmB,QAAuC;AAEtE,UAAM,oBAAoBG,UAAS,OAAO,SAAS,iBAAiB;AACpE,QAAIC,YAAW,iBAAiB,GAAG;AAEjC,YAAM,eAAeD,UAAS,OAAO,SAAS,WAAW;AACzD,UAAI,CAACC,YAAW,YAAY,GAAG;AAC7B,cAAM,MAAM,cAAc,EAAE,WAAW,KAAK,CAAC;AAAA,MAC/C;AACA,YAAM,qBAAqBD,UAAS,cAAc,aAAa;AAC/D,YAAM,SAAS,mBAAmB,kBAAkB;AAAA,IACtD;AAAA,EACF;AAAA,EAEA,MAAc,iBAAiB,SAAuB,WAA6C;AACjG,UAAM,aAAa,MAAM,0BAA0B,SAAS,SAAS;AACrE,UAAM,MAAM,UAAU;AAAA,EACxB;AAAA,EAEA,MAAc,cAAc,QAAwB,MAAwC;AAC1F,UAAM,UAAU,OAAO;AACvB,UAAM,UAAU,OAAO;AACvB,UAAM,aAAa,yBAAyB,SAAS,SAAS,IAAI;AAClE,UAAM,MAAM,UAAU;AAAA,EACxB;AAAA,EAEA,MAAc,UAAU,SAAuB,aAA4C;AACzF,YAAQ,IAAI,QAAQ,yBAAyB;AAI7C,UAAM,UAAU,MAAM,OAAO,qBAAqB,YAAY,iBAAiB;AAC/E,UAAM,UAAU,QAAQ,aAAa;AACrC,UAAM,cAAc,YAAY;AAMhC,QAAI;AACJ,QAAI;AACJ,QAAI,KAAK,SAAS,UAAU;AAC1B,YAAM,UAAU,MAAM,OAAO,qBAAqB,KAAK,QAAQ,SAAS,IAAI;AAE5E,YAAM,aAAkB,QAAQ,aAAa;AAC7C,UAAI,WAAW,YAAY,QAAQ,SAAS;AAC1C,kBAAU;AACV,sBAAc,KAAK,QAAQ,SAAS;AAAA,MACtC;AAAA,IACF;AAGA,UAAM,mBAAmB,MAAM,OAAO,qBAAqB,YAAY,cAAc;AACrF,UAAM,oBAAuC;AAAA,MAC3C;AAAA,MACA;AAAA,IACF;AACA,UAAM,gBAAgB,MAAM,iBAAiB,iBAAiB,SAAS,SAAS,iBAAiB;AAGjG,UAAM,cAAc,MAAM,aAAa,aAAa;AACpD,UAAM,SAAS,MAAM;AAAA,MAAa;AAAA,MAAS;AAAA;AAAA,MAA0B;AAAA,IAAK;AAG1E,YAAQ,IAAI,QAAQ,6BAA6B;AACjD,UAAM,aAAa,KAAK,0BAA0B,QAAQ,QAAQ,aAAa,OAAO,YAAY;AAClG,UAAM,MAAM,UAAU;AAItB,WAAO,OAAO;AAAA,EAChB;AAAA,EAEQ,mBAAmB,QAAqC;AAC9D,QAAI;AACJ,QAAI;AACJ,QAAI,KAAK,SAAS,YAAY,QAAW;AAEvC,0BAAoB;AACpB,0BAAoBA,UAAS,OAAO,SAAS,iBAAiB;AAAA,IAChE,OAAO;AAEL,0BAAoB,KAAK,QAAQ,QAAQ;AACzC,0BAAoB,KAAK,QAAQ,QAAQ;AAAA,IAC3C;AAEA,QAAI;AACJ,QAAI,KAAK,SAAS,mBAAmB,QAAW;AAE9C,uBAAiBA,UAAS,OAAO,SAAS,gBAAgB;AAAA,IAC5D,OAAO;AAEL,uBAAiB,KAAK,QAAQ;AAAA,IAChC;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,0BACN,QACA,aACA,cACc;AACd,WAAO;AAAA,MACL,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF;AASA,SAAS,qBAAqB,UAA0B;AACtD,QAAM,SAASE,SAAQC,eAAc,YAAY,GAAG,CAAC;AACrD,QAAM,UAAUC,UAAS,QAAQ,QAAQ;AACzC,SAAO,QAAQ,WAAW,MAAM,GAAG;AACrC;","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.
|
|
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.
|
|
29
|
-
"@sdeverywhere/check-ui-shell": "^0.2.
|
|
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 {
|
|
14
|
+
import type { InputVarId } from '@sdeverywhere/runtime'
|
|
17
15
|
import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async'
|
|
18
16
|
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
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
|
|
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
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
115
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
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
|
}
|