@sdeverywhere/plugin-check 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -204,8 +204,6 @@ function injectModelSpec(prepDir, modelSpec) {
204
204
  const modelSizeInBytes = stagedFileSize("wasm-model.js");
205
205
  const dataSizeInBytes = stagedFileSize("static-data.ts");
206
206
  const moduleSrc = `
207
- export const startTime = ${modelSpec.startTime};
208
- export const endTime = ${modelSpec.endTime};
209
207
  export const inputSpecs = ${JSON.stringify(inputSpecs)};
210
208
  export const outputSpecs = ${JSON.stringify(outputSpecs)};
211
209
  export const modelSizeInBytes = ${modelSizeInBytes};
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/run-suite.ts","../src/vite-config-for-bundle.ts","../src/var-names.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","// 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, ModelSpec, Plugin, ResolvedConfig } from '@sdeverywhere/build'\n\nimport type { Bundle, 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 *.check.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 `.check.yaml` files\n // in the test config, so we should also reload if files match/unmatch\n // the `.check.yaml` glob\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: ModelSpec): 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.config, 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(config: ResolvedConfig, modelSpec: ModelSpec): Promise<void> {\n const prepDir = config.prepDir\n const viteConfig = await createViteConfigForBundle(prepDir, 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 nameR = 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 nameL: 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 nameL = this.options.baseline.name\n }\n }\n\n // Get the model check/compare configuration\n const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath))\n const checkOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, {\n nameL,\n nameR\n })\n\n // Run the suite of checks and comparisons\n const checkConfig = await createConfig(checkOptions)\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 CompareReport,\n PerfReport,\n RunSuiteCallbacks,\n CheckReport,\n CheckStatus,\n CompareConfig,\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.compareReport) {\n // Print the perf stats to the console\n printPerfStats(context, config.compare, report.compareReport)\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, compareConfig: CompareConfig, report: CompareReport): void {\n context.log('info', '\\nPerformance stats:')\n context.log('info', ` ${compareConfig.bundleL.name}:`)\n printPerfReportLine(context, report.perfReportL)\n context.log('info', ` ${compareConfig.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 { dirname, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, Plugin as VitePlugin } from 'vite'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nimport type { ModelSpec } from '@sdeverywhere/build'\n\nimport { sdeNameForVensimVarName } from './var-names'\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(prepDir: string, modelSpec: ModelSpec): VitePlugin {\n // Include the SDE variable ID with each spec\n const inputSpecs = modelSpec.inputs.map(i => {\n return {\n varId: sdeNameForVensimVarName(i.varName),\n ...i\n }\n })\n const outputSpecs = modelSpec.outputs.map(o => {\n return {\n varId: sdeNameForVensimVarName(o.varName),\n ...o\n }\n })\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 `wasm-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('wasm-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 startTime = ${modelSpec.startTime};\nexport const endTime = ${modelSpec.endTime};\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\nexport async function createViteConfigForBundle(prepDir: string, modelSpec: ModelSpec): 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 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, so using a `customResolver` seems to be\n // the easiest way to prevent Vite from picking up the `browser` exports.\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(prepDir, modelSpec)\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-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 (it does not seem\n // to be necessary to define a polyfill).\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\n/**\n * Helper function that converts a Vensim variable or subscript name\n * into a valid C identifier as used by SDE.\n * TODO: Import helper function from `compile` package instead\n */\nfunction sdeNameForVensimName(name: string): string {\n return (\n '_' +\n name\n .trim()\n .replace(/\"/g, '_')\n .replace(/\\s+!$/g, '!')\n .replace(/\\s/g, '_')\n .replace(/,/g, '_')\n .replace(/-/g, '_')\n .replace(/\\./g, '_')\n .replace(/\\$/g, '_')\n .replace(/'/g, '_')\n .replace(/&/g, '_')\n .replace(/%/g, '_')\n .replace(/\\//g, '_')\n .replace(/\\|/g, '_')\n .toLowerCase()\n )\n}\n\n/**\n * Helper function that converts a Vensim variable name (possibly containing\n * subscripts) into a valid C identifier as used by SDE.\n * TODO: Import helper function from `compile` package instead\n */\nexport function sdeNameForVensimVarName(varName: string): string {\n const m = varName.match(/([^[]+)(?:\\[([^\\]]+)\\])?/)\n if (!m) {\n throw new Error(`Invalid Vensim name: ${varName}`)\n }\n let id = sdeNameForVensimName(m[1])\n if (m[2]) {\n const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))\n id += `[${subscripts.join('][')}]`\n }\n\n return id\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\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 // 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 'assert-never',\n 'ajv',\n 'neverthrow',\n 'yaml',\n // from check-ui-shell\n 'fontfaceobserver',\n 'copy-text-to-clipboard',\n '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('os'),\n noopPolyfillAlias('path'),\n noopPolyfillAlias('url')\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 values: {\n // Inject the path for baseline bundles\n __BASELINE_BUNDLES_PATH__: JSON.stringify(baselinesPath)\n }\n }) 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 } from 'vite'\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 // Include `*.check.yaml` files under the configured project root directory. This\n // glob path apparently must be a relative path (relative to the `template-tests/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 yamlPath from options\n const yamlPath = `${relProjDirPath}/**/*.check.yaml`\n\n // // Read the `package.json` for the template project\n // const pkgPath = resolvePath(root, 'package.json')\n // const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))\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 // Inject special values into the generated JS\n define: {\n // Inject the glob pattern for matching check yaml files\n __YAML_PATH__: JSON.stringify(yamlPath)\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","joinPath","resolvePath","import_path","import_url","import_meta","__filename","__dirname","resolvePath","joinPath","replace","import_path","import_url","import_meta","__filename","__dirname","resolvePath","import_meta","chokidar","joinPath"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/run-suite.ts","../src/vite-config-for-bundle.ts","../src/var-names.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","// 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, ModelSpec, Plugin, ResolvedConfig } from '@sdeverywhere/build'\n\nimport type { Bundle, 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 *.check.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 `.check.yaml` files\n // in the test config, so we should also reload if files match/unmatch\n // the `.check.yaml` glob\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: ModelSpec): 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.config, 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(config: ResolvedConfig, modelSpec: ModelSpec): Promise<void> {\n const prepDir = config.prepDir\n const viteConfig = await createViteConfigForBundle(prepDir, 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 nameR = 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 nameL: 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 nameL = this.options.baseline.name\n }\n }\n\n // Get the model check/compare configuration\n const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath))\n const checkOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, {\n nameL,\n nameR\n })\n\n // Run the suite of checks and comparisons\n const checkConfig = await createConfig(checkOptions)\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 CompareReport,\n PerfReport,\n RunSuiteCallbacks,\n CheckReport,\n CheckStatus,\n CompareConfig,\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.compareReport) {\n // Print the perf stats to the console\n printPerfStats(context, config.compare, report.compareReport)\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, compareConfig: CompareConfig, report: CompareReport): void {\n context.log('info', '\\nPerformance stats:')\n context.log('info', ` ${compareConfig.bundleL.name}:`)\n printPerfReportLine(context, report.perfReportL)\n context.log('info', ` ${compareConfig.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 { dirname, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, Plugin as VitePlugin } from 'vite'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nimport type { ModelSpec } from '@sdeverywhere/build'\n\nimport { sdeNameForVensimVarName } from './var-names'\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(prepDir: string, modelSpec: ModelSpec): VitePlugin {\n // Include the SDE variable ID with each spec\n const inputSpecs = modelSpec.inputs.map(i => {\n return {\n varId: sdeNameForVensimVarName(i.varName),\n ...i\n }\n })\n const outputSpecs = modelSpec.outputs.map(o => {\n return {\n varId: sdeNameForVensimVarName(o.varName),\n ...o\n }\n })\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 `wasm-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('wasm-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\nexport async function createViteConfigForBundle(prepDir: string, modelSpec: ModelSpec): 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 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, so using a `customResolver` seems to be\n // the easiest way to prevent Vite from picking up the `browser` exports.\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(prepDir, modelSpec)\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-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 (it does not seem\n // to be necessary to define a polyfill).\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\n/**\n * Helper function that converts a Vensim variable or subscript name\n * into a valid C identifier as used by SDE.\n * TODO: Import helper function from `compile` package instead\n */\nfunction sdeNameForVensimName(name: string): string {\n return (\n '_' +\n name\n .trim()\n .replace(/\"/g, '_')\n .replace(/\\s+!$/g, '!')\n .replace(/\\s/g, '_')\n .replace(/,/g, '_')\n .replace(/-/g, '_')\n .replace(/\\./g, '_')\n .replace(/\\$/g, '_')\n .replace(/'/g, '_')\n .replace(/&/g, '_')\n .replace(/%/g, '_')\n .replace(/\\//g, '_')\n .replace(/\\|/g, '_')\n .toLowerCase()\n )\n}\n\n/**\n * Helper function that converts a Vensim variable name (possibly containing\n * subscripts) into a valid C identifier as used by SDE.\n * TODO: Import helper function from `compile` package instead\n */\nexport function sdeNameForVensimVarName(varName: string): string {\n const m = varName.match(/([^[]+)(?:\\[([^\\]]+)\\])?/)\n if (!m) {\n throw new Error(`Invalid Vensim name: ${varName}`)\n }\n let id = sdeNameForVensimName(m[1])\n if (m[2]) {\n const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))\n id += `[${subscripts.join('][')}]`\n }\n\n return id\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\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 // 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 'assert-never',\n 'ajv',\n 'neverthrow',\n 'yaml',\n // from check-ui-shell\n 'fontfaceobserver',\n 'copy-text-to-clipboard',\n '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('os'),\n noopPolyfillAlias('path'),\n noopPolyfillAlias('url')\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 values: {\n // Inject the path for baseline bundles\n __BASELINE_BUNDLES_PATH__: JSON.stringify(baselinesPath)\n }\n }) 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 } from 'vite'\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 // Include `*.check.yaml` files under the configured project root directory. This\n // glob path apparently must be a relative path (relative to the `template-tests/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 yamlPath from options\n const yamlPath = `${relProjDirPath}/**/*.check.yaml`\n\n // // Read the `package.json` for the template project\n // const pkgPath = resolvePath(root, 'package.json')\n // const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))\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 // Inject special values into the generated JS\n define: {\n // Inject the glob pattern for matching check yaml files\n __YAML_PATH__: JSON.stringify(yamlPath)\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","joinPath","resolvePath","import_path","import_url","import_meta","__filename","__dirname","resolvePath","joinPath","replace","import_path","import_url","import_meta","__filename","__dirname","resolvePath","import_meta","chokidar","joinPath"]}
package/dist/index.js CHANGED
@@ -178,8 +178,6 @@ function injectModelSpec(prepDir, modelSpec) {
178
178
  const modelSizeInBytes = stagedFileSize("wasm-model.js");
179
179
  const dataSizeInBytes = stagedFileSize("static-data.ts");
180
180
  const moduleSrc = `
181
- export const startTime = ${modelSpec.startTime};
182
- export const endTime = ${modelSpec.endTime};
183
181
  export const inputSpecs = ${JSON.stringify(inputSpecs)};
184
182
  export const outputSpecs = ${JSON.stringify(outputSpecs)};
185
183
  export const modelSizeInBytes = ${modelSizeInBytes};
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/var-names.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, ModelSpec, Plugin, ResolvedConfig } from '@sdeverywhere/build'\n\nimport type { Bundle, 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 *.check.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 `.check.yaml` files\n // in the test config, so we should also reload if files match/unmatch\n // the `.check.yaml` glob\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: ModelSpec): 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.config, 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(config: ResolvedConfig, modelSpec: ModelSpec): Promise<void> {\n const prepDir = config.prepDir\n const viteConfig = await createViteConfigForBundle(prepDir, 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 nameR = 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 nameL: 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 nameL = this.options.baseline.name\n }\n }\n\n // Get the model check/compare configuration\n const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath))\n const checkOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, {\n nameL,\n nameR\n })\n\n // Run the suite of checks and comparisons\n const checkConfig = await createConfig(checkOptions)\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 CompareReport,\n PerfReport,\n RunSuiteCallbacks,\n CheckReport,\n CheckStatus,\n CompareConfig,\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.compareReport) {\n // Print the perf stats to the console\n printPerfStats(context, config.compare, report.compareReport)\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, compareConfig: CompareConfig, report: CompareReport): void {\n context.log('info', '\\nPerformance stats:')\n context.log('info', ` ${compareConfig.bundleL.name}:`)\n printPerfReportLine(context, report.perfReportL)\n context.log('info', ` ${compareConfig.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 { dirname, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, Plugin as VitePlugin } from 'vite'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nimport type { ModelSpec } from '@sdeverywhere/build'\n\nimport { sdeNameForVensimVarName } from './var-names'\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(prepDir: string, modelSpec: ModelSpec): VitePlugin {\n // Include the SDE variable ID with each spec\n const inputSpecs = modelSpec.inputs.map(i => {\n return {\n varId: sdeNameForVensimVarName(i.varName),\n ...i\n }\n })\n const outputSpecs = modelSpec.outputs.map(o => {\n return {\n varId: sdeNameForVensimVarName(o.varName),\n ...o\n }\n })\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 `wasm-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('wasm-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 startTime = ${modelSpec.startTime};\nexport const endTime = ${modelSpec.endTime};\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\nexport async function createViteConfigForBundle(prepDir: string, modelSpec: ModelSpec): 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 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, so using a `customResolver` seems to be\n // the easiest way to prevent Vite from picking up the `browser` exports.\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(prepDir, modelSpec)\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-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 (it does not seem\n // to be necessary to define a polyfill).\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\n/**\n * Helper function that converts a Vensim variable or subscript name\n * into a valid C identifier as used by SDE.\n * TODO: Import helper function from `compile` package instead\n */\nfunction sdeNameForVensimName(name: string): string {\n return (\n '_' +\n name\n .trim()\n .replace(/\"/g, '_')\n .replace(/\\s+!$/g, '!')\n .replace(/\\s/g, '_')\n .replace(/,/g, '_')\n .replace(/-/g, '_')\n .replace(/\\./g, '_')\n .replace(/\\$/g, '_')\n .replace(/'/g, '_')\n .replace(/&/g, '_')\n .replace(/%/g, '_')\n .replace(/\\//g, '_')\n .replace(/\\|/g, '_')\n .toLowerCase()\n )\n}\n\n/**\n * Helper function that converts a Vensim variable name (possibly containing\n * subscripts) into a valid C identifier as used by SDE.\n * TODO: Import helper function from `compile` package instead\n */\nexport function sdeNameForVensimVarName(varName: string): string {\n const m = varName.match(/([^[]+)(?:\\[([^\\]]+)\\])?/)\n if (!m) {\n throw new Error(`Invalid Vensim name: ${varName}`)\n }\n let id = sdeNameForVensimName(m[1])\n if (m[2]) {\n const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))\n id += `[${subscripts.join('][')}]`\n }\n\n return id\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\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 // 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 'assert-never',\n 'ajv',\n 'neverthrow',\n 'yaml',\n // from check-ui-shell\n 'fontfaceobserver',\n 'copy-text-to-clipboard',\n '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('os'),\n noopPolyfillAlias('path'),\n noopPolyfillAlias('url')\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 values: {\n // Inject the path for baseline bundles\n __BASELINE_BUNDLES_PATH__: JSON.stringify(baselinesPath)\n }\n }) 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 } from 'vite'\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 // Include `*.check.yaml` files under the configured project root directory. This\n // glob path apparently must be a relative path (relative to the `template-tests/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 yamlPath from options\n const yamlPath = `${relProjDirPath}/**/*.check.yaml`\n\n // // Read the `package.json` for the template project\n // const pkgPath = resolvePath(root, 'package.json')\n // const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))\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 // Inject special values into the generated JS\n define: {\n // Inject the glob pattern for matching check yaml files\n __YAML_PATH__: JSON.stringify(yamlPath)\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","dirname","relative","joinPath","resolvePath","fileURLToPath","__filename","__dirname","dirname","relative","resolvePath","fileURLToPath","__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/var-names.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, ModelSpec, Plugin, ResolvedConfig } from '@sdeverywhere/build'\n\nimport type { Bundle, 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 *.check.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 `.check.yaml` files\n // in the test config, so we should also reload if files match/unmatch\n // the `.check.yaml` glob\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: ModelSpec): 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.config, 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(config: ResolvedConfig, modelSpec: ModelSpec): Promise<void> {\n const prepDir = config.prepDir\n const viteConfig = await createViteConfigForBundle(prepDir, 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 nameR = 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 nameL: 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 nameL = this.options.baseline.name\n }\n }\n\n // Get the model check/compare configuration\n const testConfigModule = await import(relativeToSourcePath(testOptions.testConfigPath))\n const checkOptions = await testConfigModule.getConfigOptions(bundleL, bundleR, {\n nameL,\n nameR\n })\n\n // Run the suite of checks and comparisons\n const checkConfig = await createConfig(checkOptions)\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 CompareReport,\n PerfReport,\n RunSuiteCallbacks,\n CheckReport,\n CheckStatus,\n CompareConfig,\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.compareReport) {\n // Print the perf stats to the console\n printPerfStats(context, config.compare, report.compareReport)\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, compareConfig: CompareConfig, report: CompareReport): void {\n context.log('info', '\\nPerformance stats:')\n context.log('info', ` ${compareConfig.bundleL.name}:`)\n printPerfReportLine(context, report.perfReportL)\n context.log('info', ` ${compareConfig.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 { dirname, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { InlineConfig, Plugin as VitePlugin } from 'vite'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nimport type { ModelSpec } from '@sdeverywhere/build'\n\nimport { sdeNameForVensimVarName } from './var-names'\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(prepDir: string, modelSpec: ModelSpec): VitePlugin {\n // Include the SDE variable ID with each spec\n const inputSpecs = modelSpec.inputs.map(i => {\n return {\n varId: sdeNameForVensimVarName(i.varName),\n ...i\n }\n })\n const outputSpecs = modelSpec.outputs.map(o => {\n return {\n varId: sdeNameForVensimVarName(o.varName),\n ...o\n }\n })\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 `wasm-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('wasm-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\nexport async function createViteConfigForBundle(prepDir: string, modelSpec: ModelSpec): 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 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, so using a `customResolver` seems to be\n // the easiest way to prevent Vite from picking up the `browser` exports.\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(prepDir, modelSpec)\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-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 (it does not seem\n // to be necessary to define a polyfill).\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\n/**\n * Helper function that converts a Vensim variable or subscript name\n * into a valid C identifier as used by SDE.\n * TODO: Import helper function from `compile` package instead\n */\nfunction sdeNameForVensimName(name: string): string {\n return (\n '_' +\n name\n .trim()\n .replace(/\"/g, '_')\n .replace(/\\s+!$/g, '!')\n .replace(/\\s/g, '_')\n .replace(/,/g, '_')\n .replace(/-/g, '_')\n .replace(/\\./g, '_')\n .replace(/\\$/g, '_')\n .replace(/'/g, '_')\n .replace(/&/g, '_')\n .replace(/%/g, '_')\n .replace(/\\//g, '_')\n .replace(/\\|/g, '_')\n .toLowerCase()\n )\n}\n\n/**\n * Helper function that converts a Vensim variable name (possibly containing\n * subscripts) into a valid C identifier as used by SDE.\n * TODO: Import helper function from `compile` package instead\n */\nexport function sdeNameForVensimVarName(varName: string): string {\n const m = varName.match(/([^[]+)(?:\\[([^\\]]+)\\])?/)\n if (!m) {\n throw new Error(`Invalid Vensim name: ${varName}`)\n }\n let id = sdeNameForVensimName(m[1])\n if (m[2]) {\n const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))\n id += `[${subscripts.join('][')}]`\n }\n\n return id\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\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 // 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 'assert-never',\n 'ajv',\n 'neverthrow',\n 'yaml',\n // from check-ui-shell\n 'fontfaceobserver',\n 'copy-text-to-clipboard',\n '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('os'),\n noopPolyfillAlias('path'),\n noopPolyfillAlias('url')\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 values: {\n // Inject the path for baseline bundles\n __BASELINE_BUNDLES_PATH__: JSON.stringify(baselinesPath)\n }\n }) 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 } from 'vite'\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 // Include `*.check.yaml` files under the configured project root directory. This\n // glob path apparently must be a relative path (relative to the `template-tests/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 yamlPath from options\n const yamlPath = `${relProjDirPath}/**/*.check.yaml`\n\n // // Read the `package.json` for the template project\n // const pkgPath = resolvePath(root, 'package.json')\n // const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))\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 // Inject special values into the generated JS\n define: {\n // Inject the glob pattern for matching check yaml files\n __YAML_PATH__: JSON.stringify(yamlPath)\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","dirname","relative","joinPath","resolvePath","fileURLToPath","__filename","__dirname","dirname","relative","resolvePath","fileURLToPath","__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.2.2",
3
+ "version": "0.3.0",
4
4
  "files": [
5
5
  "bin/**",
6
6
  "dist/**",
@@ -27,8 +27,8 @@
27
27
  "@rollup/plugin-replace": "^4.0.0",
28
28
  "@sdeverywhere/check-core": "^0.1.0",
29
29
  "@sdeverywhere/check-ui-shell": "^0.2.1",
30
- "@sdeverywhere/runtime": "^0.1.0",
31
- "@sdeverywhere/runtime-async": "^0.1.0",
30
+ "@sdeverywhere/runtime": "^0.2.0",
31
+ "@sdeverywhere/runtime-async": "^0.2.0",
32
32
  "assert-never": "^1.2.1",
33
33
  "chokidar": "^3.5.3",
34
34
  "picocolors": "^1.0.0",
@@ -45,7 +45,7 @@
45
45
  ]
46
46
  },
47
47
  "peerDependencies": {
48
- "@sdeverywhere/build": "^0.2.0"
48
+ "@sdeverywhere/build": "^0.3.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@sdeverywhere/build": "*",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "dependencies": {
3
3
  "@sdeverywhere/check-core": "^0.1.0",
4
- "@sdeverywhere/runtime": "^0.1.0",
5
- "@sdeverywhere/runtime-async": "^0.1.0",
4
+ "@sdeverywhere/runtime": "^0.2.0",
5
+ "@sdeverywhere/runtime-async": "^0.2.0",
6
6
  "assert-never": "^1.2.1"
7
7
  }
8
8
  }
@@ -13,15 +13,14 @@ import type {
13
13
  Scenario
14
14
  } from '@sdeverywhere/check-core'
15
15
 
16
- import type { InputValue, InputVarId, ModelRunner, Point } from '@sdeverywhere/runtime'
17
- import { Outputs } from '@sdeverywhere/runtime'
16
+ import type { InputValue, InputVarId, ModelRunner, Outputs, Point } from '@sdeverywhere/runtime'
18
17
  import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async'
19
18
 
20
19
  import type { Input } from './inputs'
21
20
  import { getInputVars, setInputsForScenario } from './inputs'
22
21
  import { getOutputVars } from './outputs'
23
22
 
24
- import { startTime, endTime, inputSpecs, outputSpecs, modelSizeInBytes, dataSizeInBytes } from 'virtual:model-spec'
23
+ import { inputSpecs, outputSpecs, modelSizeInBytes, dataSizeInBytes } from 'virtual:model-spec'
25
24
 
26
25
  import modelWorkerJs from '@_model_worker_/worker.js?raw'
27
26
 
@@ -50,8 +49,7 @@ export class BundleModel implements CheckBundleModel {
50
49
 
51
50
  // Create an `Outputs` instance that is initialized to hold output data
52
51
  // produced by the Wasm model
53
- const outputVarIds = outputSpecs.map(o => o.varId)
54
- this.outputs = new Outputs(outputVarIds, startTime, endTime)
52
+ this.outputs = modelRunner.createOutputs()
55
53
  }
56
54
 
57
55
  // from CheckBundleModel interface
@@ -149,9 +147,13 @@ export function createBundle(): Bundle {
149
147
  outputVars,
150
148
  implVars: new Map(),
151
149
  inputGroups: new Map(),
152
- datasetGroups: new Map(),
153
- startTime,
154
- endTime
150
+ datasetGroups: new Map()
151
+ // TODO: startTime and endTime are optional; the comparison graphs work OK if
152
+ // they are undefined. The main benefit of using these is to set a specific
153
+ // range for the x-axis on the comparison graphs, so maybe we should find
154
+ // another way to allow these to be defined.
155
+ // startTime,
156
+ // endTime
155
157
  }
156
158
 
157
159
  return {
@@ -9,8 +9,6 @@
9
9
  import type { InputSpec } from './inputs'
10
10
  import type { OutputSpec } from './outputs'
11
11
 
12
- export const startTime = 0
13
- export const endTime = 0
14
12
  export const inputSpecs: InputSpec[] = []
15
13
  export const outputSpecs: OutputSpec[] = []
16
14
  export const modelSizeInBytes = 0