@sdeverywhere/build 0.3.8 → 0.3.10

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
@@ -115,11 +115,11 @@ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
115
115
  } else {
116
116
  modelInputPaths = modelFiles;
117
117
  }
118
- let watchPaths;
118
+ let watchPaths2;
119
119
  if (userConfig.watchPaths && userConfig.watchPaths.length > 0) {
120
- watchPaths = userConfig.watchPaths;
120
+ watchPaths2 = userConfig.watchPaths;
121
121
  } else {
122
- watchPaths = modelFiles;
122
+ watchPaths2 = modelFiles;
123
123
  }
124
124
  const rawGenFormat = userConfig.genFormat || "js";
125
125
  let genFormat;
@@ -147,7 +147,7 @@ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
147
147
  prepDir,
148
148
  modelFiles,
149
149
  modelInputPaths,
150
- watchPaths,
150
+ watchPaths: watchPaths2,
151
151
  genFormat,
152
152
  outListingFile,
153
153
  sdeDir,
@@ -748,8 +748,9 @@ async function buildOnce(config, userConfig, plugins, options) {
748
748
  outputVarNames: modelSpec.outputVarNames,
749
749
  externalDatfiles: modelSpec.datFiles,
750
750
  bundleListing: modelSpec.bundleListing,
751
- customLookups: modelSpec.customLookups,
752
- customOutputs: modelSpec.customOutputs,
751
+ customConstants: modelSpec.customConstants || false,
752
+ customLookups: modelSpec.customLookups || false,
753
+ customOutputs: modelSpec.customOutputs || false,
753
754
  ...modelSpec.options
754
755
  };
755
756
  const specPath = (0, import_path5.join)(config.prepDir, "spec.json");
@@ -842,6 +843,12 @@ function resolveModelSpec(modelSpec) {
842
843
  outputVarNames = [];
843
844
  outputSpecs = [];
844
845
  }
846
+ let customConstants;
847
+ if (modelSpec.customConstants !== void 0) {
848
+ customConstants = modelSpec.customConstants;
849
+ } else {
850
+ customConstants = false;
851
+ }
845
852
  let customLookups;
846
853
  if (modelSpec.customLookups !== void 0) {
847
854
  customLookups = modelSpec.customLookups;
@@ -861,6 +868,7 @@ function resolveModelSpec(modelSpec) {
861
868
  outputs: outputSpecs,
862
869
  datFiles: modelSpec.datFiles || [],
863
870
  bundleListing: modelSpec.bundleListing === true,
871
+ customConstants,
864
872
  customLookups,
865
873
  customOutputs,
866
874
  options: modelSpec.options
@@ -869,8 +877,64 @@ function resolveModelSpec(modelSpec) {
869
877
 
870
878
  // src/build/impl/watch.ts
871
879
  var import_path6 = require("path");
880
+
881
+ // src/build/impl/watch-paths.ts
872
882
  var import_chokidar = __toESM(require("chokidar"), 1);
873
883
  var import_tinyglobby2 = require("tinyglobby");
884
+ function resolveWatchPaths(patterns, cwd) {
885
+ const resolvedPaths = [];
886
+ const regularPaths = [];
887
+ const globPatterns = [];
888
+ for (const pattern of patterns) {
889
+ if ((0, import_tinyglobby2.isDynamicPattern)(pattern)) {
890
+ globPatterns.push(pattern);
891
+ } else {
892
+ regularPaths.push(pattern);
893
+ }
894
+ }
895
+ resolvedPaths.push(...regularPaths);
896
+ if (globPatterns.length > 0) {
897
+ const paths = (0, import_tinyglobby2.globSync)(globPatterns, {
898
+ // Watch paths are resolved relative to the provided cwd
899
+ cwd,
900
+ // Resolve to absolute paths
901
+ absolute: true
902
+ });
903
+ resolvedPaths.push(...paths);
904
+ }
905
+ return resolvedPaths;
906
+ }
907
+ function watchPaths(patterns, cwd, onChange, onReady) {
908
+ const resolvedPathsToWatch = resolveWatchPaths(patterns, cwd);
909
+ const watcher = import_chokidar.default.watch(resolvedPathsToWatch, {
910
+ // Watch paths are resolved relative to the provided cwd
911
+ cwd,
912
+ // Ignore the initial add events when the watcher is created
913
+ ignoreInitial: true,
914
+ // Include a delay, otherwise on macOS we sometimes get multiple
915
+ // change events when the file is saved just once
916
+ awaitWriteFinish: {
917
+ stabilityThreshold: 200
918
+ }
919
+ });
920
+ watcher.on("change", (path) => {
921
+ onChange(path);
922
+ });
923
+ watcher.on("add", (path) => {
924
+ onChange(path);
925
+ });
926
+ watcher.on("unlink", (path) => {
927
+ onChange(path);
928
+ });
929
+ if (onReady) {
930
+ watcher.on("ready", onReady);
931
+ }
932
+ return () => {
933
+ watcher.close();
934
+ };
935
+ }
936
+
937
+ // src/build/impl/watch.ts
874
938
  var BuildState = class {
875
939
  constructor() {
876
940
  this.abortController = new AbortController();
@@ -913,36 +977,13 @@ function watch(config, userConfig, plugins) {
913
977
  }, delay);
914
978
  }
915
979
  }
916
- let watchPaths;
980
+ let watchPatterns;
917
981
  if (config.watchPaths && config.watchPaths.length > 0) {
918
- watchPaths = config.watchPaths;
982
+ watchPatterns = config.watchPaths;
919
983
  } else {
920
- watchPaths = config.modelFiles;
921
- }
922
- const resolvedWatchPaths = [];
923
- for (const watchPath of watchPaths) {
924
- if ((0, import_tinyglobby2.isDynamicPattern)(watchPath)) {
925
- const paths = (0, import_tinyglobby2.globSync)(watchPath, {
926
- // Watch paths are resolved relative to the project root directory
927
- cwd: config.rootDir,
928
- // Resolve to absolute paths
929
- absolute: true
930
- });
931
- resolvedWatchPaths.push(...paths);
932
- } else {
933
- resolvedWatchPaths.push(watchPath);
934
- }
984
+ watchPatterns = config.modelFiles;
935
985
  }
936
- const watcher = import_chokidar.default.watch(resolvedWatchPaths, {
937
- // Watch paths are resolved relative to the project root directory
938
- cwd: config.rootDir,
939
- // XXX: Include a delay, otherwise on macOS we sometimes get multiple
940
- // change events when the csv file is saved just once
941
- awaitWriteFinish: {
942
- stabilityThreshold: 200
943
- }
944
- });
945
- watcher.on("change", (path) => {
986
+ watchPaths(watchPatterns, config.rootDir, (path) => {
946
987
  scheduleBuild(path);
947
988
  });
948
989
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_typescript@5.2.2/node_modules/tsup/assets/cjs_shims.js","../src/build/build.ts","../src/config/config-loader.ts","../src/_shared/log.ts","../src/build/impl/build-once.ts","../src/context/context.ts","../src/context/spawn-child.ts","../src/context/staged-files.ts","../src/build/impl/gen-model.ts","../src/build/impl/hash-files.ts","../src/build/impl/watch.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\n//\n// Note that exports are ordered here according to the dependency chain,\n// i.e., lower items depend on the ones above.\n//\n\nexport type { LogLevel } from './_shared/log'\nexport type { BuildMode } from './_shared/mode'\nexport type { InputSpec, ModelSpec, OutputSpec, ResolvedModelSpec, VarName } from './_shared/model-spec'\nexport type { ResolvedConfig } from './_shared/resolved-config'\n\nexport type { BuildContext } from './context/context'\n\nexport type { Plugin } from './plugin/plugin'\n\nexport type { UserConfig } from './config/user-config'\n\nexport type { BuildOptions, BuildResult } from './build/build'\nexport { build } from './build/build'\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport type { BuildMode } from '../_shared/mode'\n\nimport { loadConfig } from '../config/config-loader'\nimport type { UserConfig } from '../config/user-config'\nimport type { LogLevel } from '../_shared/log'\nimport { setOverlayFile, setActiveLevels } from '../_shared/log'\nimport { buildOnce } from './impl/build-once'\nimport { watch } from './impl/watch'\n\nexport interface BuildOptions {\n /** The path to an `sde.config.js` file, or a `UserConfig` object. */\n config?: string | UserConfig\n\n /**\n * The log levels to include. If undefined, the default 'info' and 'error' levels\n * will be active.\n */\n logLevels?: LogLevel[]\n\n /**\n * The path to the `@sdeverywhere/cli` package. This is currently only used to get\n * access to the files in the `src/c` directory.\n * @hidden This should be removed once we have tighter integration with the `cli` package.\n */\n sdeDir: string\n\n /**\n * The path to the `sde` command.\n * @hidden This should be removed once we have tighter integration with the `cli` package.\n */\n sdeCmdPath: string\n}\n\nexport interface BuildResult {\n /**\n * The exit code that should be set by the process. This will be undefined\n * if `mode` is 'development', indicating that the process should be kept alive.\n */\n exitCode?: number\n}\n\n/**\n * Initiate the build process, which can either be a single build if `mode` is\n * 'production', or a live development environment if `mode` is 'development'.\n *\n * @param mode The build mode.\n * @param options The build options.\n * @return An `ok` result if the build completed, otherwise an `err` result.\n */\nexport async function build(mode: BuildMode, options: BuildOptions): Promise<Result<BuildResult, Error>> {\n // Load the config\n const configResult = await loadConfig(mode, options.config, options.sdeDir, options.sdeCmdPath)\n if (configResult.isErr()) {\n return err(configResult.error)\n }\n const { userConfig, resolvedConfig } = configResult.value\n\n // Configure logging level\n if (options.logLevels !== undefined) {\n setActiveLevels(options.logLevels)\n }\n\n // Configure the overlay `messages.html` file, which is written under the\n // `sde-prep` directory. For production builds, this file will remain empty.\n const messagesPath = joinPath(resolvedConfig.prepDir, 'messages.html')\n const overlayEnabled = mode === 'development'\n setOverlayFile(messagesPath, overlayEnabled)\n\n try {\n // Initialize plugins\n const plugins = userConfig.plugins || []\n for (const plugin of plugins) {\n if (plugin.init) {\n await plugin.init(resolvedConfig)\n }\n }\n\n if (mode === 'development') {\n // Enable dev mode (which will rebuild when watched files are changed).\n // First run an initial build so that we have a baseline, and then\n // once that is complete, enable file watchers.\n const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {})\n // TODO: We should trap errors here and keep the dev process\n // running if the initial build fails\n if (buildResult.isErr()) {\n return err(buildResult.error)\n }\n\n // Allow plugins to set up watchers after the initial build completes\n for (const plugin of plugins) {\n if (plugin.watch) {\n await plugin.watch(resolvedConfig)\n }\n }\n\n // Watch for changes to the source/model/test files\n watch(resolvedConfig, userConfig, plugins)\n\n // Return a build result with undefined exit code, indicating that the\n // process should be kept alive\n return ok({})\n } else {\n // Run a single build\n const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {})\n if (buildResult.isErr()) {\n return err(buildResult.error)\n }\n\n // Configure the exit code depending on whether any plugins failed.\n // Currently we use the following exit code values:\n // 0 == build succeeded, AND all plugins succeeded\n // 1 == build failed (or a plugin reported a \"hard\" error)\n // 2 == build succeeded, BUT one or more plugins failed\n const allPluginsSucceeded = buildResult.value\n const exitCode = allPluginsSucceeded ? 0 : 2\n return ok({ exitCode })\n }\n } catch (e) {\n return err(e)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, lstatSync, mkdirSync } from 'fs'\nimport { dirname, isAbsolute, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport type { BuildMode } from '../_shared/mode'\nimport type { ResolvedConfig } from '../_shared/resolved-config'\n\nimport type { UserConfig } from './user-config'\n\nexport interface ConfigResult {\n userConfig: UserConfig\n resolvedConfig: ResolvedConfig\n}\n\n/**\n * Load a user-defined config file or a given `UserConfig` object. This validates\n * all paths and if successful, this returns a `ResolvedConfig`, otherwise returns\n * an error result.\n *\n * @param mode The build mode.\n * @param config The path to a config file, or a `UserConfig` object; if undefined,\n * this will look for a `sde.config.js` file in the current directory.\n * @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).\n * @param sdeCmdPath Temporary (the path to the `sde` command).\n * @return An `ok` result with the `ResolvedConfig`, otherwise an `err` result.\n */\nexport async function loadConfig(\n mode: BuildMode,\n config: string | UserConfig | undefined,\n sdeDir: string,\n sdeCmdPath: string\n): Promise<Result<ConfigResult, Error>> {\n let userConfig: UserConfig\n if (typeof config === 'object') {\n // Use the given `UserConfig` object\n userConfig = config\n } else {\n // Load the project-specific config. If no `--config` arg was specified\n // on the command line, look for a `sde.config.js` file in the current\n // directory, and failing that, use a default config.\n // TODO: Create a default config if no file found; for now, we just fail\n let configPath: string\n if (typeof config === 'string') {\n configPath = config\n } else {\n configPath = joinPath(process.cwd(), 'sde.config.js')\n }\n try {\n if (!existsSync(configPath)) {\n return err(new Error(`Cannot find config file '${configPath}'`))\n }\n const configRelPath = relativeToSourcePath(configPath)\n const configModule = await import(configRelPath)\n userConfig = await configModule.config()\n } catch (e) {\n return err(new Error(`Failed to load config file '${configPath}': ${e.message}`))\n }\n }\n\n // Resolve the config\n try {\n const resolvedConfig = resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath)\n return ok({\n userConfig,\n resolvedConfig\n })\n } catch (e) {\n return err(e)\n }\n}\n\n/**\n * Resolve the given user configuration by resolving all paths. This will create\n * the prep directory if it does not already exist. This will throw an error if\n * any other paths are invalid or do not exist.\n *\n * @param userConfig The user-defined configuration.\n * @param mode The active build mode.\n * @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).\n * @param sdeCmdPath Temporary (the path to the `sde` command).\n * @return The resolved configuration.\n */\nfunction resolveUserConfig(\n userConfig: UserConfig,\n mode: BuildMode,\n sdeDir: string,\n sdeCmdPath: string\n): ResolvedConfig {\n function expectDirectory(propName: string, path: string): void {\n if (!existsSync(path)) {\n throw new Error(`The configured ${propName} (${path}) does not exist`)\n } else if (!lstatSync(path).isDirectory()) {\n throw new Error(`The configured ${propName} (${path}) is not a directory`)\n }\n }\n\n // function expectFile(propName: string, path: string): void {\n // if (!existsSync(path)) {\n // throw new Error(`The configured ${propName} (${path}) does not exist`)\n // // TODO: Don't include the \"is file\" check for now; need to update this to\n // // handle symlinks\n // // } else if (!lstatSync(path).isFile()) {\n // // throw new Error(`The configured ${propName} (${path}) is not a file`)\n // }\n // }\n\n // Validate the root directory\n let rootDir: string\n if (userConfig.rootDir) {\n // Verify that the configured root directory exists\n rootDir = resolvePath(userConfig.rootDir)\n expectDirectory('rootDir', rootDir)\n } else {\n // Use the current working directory as the project root\n rootDir = process.cwd()\n }\n\n // Validate the prep directory\n let prepDir: string\n if (userConfig.prepDir) {\n // Resolve the configured prep directory\n prepDir = resolvePath(userConfig.prepDir)\n } else {\n // Create an 'sde-prep' directory under the configured root directory\n prepDir = resolvePath(rootDir, 'sde-prep')\n }\n mkdirSync(prepDir, { recursive: true })\n\n // Validate the model files\n const userModelFiles = userConfig.modelFiles\n const modelFiles: string[] = []\n for (const userModelFile of userModelFiles) {\n const modelFile = resolvePath(userModelFile)\n if (!existsSync(modelFile)) {\n throw new Error(`The configured model file (${modelFile}) does not exist`)\n }\n modelFiles.push(modelFile)\n }\n\n // TODO: Validate the watch paths; these are allowed to be globs, so need to\n // figure out the best way to resolve them\n let modelInputPaths: string[]\n if (userConfig.modelInputPaths && userConfig.modelInputPaths.length > 0) {\n modelInputPaths = userConfig.modelInputPaths\n } else {\n modelInputPaths = modelFiles\n }\n let watchPaths: string[]\n if (userConfig.watchPaths && userConfig.watchPaths.length > 0) {\n watchPaths = userConfig.watchPaths\n } else {\n watchPaths = modelFiles\n }\n\n // Validate the code generation format\n const rawGenFormat = userConfig.genFormat || 'js'\n let genFormat: 'js' | 'c'\n switch (rawGenFormat) {\n case 'js':\n genFormat = 'js'\n break\n case 'c':\n genFormat = 'c'\n break\n default:\n throw new Error(`The configured genFormat value is invalid; must be either 'js' or 'c'`)\n }\n\n // Validate the out listing file, if defined\n let outListingFile: string\n if (userConfig.outListingFile) {\n // Get the absolute path of the output file\n if (isAbsolute(userConfig.outListingFile)) {\n outListingFile = userConfig.outListingFile\n } else {\n outListingFile = resolvePath(rootDir, userConfig.outListingFile)\n }\n }\n\n return {\n mode,\n rootDir,\n prepDir,\n modelFiles,\n modelInputPaths,\n watchPaths,\n genFormat,\n outListingFile,\n sdeDir,\n sdeCmdPath\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 { writeFileSync } from 'fs'\nimport pico from 'picocolors'\n\nexport type LogLevel = 'error' | 'info' | 'verbose'\n\nconst activeLevels: Set<LogLevel> = new Set(['error', 'info'])\n\nlet overlayFile: string\nlet overlayEnabled = false\nlet overlayHtml = ''\n\n/**\n * Set the active logging levels. By default, only 'error' and 'info'\n * messages are emitted.\n *\n * @param logLevels The logging levels to include.\n */\nexport function setActiveLevels(logLevels: LogLevel[]): void {\n activeLevels.clear()\n for (const level of logLevels) {\n activeLevels.add(level)\n }\n}\n\n/**\n * Set the path to the `messages.html` file where overlay messages will be written.\n *\n * @param file The absolute path to the HTML file where messages will be written.\n * @param enabled Whether to write messages to the file; if false, the file will be\n * emptied and no further messages will be written.\n */\nexport function setOverlayFile(file: string, enabled: boolean): void {\n overlayFile = file\n overlayEnabled = enabled\n\n // Write an empty file by default; this will ensure that messages from a previous\n // build aren't included in the current build\n writeFileSync(overlayFile, '')\n}\n\n/**\n * Log a message to the console and/or overlay.\n *\n * @param level The logging level.\n * @param msg The message to emit.\n */\nexport function log(level: LogLevel, msg: string): void {\n if (activeLevels.has(level)) {\n if (level === 'error') {\n console.error(pico.red(msg))\n logToOverlay(msg)\n } else {\n console.log(msg)\n logToOverlay(msg)\n }\n }\n}\n\n/**\n * Log an error to the console and/or overlay.\n *\n * @param e The error to log.\n */\nexport function logError(e: Error): void {\n // Remove the first part of the stack trace (which contains the message)\n // so that we can control the formatting of the message separately, then\n // only include up to 3 lines of the stack to keep the log cleaner\n const stack = e.stack || ''\n const stackLines = stack.split('\\n').filter(s => s.match(/^\\s+at/))\n const trace = stackLines.slice(0, 3).join('\\n')\n\n // Log the error message followed by the stack trace\n console.error(pico.red(`\\nERROR: ${e.message}`))\n console.error(pico.dim(pico.red(`${trace}\\n`)))\n logToOverlay(`\\nERROR: ${e.message}`, true)\n logToOverlay(`${trace}\\n`, true)\n}\n\nfunction writeOverlayFiles(): void {\n writeFileSync(overlayFile, overlayHtml)\n}\n\nexport function clearOverlay(): void {\n if (!overlayEnabled) {\n return\n }\n\n overlayHtml = ''\n writeOverlayFiles()\n}\n\nconst indent = '&nbsp;'.repeat(4)\n\nexport function logToOverlay(msg: string, error = false): void {\n if (!overlayEnabled) {\n return\n }\n\n if (error) {\n msg = `<span class=\"overlay-error\">${msg}</span>`\n }\n const msgHtml = msg.replace(/\\n/g, '\\n<br/>').replace(/\\s{2}/g, indent)\n if (overlayHtml) {\n overlayHtml += `<br/>${msgHtml}`\n } else {\n overlayHtml = `${msgHtml}`\n }\n writeOverlayFiles()\n}\n\nexport function clearConsole(): void {\n // TODO: This is disabled for now; maybe re-enable it under an optional flag\n // console.log('\\x1Bc')\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, readFileSync, writeFileSync } from 'fs'\nimport { writeFile } from 'fs/promises'\nimport { join as joinPath } from 'path'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport { clearOverlay, log } from '../../_shared/log'\nimport type { InputSpec, ModelSpec, OutputSpec, ResolvedModelSpec, VarName } from '../../_shared/model-spec'\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\nimport type { UserConfig } from '../../config/user-config'\nimport { BuildContext } from '../../context/context'\nimport { StagedFiles } from '../../context/staged-files'\nimport type { Plugin } from '../../plugin/plugin'\n\nimport { generateModel } from './gen-model'\nimport { computeInputFilesHash } from './hash-files'\n\nexport interface BuildOnceOptions {\n forceModelGen?: boolean\n abortSignal?: AbortSignal\n}\n\n/**\n * Perform a single build.\n *\n * This will return an error if a build failure occurred, or if a plugin encounters\n * an error. Otherwise, it will return true if the build and all plugins\n * succeeded, or false if a plugin wants to report a \"soft\" failure (for example,\n * if the model check plugin reports failing checks).\n *\n * @param config The resolved build configuration.\n * @param plugins The configured plugins.\n * @param options Options specific to the build process.\n * @return An `ok` result with true if the build and all plugins succeeded, or false if\n * one or more plugins failed; otherwise, an `err` result if there was a hard error.\n */\nexport async function buildOnce(\n config: ResolvedConfig,\n userConfig: UserConfig,\n plugins: Plugin[],\n options: BuildOnceOptions\n): Promise<Result<boolean, Error>> {\n // Create the build context\n const stagedFiles = new StagedFiles(config.prepDir)\n const context = new BuildContext(config, stagedFiles, options.abortSignal)\n const modelHashPath = joinPath(config.prepDir, 'model-hash.txt')\n\n // Note that the entire body of this function is wrapped in a try/catch. Any\n // errors that are thrown by plugin functions or the core `generateModel`\n // function will be caught and handled as appropriate.\n let succeeded = true\n try {\n // Get the model spec from the config\n const userModelSpec = await userConfig.modelSpec(context)\n if (userModelSpec === undefined) {\n return err(new Error('The model spec must be defined'))\n }\n\n // Resolve the model spec\n const modelSpec = resolveModelSpec(userModelSpec)\n\n // Run plugins that implement `preGenerate`\n for (const plugin of plugins) {\n if (plugin.preGenerate) {\n await plugin.preGenerate(context, modelSpec)\n }\n }\n\n // Write the spec file\n const specJson = {\n inputVarNames: modelSpec.inputVarNames,\n outputVarNames: modelSpec.outputVarNames,\n externalDatfiles: modelSpec.datFiles,\n bundleListing: modelSpec.bundleListing,\n customLookups: modelSpec.customLookups,\n customOutputs: modelSpec.customOutputs,\n ...modelSpec.options\n }\n const specPath = joinPath(config.prepDir, 'spec.json')\n await writeFile(specPath, JSON.stringify(specJson, null, 2))\n\n // Read the hash from the last successful model build, if available\n let previousModelHash: string\n if (existsSync(modelHashPath)) {\n previousModelHash = readFileSync(modelHashPath, 'utf8')\n } else {\n previousModelHash = 'NONE'\n }\n\n // The code gen and Wasm build steps are time consuming, so we avoid rebuilding\n // it if the build input files are unchanged since the last successful build\n const inputFilesHash = await computeInputFilesHash(config)\n let needModelGen: boolean\n if (options.forceModelGen === true) {\n needModelGen = true\n } else {\n const hashMismatch = inputFilesHash !== previousModelHash\n needModelGen = hashMismatch\n }\n\n if (needModelGen) {\n // Generate the model\n await generateModel(context, plugins)\n\n // Save the hash of the input files, which can be used to determine if\n // we need to rebuild the model the next time\n writeFileSync(modelHashPath, inputFilesHash)\n } else {\n // Skip code generation\n log('info', 'Skipping model code generation; already up-to-date')\n }\n\n // Run plugins that implement `postGenerate`\n // TODO: For now we run all plugins even if one or more of them returns\n // false, which allows (in theory) for there to be multiple plugins that\n // run tests or checks as a post-build step. We should eventually make\n // this configurable so that a plugin can halt the build if it fails.\n // (Technically this is already possible if the plugin throws an error\n // instead of returning false, but maybe it should be more configurable.)\n for (const plugin of plugins) {\n if (plugin.postGenerate) {\n const pluginSucceeded = await plugin.postGenerate(context, modelSpec)\n if (!pluginSucceeded) {\n succeeded = false\n }\n }\n }\n\n // Copy staged files to their destination; this will only copy the staged\n // files if they are different than the existing destination files. We\n // copy the files in a batch like this so that hot module reload is only\n // triggered once at the end of the whole build process.\n stagedFiles.copyChangedFiles()\n\n // Run plugins that implement `postBuild` (this is specified to run after\n // the \"copy staged files\" step)\n for (const plugin of plugins) {\n if (plugin.postBuild) {\n const pluginSucceeded = await plugin.postBuild(context, modelSpec)\n if (!pluginSucceeded) {\n succeeded = false\n }\n }\n }\n\n if (config.mode === 'development') {\n // Hide the \"rebuilding\" message in the dev overlay in the app if the\n // build succeeded; otherwise keep the error message visible\n log('info', 'Waiting for changes...\\n')\n clearOverlay()\n }\n } catch (e) {\n // When a build is aborted, the error will have \"ABORT\" as the message,\n // in which case we can swallow the error; for actual errors, rethrow\n if (e.message !== 'ABORT') {\n // Clear the hash so that the model is rebuilt next time\n writeFileSync(modelHashPath, '')\n\n // Return an error result\n return err(e)\n }\n }\n\n return ok(succeeded)\n}\n\n/**\n * Convert a `ModelSpec` instance to a `ResolvedModelSpec` instance.\n *\n * @param modelSpec The `ModelSpec` instance returned by the `UserConfig`.\n */\nfunction resolveModelSpec(modelSpec: ModelSpec): ResolvedModelSpec {\n let inputVarNames: VarName[]\n let inputSpecs: InputSpec[]\n if (modelSpec.inputs.length > 0) {\n const item = modelSpec.inputs[0]\n if (typeof item === 'string') {\n // The array contains variable names; derive `InputSpec` instances\n inputVarNames = modelSpec.inputs as VarName[]\n inputSpecs = inputVarNames.map(varName => {\n return {\n varName\n }\n })\n } else {\n // The array contains `InputSpec` objects; derive variable names\n inputSpecs = modelSpec.inputs as InputSpec[]\n inputVarNames = inputSpecs.map(spec => spec.varName)\n }\n } else {\n // The inputs array is empty, so return empty arrays\n inputVarNames = []\n inputSpecs = []\n }\n\n let outputVarNames: VarName[]\n let outputSpecs: OutputSpec[]\n if (modelSpec.outputs.length > 0) {\n const item = modelSpec.outputs[0]\n if (typeof item === 'string') {\n // The array contains variable names; derive `OutputSpec` instances\n outputVarNames = modelSpec.outputs as VarName[]\n outputSpecs = outputVarNames.map(varName => {\n return {\n varName\n }\n })\n } else {\n // The array contains `OutputSpec` objects; derive variable names\n outputSpecs = modelSpec.outputs as OutputSpec[]\n outputVarNames = outputSpecs.map(spec => spec.varName)\n }\n } else {\n // The outputs array is empty, so return empty arrays\n outputVarNames = []\n outputSpecs = []\n }\n\n let customLookups: boolean | VarName[]\n if (modelSpec.customLookups !== undefined) {\n customLookups = modelSpec.customLookups\n } else {\n customLookups = false\n }\n\n let customOutputs: boolean | VarName[]\n if (modelSpec.customOutputs !== undefined) {\n customOutputs = modelSpec.customOutputs\n } else {\n customOutputs = false\n }\n\n return {\n inputVarNames,\n inputs: inputSpecs,\n outputVarNames,\n outputs: outputSpecs,\n datFiles: modelSpec.datFiles || [],\n bundleListing: modelSpec.bundleListing === true,\n customLookups,\n customOutputs,\n options: modelSpec.options\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { canonicalVarId } from '@sdeverywhere/parse'\nimport type { LogLevel } from '../_shared/log'\nimport { log } from '../_shared/log'\n\nimport type { ResolvedConfig } from '../_shared/resolved-config'\n\nimport type { ProcessOptions, ProcessOutput } from './spawn-child'\nimport { spawnChild } from './spawn-child'\nimport type { StagedFiles } from './staged-files'\n\n/**\n * Provides access to common functionality that is needed during the build process.\n * This is passed to most plugin functions.\n */\nexport class BuildContext {\n /**\n * @param config The resolved configuration.\n * @hidden\n */\n constructor(\n public readonly config: ResolvedConfig,\n private readonly stagedFiles: StagedFiles,\n private readonly abortSignal: AbortSignal | undefined\n ) {}\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n log(level, msg)\n }\n\n /**\n * Prepare for writing a file to the staged directory.\n *\n * This will add the path to the array of tracked files and will create the\n * staged directory if needed.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param srcFile The name of the file as written to the `staged` directory.\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param dstFile The name of the file as written to the destination directory.\n * @return The absolute path to the staged file.\n */\n prepareStagedFile(srcDir: string, srcFile: string, dstDir: string, dstFile: string): string {\n return this.stagedFiles.prepareStagedFile(srcDir, srcFile, dstDir, dstFile)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.stagedFiles.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n /**\n * Spawn a child process that runs the given command.\n *\n * @param cwd The directory in which the command will be executed.\n * @param command The command to execute.\n * @param args The arguments to pass to the command.\n * @param opts Additional options to configure the process.\n * @returns The output of the process.\n */\n spawnChild(cwd: string, command: string, args: string[], opts?: ProcessOptions): Promise<ProcessOutput> {\n return spawnChild(cwd, command, args, this.abortSignal, opts)\n }\n\n /**\n * Format a (subscripted or non-subscripted) model variable name into a canonical\n * identifier (with special characters converted to underscore, and subscript/dimension\n * parts separated by commas).\n *\n * @param name The name of the variable in the source model, e.g., `Variable name[DimA, B2]`.\n * @returns The canonical identifier for the given name, e.g., `_variable_name[_dima,_b2]`.\n */\n canonicalVarId(name: string): string {\n return canonicalVarId(name)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ChildProcess } from 'child_process'\n\nimport { spawn } from 'cross-spawn'\n\nimport { log } from '../_shared/log'\n\n/**\n * @hidden This isn't ready to be included in the public API just yet.\n */\nexport interface ProcessOptions {\n logOutput?: boolean\n ignoredMessageFilter?: string\n captureOutput?: boolean\n ignoreError?: boolean\n}\n\n/**\n * @hidden This isn't ready to be included in the public API just yet.\n */\nexport interface ProcessOutput {\n exitCode: number\n stdoutMessages: string[]\n stderrMessages: string[]\n}\n\n/**\n * Spawn a child process that runs the given command.\n *\n * @param cwd The directory in which the command will be executed.\n * @param command The command to execute.\n * @param args The arguments to pass to the command.\n * @param abortSignal The signal used to abort the process.\n * @param opts Additional options to configure the process.\n * @returns The output of the process.\n */\nexport function spawnChild(\n cwd: string,\n command: string,\n args: string[],\n abortSignal?: AbortSignal,\n opts?: ProcessOptions\n): Promise<ProcessOutput> {\n return new Promise((resolve, reject) => {\n if (abortSignal?.aborted) {\n reject(new Error('ABORT'))\n return\n }\n\n let childProc: ChildProcess\n\n const localLog = (s: string, err = false) => {\n // Don't log anything after the process has been killed\n if (childProc === undefined) {\n return\n }\n log(err ? 'error' : 'info', s)\n }\n\n const abortHandler = () => {\n if (childProc) {\n log('info', 'Killing existing build process...')\n childProc.kill('SIGKILL')\n childProc = undefined\n }\n reject(new Error('ABORT'))\n }\n\n // Kill the process if abort is requested\n abortSignal?.addEventListener('abort', abortHandler, { once: true })\n\n // Prepare for capturing output, if requested\n const stdoutMessages: string[] = []\n const stderrMessages: string[] = []\n const logMessage = (msg: string, err: boolean) => {\n let includeMessage = true\n if (opts?.ignoredMessageFilter && msg.trim().startsWith(opts.ignoredMessageFilter)) {\n includeMessage = false\n }\n if (includeMessage) {\n const lines = msg.trim().split('\\n')\n for (const line of lines) {\n localLog(` ${line}`, err)\n }\n }\n }\n\n // Spawn the (asynchronous) child process. Note that we are using `spawn`\n // from the `cross-spawn` package as an alternative to the built-in\n // `child_process` module, which doesn't handle spaces in command path\n // on Windows.\n childProc = spawn(command, args, {\n cwd\n })\n\n childProc.stdout.on('data', (data: Buffer) => {\n const msg = data.toString()\n if (opts?.captureOutput === true) {\n stdoutMessages.push(msg)\n }\n if (opts?.logOutput !== false) {\n logMessage(msg, false)\n }\n })\n childProc.stderr.on('data', (data: Buffer) => {\n const msg = data.toString()\n if (opts?.captureOutput === true) {\n stderrMessages.push(msg)\n }\n if (opts?.logOutput !== false) {\n logMessage(msg, true)\n }\n })\n childProc.on('error', err => {\n localLog(`Process error: ${err}`, true)\n })\n childProc.on('close', (code, signal) => {\n // Stop listening for abort events\n abortSignal?.removeEventListener('abort', abortHandler)\n childProc = undefined\n\n if (signal) {\n // The process was killed by a signal, so we don't need to print anything\n return\n }\n\n const processOutput: ProcessOutput = {\n exitCode: code,\n stdoutMessages,\n stderrMessages\n }\n\n if (code === 0) {\n // The process exited cleanly; resolve the promise\n resolve(processOutput)\n } else if (!signal) {\n // The process failed\n if (opts?.ignoreError === true) {\n // Resolve the promise (but with a non-zero exit code)\n resolve(processOutput)\n } else {\n // Reject the promise\n reject(new Error(`Child process failed (code=${code})`))\n }\n }\n })\n })\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport { log } from '../_shared/log'\n\ninterface StagedFile {\n srcDir: string\n srcFile: string\n dstDir: string\n dstFile: string\n}\n\nexport class StagedFiles {\n private readonly baseStagedDir: string\n private readonly stagedFiles: StagedFile[] = []\n\n constructor(prepDir: string) {\n this.baseStagedDir = joinPath(prepDir, 'staged')\n }\n\n /**\n * Prepare for writing a file to the staged directory.\n *\n * This will add the path to the array of tracked files and will create the\n * staged directory if needed.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param srcFile The name of the file as written to the `staged` directory.\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param dstFile The name of the file as written to the destination directory.\n * @return The absolute path to the staged file.\n */\n prepareStagedFile(srcDir: string, srcFile: string, dstDir: string, dstFile: string): string {\n // Add an entry to the array of staged files (only if an entry does not already\n // exist) so that we can copy the file to the destination directory when the build\n // process has completed\n const stagedFile = {\n srcDir,\n srcFile,\n dstDir,\n dstFile\n }\n // TODO: We allow there to be more than one entry for each source path to\n // support the case where a single source file gets copied to multiple\n // destination paths. But we should throw an error if the same destination\n // path is configured for different source paths.\n if (this.stagedFiles.indexOf(stagedFile) < 0) {\n this.stagedFiles.push(stagedFile)\n }\n\n // Create the directory underneath the staged directory if needed\n const stagedDir = joinPath(this.baseStagedDir, srcDir)\n if (!existsSync(stagedDir)) {\n mkdirSync(stagedDir, { recursive: true })\n }\n\n return joinPath(stagedDir, srcFile)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n // Add an entry to track the file and create the staged directory if needed\n const stagedFilePath = this.prepareStagedFile(srcDir, filename, dstDir, filename)\n\n // Write the file to the staged directory\n writeFileSync(stagedFilePath, content)\n }\n\n /**\n * Return the absolute path to the staged file for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n getStagedFilePath(srcDir: string, srcFile: string): string {\n return joinPath(this.baseStagedDir, srcDir, srcFile)\n }\n\n /**\n * Return true if the staged file exists for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n stagedFileExists(srcDir: string, srcFile: string): boolean {\n const fullSrcPath = this.getStagedFilePath(srcDir, srcFile)\n return existsSync(fullSrcPath)\n }\n\n /**\n * Return true if the destination file exists for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n destinationFileExists(srcDir: string, srcFile: string): boolean {\n const f = this.stagedFiles.find(f => f.srcDir === srcDir && f.srcFile === srcFile)\n if (f === undefined) {\n return false\n }\n const fullDstPath = joinPath(f.dstDir, f.dstFile)\n return existsSync(fullDstPath)\n }\n\n /**\n * Copy staged files to their destination; this will only copy the staged\n * files if they are different than the existing destination files. We\n * copy the files in a batch like this so that hot module reload is only\n * triggered once at the end of the whole build process.\n */\n copyChangedFiles(): void {\n log('info', 'Copying changed files into place...')\n\n for (const f of this.stagedFiles) {\n this.copyStagedFile(f)\n }\n\n log('info', 'Done copying files')\n }\n\n /**\n * Copy a file from the `staged` directory to its destination. If the file already\n * exists in the destination directory and has the same contents as the source file,\n * the file will not be copied and this function will return false.\n *\n * @param f The staged file entry.\n */\n private copyStagedFile(f: StagedFile): boolean {\n // Create the destination directory, if needed\n if (!existsSync(f.dstDir)) {\n mkdirSync(f.dstDir, { recursive: true })\n }\n\n // If the destination file already exists and has the same contents as the source\n // file, we can skip copying it\n const fullSrcPath = this.getStagedFilePath(f.srcDir, f.srcFile)\n const fullDstPath = joinPath(f.dstDir, f.dstFile)\n const needsCopy = filesDiffer(fullSrcPath, fullDstPath)\n if (needsCopy) {\n log('verbose', ` Copying ${f.srcFile} to ${fullDstPath}`)\n copyFileSync(fullSrcPath, fullDstPath)\n }\n return needsCopy\n }\n}\n\n/**\n * Return true if both files exist at the given paths and have the same contents, false otherwise.\n */\nfunction filesDiffer(aPath: string, bPath: string): boolean {\n if (existsSync(aPath) && existsSync(bPath)) {\n // The files exist; see if they are different\n const aSize = statSync(aPath).size\n const bSize = statSync(bPath).size\n if (aSize !== bSize) {\n // The sizes are different, so the contents must be different\n return true\n } else {\n // The sizes are the same, so check the contents\n const aBuf = readFileSync(aPath)\n const bBuf = readFileSync(bPath)\n return !aBuf.equals(bBuf)\n }\n } else {\n // One or both files do not exist\n return true\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { copyFile, readdir, readFile, writeFile } from 'fs/promises'\nimport { basename, dirname, join as joinPath } from 'path'\n\nimport { log } from '../../_shared/log'\n\nimport type { BuildContext } from '../../context/context'\n\nimport type { Plugin } from '../../plugin/plugin'\n\n/**\n * Generate the model. This will run the core SDEverywhere code generation steps\n * and will also invoke the following plugin functions:\n * - `preProcessMdl`\n * - `postProcessMdl`\n * - `preGenerateC`\n * - `postGenerateC`\n */\nexport async function generateModel(context: BuildContext, plugins: Plugin[]): Promise<void> {\n const config = context.config\n if (config.modelFiles.length === 0) {\n log('info', 'No model input files specified, skipping model generation steps')\n return\n }\n\n log('info', 'Generating model...')\n\n const t0 = performance.now()\n\n // Use the defined prep directory\n const prepDir = config.prepDir\n\n // TODO: For now we assume the path is to the `main.js` file in the cli package;\n // this seems to work on both Unix and Windows, but we may need to revisit this\n // as part of removing the `sdeCmdPath` config hack\n const sdeCmdPath = config.sdeCmdPath\n\n // Process the mdl file(s)\n for (const plugin of plugins) {\n if (plugin.preProcessMdl) {\n await plugin.preProcessMdl(context)\n }\n }\n if (config.modelFiles.length === 1) {\n // Preprocess the single mdl file\n await preprocessMdl(context, sdeCmdPath, prepDir, config.modelFiles[0])\n } else {\n // Flatten and preprocess the multiple mdl files into a single mdl file\n await flattenMdls(context, sdeCmdPath, prepDir, config.modelFiles)\n }\n for (const plugin of plugins) {\n if (plugin.postProcessMdl) {\n const mdlPath = joinPath(prepDir, 'processed.mdl')\n let mdlContent = await readFile(mdlPath, 'utf8')\n mdlContent = await plugin.postProcessMdl(context, mdlContent)\n await writeFile(mdlPath, mdlContent)\n }\n }\n\n // Generate the JS or C file\n for (const plugin of plugins) {\n if (plugin.preGenerateCode) {\n await plugin.preGenerateCode(context, config.genFormat)\n }\n }\n await generateCode(context, config.sdeDir, sdeCmdPath, prepDir)\n const generatedCodeFile = `processed.${config.genFormat}`\n const generatedCodePath = joinPath(prepDir, 'build', generatedCodeFile)\n for (const plugin of plugins) {\n if (plugin.postGenerateCode) {\n let generatedCodeContent = await readFile(generatedCodePath, 'utf8')\n generatedCodeContent = await plugin.postGenerateCode(context, config.genFormat, generatedCodeContent)\n await writeFile(generatedCodePath, generatedCodeContent)\n }\n }\n\n if (config.genFormat === 'js') {\n // When generating JS code, copy the generated JS file to the `staged/model`\n // directory, because that's where plugin-worker expects to find it, but also\n // set it up to be copied to the `prepDir`, which is where other code expects\n // to find it\n // TODO: Maybe we can change plugin-worker to use the one in `prepDir`, and/or\n // add a build config setting to allow for customizing the output location\n const outputJsFile = 'generated-model.js'\n const stagedOutputJsPath = context.prepareStagedFile('model', outputJsFile, prepDir, outputJsFile)\n await copyFile(generatedCodePath, stagedOutputJsPath)\n }\n\n if (config.outListingFile) {\n // Copy the model listing file\n const srcListingJsonPath = joinPath(prepDir, 'build', 'processed.json')\n const stagedDir = 'model'\n const stagedFile = 'listing.json'\n const dstDir = dirname(config.outListingFile)\n const dstFile = basename(config.outListingFile)\n const stagedListingJsonPath = context.prepareStagedFile(stagedDir, stagedFile, dstDir, dstFile)\n await copyFile(srcListingJsonPath, stagedListingJsonPath)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n log('info', `Done generating model (${elapsed}s)`)\n}\n\n/**\n * Preprocess a single mdl file and copy the resulting `processed.mdl` to the prep directory.\n */\nasync function preprocessMdl(\n context: BuildContext,\n sdeCmdPath: string,\n prepDir: string,\n modelFile: string\n): Promise<void> {\n log('verbose', ' Preprocessing mdl file')\n\n // Copy the source file to the prep directory to make the next steps easier\n await copyFile(modelFile, joinPath(prepDir, 'processed.mdl'))\n\n // Use SDE to preprocess the model to strip anything that's not needed to build it\n const command = sdeCmdPath\n const args = ['generate', '--preprocess', 'processed.mdl']\n const ppOutput = await context.spawnChild(prepDir, command, args, {\n // The default error message from `spawnChild` is not very informative, so the\n // following allows us to throw our own error\n ignoreError: true\n })\n if (ppOutput.exitCode !== 0) {\n throw new Error(`Failed to preprocess mdl file: 'sde generate' command failed (code=${ppOutput.exitCode})`)\n }\n\n // Copy the processed file back to the prep directory\n await copyFile(joinPath(prepDir, 'build', 'processed.mdl'), joinPath(prepDir, 'processed.mdl'))\n}\n\n/**\n * Flatten multiple mdl files and copy the resulting `processed.mdl` to the prep directory.\n */\nasync function flattenMdls(\n context: BuildContext,\n sdeCmdPath: string,\n prepDir: string,\n modelFiles: string[]\n): Promise<void> {\n log('verbose', ' Flattening and preprocessing mdl files')\n\n // Use SDE to flatten the parent model and submodels into a single mdl file,\n // then preprocess to strip anything that's not needed to build the model\n const command = sdeCmdPath\n const args: string[] = []\n args.push('flatten')\n args.push('processed.mdl')\n args.push('--inputs')\n for (const path of modelFiles) {\n args.push(path)\n }\n\n // Disable logging by default; this will suppress flatten warnings, which are\n // sometimes unavoidable and not helpful\n const output = await context.spawnChild(prepDir, command, args, {\n logOutput: false,\n captureOutput: true,\n ignoreError: true\n })\n\n // Check for flattening errors\n let flattenErrors = false\n for (const msg of output.stderrMessages) {\n if (msg.includes('ERROR')) {\n flattenErrors = true\n break\n }\n }\n if (flattenErrors) {\n log('error', 'There were errors reported when flattening the model:')\n for (const msg of output.stderrMessages) {\n const lines = msg.split('\\n')\n for (const line of lines) {\n log('error', ` ${line}`)\n }\n }\n throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`)\n } else if (output.exitCode !== 0) {\n throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`)\n }\n\n // Copy the processed file back to the prep directory\n await copyFile(joinPath(prepDir, 'build', 'processed.mdl'), joinPath(prepDir, 'processed.mdl'))\n}\n\n/**\n * Generate a JS or C file from the `processed.mdl` file.\n */\nasync function generateCode(context: BuildContext, sdeDir: string, sdeCmdPath: string, prepDir: string): Promise<void> {\n const genFormat = context.config.genFormat\n const genFormatName = genFormat.toUpperCase()\n log('verbose', ` Generating ${genFormatName} code`)\n\n // Use SDE to generate both a JS/C version of the model (`--outformat`) AND a JSON list of all model\n // dimensions and variables (`--list`)\n const command = sdeCmdPath\n const outFormat = `--outformat=${genFormat}`\n const genCmdArgs = ['generate', outFormat, '--list', '--spec', 'spec.json', 'processed']\n const genCmdOutput = await context.spawnChild(prepDir, command, genCmdArgs, {\n // By default, ignore lines that start with \"WARNING: Data for\" since these are often harmless\n // TODO: Don't filter by default, but make it configurable\n // ignoredMessageFilter: 'WARNING: Data for'\n // The default error message from `spawnChild` is not very informative, so the\n // following allows us to throw our own error\n ignoreError: true\n })\n if (genCmdOutput.exitCode !== 0) {\n throw new Error(\n `Failed to generate ${genFormatName} code: 'sde generate' command failed (code=${genCmdOutput.exitCode})`\n )\n }\n\n if (genFormat === 'c') {\n // Copy SDE's supporting C files into the build directory\n const buildDir = joinPath(prepDir, 'build')\n const sdeCDir = joinPath(sdeDir, 'src', 'c')\n const files = await readdir(sdeCDir)\n const copyOps = []\n for (const file of files) {\n if (file.endsWith('.c') || file.endsWith('.h')) {\n copyOps.push(copyFile(joinPath(sdeCDir, file), joinPath(buildDir, file)))\n }\n }\n await Promise.all(copyOps)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\nimport { hashElement } from 'folder-hash'\nimport { glob } from 'tinyglobby'\n\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\n/**\n * Asynchronously compute the hash of the files that are inputs to the model\n * build process.\n */\nexport async function computeInputFilesHash(config: ResolvedConfig): Promise<string> {\n const inputFiles: string[] = []\n\n // Always include the `spec.json` file, since that is a primary input\n // to the model build process\n const specFile = joinPath(config.prepDir, 'spec.json')\n inputFiles.push(specFile)\n\n if (config.modelInputPaths && config.modelInputPaths.length > 0) {\n // Include the files that match the glob patterns in the config file.\n // Note that the folder-hash package supports glob patterns, but its\n // configuration is complicated, so it is easier if we resolve the\n // glob patterns here and pass each individual file to the\n // `hashElement` function.\n for (const globPath of config.modelInputPaths) {\n const paths = await glob(globPath, {\n cwd: config.rootDir,\n absolute: true,\n onlyFiles: true\n })\n inputFiles.push(...paths)\n }\n } else {\n // Only use the mdl files to compute the hash\n inputFiles.push(...config.modelFiles)\n }\n\n // Compute the hash of each input file and concatenate into a single string\n let hash = ''\n for (const inputFile of inputFiles) {\n const result = await hashElement(inputFile)\n hash += result.hash\n }\n\n return hash\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { basename } from 'path'\n\nimport chokidar from 'chokidar'\nimport { globSync, isDynamicPattern } from 'tinyglobby'\n\nimport { clearOverlay, log, logError } from '../../_shared/log'\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\nimport type { UserConfig } from '../../config/user-config'\nimport type { Plugin } from '../../plugin/plugin'\n\nimport type { BuildOnceOptions } from './build-once'\nimport { buildOnce } from './build-once'\n\nclass BuildState {\n readonly abortController = new AbortController()\n}\n\nexport function watch(config: ResolvedConfig, userConfig: UserConfig, plugins: Plugin[]): void {\n // Add a small delay so that if multiple files are changed at once (as is\n // often the case when switching branches), we batch them up and start the\n // the build after things settle down\n const delay = 150\n const changedPaths: Set<string> = new Set()\n\n // Keep track of the current build so that we only have one active at a time\n let currentBuildState: BuildState\n\n function performBuild() {\n clearOverlay()\n\n // Log the input files that have changed\n for (const path of changedPaths) {\n log('info', `Input file ${basename(path)} has been changed`)\n }\n\n // Clear the set of pending files\n changedPaths.clear()\n\n // Keep track of builds; if one is already in progress, abort it\n // before starting another one\n if (currentBuildState) {\n currentBuildState.abortController.abort()\n // TODO: Prevent aborted build from logging?\n currentBuildState = undefined\n }\n\n // Generate files and build the model\n currentBuildState = new BuildState()\n const buildOptions: BuildOnceOptions = {\n abortSignal: currentBuildState.abortController.signal\n }\n buildOnce(config, userConfig, plugins, buildOptions)\n .then(result => {\n // Log the error message in case of error result\n if (result.isErr()) {\n logError(result.error)\n }\n })\n .catch(e => {\n // Also catch thrown errors that may have not already been\n // handled by `buildOnce`\n logError(e)\n // log('info', 'Waiting for changes...')\n })\n .finally(() => {\n currentBuildState = undefined\n })\n }\n\n function scheduleBuild(changedPath: string) {\n // Only schedule the build if the set is currently empty\n const schedule = changedPaths.size === 0\n\n // Add the path to the set of changed files\n changedPaths.add(changedPath)\n\n if (schedule) {\n // Schedule the build to start after a delay\n setTimeout(() => {\n performBuild()\n }, delay)\n }\n }\n\n let watchPaths: string[]\n if (config.watchPaths && config.watchPaths.length > 0) {\n // Watch the configured files\n watchPaths = config.watchPaths\n } else {\n // Only watch the mdl files\n watchPaths = config.modelFiles\n }\n\n // The chokidar package no longer supports glob patterns, so we need to resolve\n // glob patterns first and then pass the resolved paths to chokidar\n const resolvedWatchPaths: string[] = []\n for (const watchPath of watchPaths) {\n if (isDynamicPattern(watchPath)) {\n // This is a glob pattern; resolve files that match the pattern\n const paths = globSync(watchPath, {\n // Watch paths are resolved relative to the project root directory\n cwd: config.rootDir,\n // Resolve to absolute paths\n absolute: true\n })\n resolvedWatchPaths.push(...paths)\n } else {\n // This is regular file or directory path; let chokidar resolve it\n resolvedWatchPaths.push(watchPath)\n }\n }\n\n // Watch the config and model files; if changes are detected, generate the specs\n // and rebuild the model if needed\n const watcher = chokidar.watch(resolvedWatchPaths, {\n // Watch paths are resolved relative to the project root directory\n cwd: config.rootDir,\n // XXX: Include a delay, otherwise on macOS we sometimes get multiple\n // change events when the csv file is saved just once\n awaitWriteFinish: {\n stabilityThreshold: 200\n }\n })\n watcher.on('change', path => {\n scheduleBuild(path)\n })\n}\n"],"mappings":"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","names":["import_path","import_neverthrow","joinPath","resolvePath","import_fs","pico","import_fs","import_promises","import_path","import_neverthrow","err","import_fs","import_path","joinPath","f","import_path","joinPath","import_path","joinPath","joinPath","import_path","import_tinyglobby","chokidar","joinPath","overlayEnabled"]}
1
+ {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_typescript@5.2.2/node_modules/tsup/assets/cjs_shims.js","../src/build/build.ts","../src/config/config-loader.ts","../src/_shared/log.ts","../src/build/impl/build-once.ts","../src/context/context.ts","../src/context/spawn-child.ts","../src/context/staged-files.ts","../src/build/impl/gen-model.ts","../src/build/impl/hash-files.ts","../src/build/impl/watch.ts","../src/build/impl/watch-paths.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\n//\n// Note that exports are ordered here according to the dependency chain,\n// i.e., lower items depend on the ones above.\n//\n\nexport type { LogLevel } from './_shared/log'\nexport type { BuildMode } from './_shared/mode'\nexport type { InputSpec, ModelSpec, OutputSpec, ResolvedModelSpec, VarName } from './_shared/model-spec'\nexport type { ResolvedConfig } from './_shared/resolved-config'\n\nexport type { BuildContext } from './context/context'\n\nexport type { Plugin } from './plugin/plugin'\n\nexport type { UserConfig } from './config/user-config'\n\nexport type { BuildOptions, BuildResult } from './build/build'\nexport { build } from './build/build'\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport type { BuildMode } from '../_shared/mode'\n\nimport { loadConfig } from '../config/config-loader'\nimport type { UserConfig } from '../config/user-config'\nimport type { LogLevel } from '../_shared/log'\nimport { setOverlayFile, setActiveLevels } from '../_shared/log'\nimport { buildOnce } from './impl/build-once'\nimport { watch } from './impl/watch'\n\nexport interface BuildOptions {\n /** The path to an `sde.config.js` file, or a `UserConfig` object. */\n config?: string | UserConfig\n\n /**\n * The log levels to include. If undefined, the default 'info' and 'error' levels\n * will be active.\n */\n logLevels?: LogLevel[]\n\n /**\n * The path to the `@sdeverywhere/cli` package. This is currently only used to get\n * access to the files in the `src/c` directory.\n * @hidden This should be removed once we have tighter integration with the `cli` package.\n */\n sdeDir: string\n\n /**\n * The path to the `sde` command.\n * @hidden This should be removed once we have tighter integration with the `cli` package.\n */\n sdeCmdPath: string\n}\n\nexport interface BuildResult {\n /**\n * The exit code that should be set by the process. This will be undefined\n * if `mode` is 'development', indicating that the process should be kept alive.\n */\n exitCode?: number\n}\n\n/**\n * Initiate the build process, which can either be a single build if `mode` is\n * 'production', or a live development environment if `mode` is 'development'.\n *\n * @param mode The build mode.\n * @param options The build options.\n * @return An `ok` result if the build completed, otherwise an `err` result.\n */\nexport async function build(mode: BuildMode, options: BuildOptions): Promise<Result<BuildResult, Error>> {\n // Load the config\n const configResult = await loadConfig(mode, options.config, options.sdeDir, options.sdeCmdPath)\n if (configResult.isErr()) {\n return err(configResult.error)\n }\n const { userConfig, resolvedConfig } = configResult.value\n\n // Configure logging level\n if (options.logLevels !== undefined) {\n setActiveLevels(options.logLevels)\n }\n\n // Configure the overlay `messages.html` file, which is written under the\n // `sde-prep` directory. For production builds, this file will remain empty.\n const messagesPath = joinPath(resolvedConfig.prepDir, 'messages.html')\n const overlayEnabled = mode === 'development'\n setOverlayFile(messagesPath, overlayEnabled)\n\n try {\n // Initialize plugins\n const plugins = userConfig.plugins || []\n for (const plugin of plugins) {\n if (plugin.init) {\n await plugin.init(resolvedConfig)\n }\n }\n\n if (mode === 'development') {\n // Enable dev mode (which will rebuild when watched files are changed).\n // First run an initial build so that we have a baseline, and then\n // once that is complete, enable file watchers.\n const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {})\n // TODO: We should trap errors here and keep the dev process\n // running if the initial build fails\n if (buildResult.isErr()) {\n return err(buildResult.error)\n }\n\n // Allow plugins to set up watchers after the initial build completes\n for (const plugin of plugins) {\n if (plugin.watch) {\n await plugin.watch(resolvedConfig)\n }\n }\n\n // Watch for changes to the source/model/test files\n watch(resolvedConfig, userConfig, plugins)\n\n // Return a build result with undefined exit code, indicating that the\n // process should be kept alive\n return ok({})\n } else {\n // Run a single build\n const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {})\n if (buildResult.isErr()) {\n return err(buildResult.error)\n }\n\n // Configure the exit code depending on whether any plugins failed.\n // Currently we use the following exit code values:\n // 0 == build succeeded, AND all plugins succeeded\n // 1 == build failed (or a plugin reported a \"hard\" error)\n // 2 == build succeeded, BUT one or more plugins failed\n const allPluginsSucceeded = buildResult.value\n const exitCode = allPluginsSucceeded ? 0 : 2\n return ok({ exitCode })\n }\n } catch (e) {\n return err(e)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, lstatSync, mkdirSync } from 'fs'\nimport { dirname, isAbsolute, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport type { BuildMode } from '../_shared/mode'\nimport type { ResolvedConfig } from '../_shared/resolved-config'\n\nimport type { UserConfig } from './user-config'\n\nexport interface ConfigResult {\n userConfig: UserConfig\n resolvedConfig: ResolvedConfig\n}\n\n/**\n * Load a user-defined config file or a given `UserConfig` object. This validates\n * all paths and if successful, this returns a `ResolvedConfig`, otherwise returns\n * an error result.\n *\n * @param mode The build mode.\n * @param config The path to a config file, or a `UserConfig` object; if undefined,\n * this will look for a `sde.config.js` file in the current directory.\n * @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).\n * @param sdeCmdPath Temporary (the path to the `sde` command).\n * @return An `ok` result with the `ResolvedConfig`, otherwise an `err` result.\n */\nexport async function loadConfig(\n mode: BuildMode,\n config: string | UserConfig | undefined,\n sdeDir: string,\n sdeCmdPath: string\n): Promise<Result<ConfigResult, Error>> {\n let userConfig: UserConfig\n if (typeof config === 'object') {\n // Use the given `UserConfig` object\n userConfig = config\n } else {\n // Load the project-specific config. If no `--config` arg was specified\n // on the command line, look for a `sde.config.js` file in the current\n // directory, and failing that, use a default config.\n // TODO: Create a default config if no file found; for now, we just fail\n let configPath: string\n if (typeof config === 'string') {\n configPath = config\n } else {\n configPath = joinPath(process.cwd(), 'sde.config.js')\n }\n try {\n if (!existsSync(configPath)) {\n return err(new Error(`Cannot find config file '${configPath}'`))\n }\n const configRelPath = relativeToSourcePath(configPath)\n const configModule = await import(configRelPath)\n userConfig = await configModule.config()\n } catch (e) {\n return err(new Error(`Failed to load config file '${configPath}': ${e.message}`))\n }\n }\n\n // Resolve the config\n try {\n const resolvedConfig = resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath)\n return ok({\n userConfig,\n resolvedConfig\n })\n } catch (e) {\n return err(e)\n }\n}\n\n/**\n * Resolve the given user configuration by resolving all paths. This will create\n * the prep directory if it does not already exist. This will throw an error if\n * any other paths are invalid or do not exist.\n *\n * @param userConfig The user-defined configuration.\n * @param mode The active build mode.\n * @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).\n * @param sdeCmdPath Temporary (the path to the `sde` command).\n * @return The resolved configuration.\n */\nfunction resolveUserConfig(\n userConfig: UserConfig,\n mode: BuildMode,\n sdeDir: string,\n sdeCmdPath: string\n): ResolvedConfig {\n function expectDirectory(propName: string, path: string): void {\n if (!existsSync(path)) {\n throw new Error(`The configured ${propName} (${path}) does not exist`)\n } else if (!lstatSync(path).isDirectory()) {\n throw new Error(`The configured ${propName} (${path}) is not a directory`)\n }\n }\n\n // function expectFile(propName: string, path: string): void {\n // if (!existsSync(path)) {\n // throw new Error(`The configured ${propName} (${path}) does not exist`)\n // // TODO: Don't include the \"is file\" check for now; need to update this to\n // // handle symlinks\n // // } else if (!lstatSync(path).isFile()) {\n // // throw new Error(`The configured ${propName} (${path}) is not a file`)\n // }\n // }\n\n // Validate the root directory\n let rootDir: string\n if (userConfig.rootDir) {\n // Verify that the configured root directory exists\n rootDir = resolvePath(userConfig.rootDir)\n expectDirectory('rootDir', rootDir)\n } else {\n // Use the current working directory as the project root\n rootDir = process.cwd()\n }\n\n // Validate the prep directory\n let prepDir: string\n if (userConfig.prepDir) {\n // Resolve the configured prep directory\n prepDir = resolvePath(userConfig.prepDir)\n } else {\n // Create an 'sde-prep' directory under the configured root directory\n prepDir = resolvePath(rootDir, 'sde-prep')\n }\n mkdirSync(prepDir, { recursive: true })\n\n // Validate the model files\n const userModelFiles = userConfig.modelFiles\n const modelFiles: string[] = []\n for (const userModelFile of userModelFiles) {\n const modelFile = resolvePath(userModelFile)\n if (!existsSync(modelFile)) {\n throw new Error(`The configured model file (${modelFile}) does not exist`)\n }\n modelFiles.push(modelFile)\n }\n\n // TODO: Validate the watch paths; these are allowed to be globs, so need to\n // figure out the best way to resolve them\n let modelInputPaths: string[]\n if (userConfig.modelInputPaths && userConfig.modelInputPaths.length > 0) {\n modelInputPaths = userConfig.modelInputPaths\n } else {\n modelInputPaths = modelFiles\n }\n let watchPaths: string[]\n if (userConfig.watchPaths && userConfig.watchPaths.length > 0) {\n watchPaths = userConfig.watchPaths\n } else {\n watchPaths = modelFiles\n }\n\n // Validate the code generation format\n const rawGenFormat = userConfig.genFormat || 'js'\n let genFormat: 'js' | 'c'\n switch (rawGenFormat) {\n case 'js':\n genFormat = 'js'\n break\n case 'c':\n genFormat = 'c'\n break\n default:\n throw new Error(`The configured genFormat value is invalid; must be either 'js' or 'c'`)\n }\n\n // Validate the out listing file, if defined\n let outListingFile: string\n if (userConfig.outListingFile) {\n // Get the absolute path of the output file\n if (isAbsolute(userConfig.outListingFile)) {\n outListingFile = userConfig.outListingFile\n } else {\n outListingFile = resolvePath(rootDir, userConfig.outListingFile)\n }\n }\n\n return {\n mode,\n rootDir,\n prepDir,\n modelFiles,\n modelInputPaths,\n watchPaths,\n genFormat,\n outListingFile,\n sdeDir,\n sdeCmdPath\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 { writeFileSync } from 'fs'\nimport pico from 'picocolors'\n\nexport type LogLevel = 'error' | 'info' | 'verbose'\n\nconst activeLevels: Set<LogLevel> = new Set(['error', 'info'])\n\nlet overlayFile: string\nlet overlayEnabled = false\nlet overlayHtml = ''\n\n/**\n * Set the active logging levels. By default, only 'error' and 'info'\n * messages are emitted.\n *\n * @param logLevels The logging levels to include.\n */\nexport function setActiveLevels(logLevels: LogLevel[]): void {\n activeLevels.clear()\n for (const level of logLevels) {\n activeLevels.add(level)\n }\n}\n\n/**\n * Set the path to the `messages.html` file where overlay messages will be written.\n *\n * @param file The absolute path to the HTML file where messages will be written.\n * @param enabled Whether to write messages to the file; if false, the file will be\n * emptied and no further messages will be written.\n */\nexport function setOverlayFile(file: string, enabled: boolean): void {\n overlayFile = file\n overlayEnabled = enabled\n\n // Write an empty file by default; this will ensure that messages from a previous\n // build aren't included in the current build\n writeFileSync(overlayFile, '')\n}\n\n/**\n * Log a message to the console and/or overlay.\n *\n * @param level The logging level.\n * @param msg The message to emit.\n */\nexport function log(level: LogLevel, msg: string): void {\n if (activeLevels.has(level)) {\n if (level === 'error') {\n console.error(pico.red(msg))\n logToOverlay(msg)\n } else {\n console.log(msg)\n logToOverlay(msg)\n }\n }\n}\n\n/**\n * Log an error to the console and/or overlay.\n *\n * @param e The error to log.\n */\nexport function logError(e: Error): void {\n // Remove the first part of the stack trace (which contains the message)\n // so that we can control the formatting of the message separately, then\n // only include up to 3 lines of the stack to keep the log cleaner\n const stack = e.stack || ''\n const stackLines = stack.split('\\n').filter(s => s.match(/^\\s+at/))\n const trace = stackLines.slice(0, 3).join('\\n')\n\n // Log the error message followed by the stack trace\n console.error(pico.red(`\\nERROR: ${e.message}`))\n console.error(pico.dim(pico.red(`${trace}\\n`)))\n logToOverlay(`\\nERROR: ${e.message}`, true)\n logToOverlay(`${trace}\\n`, true)\n}\n\nfunction writeOverlayFiles(): void {\n writeFileSync(overlayFile, overlayHtml)\n}\n\nexport function clearOverlay(): void {\n if (!overlayEnabled) {\n return\n }\n\n overlayHtml = ''\n writeOverlayFiles()\n}\n\nconst indent = '&nbsp;'.repeat(4)\n\nexport function logToOverlay(msg: string, error = false): void {\n if (!overlayEnabled) {\n return\n }\n\n if (error) {\n msg = `<span class=\"overlay-error\">${msg}</span>`\n }\n const msgHtml = msg.replace(/\\n/g, '\\n<br/>').replace(/\\s{2}/g, indent)\n if (overlayHtml) {\n overlayHtml += `<br/>${msgHtml}`\n } else {\n overlayHtml = `${msgHtml}`\n }\n writeOverlayFiles()\n}\n\nexport function clearConsole(): void {\n // TODO: This is disabled for now; maybe re-enable it under an optional flag\n // console.log('\\x1Bc')\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, readFileSync, writeFileSync } from 'fs'\nimport { writeFile } from 'fs/promises'\nimport { join as joinPath } from 'path'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport { clearOverlay, log } from '../../_shared/log'\nimport type { InputSpec, ModelSpec, OutputSpec, ResolvedModelSpec, VarName } from '../../_shared/model-spec'\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\nimport type { UserConfig } from '../../config/user-config'\nimport { BuildContext } from '../../context/context'\nimport { StagedFiles } from '../../context/staged-files'\nimport type { Plugin } from '../../plugin/plugin'\n\nimport { generateModel } from './gen-model'\nimport { computeInputFilesHash } from './hash-files'\n\nexport interface BuildOnceOptions {\n forceModelGen?: boolean\n abortSignal?: AbortSignal\n}\n\n/**\n * Perform a single build.\n *\n * This will return an error if a build failure occurred, or if a plugin encounters\n * an error. Otherwise, it will return true if the build and all plugins\n * succeeded, or false if a plugin wants to report a \"soft\" failure (for example,\n * if the model check plugin reports failing checks).\n *\n * @param config The resolved build configuration.\n * @param plugins The configured plugins.\n * @param options Options specific to the build process.\n * @return An `ok` result with true if the build and all plugins succeeded, or false if\n * one or more plugins failed; otherwise, an `err` result if there was a hard error.\n */\nexport async function buildOnce(\n config: ResolvedConfig,\n userConfig: UserConfig,\n plugins: Plugin[],\n options: BuildOnceOptions\n): Promise<Result<boolean, Error>> {\n // Create the build context\n const stagedFiles = new StagedFiles(config.prepDir)\n const context = new BuildContext(config, stagedFiles, options.abortSignal)\n const modelHashPath = joinPath(config.prepDir, 'model-hash.txt')\n\n // Note that the entire body of this function is wrapped in a try/catch. Any\n // errors that are thrown by plugin functions or the core `generateModel`\n // function will be caught and handled as appropriate.\n let succeeded = true\n try {\n // Get the model spec from the config\n const userModelSpec = await userConfig.modelSpec(context)\n if (userModelSpec === undefined) {\n return err(new Error('The model spec must be defined'))\n }\n\n // Resolve the model spec\n const modelSpec = resolveModelSpec(userModelSpec)\n\n // Run plugins that implement `preGenerate`\n for (const plugin of plugins) {\n if (plugin.preGenerate) {\n await plugin.preGenerate(context, modelSpec)\n }\n }\n\n // Write the spec file\n const specJson = {\n inputVarNames: modelSpec.inputVarNames,\n outputVarNames: modelSpec.outputVarNames,\n externalDatfiles: modelSpec.datFiles,\n bundleListing: modelSpec.bundleListing,\n customConstants: modelSpec.customConstants || false,\n customLookups: modelSpec.customLookups || false,\n customOutputs: modelSpec.customOutputs || false,\n ...modelSpec.options\n }\n const specPath = joinPath(config.prepDir, 'spec.json')\n await writeFile(specPath, JSON.stringify(specJson, null, 2))\n\n // Read the hash from the last successful model build, if available\n let previousModelHash: string\n if (existsSync(modelHashPath)) {\n previousModelHash = readFileSync(modelHashPath, 'utf8')\n } else {\n previousModelHash = 'NONE'\n }\n\n // The code gen and Wasm build steps are time consuming, so we avoid rebuilding\n // it if the build input files are unchanged since the last successful build\n const inputFilesHash = await computeInputFilesHash(config)\n let needModelGen: boolean\n if (options.forceModelGen === true) {\n needModelGen = true\n } else {\n const hashMismatch = inputFilesHash !== previousModelHash\n needModelGen = hashMismatch\n }\n\n if (needModelGen) {\n // Generate the model\n await generateModel(context, plugins)\n\n // Save the hash of the input files, which can be used to determine if\n // we need to rebuild the model the next time\n writeFileSync(modelHashPath, inputFilesHash)\n } else {\n // Skip code generation\n log('info', 'Skipping model code generation; already up-to-date')\n }\n\n // Run plugins that implement `postGenerate`\n // TODO: For now we run all plugins even if one or more of them returns\n // false, which allows (in theory) for there to be multiple plugins that\n // run tests or checks as a post-build step. We should eventually make\n // this configurable so that a plugin can halt the build if it fails.\n // (Technically this is already possible if the plugin throws an error\n // instead of returning false, but maybe it should be more configurable.)\n for (const plugin of plugins) {\n if (plugin.postGenerate) {\n const pluginSucceeded = await plugin.postGenerate(context, modelSpec)\n if (!pluginSucceeded) {\n succeeded = false\n }\n }\n }\n\n // Copy staged files to their destination; this will only copy the staged\n // files if they are different than the existing destination files. We\n // copy the files in a batch like this so that hot module reload is only\n // triggered once at the end of the whole build process.\n stagedFiles.copyChangedFiles()\n\n // Run plugins that implement `postBuild` (this is specified to run after\n // the \"copy staged files\" step)\n for (const plugin of plugins) {\n if (plugin.postBuild) {\n const pluginSucceeded = await plugin.postBuild(context, modelSpec)\n if (!pluginSucceeded) {\n succeeded = false\n }\n }\n }\n\n if (config.mode === 'development') {\n // Hide the \"rebuilding\" message in the dev overlay in the app if the\n // build succeeded; otherwise keep the error message visible\n log('info', 'Waiting for changes...\\n')\n clearOverlay()\n }\n } catch (e) {\n // When a build is aborted, the error will have \"ABORT\" as the message,\n // in which case we can swallow the error; for actual errors, rethrow\n if (e.message !== 'ABORT') {\n // Clear the hash so that the model is rebuilt next time\n writeFileSync(modelHashPath, '')\n\n // Return an error result\n return err(e)\n }\n }\n\n return ok(succeeded)\n}\n\n/**\n * Convert a `ModelSpec` instance to a `ResolvedModelSpec` instance.\n *\n * @param modelSpec The `ModelSpec` instance returned by the `UserConfig`.\n */\nfunction resolveModelSpec(modelSpec: ModelSpec): ResolvedModelSpec {\n let inputVarNames: VarName[]\n let inputSpecs: InputSpec[]\n if (modelSpec.inputs.length > 0) {\n const item = modelSpec.inputs[0]\n if (typeof item === 'string') {\n // The array contains variable names; derive `InputSpec` instances\n inputVarNames = modelSpec.inputs as VarName[]\n inputSpecs = inputVarNames.map(varName => {\n return {\n varName\n }\n })\n } else {\n // The array contains `InputSpec` objects; derive variable names\n inputSpecs = modelSpec.inputs as InputSpec[]\n inputVarNames = inputSpecs.map(spec => spec.varName)\n }\n } else {\n // The inputs array is empty, so return empty arrays\n inputVarNames = []\n inputSpecs = []\n }\n\n let outputVarNames: VarName[]\n let outputSpecs: OutputSpec[]\n if (modelSpec.outputs.length > 0) {\n const item = modelSpec.outputs[0]\n if (typeof item === 'string') {\n // The array contains variable names; derive `OutputSpec` instances\n outputVarNames = modelSpec.outputs as VarName[]\n outputSpecs = outputVarNames.map(varName => {\n return {\n varName\n }\n })\n } else {\n // The array contains `OutputSpec` objects; derive variable names\n outputSpecs = modelSpec.outputs as OutputSpec[]\n outputVarNames = outputSpecs.map(spec => spec.varName)\n }\n } else {\n // The outputs array is empty, so return empty arrays\n outputVarNames = []\n outputSpecs = []\n }\n\n let customConstants: boolean | VarName[]\n if (modelSpec.customConstants !== undefined) {\n customConstants = modelSpec.customConstants\n } else {\n customConstants = false\n }\n\n let customLookups: boolean | VarName[]\n if (modelSpec.customLookups !== undefined) {\n customLookups = modelSpec.customLookups\n } else {\n customLookups = false\n }\n\n let customOutputs: boolean | VarName[]\n if (modelSpec.customOutputs !== undefined) {\n customOutputs = modelSpec.customOutputs\n } else {\n customOutputs = false\n }\n\n return {\n inputVarNames,\n inputs: inputSpecs,\n outputVarNames,\n outputs: outputSpecs,\n datFiles: modelSpec.datFiles || [],\n bundleListing: modelSpec.bundleListing === true,\n customConstants,\n customLookups,\n customOutputs,\n options: modelSpec.options\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { canonicalVarId } from '@sdeverywhere/parse'\nimport type { LogLevel } from '../_shared/log'\nimport { log } from '../_shared/log'\n\nimport type { ResolvedConfig } from '../_shared/resolved-config'\n\nimport type { ProcessOptions, ProcessOutput } from './spawn-child'\nimport { spawnChild } from './spawn-child'\nimport type { StagedFiles } from './staged-files'\n\n/**\n * Provides access to common functionality that is needed during the build process.\n * This is passed to most plugin functions.\n */\nexport class BuildContext {\n /**\n * @param config The resolved configuration.\n * @hidden\n */\n constructor(\n public readonly config: ResolvedConfig,\n private readonly stagedFiles: StagedFiles,\n private readonly abortSignal: AbortSignal | undefined\n ) {}\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n log(level, msg)\n }\n\n /**\n * Prepare for writing a file to the staged directory.\n *\n * This will add the path to the array of tracked files and will create the\n * staged directory if needed.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param srcFile The name of the file as written to the `staged` directory.\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param dstFile The name of the file as written to the destination directory.\n * @return The absolute path to the staged file.\n */\n prepareStagedFile(srcDir: string, srcFile: string, dstDir: string, dstFile: string): string {\n return this.stagedFiles.prepareStagedFile(srcDir, srcFile, dstDir, dstFile)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.stagedFiles.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n /**\n * Spawn a child process that runs the given command.\n *\n * @param cwd The directory in which the command will be executed.\n * @param command The command to execute.\n * @param args The arguments to pass to the command.\n * @param opts Additional options to configure the process.\n * @returns The output of the process.\n */\n spawnChild(cwd: string, command: string, args: string[], opts?: ProcessOptions): Promise<ProcessOutput> {\n return spawnChild(cwd, command, args, this.abortSignal, opts)\n }\n\n /**\n * Format a (subscripted or non-subscripted) model variable name into a canonical\n * identifier (with special characters converted to underscore, and subscript/dimension\n * parts separated by commas).\n *\n * @param name The name of the variable in the source model, e.g., `Variable name[DimA, B2]`.\n * @returns The canonical identifier for the given name, e.g., `_variable_name[_dima,_b2]`.\n */\n canonicalVarId(name: string): string {\n return canonicalVarId(name)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ChildProcess } from 'child_process'\n\nimport { spawn } from 'cross-spawn'\n\nimport { log } from '../_shared/log'\n\n/**\n * @hidden This isn't ready to be included in the public API just yet.\n */\nexport interface ProcessOptions {\n logOutput?: boolean\n ignoredMessageFilter?: string\n captureOutput?: boolean\n ignoreError?: boolean\n}\n\n/**\n * @hidden This isn't ready to be included in the public API just yet.\n */\nexport interface ProcessOutput {\n exitCode: number\n stdoutMessages: string[]\n stderrMessages: string[]\n}\n\n/**\n * Spawn a child process that runs the given command.\n *\n * @param cwd The directory in which the command will be executed.\n * @param command The command to execute.\n * @param args The arguments to pass to the command.\n * @param abortSignal The signal used to abort the process.\n * @param opts Additional options to configure the process.\n * @returns The output of the process.\n */\nexport function spawnChild(\n cwd: string,\n command: string,\n args: string[],\n abortSignal?: AbortSignal,\n opts?: ProcessOptions\n): Promise<ProcessOutput> {\n return new Promise((resolve, reject) => {\n if (abortSignal?.aborted) {\n reject(new Error('ABORT'))\n return\n }\n\n let childProc: ChildProcess\n\n const localLog = (s: string, err = false) => {\n // Don't log anything after the process has been killed\n if (childProc === undefined) {\n return\n }\n log(err ? 'error' : 'info', s)\n }\n\n const abortHandler = () => {\n if (childProc) {\n log('info', 'Killing existing build process...')\n childProc.kill('SIGKILL')\n childProc = undefined\n }\n reject(new Error('ABORT'))\n }\n\n // Kill the process if abort is requested\n abortSignal?.addEventListener('abort', abortHandler, { once: true })\n\n // Prepare for capturing output, if requested\n const stdoutMessages: string[] = []\n const stderrMessages: string[] = []\n const logMessage = (msg: string, err: boolean) => {\n let includeMessage = true\n if (opts?.ignoredMessageFilter && msg.trim().startsWith(opts.ignoredMessageFilter)) {\n includeMessage = false\n }\n if (includeMessage) {\n const lines = msg.trim().split('\\n')\n for (const line of lines) {\n localLog(` ${line}`, err)\n }\n }\n }\n\n // Spawn the (asynchronous) child process. Note that we are using `spawn`\n // from the `cross-spawn` package as an alternative to the built-in\n // `child_process` module, which doesn't handle spaces in command path\n // on Windows.\n childProc = spawn(command, args, {\n cwd\n })\n\n childProc.stdout.on('data', (data: Buffer) => {\n const msg = data.toString()\n if (opts?.captureOutput === true) {\n stdoutMessages.push(msg)\n }\n if (opts?.logOutput !== false) {\n logMessage(msg, false)\n }\n })\n childProc.stderr.on('data', (data: Buffer) => {\n const msg = data.toString()\n if (opts?.captureOutput === true) {\n stderrMessages.push(msg)\n }\n if (opts?.logOutput !== false) {\n logMessage(msg, true)\n }\n })\n childProc.on('error', err => {\n localLog(`Process error: ${err}`, true)\n })\n childProc.on('close', (code, signal) => {\n // Stop listening for abort events\n abortSignal?.removeEventListener('abort', abortHandler)\n childProc = undefined\n\n if (signal) {\n // The process was killed by a signal, so we don't need to print anything\n return\n }\n\n const processOutput: ProcessOutput = {\n exitCode: code,\n stdoutMessages,\n stderrMessages\n }\n\n if (code === 0) {\n // The process exited cleanly; resolve the promise\n resolve(processOutput)\n } else if (!signal) {\n // The process failed\n if (opts?.ignoreError === true) {\n // Resolve the promise (but with a non-zero exit code)\n resolve(processOutput)\n } else {\n // Reject the promise\n reject(new Error(`Child process failed (code=${code})`))\n }\n }\n })\n })\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport { log } from '../_shared/log'\n\ninterface StagedFile {\n srcDir: string\n srcFile: string\n dstDir: string\n dstFile: string\n}\n\nexport class StagedFiles {\n private readonly baseStagedDir: string\n private readonly stagedFiles: StagedFile[] = []\n\n constructor(prepDir: string) {\n this.baseStagedDir = joinPath(prepDir, 'staged')\n }\n\n /**\n * Prepare for writing a file to the staged directory.\n *\n * This will add the path to the array of tracked files and will create the\n * staged directory if needed.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param srcFile The name of the file as written to the `staged` directory.\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param dstFile The name of the file as written to the destination directory.\n * @return The absolute path to the staged file.\n */\n prepareStagedFile(srcDir: string, srcFile: string, dstDir: string, dstFile: string): string {\n // Add an entry to the array of staged files (only if an entry does not already\n // exist) so that we can copy the file to the destination directory when the build\n // process has completed\n const stagedFile = {\n srcDir,\n srcFile,\n dstDir,\n dstFile\n }\n // TODO: We allow there to be more than one entry for each source path to\n // support the case where a single source file gets copied to multiple\n // destination paths. But we should throw an error if the same destination\n // path is configured for different source paths.\n if (this.stagedFiles.indexOf(stagedFile) < 0) {\n this.stagedFiles.push(stagedFile)\n }\n\n // Create the directory underneath the staged directory if needed\n const stagedDir = joinPath(this.baseStagedDir, srcDir)\n if (!existsSync(stagedDir)) {\n mkdirSync(stagedDir, { recursive: true })\n }\n\n return joinPath(stagedDir, srcFile)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n // Add an entry to track the file and create the staged directory if needed\n const stagedFilePath = this.prepareStagedFile(srcDir, filename, dstDir, filename)\n\n // Write the file to the staged directory\n writeFileSync(stagedFilePath, content)\n }\n\n /**\n * Return the absolute path to the staged file for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n getStagedFilePath(srcDir: string, srcFile: string): string {\n return joinPath(this.baseStagedDir, srcDir, srcFile)\n }\n\n /**\n * Return true if the staged file exists for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n stagedFileExists(srcDir: string, srcFile: string): boolean {\n const fullSrcPath = this.getStagedFilePath(srcDir, srcFile)\n return existsSync(fullSrcPath)\n }\n\n /**\n * Return true if the destination file exists for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n destinationFileExists(srcDir: string, srcFile: string): boolean {\n const f = this.stagedFiles.find(f => f.srcDir === srcDir && f.srcFile === srcFile)\n if (f === undefined) {\n return false\n }\n const fullDstPath = joinPath(f.dstDir, f.dstFile)\n return existsSync(fullDstPath)\n }\n\n /**\n * Copy staged files to their destination; this will only copy the staged\n * files if they are different than the existing destination files. We\n * copy the files in a batch like this so that hot module reload is only\n * triggered once at the end of the whole build process.\n */\n copyChangedFiles(): void {\n log('info', 'Copying changed files into place...')\n\n for (const f of this.stagedFiles) {\n this.copyStagedFile(f)\n }\n\n log('info', 'Done copying files')\n }\n\n /**\n * Copy a file from the `staged` directory to its destination. If the file already\n * exists in the destination directory and has the same contents as the source file,\n * the file will not be copied and this function will return false.\n *\n * @param f The staged file entry.\n */\n private copyStagedFile(f: StagedFile): boolean {\n // Create the destination directory, if needed\n if (!existsSync(f.dstDir)) {\n mkdirSync(f.dstDir, { recursive: true })\n }\n\n // If the destination file already exists and has the same contents as the source\n // file, we can skip copying it\n const fullSrcPath = this.getStagedFilePath(f.srcDir, f.srcFile)\n const fullDstPath = joinPath(f.dstDir, f.dstFile)\n const needsCopy = filesDiffer(fullSrcPath, fullDstPath)\n if (needsCopy) {\n log('verbose', ` Copying ${f.srcFile} to ${fullDstPath}`)\n copyFileSync(fullSrcPath, fullDstPath)\n }\n return needsCopy\n }\n}\n\n/**\n * Return true if both files exist at the given paths and have the same contents, false otherwise.\n */\nfunction filesDiffer(aPath: string, bPath: string): boolean {\n if (existsSync(aPath) && existsSync(bPath)) {\n // The files exist; see if they are different\n const aSize = statSync(aPath).size\n const bSize = statSync(bPath).size\n if (aSize !== bSize) {\n // The sizes are different, so the contents must be different\n return true\n } else {\n // The sizes are the same, so check the contents\n const aBuf = readFileSync(aPath)\n const bBuf = readFileSync(bPath)\n return !aBuf.equals(bBuf)\n }\n } else {\n // One or both files do not exist\n return true\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { copyFile, readdir, readFile, writeFile } from 'fs/promises'\nimport { basename, dirname, join as joinPath } from 'path'\n\nimport { log } from '../../_shared/log'\n\nimport type { BuildContext } from '../../context/context'\n\nimport type { Plugin } from '../../plugin/plugin'\n\n/**\n * Generate the model. This will run the core SDEverywhere code generation steps\n * and will also invoke the following plugin functions:\n * - `preProcessMdl`\n * - `postProcessMdl`\n * - `preGenerateC`\n * - `postGenerateC`\n */\nexport async function generateModel(context: BuildContext, plugins: Plugin[]): Promise<void> {\n const config = context.config\n if (config.modelFiles.length === 0) {\n log('info', 'No model input files specified, skipping model generation steps')\n return\n }\n\n log('info', 'Generating model...')\n\n const t0 = performance.now()\n\n // Use the defined prep directory\n const prepDir = config.prepDir\n\n // TODO: For now we assume the path is to the `main.js` file in the cli package;\n // this seems to work on both Unix and Windows, but we may need to revisit this\n // as part of removing the `sdeCmdPath` config hack\n const sdeCmdPath = config.sdeCmdPath\n\n // Process the mdl file(s)\n for (const plugin of plugins) {\n if (plugin.preProcessMdl) {\n await plugin.preProcessMdl(context)\n }\n }\n if (config.modelFiles.length === 1) {\n // Preprocess the single mdl file\n await preprocessMdl(context, sdeCmdPath, prepDir, config.modelFiles[0])\n } else {\n // Flatten and preprocess the multiple mdl files into a single mdl file\n await flattenMdls(context, sdeCmdPath, prepDir, config.modelFiles)\n }\n for (const plugin of plugins) {\n if (plugin.postProcessMdl) {\n const mdlPath = joinPath(prepDir, 'processed.mdl')\n let mdlContent = await readFile(mdlPath, 'utf8')\n mdlContent = await plugin.postProcessMdl(context, mdlContent)\n await writeFile(mdlPath, mdlContent)\n }\n }\n\n // Generate the JS or C file\n for (const plugin of plugins) {\n if (plugin.preGenerateCode) {\n await plugin.preGenerateCode(context, config.genFormat)\n }\n }\n await generateCode(context, config.sdeDir, sdeCmdPath, prepDir)\n const generatedCodeFile = `processed.${config.genFormat}`\n const generatedCodePath = joinPath(prepDir, 'build', generatedCodeFile)\n for (const plugin of plugins) {\n if (plugin.postGenerateCode) {\n let generatedCodeContent = await readFile(generatedCodePath, 'utf8')\n generatedCodeContent = await plugin.postGenerateCode(context, config.genFormat, generatedCodeContent)\n await writeFile(generatedCodePath, generatedCodeContent)\n }\n }\n\n if (config.genFormat === 'js') {\n // When generating JS code, copy the generated JS file to the `staged/model`\n // directory, because that's where plugin-worker expects to find it, but also\n // set it up to be copied to the `prepDir`, which is where other code expects\n // to find it\n // TODO: Maybe we can change plugin-worker to use the one in `prepDir`, and/or\n // add a build config setting to allow for customizing the output location\n const outputJsFile = 'generated-model.js'\n const stagedOutputJsPath = context.prepareStagedFile('model', outputJsFile, prepDir, outputJsFile)\n await copyFile(generatedCodePath, stagedOutputJsPath)\n }\n\n if (config.outListingFile) {\n // Copy the model listing file\n const srcListingJsonPath = joinPath(prepDir, 'build', 'processed.json')\n const stagedDir = 'model'\n const stagedFile = 'listing.json'\n const dstDir = dirname(config.outListingFile)\n const dstFile = basename(config.outListingFile)\n const stagedListingJsonPath = context.prepareStagedFile(stagedDir, stagedFile, dstDir, dstFile)\n await copyFile(srcListingJsonPath, stagedListingJsonPath)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n log('info', `Done generating model (${elapsed}s)`)\n}\n\n/**\n * Preprocess a single mdl file and copy the resulting `processed.mdl` to the prep directory.\n */\nasync function preprocessMdl(\n context: BuildContext,\n sdeCmdPath: string,\n prepDir: string,\n modelFile: string\n): Promise<void> {\n log('verbose', ' Preprocessing mdl file')\n\n // Copy the source file to the prep directory to make the next steps easier\n await copyFile(modelFile, joinPath(prepDir, 'processed.mdl'))\n\n // Use SDE to preprocess the model to strip anything that's not needed to build it\n const command = sdeCmdPath\n const args = ['generate', '--preprocess', 'processed.mdl']\n const ppOutput = await context.spawnChild(prepDir, command, args, {\n // The default error message from `spawnChild` is not very informative, so the\n // following allows us to throw our own error\n ignoreError: true\n })\n if (ppOutput.exitCode !== 0) {\n throw new Error(`Failed to preprocess mdl file: 'sde generate' command failed (code=${ppOutput.exitCode})`)\n }\n\n // Copy the processed file back to the prep directory\n await copyFile(joinPath(prepDir, 'build', 'processed.mdl'), joinPath(prepDir, 'processed.mdl'))\n}\n\n/**\n * Flatten multiple mdl files and copy the resulting `processed.mdl` to the prep directory.\n */\nasync function flattenMdls(\n context: BuildContext,\n sdeCmdPath: string,\n prepDir: string,\n modelFiles: string[]\n): Promise<void> {\n log('verbose', ' Flattening and preprocessing mdl files')\n\n // Use SDE to flatten the parent model and submodels into a single mdl file,\n // then preprocess to strip anything that's not needed to build the model\n const command = sdeCmdPath\n const args: string[] = []\n args.push('flatten')\n args.push('processed.mdl')\n args.push('--inputs')\n for (const path of modelFiles) {\n args.push(path)\n }\n\n // Disable logging by default; this will suppress flatten warnings, which are\n // sometimes unavoidable and not helpful\n const output = await context.spawnChild(prepDir, command, args, {\n logOutput: false,\n captureOutput: true,\n ignoreError: true\n })\n\n // Check for flattening errors\n let flattenErrors = false\n for (const msg of output.stderrMessages) {\n if (msg.includes('ERROR')) {\n flattenErrors = true\n break\n }\n }\n if (flattenErrors) {\n log('error', 'There were errors reported when flattening the model:')\n for (const msg of output.stderrMessages) {\n const lines = msg.split('\\n')\n for (const line of lines) {\n log('error', ` ${line}`)\n }\n }\n throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`)\n } else if (output.exitCode !== 0) {\n throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`)\n }\n\n // Copy the processed file back to the prep directory\n await copyFile(joinPath(prepDir, 'build', 'processed.mdl'), joinPath(prepDir, 'processed.mdl'))\n}\n\n/**\n * Generate a JS or C file from the `processed.mdl` file.\n */\nasync function generateCode(context: BuildContext, sdeDir: string, sdeCmdPath: string, prepDir: string): Promise<void> {\n const genFormat = context.config.genFormat\n const genFormatName = genFormat.toUpperCase()\n log('verbose', ` Generating ${genFormatName} code`)\n\n // Use SDE to generate both a JS/C version of the model (`--outformat`) AND a JSON list of all model\n // dimensions and variables (`--list`)\n const command = sdeCmdPath\n const outFormat = `--outformat=${genFormat}`\n const genCmdArgs = ['generate', outFormat, '--list', '--spec', 'spec.json', 'processed']\n const genCmdOutput = await context.spawnChild(prepDir, command, genCmdArgs, {\n // By default, ignore lines that start with \"WARNING: Data for\" since these are often harmless\n // TODO: Don't filter by default, but make it configurable\n // ignoredMessageFilter: 'WARNING: Data for'\n // The default error message from `spawnChild` is not very informative, so the\n // following allows us to throw our own error\n ignoreError: true\n })\n if (genCmdOutput.exitCode !== 0) {\n throw new Error(\n `Failed to generate ${genFormatName} code: 'sde generate' command failed (code=${genCmdOutput.exitCode})`\n )\n }\n\n if (genFormat === 'c') {\n // Copy SDE's supporting C files into the build directory\n const buildDir = joinPath(prepDir, 'build')\n const sdeCDir = joinPath(sdeDir, 'src', 'c')\n const files = await readdir(sdeCDir)\n const copyOps = []\n for (const file of files) {\n if (file.endsWith('.c') || file.endsWith('.h')) {\n copyOps.push(copyFile(joinPath(sdeCDir, file), joinPath(buildDir, file)))\n }\n }\n await Promise.all(copyOps)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\nimport { hashElement } from 'folder-hash'\nimport { glob } from 'tinyglobby'\n\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\n/**\n * Asynchronously compute the hash of the files that are inputs to the model\n * build process.\n */\nexport async function computeInputFilesHash(config: ResolvedConfig): Promise<string> {\n const inputFiles: string[] = []\n\n // Always include the `spec.json` file, since that is a primary input\n // to the model build process\n const specFile = joinPath(config.prepDir, 'spec.json')\n inputFiles.push(specFile)\n\n if (config.modelInputPaths && config.modelInputPaths.length > 0) {\n // Include the files that match the glob patterns in the config file.\n // Note that the folder-hash package supports glob patterns, but its\n // configuration is complicated, so it is easier if we resolve the\n // glob patterns here and pass each individual file to the\n // `hashElement` function.\n for (const globPath of config.modelInputPaths) {\n const paths = await glob(globPath, {\n cwd: config.rootDir,\n absolute: true,\n onlyFiles: true\n })\n inputFiles.push(...paths)\n }\n } else {\n // Only use the mdl files to compute the hash\n inputFiles.push(...config.modelFiles)\n }\n\n // Compute the hash of each input file and concatenate into a single string\n let hash = ''\n for (const inputFile of inputFiles) {\n const result = await hashElement(inputFile)\n hash += result.hash\n }\n\n return hash\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { basename } from 'path'\n\nimport { clearOverlay, log, logError } from '../../_shared/log'\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\nimport type { UserConfig } from '../../config/user-config'\nimport type { Plugin } from '../../plugin/plugin'\n\nimport type { BuildOnceOptions } from './build-once'\nimport { buildOnce } from './build-once'\nimport { watchPaths } from './watch-paths'\n\nclass BuildState {\n readonly abortController = new AbortController()\n}\n\nexport function watch(config: ResolvedConfig, userConfig: UserConfig, plugins: Plugin[]): void {\n // Add a small delay so that if multiple files are changed at once (as is\n // often the case when switching branches), we batch them up and start the\n // the build after things settle down\n const delay = 150\n const changedPaths: Set<string> = new Set()\n\n // Keep track of the current build so that we only have one active at a time\n let currentBuildState: BuildState\n\n function performBuild() {\n clearOverlay()\n\n // Log the input files that have changed\n for (const path of changedPaths) {\n log('info', `Input file ${basename(path)} has been changed`)\n }\n\n // Clear the set of pending files\n changedPaths.clear()\n\n // Keep track of builds; if one is already in progress, abort it\n // before starting another one\n if (currentBuildState) {\n currentBuildState.abortController.abort()\n // TODO: Prevent aborted build from logging?\n currentBuildState = undefined\n }\n\n // Generate files and build the model\n currentBuildState = new BuildState()\n const buildOptions: BuildOnceOptions = {\n abortSignal: currentBuildState.abortController.signal\n }\n buildOnce(config, userConfig, plugins, buildOptions)\n .then(result => {\n // Log the error message in case of error result\n if (result.isErr()) {\n logError(result.error)\n }\n })\n .catch(e => {\n // Also catch thrown errors that may have not already been\n // handled by `buildOnce`\n logError(e)\n // log('info', 'Waiting for changes...')\n })\n .finally(() => {\n currentBuildState = undefined\n })\n }\n\n function scheduleBuild(changedPath: string) {\n // Only schedule the build if the set is currently empty\n const schedule = changedPaths.size === 0\n\n // Add the path to the set of changed files\n changedPaths.add(changedPath)\n\n if (schedule) {\n // Schedule the build to start after a delay\n setTimeout(() => {\n performBuild()\n }, delay)\n }\n }\n\n let watchPatterns: string[]\n if (config.watchPaths && config.watchPaths.length > 0) {\n // Watch the configured files\n watchPatterns = config.watchPaths\n } else {\n // Only watch the mdl files\n watchPatterns = config.modelFiles\n }\n\n // Watch the configured files; if changes are detected, generate the specs\n // and rebuild the model if needed\n watchPaths(watchPatterns, config.rootDir, path => {\n scheduleBuild(path)\n })\n}\n","// Copyright (c) 2025 Climate Interactive / New Venture Fund\n\nimport chokidar from 'chokidar'\nimport { globSync, isDynamicPattern } from 'tinyglobby'\n\n/**\n * Resolve watch paths, expanding glob patterns into concrete file paths.\n *\n * This function doesn't attempt to expand directories. This is intentional\n * as it allows chokidar to handle directory changes (added and removed files)\n * instead of it being passed a specific set of files when the watch is first\n * set up.\n *\n * @param patterns The watch paths to resolve (may include glob patterns).\n * @param cwd The current working directory to resolve paths relative to.\n * @returns An array of resolved paths. Non-glob paths are returned as-is,\n * while glob patterns are expanded to absolute paths of matching files.\n */\nexport function resolveWatchPaths(patterns: string[], cwd: string): string[] {\n const resolvedPaths: string[] = []\n\n // Separate regular file/directory paths from glob patterns\n const regularPaths: string[] = []\n const globPatterns: string[] = []\n for (const pattern of patterns) {\n if (isDynamicPattern(pattern)) {\n globPatterns.push(pattern)\n } else {\n regularPaths.push(pattern)\n }\n }\n\n // Add the regular paths as is (don't attempt to expand directories)\n resolvedPaths.push(...regularPaths)\n\n // Resolve the glob patterns; we pass these to tinyglobby all at once so that\n // it can handle negation involving multiple patterns\n if (globPatterns.length > 0) {\n const paths = globSync(globPatterns, {\n // Watch paths are resolved relative to the provided cwd\n cwd,\n // Resolve to absolute paths\n absolute: true\n })\n resolvedPaths.push(...paths)\n }\n\n return resolvedPaths\n}\n\n/**\n * Watch file paths and invoke callbacks when files are changed, added, or removed.\n *\n * This function sets up a file watcher using chokidar. Glob patterns in the paths\n * are resolved before being passed to chokidar (since chokidar no longer supports\n * glob patterns).\n *\n * @param patterns The paths to watch (may include glob patterns).\n * @param cwd The current working directory to resolve paths relative to.\n * @param onChange Callback invoked when a file is changed.\n * @param onReady Optional callback invoked when the watcher is ready.\n * @returns A cleanup function that closes the watcher.\n */\nexport function watchPaths(\n patterns: string[],\n cwd: string,\n onChange: (path: string) => void,\n onReady?: () => void\n): () => void {\n // The chokidar package no longer supports glob patterns, so we need to resolve\n // glob patterns first and then pass the resolved paths to chokidar\n const resolvedPathsToWatch = resolveWatchPaths(patterns, cwd)\n\n // Watch the resolved paths; if changes are detected, invoke the callbacks\n const watcher = chokidar.watch(resolvedPathsToWatch, {\n // Watch paths are resolved relative to the provided cwd\n cwd,\n // Ignore the initial add events when the watcher is created\n ignoreInitial: true,\n // Include a delay, otherwise on macOS we sometimes get multiple\n // change events when the file is saved just once\n awaitWriteFinish: {\n stabilityThreshold: 200\n }\n })\n watcher.on('change', path => {\n onChange(path)\n })\n watcher.on('add', path => {\n onChange(path)\n })\n watcher.on('unlink', path => {\n onChange(path)\n })\n if (onReady) {\n watcher.on('ready', onReady)\n }\n\n // Return cleanup function\n return () => {\n watcher.close()\n }\n}\n"],"mappings":"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","names":["import_path","import_neverthrow","joinPath","resolvePath","watchPaths","import_fs","pico","import_fs","import_promises","import_path","import_neverthrow","err","import_fs","import_path","joinPath","f","import_path","joinPath","import_path","joinPath","joinPath","import_path","import_tinyglobby","chokidar","joinPath","overlayEnabled"]}
package/dist/index.d.cts CHANGED
@@ -74,6 +74,19 @@ interface ModelSpec {
74
74
  * if it is needed.
75
75
  */
76
76
  bundleListing?: boolean;
77
+ /**
78
+ * Whether to allow constants to be overridden at runtime using `setConstant`.
79
+ *
80
+ * If undefined or false, the generated model will implement `setConstant`
81
+ * as a no-op, meaning that constants cannot be overridden at runtime.
82
+ *
83
+ * If true, all constants in the generated model will be available to be
84
+ * overridden.
85
+ *
86
+ * If an array is provided, only those variable names in the array will
87
+ * be available to be overridden.
88
+ */
89
+ customConstants?: boolean | VarName[];
77
90
  /**
78
91
  * Whether to allow lookups to be overridden at runtime using `setLookup`.
79
92
  *
@@ -154,6 +167,19 @@ interface ResolvedModelSpec {
154
167
  * if it is needed.
155
168
  */
156
169
  bundleListing: boolean;
170
+ /**
171
+ * Whether to allow constants to be overridden at runtime using `setConstant`.
172
+ *
173
+ * If false, the generated model will contain a `setConstant` function that
174
+ * throws an error, meaning that constants cannot be overridden at runtime.
175
+ *
176
+ * If true, all constants in the generated model will be available to be
177
+ * overridden.
178
+ *
179
+ * If an array is provided, only those variable names in the array will
180
+ * be available to be overridden.
181
+ */
182
+ customConstants: boolean | VarName[];
157
183
  /**
158
184
  * Whether to allow lookups to be overridden at runtime using `setLookup`.
159
185
  *
package/dist/index.d.ts CHANGED
@@ -74,6 +74,19 @@ interface ModelSpec {
74
74
  * if it is needed.
75
75
  */
76
76
  bundleListing?: boolean;
77
+ /**
78
+ * Whether to allow constants to be overridden at runtime using `setConstant`.
79
+ *
80
+ * If undefined or false, the generated model will implement `setConstant`
81
+ * as a no-op, meaning that constants cannot be overridden at runtime.
82
+ *
83
+ * If true, all constants in the generated model will be available to be
84
+ * overridden.
85
+ *
86
+ * If an array is provided, only those variable names in the array will
87
+ * be available to be overridden.
88
+ */
89
+ customConstants?: boolean | VarName[];
77
90
  /**
78
91
  * Whether to allow lookups to be overridden at runtime using `setLookup`.
79
92
  *
@@ -154,6 +167,19 @@ interface ResolvedModelSpec {
154
167
  * if it is needed.
155
168
  */
156
169
  bundleListing: boolean;
170
+ /**
171
+ * Whether to allow constants to be overridden at runtime using `setConstant`.
172
+ *
173
+ * If false, the generated model will contain a `setConstant` function that
174
+ * throws an error, meaning that constants cannot be overridden at runtime.
175
+ *
176
+ * If true, all constants in the generated model will be available to be
177
+ * overridden.
178
+ *
179
+ * If an array is provided, only those variable names in the array will
180
+ * be available to be overridden.
181
+ */
182
+ customConstants: boolean | VarName[];
157
183
  /**
158
184
  * Whether to allow lookups to be overridden at runtime using `setLookup`.
159
185
  *
package/dist/index.js CHANGED
@@ -76,11 +76,11 @@ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
76
76
  } else {
77
77
  modelInputPaths = modelFiles;
78
78
  }
79
- let watchPaths;
79
+ let watchPaths2;
80
80
  if (userConfig.watchPaths && userConfig.watchPaths.length > 0) {
81
- watchPaths = userConfig.watchPaths;
81
+ watchPaths2 = userConfig.watchPaths;
82
82
  } else {
83
- watchPaths = modelFiles;
83
+ watchPaths2 = modelFiles;
84
84
  }
85
85
  const rawGenFormat = userConfig.genFormat || "js";
86
86
  let genFormat;
@@ -108,7 +108,7 @@ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
108
108
  prepDir,
109
109
  modelFiles,
110
110
  modelInputPaths,
111
- watchPaths,
111
+ watchPaths: watchPaths2,
112
112
  genFormat,
113
113
  outListingFile,
114
114
  sdeDir,
@@ -709,8 +709,9 @@ async function buildOnce(config, userConfig, plugins, options) {
709
709
  outputVarNames: modelSpec.outputVarNames,
710
710
  externalDatfiles: modelSpec.datFiles,
711
711
  bundleListing: modelSpec.bundleListing,
712
- customLookups: modelSpec.customLookups,
713
- customOutputs: modelSpec.customOutputs,
712
+ customConstants: modelSpec.customConstants || false,
713
+ customLookups: modelSpec.customLookups || false,
714
+ customOutputs: modelSpec.customOutputs || false,
714
715
  ...modelSpec.options
715
716
  };
716
717
  const specPath = joinPath5(config.prepDir, "spec.json");
@@ -803,6 +804,12 @@ function resolveModelSpec(modelSpec) {
803
804
  outputVarNames = [];
804
805
  outputSpecs = [];
805
806
  }
807
+ let customConstants;
808
+ if (modelSpec.customConstants !== void 0) {
809
+ customConstants = modelSpec.customConstants;
810
+ } else {
811
+ customConstants = false;
812
+ }
806
813
  let customLookups;
807
814
  if (modelSpec.customLookups !== void 0) {
808
815
  customLookups = modelSpec.customLookups;
@@ -822,6 +829,7 @@ function resolveModelSpec(modelSpec) {
822
829
  outputs: outputSpecs,
823
830
  datFiles: modelSpec.datFiles || [],
824
831
  bundleListing: modelSpec.bundleListing === true,
832
+ customConstants,
825
833
  customLookups,
826
834
  customOutputs,
827
835
  options: modelSpec.options
@@ -830,8 +838,64 @@ function resolveModelSpec(modelSpec) {
830
838
 
831
839
  // src/build/impl/watch.ts
832
840
  import { basename as basename2 } from "path";
841
+
842
+ // src/build/impl/watch-paths.ts
833
843
  import chokidar from "chokidar";
834
844
  import { globSync, isDynamicPattern } from "tinyglobby";
845
+ function resolveWatchPaths(patterns, cwd) {
846
+ const resolvedPaths = [];
847
+ const regularPaths = [];
848
+ const globPatterns = [];
849
+ for (const pattern of patterns) {
850
+ if (isDynamicPattern(pattern)) {
851
+ globPatterns.push(pattern);
852
+ } else {
853
+ regularPaths.push(pattern);
854
+ }
855
+ }
856
+ resolvedPaths.push(...regularPaths);
857
+ if (globPatterns.length > 0) {
858
+ const paths = globSync(globPatterns, {
859
+ // Watch paths are resolved relative to the provided cwd
860
+ cwd,
861
+ // Resolve to absolute paths
862
+ absolute: true
863
+ });
864
+ resolvedPaths.push(...paths);
865
+ }
866
+ return resolvedPaths;
867
+ }
868
+ function watchPaths(patterns, cwd, onChange, onReady) {
869
+ const resolvedPathsToWatch = resolveWatchPaths(patterns, cwd);
870
+ const watcher = chokidar.watch(resolvedPathsToWatch, {
871
+ // Watch paths are resolved relative to the provided cwd
872
+ cwd,
873
+ // Ignore the initial add events when the watcher is created
874
+ ignoreInitial: true,
875
+ // Include a delay, otherwise on macOS we sometimes get multiple
876
+ // change events when the file is saved just once
877
+ awaitWriteFinish: {
878
+ stabilityThreshold: 200
879
+ }
880
+ });
881
+ watcher.on("change", (path) => {
882
+ onChange(path);
883
+ });
884
+ watcher.on("add", (path) => {
885
+ onChange(path);
886
+ });
887
+ watcher.on("unlink", (path) => {
888
+ onChange(path);
889
+ });
890
+ if (onReady) {
891
+ watcher.on("ready", onReady);
892
+ }
893
+ return () => {
894
+ watcher.close();
895
+ };
896
+ }
897
+
898
+ // src/build/impl/watch.ts
835
899
  var BuildState = class {
836
900
  constructor() {
837
901
  this.abortController = new AbortController();
@@ -874,36 +938,13 @@ function watch(config, userConfig, plugins) {
874
938
  }, delay);
875
939
  }
876
940
  }
877
- let watchPaths;
941
+ let watchPatterns;
878
942
  if (config.watchPaths && config.watchPaths.length > 0) {
879
- watchPaths = config.watchPaths;
943
+ watchPatterns = config.watchPaths;
880
944
  } else {
881
- watchPaths = config.modelFiles;
882
- }
883
- const resolvedWatchPaths = [];
884
- for (const watchPath of watchPaths) {
885
- if (isDynamicPattern(watchPath)) {
886
- const paths = globSync(watchPath, {
887
- // Watch paths are resolved relative to the project root directory
888
- cwd: config.rootDir,
889
- // Resolve to absolute paths
890
- absolute: true
891
- });
892
- resolvedWatchPaths.push(...paths);
893
- } else {
894
- resolvedWatchPaths.push(watchPath);
895
- }
945
+ watchPatterns = config.modelFiles;
896
946
  }
897
- const watcher = chokidar.watch(resolvedWatchPaths, {
898
- // Watch paths are resolved relative to the project root directory
899
- cwd: config.rootDir,
900
- // XXX: Include a delay, otherwise on macOS we sometimes get multiple
901
- // change events when the csv file is saved just once
902
- awaitWriteFinish: {
903
- stabilityThreshold: 200
904
- }
905
- });
906
- watcher.on("change", (path) => {
947
+ watchPaths(watchPatterns, config.rootDir, (path) => {
907
948
  scheduleBuild(path);
908
949
  });
909
950
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/build/build.ts","../src/config/config-loader.ts","../src/_shared/log.ts","../src/build/impl/build-once.ts","../src/context/context.ts","../src/context/spawn-child.ts","../src/context/staged-files.ts","../src/build/impl/gen-model.ts","../src/build/impl/hash-files.ts","../src/build/impl/watch.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport type { BuildMode } from '../_shared/mode'\n\nimport { loadConfig } from '../config/config-loader'\nimport type { UserConfig } from '../config/user-config'\nimport type { LogLevel } from '../_shared/log'\nimport { setOverlayFile, setActiveLevels } from '../_shared/log'\nimport { buildOnce } from './impl/build-once'\nimport { watch } from './impl/watch'\n\nexport interface BuildOptions {\n /** The path to an `sde.config.js` file, or a `UserConfig` object. */\n config?: string | UserConfig\n\n /**\n * The log levels to include. If undefined, the default 'info' and 'error' levels\n * will be active.\n */\n logLevels?: LogLevel[]\n\n /**\n * The path to the `@sdeverywhere/cli` package. This is currently only used to get\n * access to the files in the `src/c` directory.\n * @hidden This should be removed once we have tighter integration with the `cli` package.\n */\n sdeDir: string\n\n /**\n * The path to the `sde` command.\n * @hidden This should be removed once we have tighter integration with the `cli` package.\n */\n sdeCmdPath: string\n}\n\nexport interface BuildResult {\n /**\n * The exit code that should be set by the process. This will be undefined\n * if `mode` is 'development', indicating that the process should be kept alive.\n */\n exitCode?: number\n}\n\n/**\n * Initiate the build process, which can either be a single build if `mode` is\n * 'production', or a live development environment if `mode` is 'development'.\n *\n * @param mode The build mode.\n * @param options The build options.\n * @return An `ok` result if the build completed, otherwise an `err` result.\n */\nexport async function build(mode: BuildMode, options: BuildOptions): Promise<Result<BuildResult, Error>> {\n // Load the config\n const configResult = await loadConfig(mode, options.config, options.sdeDir, options.sdeCmdPath)\n if (configResult.isErr()) {\n return err(configResult.error)\n }\n const { userConfig, resolvedConfig } = configResult.value\n\n // Configure logging level\n if (options.logLevels !== undefined) {\n setActiveLevels(options.logLevels)\n }\n\n // Configure the overlay `messages.html` file, which is written under the\n // `sde-prep` directory. For production builds, this file will remain empty.\n const messagesPath = joinPath(resolvedConfig.prepDir, 'messages.html')\n const overlayEnabled = mode === 'development'\n setOverlayFile(messagesPath, overlayEnabled)\n\n try {\n // Initialize plugins\n const plugins = userConfig.plugins || []\n for (const plugin of plugins) {\n if (plugin.init) {\n await plugin.init(resolvedConfig)\n }\n }\n\n if (mode === 'development') {\n // Enable dev mode (which will rebuild when watched files are changed).\n // First run an initial build so that we have a baseline, and then\n // once that is complete, enable file watchers.\n const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {})\n // TODO: We should trap errors here and keep the dev process\n // running if the initial build fails\n if (buildResult.isErr()) {\n return err(buildResult.error)\n }\n\n // Allow plugins to set up watchers after the initial build completes\n for (const plugin of plugins) {\n if (plugin.watch) {\n await plugin.watch(resolvedConfig)\n }\n }\n\n // Watch for changes to the source/model/test files\n watch(resolvedConfig, userConfig, plugins)\n\n // Return a build result with undefined exit code, indicating that the\n // process should be kept alive\n return ok({})\n } else {\n // Run a single build\n const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {})\n if (buildResult.isErr()) {\n return err(buildResult.error)\n }\n\n // Configure the exit code depending on whether any plugins failed.\n // Currently we use the following exit code values:\n // 0 == build succeeded, AND all plugins succeeded\n // 1 == build failed (or a plugin reported a \"hard\" error)\n // 2 == build succeeded, BUT one or more plugins failed\n const allPluginsSucceeded = buildResult.value\n const exitCode = allPluginsSucceeded ? 0 : 2\n return ok({ exitCode })\n }\n } catch (e) {\n return err(e)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, lstatSync, mkdirSync } from 'fs'\nimport { dirname, isAbsolute, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport type { BuildMode } from '../_shared/mode'\nimport type { ResolvedConfig } from '../_shared/resolved-config'\n\nimport type { UserConfig } from './user-config'\n\nexport interface ConfigResult {\n userConfig: UserConfig\n resolvedConfig: ResolvedConfig\n}\n\n/**\n * Load a user-defined config file or a given `UserConfig` object. This validates\n * all paths and if successful, this returns a `ResolvedConfig`, otherwise returns\n * an error result.\n *\n * @param mode The build mode.\n * @param config The path to a config file, or a `UserConfig` object; if undefined,\n * this will look for a `sde.config.js` file in the current directory.\n * @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).\n * @param sdeCmdPath Temporary (the path to the `sde` command).\n * @return An `ok` result with the `ResolvedConfig`, otherwise an `err` result.\n */\nexport async function loadConfig(\n mode: BuildMode,\n config: string | UserConfig | undefined,\n sdeDir: string,\n sdeCmdPath: string\n): Promise<Result<ConfigResult, Error>> {\n let userConfig: UserConfig\n if (typeof config === 'object') {\n // Use the given `UserConfig` object\n userConfig = config\n } else {\n // Load the project-specific config. If no `--config` arg was specified\n // on the command line, look for a `sde.config.js` file in the current\n // directory, and failing that, use a default config.\n // TODO: Create a default config if no file found; for now, we just fail\n let configPath: string\n if (typeof config === 'string') {\n configPath = config\n } else {\n configPath = joinPath(process.cwd(), 'sde.config.js')\n }\n try {\n if (!existsSync(configPath)) {\n return err(new Error(`Cannot find config file '${configPath}'`))\n }\n const configRelPath = relativeToSourcePath(configPath)\n const configModule = await import(configRelPath)\n userConfig = await configModule.config()\n } catch (e) {\n return err(new Error(`Failed to load config file '${configPath}': ${e.message}`))\n }\n }\n\n // Resolve the config\n try {\n const resolvedConfig = resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath)\n return ok({\n userConfig,\n resolvedConfig\n })\n } catch (e) {\n return err(e)\n }\n}\n\n/**\n * Resolve the given user configuration by resolving all paths. This will create\n * the prep directory if it does not already exist. This will throw an error if\n * any other paths are invalid or do not exist.\n *\n * @param userConfig The user-defined configuration.\n * @param mode The active build mode.\n * @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).\n * @param sdeCmdPath Temporary (the path to the `sde` command).\n * @return The resolved configuration.\n */\nfunction resolveUserConfig(\n userConfig: UserConfig,\n mode: BuildMode,\n sdeDir: string,\n sdeCmdPath: string\n): ResolvedConfig {\n function expectDirectory(propName: string, path: string): void {\n if (!existsSync(path)) {\n throw new Error(`The configured ${propName} (${path}) does not exist`)\n } else if (!lstatSync(path).isDirectory()) {\n throw new Error(`The configured ${propName} (${path}) is not a directory`)\n }\n }\n\n // function expectFile(propName: string, path: string): void {\n // if (!existsSync(path)) {\n // throw new Error(`The configured ${propName} (${path}) does not exist`)\n // // TODO: Don't include the \"is file\" check for now; need to update this to\n // // handle symlinks\n // // } else if (!lstatSync(path).isFile()) {\n // // throw new Error(`The configured ${propName} (${path}) is not a file`)\n // }\n // }\n\n // Validate the root directory\n let rootDir: string\n if (userConfig.rootDir) {\n // Verify that the configured root directory exists\n rootDir = resolvePath(userConfig.rootDir)\n expectDirectory('rootDir', rootDir)\n } else {\n // Use the current working directory as the project root\n rootDir = process.cwd()\n }\n\n // Validate the prep directory\n let prepDir: string\n if (userConfig.prepDir) {\n // Resolve the configured prep directory\n prepDir = resolvePath(userConfig.prepDir)\n } else {\n // Create an 'sde-prep' directory under the configured root directory\n prepDir = resolvePath(rootDir, 'sde-prep')\n }\n mkdirSync(prepDir, { recursive: true })\n\n // Validate the model files\n const userModelFiles = userConfig.modelFiles\n const modelFiles: string[] = []\n for (const userModelFile of userModelFiles) {\n const modelFile = resolvePath(userModelFile)\n if (!existsSync(modelFile)) {\n throw new Error(`The configured model file (${modelFile}) does not exist`)\n }\n modelFiles.push(modelFile)\n }\n\n // TODO: Validate the watch paths; these are allowed to be globs, so need to\n // figure out the best way to resolve them\n let modelInputPaths: string[]\n if (userConfig.modelInputPaths && userConfig.modelInputPaths.length > 0) {\n modelInputPaths = userConfig.modelInputPaths\n } else {\n modelInputPaths = modelFiles\n }\n let watchPaths: string[]\n if (userConfig.watchPaths && userConfig.watchPaths.length > 0) {\n watchPaths = userConfig.watchPaths\n } else {\n watchPaths = modelFiles\n }\n\n // Validate the code generation format\n const rawGenFormat = userConfig.genFormat || 'js'\n let genFormat: 'js' | 'c'\n switch (rawGenFormat) {\n case 'js':\n genFormat = 'js'\n break\n case 'c':\n genFormat = 'c'\n break\n default:\n throw new Error(`The configured genFormat value is invalid; must be either 'js' or 'c'`)\n }\n\n // Validate the out listing file, if defined\n let outListingFile: string\n if (userConfig.outListingFile) {\n // Get the absolute path of the output file\n if (isAbsolute(userConfig.outListingFile)) {\n outListingFile = userConfig.outListingFile\n } else {\n outListingFile = resolvePath(rootDir, userConfig.outListingFile)\n }\n }\n\n return {\n mode,\n rootDir,\n prepDir,\n modelFiles,\n modelInputPaths,\n watchPaths,\n genFormat,\n outListingFile,\n sdeDir,\n sdeCmdPath\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 { writeFileSync } from 'fs'\nimport pico from 'picocolors'\n\nexport type LogLevel = 'error' | 'info' | 'verbose'\n\nconst activeLevels: Set<LogLevel> = new Set(['error', 'info'])\n\nlet overlayFile: string\nlet overlayEnabled = false\nlet overlayHtml = ''\n\n/**\n * Set the active logging levels. By default, only 'error' and 'info'\n * messages are emitted.\n *\n * @param logLevels The logging levels to include.\n */\nexport function setActiveLevels(logLevels: LogLevel[]): void {\n activeLevels.clear()\n for (const level of logLevels) {\n activeLevels.add(level)\n }\n}\n\n/**\n * Set the path to the `messages.html` file where overlay messages will be written.\n *\n * @param file The absolute path to the HTML file where messages will be written.\n * @param enabled Whether to write messages to the file; if false, the file will be\n * emptied and no further messages will be written.\n */\nexport function setOverlayFile(file: string, enabled: boolean): void {\n overlayFile = file\n overlayEnabled = enabled\n\n // Write an empty file by default; this will ensure that messages from a previous\n // build aren't included in the current build\n writeFileSync(overlayFile, '')\n}\n\n/**\n * Log a message to the console and/or overlay.\n *\n * @param level The logging level.\n * @param msg The message to emit.\n */\nexport function log(level: LogLevel, msg: string): void {\n if (activeLevels.has(level)) {\n if (level === 'error') {\n console.error(pico.red(msg))\n logToOverlay(msg)\n } else {\n console.log(msg)\n logToOverlay(msg)\n }\n }\n}\n\n/**\n * Log an error to the console and/or overlay.\n *\n * @param e The error to log.\n */\nexport function logError(e: Error): void {\n // Remove the first part of the stack trace (which contains the message)\n // so that we can control the formatting of the message separately, then\n // only include up to 3 lines of the stack to keep the log cleaner\n const stack = e.stack || ''\n const stackLines = stack.split('\\n').filter(s => s.match(/^\\s+at/))\n const trace = stackLines.slice(0, 3).join('\\n')\n\n // Log the error message followed by the stack trace\n console.error(pico.red(`\\nERROR: ${e.message}`))\n console.error(pico.dim(pico.red(`${trace}\\n`)))\n logToOverlay(`\\nERROR: ${e.message}`, true)\n logToOverlay(`${trace}\\n`, true)\n}\n\nfunction writeOverlayFiles(): void {\n writeFileSync(overlayFile, overlayHtml)\n}\n\nexport function clearOverlay(): void {\n if (!overlayEnabled) {\n return\n }\n\n overlayHtml = ''\n writeOverlayFiles()\n}\n\nconst indent = '&nbsp;'.repeat(4)\n\nexport function logToOverlay(msg: string, error = false): void {\n if (!overlayEnabled) {\n return\n }\n\n if (error) {\n msg = `<span class=\"overlay-error\">${msg}</span>`\n }\n const msgHtml = msg.replace(/\\n/g, '\\n<br/>').replace(/\\s{2}/g, indent)\n if (overlayHtml) {\n overlayHtml += `<br/>${msgHtml}`\n } else {\n overlayHtml = `${msgHtml}`\n }\n writeOverlayFiles()\n}\n\nexport function clearConsole(): void {\n // TODO: This is disabled for now; maybe re-enable it under an optional flag\n // console.log('\\x1Bc')\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, readFileSync, writeFileSync } from 'fs'\nimport { writeFile } from 'fs/promises'\nimport { join as joinPath } from 'path'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport { clearOverlay, log } from '../../_shared/log'\nimport type { InputSpec, ModelSpec, OutputSpec, ResolvedModelSpec, VarName } from '../../_shared/model-spec'\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\nimport type { UserConfig } from '../../config/user-config'\nimport { BuildContext } from '../../context/context'\nimport { StagedFiles } from '../../context/staged-files'\nimport type { Plugin } from '../../plugin/plugin'\n\nimport { generateModel } from './gen-model'\nimport { computeInputFilesHash } from './hash-files'\n\nexport interface BuildOnceOptions {\n forceModelGen?: boolean\n abortSignal?: AbortSignal\n}\n\n/**\n * Perform a single build.\n *\n * This will return an error if a build failure occurred, or if a plugin encounters\n * an error. Otherwise, it will return true if the build and all plugins\n * succeeded, or false if a plugin wants to report a \"soft\" failure (for example,\n * if the model check plugin reports failing checks).\n *\n * @param config The resolved build configuration.\n * @param plugins The configured plugins.\n * @param options Options specific to the build process.\n * @return An `ok` result with true if the build and all plugins succeeded, or false if\n * one or more plugins failed; otherwise, an `err` result if there was a hard error.\n */\nexport async function buildOnce(\n config: ResolvedConfig,\n userConfig: UserConfig,\n plugins: Plugin[],\n options: BuildOnceOptions\n): Promise<Result<boolean, Error>> {\n // Create the build context\n const stagedFiles = new StagedFiles(config.prepDir)\n const context = new BuildContext(config, stagedFiles, options.abortSignal)\n const modelHashPath = joinPath(config.prepDir, 'model-hash.txt')\n\n // Note that the entire body of this function is wrapped in a try/catch. Any\n // errors that are thrown by plugin functions or the core `generateModel`\n // function will be caught and handled as appropriate.\n let succeeded = true\n try {\n // Get the model spec from the config\n const userModelSpec = await userConfig.modelSpec(context)\n if (userModelSpec === undefined) {\n return err(new Error('The model spec must be defined'))\n }\n\n // Resolve the model spec\n const modelSpec = resolveModelSpec(userModelSpec)\n\n // Run plugins that implement `preGenerate`\n for (const plugin of plugins) {\n if (plugin.preGenerate) {\n await plugin.preGenerate(context, modelSpec)\n }\n }\n\n // Write the spec file\n const specJson = {\n inputVarNames: modelSpec.inputVarNames,\n outputVarNames: modelSpec.outputVarNames,\n externalDatfiles: modelSpec.datFiles,\n bundleListing: modelSpec.bundleListing,\n customLookups: modelSpec.customLookups,\n customOutputs: modelSpec.customOutputs,\n ...modelSpec.options\n }\n const specPath = joinPath(config.prepDir, 'spec.json')\n await writeFile(specPath, JSON.stringify(specJson, null, 2))\n\n // Read the hash from the last successful model build, if available\n let previousModelHash: string\n if (existsSync(modelHashPath)) {\n previousModelHash = readFileSync(modelHashPath, 'utf8')\n } else {\n previousModelHash = 'NONE'\n }\n\n // The code gen and Wasm build steps are time consuming, so we avoid rebuilding\n // it if the build input files are unchanged since the last successful build\n const inputFilesHash = await computeInputFilesHash(config)\n let needModelGen: boolean\n if (options.forceModelGen === true) {\n needModelGen = true\n } else {\n const hashMismatch = inputFilesHash !== previousModelHash\n needModelGen = hashMismatch\n }\n\n if (needModelGen) {\n // Generate the model\n await generateModel(context, plugins)\n\n // Save the hash of the input files, which can be used to determine if\n // we need to rebuild the model the next time\n writeFileSync(modelHashPath, inputFilesHash)\n } else {\n // Skip code generation\n log('info', 'Skipping model code generation; already up-to-date')\n }\n\n // Run plugins that implement `postGenerate`\n // TODO: For now we run all plugins even if one or more of them returns\n // false, which allows (in theory) for there to be multiple plugins that\n // run tests or checks as a post-build step. We should eventually make\n // this configurable so that a plugin can halt the build if it fails.\n // (Technically this is already possible if the plugin throws an error\n // instead of returning false, but maybe it should be more configurable.)\n for (const plugin of plugins) {\n if (plugin.postGenerate) {\n const pluginSucceeded = await plugin.postGenerate(context, modelSpec)\n if (!pluginSucceeded) {\n succeeded = false\n }\n }\n }\n\n // Copy staged files to their destination; this will only copy the staged\n // files if they are different than the existing destination files. We\n // copy the files in a batch like this so that hot module reload is only\n // triggered once at the end of the whole build process.\n stagedFiles.copyChangedFiles()\n\n // Run plugins that implement `postBuild` (this is specified to run after\n // the \"copy staged files\" step)\n for (const plugin of plugins) {\n if (plugin.postBuild) {\n const pluginSucceeded = await plugin.postBuild(context, modelSpec)\n if (!pluginSucceeded) {\n succeeded = false\n }\n }\n }\n\n if (config.mode === 'development') {\n // Hide the \"rebuilding\" message in the dev overlay in the app if the\n // build succeeded; otherwise keep the error message visible\n log('info', 'Waiting for changes...\\n')\n clearOverlay()\n }\n } catch (e) {\n // When a build is aborted, the error will have \"ABORT\" as the message,\n // in which case we can swallow the error; for actual errors, rethrow\n if (e.message !== 'ABORT') {\n // Clear the hash so that the model is rebuilt next time\n writeFileSync(modelHashPath, '')\n\n // Return an error result\n return err(e)\n }\n }\n\n return ok(succeeded)\n}\n\n/**\n * Convert a `ModelSpec` instance to a `ResolvedModelSpec` instance.\n *\n * @param modelSpec The `ModelSpec` instance returned by the `UserConfig`.\n */\nfunction resolveModelSpec(modelSpec: ModelSpec): ResolvedModelSpec {\n let inputVarNames: VarName[]\n let inputSpecs: InputSpec[]\n if (modelSpec.inputs.length > 0) {\n const item = modelSpec.inputs[0]\n if (typeof item === 'string') {\n // The array contains variable names; derive `InputSpec` instances\n inputVarNames = modelSpec.inputs as VarName[]\n inputSpecs = inputVarNames.map(varName => {\n return {\n varName\n }\n })\n } else {\n // The array contains `InputSpec` objects; derive variable names\n inputSpecs = modelSpec.inputs as InputSpec[]\n inputVarNames = inputSpecs.map(spec => spec.varName)\n }\n } else {\n // The inputs array is empty, so return empty arrays\n inputVarNames = []\n inputSpecs = []\n }\n\n let outputVarNames: VarName[]\n let outputSpecs: OutputSpec[]\n if (modelSpec.outputs.length > 0) {\n const item = modelSpec.outputs[0]\n if (typeof item === 'string') {\n // The array contains variable names; derive `OutputSpec` instances\n outputVarNames = modelSpec.outputs as VarName[]\n outputSpecs = outputVarNames.map(varName => {\n return {\n varName\n }\n })\n } else {\n // The array contains `OutputSpec` objects; derive variable names\n outputSpecs = modelSpec.outputs as OutputSpec[]\n outputVarNames = outputSpecs.map(spec => spec.varName)\n }\n } else {\n // The outputs array is empty, so return empty arrays\n outputVarNames = []\n outputSpecs = []\n }\n\n let customLookups: boolean | VarName[]\n if (modelSpec.customLookups !== undefined) {\n customLookups = modelSpec.customLookups\n } else {\n customLookups = false\n }\n\n let customOutputs: boolean | VarName[]\n if (modelSpec.customOutputs !== undefined) {\n customOutputs = modelSpec.customOutputs\n } else {\n customOutputs = false\n }\n\n return {\n inputVarNames,\n inputs: inputSpecs,\n outputVarNames,\n outputs: outputSpecs,\n datFiles: modelSpec.datFiles || [],\n bundleListing: modelSpec.bundleListing === true,\n customLookups,\n customOutputs,\n options: modelSpec.options\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { canonicalVarId } from '@sdeverywhere/parse'\nimport type { LogLevel } from '../_shared/log'\nimport { log } from '../_shared/log'\n\nimport type { ResolvedConfig } from '../_shared/resolved-config'\n\nimport type { ProcessOptions, ProcessOutput } from './spawn-child'\nimport { spawnChild } from './spawn-child'\nimport type { StagedFiles } from './staged-files'\n\n/**\n * Provides access to common functionality that is needed during the build process.\n * This is passed to most plugin functions.\n */\nexport class BuildContext {\n /**\n * @param config The resolved configuration.\n * @hidden\n */\n constructor(\n public readonly config: ResolvedConfig,\n private readonly stagedFiles: StagedFiles,\n private readonly abortSignal: AbortSignal | undefined\n ) {}\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n log(level, msg)\n }\n\n /**\n * Prepare for writing a file to the staged directory.\n *\n * This will add the path to the array of tracked files and will create the\n * staged directory if needed.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param srcFile The name of the file as written to the `staged` directory.\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param dstFile The name of the file as written to the destination directory.\n * @return The absolute path to the staged file.\n */\n prepareStagedFile(srcDir: string, srcFile: string, dstDir: string, dstFile: string): string {\n return this.stagedFiles.prepareStagedFile(srcDir, srcFile, dstDir, dstFile)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.stagedFiles.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n /**\n * Spawn a child process that runs the given command.\n *\n * @param cwd The directory in which the command will be executed.\n * @param command The command to execute.\n * @param args The arguments to pass to the command.\n * @param opts Additional options to configure the process.\n * @returns The output of the process.\n */\n spawnChild(cwd: string, command: string, args: string[], opts?: ProcessOptions): Promise<ProcessOutput> {\n return spawnChild(cwd, command, args, this.abortSignal, opts)\n }\n\n /**\n * Format a (subscripted or non-subscripted) model variable name into a canonical\n * identifier (with special characters converted to underscore, and subscript/dimension\n * parts separated by commas).\n *\n * @param name The name of the variable in the source model, e.g., `Variable name[DimA, B2]`.\n * @returns The canonical identifier for the given name, e.g., `_variable_name[_dima,_b2]`.\n */\n canonicalVarId(name: string): string {\n return canonicalVarId(name)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ChildProcess } from 'child_process'\n\nimport { spawn } from 'cross-spawn'\n\nimport { log } from '../_shared/log'\n\n/**\n * @hidden This isn't ready to be included in the public API just yet.\n */\nexport interface ProcessOptions {\n logOutput?: boolean\n ignoredMessageFilter?: string\n captureOutput?: boolean\n ignoreError?: boolean\n}\n\n/**\n * @hidden This isn't ready to be included in the public API just yet.\n */\nexport interface ProcessOutput {\n exitCode: number\n stdoutMessages: string[]\n stderrMessages: string[]\n}\n\n/**\n * Spawn a child process that runs the given command.\n *\n * @param cwd The directory in which the command will be executed.\n * @param command The command to execute.\n * @param args The arguments to pass to the command.\n * @param abortSignal The signal used to abort the process.\n * @param opts Additional options to configure the process.\n * @returns The output of the process.\n */\nexport function spawnChild(\n cwd: string,\n command: string,\n args: string[],\n abortSignal?: AbortSignal,\n opts?: ProcessOptions\n): Promise<ProcessOutput> {\n return new Promise((resolve, reject) => {\n if (abortSignal?.aborted) {\n reject(new Error('ABORT'))\n return\n }\n\n let childProc: ChildProcess\n\n const localLog = (s: string, err = false) => {\n // Don't log anything after the process has been killed\n if (childProc === undefined) {\n return\n }\n log(err ? 'error' : 'info', s)\n }\n\n const abortHandler = () => {\n if (childProc) {\n log('info', 'Killing existing build process...')\n childProc.kill('SIGKILL')\n childProc = undefined\n }\n reject(new Error('ABORT'))\n }\n\n // Kill the process if abort is requested\n abortSignal?.addEventListener('abort', abortHandler, { once: true })\n\n // Prepare for capturing output, if requested\n const stdoutMessages: string[] = []\n const stderrMessages: string[] = []\n const logMessage = (msg: string, err: boolean) => {\n let includeMessage = true\n if (opts?.ignoredMessageFilter && msg.trim().startsWith(opts.ignoredMessageFilter)) {\n includeMessage = false\n }\n if (includeMessage) {\n const lines = msg.trim().split('\\n')\n for (const line of lines) {\n localLog(` ${line}`, err)\n }\n }\n }\n\n // Spawn the (asynchronous) child process. Note that we are using `spawn`\n // from the `cross-spawn` package as an alternative to the built-in\n // `child_process` module, which doesn't handle spaces in command path\n // on Windows.\n childProc = spawn(command, args, {\n cwd\n })\n\n childProc.stdout.on('data', (data: Buffer) => {\n const msg = data.toString()\n if (opts?.captureOutput === true) {\n stdoutMessages.push(msg)\n }\n if (opts?.logOutput !== false) {\n logMessage(msg, false)\n }\n })\n childProc.stderr.on('data', (data: Buffer) => {\n const msg = data.toString()\n if (opts?.captureOutput === true) {\n stderrMessages.push(msg)\n }\n if (opts?.logOutput !== false) {\n logMessage(msg, true)\n }\n })\n childProc.on('error', err => {\n localLog(`Process error: ${err}`, true)\n })\n childProc.on('close', (code, signal) => {\n // Stop listening for abort events\n abortSignal?.removeEventListener('abort', abortHandler)\n childProc = undefined\n\n if (signal) {\n // The process was killed by a signal, so we don't need to print anything\n return\n }\n\n const processOutput: ProcessOutput = {\n exitCode: code,\n stdoutMessages,\n stderrMessages\n }\n\n if (code === 0) {\n // The process exited cleanly; resolve the promise\n resolve(processOutput)\n } else if (!signal) {\n // The process failed\n if (opts?.ignoreError === true) {\n // Resolve the promise (but with a non-zero exit code)\n resolve(processOutput)\n } else {\n // Reject the promise\n reject(new Error(`Child process failed (code=${code})`))\n }\n }\n })\n })\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport { log } from '../_shared/log'\n\ninterface StagedFile {\n srcDir: string\n srcFile: string\n dstDir: string\n dstFile: string\n}\n\nexport class StagedFiles {\n private readonly baseStagedDir: string\n private readonly stagedFiles: StagedFile[] = []\n\n constructor(prepDir: string) {\n this.baseStagedDir = joinPath(prepDir, 'staged')\n }\n\n /**\n * Prepare for writing a file to the staged directory.\n *\n * This will add the path to the array of tracked files and will create the\n * staged directory if needed.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param srcFile The name of the file as written to the `staged` directory.\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param dstFile The name of the file as written to the destination directory.\n * @return The absolute path to the staged file.\n */\n prepareStagedFile(srcDir: string, srcFile: string, dstDir: string, dstFile: string): string {\n // Add an entry to the array of staged files (only if an entry does not already\n // exist) so that we can copy the file to the destination directory when the build\n // process has completed\n const stagedFile = {\n srcDir,\n srcFile,\n dstDir,\n dstFile\n }\n // TODO: We allow there to be more than one entry for each source path to\n // support the case where a single source file gets copied to multiple\n // destination paths. But we should throw an error if the same destination\n // path is configured for different source paths.\n if (this.stagedFiles.indexOf(stagedFile) < 0) {\n this.stagedFiles.push(stagedFile)\n }\n\n // Create the directory underneath the staged directory if needed\n const stagedDir = joinPath(this.baseStagedDir, srcDir)\n if (!existsSync(stagedDir)) {\n mkdirSync(stagedDir, { recursive: true })\n }\n\n return joinPath(stagedDir, srcFile)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n // Add an entry to track the file and create the staged directory if needed\n const stagedFilePath = this.prepareStagedFile(srcDir, filename, dstDir, filename)\n\n // Write the file to the staged directory\n writeFileSync(stagedFilePath, content)\n }\n\n /**\n * Return the absolute path to the staged file for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n getStagedFilePath(srcDir: string, srcFile: string): string {\n return joinPath(this.baseStagedDir, srcDir, srcFile)\n }\n\n /**\n * Return true if the staged file exists for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n stagedFileExists(srcDir: string, srcFile: string): boolean {\n const fullSrcPath = this.getStagedFilePath(srcDir, srcFile)\n return existsSync(fullSrcPath)\n }\n\n /**\n * Return true if the destination file exists for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n destinationFileExists(srcDir: string, srcFile: string): boolean {\n const f = this.stagedFiles.find(f => f.srcDir === srcDir && f.srcFile === srcFile)\n if (f === undefined) {\n return false\n }\n const fullDstPath = joinPath(f.dstDir, f.dstFile)\n return existsSync(fullDstPath)\n }\n\n /**\n * Copy staged files to their destination; this will only copy the staged\n * files if they are different than the existing destination files. We\n * copy the files in a batch like this so that hot module reload is only\n * triggered once at the end of the whole build process.\n */\n copyChangedFiles(): void {\n log('info', 'Copying changed files into place...')\n\n for (const f of this.stagedFiles) {\n this.copyStagedFile(f)\n }\n\n log('info', 'Done copying files')\n }\n\n /**\n * Copy a file from the `staged` directory to its destination. If the file already\n * exists in the destination directory and has the same contents as the source file,\n * the file will not be copied and this function will return false.\n *\n * @param f The staged file entry.\n */\n private copyStagedFile(f: StagedFile): boolean {\n // Create the destination directory, if needed\n if (!existsSync(f.dstDir)) {\n mkdirSync(f.dstDir, { recursive: true })\n }\n\n // If the destination file already exists and has the same contents as the source\n // file, we can skip copying it\n const fullSrcPath = this.getStagedFilePath(f.srcDir, f.srcFile)\n const fullDstPath = joinPath(f.dstDir, f.dstFile)\n const needsCopy = filesDiffer(fullSrcPath, fullDstPath)\n if (needsCopy) {\n log('verbose', ` Copying ${f.srcFile} to ${fullDstPath}`)\n copyFileSync(fullSrcPath, fullDstPath)\n }\n return needsCopy\n }\n}\n\n/**\n * Return true if both files exist at the given paths and have the same contents, false otherwise.\n */\nfunction filesDiffer(aPath: string, bPath: string): boolean {\n if (existsSync(aPath) && existsSync(bPath)) {\n // The files exist; see if they are different\n const aSize = statSync(aPath).size\n const bSize = statSync(bPath).size\n if (aSize !== bSize) {\n // The sizes are different, so the contents must be different\n return true\n } else {\n // The sizes are the same, so check the contents\n const aBuf = readFileSync(aPath)\n const bBuf = readFileSync(bPath)\n return !aBuf.equals(bBuf)\n }\n } else {\n // One or both files do not exist\n return true\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { copyFile, readdir, readFile, writeFile } from 'fs/promises'\nimport { basename, dirname, join as joinPath } from 'path'\n\nimport { log } from '../../_shared/log'\n\nimport type { BuildContext } from '../../context/context'\n\nimport type { Plugin } from '../../plugin/plugin'\n\n/**\n * Generate the model. This will run the core SDEverywhere code generation steps\n * and will also invoke the following plugin functions:\n * - `preProcessMdl`\n * - `postProcessMdl`\n * - `preGenerateC`\n * - `postGenerateC`\n */\nexport async function generateModel(context: BuildContext, plugins: Plugin[]): Promise<void> {\n const config = context.config\n if (config.modelFiles.length === 0) {\n log('info', 'No model input files specified, skipping model generation steps')\n return\n }\n\n log('info', 'Generating model...')\n\n const t0 = performance.now()\n\n // Use the defined prep directory\n const prepDir = config.prepDir\n\n // TODO: For now we assume the path is to the `main.js` file in the cli package;\n // this seems to work on both Unix and Windows, but we may need to revisit this\n // as part of removing the `sdeCmdPath` config hack\n const sdeCmdPath = config.sdeCmdPath\n\n // Process the mdl file(s)\n for (const plugin of plugins) {\n if (plugin.preProcessMdl) {\n await plugin.preProcessMdl(context)\n }\n }\n if (config.modelFiles.length === 1) {\n // Preprocess the single mdl file\n await preprocessMdl(context, sdeCmdPath, prepDir, config.modelFiles[0])\n } else {\n // Flatten and preprocess the multiple mdl files into a single mdl file\n await flattenMdls(context, sdeCmdPath, prepDir, config.modelFiles)\n }\n for (const plugin of plugins) {\n if (plugin.postProcessMdl) {\n const mdlPath = joinPath(prepDir, 'processed.mdl')\n let mdlContent = await readFile(mdlPath, 'utf8')\n mdlContent = await plugin.postProcessMdl(context, mdlContent)\n await writeFile(mdlPath, mdlContent)\n }\n }\n\n // Generate the JS or C file\n for (const plugin of plugins) {\n if (plugin.preGenerateCode) {\n await plugin.preGenerateCode(context, config.genFormat)\n }\n }\n await generateCode(context, config.sdeDir, sdeCmdPath, prepDir)\n const generatedCodeFile = `processed.${config.genFormat}`\n const generatedCodePath = joinPath(prepDir, 'build', generatedCodeFile)\n for (const plugin of plugins) {\n if (plugin.postGenerateCode) {\n let generatedCodeContent = await readFile(generatedCodePath, 'utf8')\n generatedCodeContent = await plugin.postGenerateCode(context, config.genFormat, generatedCodeContent)\n await writeFile(generatedCodePath, generatedCodeContent)\n }\n }\n\n if (config.genFormat === 'js') {\n // When generating JS code, copy the generated JS file to the `staged/model`\n // directory, because that's where plugin-worker expects to find it, but also\n // set it up to be copied to the `prepDir`, which is where other code expects\n // to find it\n // TODO: Maybe we can change plugin-worker to use the one in `prepDir`, and/or\n // add a build config setting to allow for customizing the output location\n const outputJsFile = 'generated-model.js'\n const stagedOutputJsPath = context.prepareStagedFile('model', outputJsFile, prepDir, outputJsFile)\n await copyFile(generatedCodePath, stagedOutputJsPath)\n }\n\n if (config.outListingFile) {\n // Copy the model listing file\n const srcListingJsonPath = joinPath(prepDir, 'build', 'processed.json')\n const stagedDir = 'model'\n const stagedFile = 'listing.json'\n const dstDir = dirname(config.outListingFile)\n const dstFile = basename(config.outListingFile)\n const stagedListingJsonPath = context.prepareStagedFile(stagedDir, stagedFile, dstDir, dstFile)\n await copyFile(srcListingJsonPath, stagedListingJsonPath)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n log('info', `Done generating model (${elapsed}s)`)\n}\n\n/**\n * Preprocess a single mdl file and copy the resulting `processed.mdl` to the prep directory.\n */\nasync function preprocessMdl(\n context: BuildContext,\n sdeCmdPath: string,\n prepDir: string,\n modelFile: string\n): Promise<void> {\n log('verbose', ' Preprocessing mdl file')\n\n // Copy the source file to the prep directory to make the next steps easier\n await copyFile(modelFile, joinPath(prepDir, 'processed.mdl'))\n\n // Use SDE to preprocess the model to strip anything that's not needed to build it\n const command = sdeCmdPath\n const args = ['generate', '--preprocess', 'processed.mdl']\n const ppOutput = await context.spawnChild(prepDir, command, args, {\n // The default error message from `spawnChild` is not very informative, so the\n // following allows us to throw our own error\n ignoreError: true\n })\n if (ppOutput.exitCode !== 0) {\n throw new Error(`Failed to preprocess mdl file: 'sde generate' command failed (code=${ppOutput.exitCode})`)\n }\n\n // Copy the processed file back to the prep directory\n await copyFile(joinPath(prepDir, 'build', 'processed.mdl'), joinPath(prepDir, 'processed.mdl'))\n}\n\n/**\n * Flatten multiple mdl files and copy the resulting `processed.mdl` to the prep directory.\n */\nasync function flattenMdls(\n context: BuildContext,\n sdeCmdPath: string,\n prepDir: string,\n modelFiles: string[]\n): Promise<void> {\n log('verbose', ' Flattening and preprocessing mdl files')\n\n // Use SDE to flatten the parent model and submodels into a single mdl file,\n // then preprocess to strip anything that's not needed to build the model\n const command = sdeCmdPath\n const args: string[] = []\n args.push('flatten')\n args.push('processed.mdl')\n args.push('--inputs')\n for (const path of modelFiles) {\n args.push(path)\n }\n\n // Disable logging by default; this will suppress flatten warnings, which are\n // sometimes unavoidable and not helpful\n const output = await context.spawnChild(prepDir, command, args, {\n logOutput: false,\n captureOutput: true,\n ignoreError: true\n })\n\n // Check for flattening errors\n let flattenErrors = false\n for (const msg of output.stderrMessages) {\n if (msg.includes('ERROR')) {\n flattenErrors = true\n break\n }\n }\n if (flattenErrors) {\n log('error', 'There were errors reported when flattening the model:')\n for (const msg of output.stderrMessages) {\n const lines = msg.split('\\n')\n for (const line of lines) {\n log('error', ` ${line}`)\n }\n }\n throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`)\n } else if (output.exitCode !== 0) {\n throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`)\n }\n\n // Copy the processed file back to the prep directory\n await copyFile(joinPath(prepDir, 'build', 'processed.mdl'), joinPath(prepDir, 'processed.mdl'))\n}\n\n/**\n * Generate a JS or C file from the `processed.mdl` file.\n */\nasync function generateCode(context: BuildContext, sdeDir: string, sdeCmdPath: string, prepDir: string): Promise<void> {\n const genFormat = context.config.genFormat\n const genFormatName = genFormat.toUpperCase()\n log('verbose', ` Generating ${genFormatName} code`)\n\n // Use SDE to generate both a JS/C version of the model (`--outformat`) AND a JSON list of all model\n // dimensions and variables (`--list`)\n const command = sdeCmdPath\n const outFormat = `--outformat=${genFormat}`\n const genCmdArgs = ['generate', outFormat, '--list', '--spec', 'spec.json', 'processed']\n const genCmdOutput = await context.spawnChild(prepDir, command, genCmdArgs, {\n // By default, ignore lines that start with \"WARNING: Data for\" since these are often harmless\n // TODO: Don't filter by default, but make it configurable\n // ignoredMessageFilter: 'WARNING: Data for'\n // The default error message from `spawnChild` is not very informative, so the\n // following allows us to throw our own error\n ignoreError: true\n })\n if (genCmdOutput.exitCode !== 0) {\n throw new Error(\n `Failed to generate ${genFormatName} code: 'sde generate' command failed (code=${genCmdOutput.exitCode})`\n )\n }\n\n if (genFormat === 'c') {\n // Copy SDE's supporting C files into the build directory\n const buildDir = joinPath(prepDir, 'build')\n const sdeCDir = joinPath(sdeDir, 'src', 'c')\n const files = await readdir(sdeCDir)\n const copyOps = []\n for (const file of files) {\n if (file.endsWith('.c') || file.endsWith('.h')) {\n copyOps.push(copyFile(joinPath(sdeCDir, file), joinPath(buildDir, file)))\n }\n }\n await Promise.all(copyOps)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\nimport { hashElement } from 'folder-hash'\nimport { glob } from 'tinyglobby'\n\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\n/**\n * Asynchronously compute the hash of the files that are inputs to the model\n * build process.\n */\nexport async function computeInputFilesHash(config: ResolvedConfig): Promise<string> {\n const inputFiles: string[] = []\n\n // Always include the `spec.json` file, since that is a primary input\n // to the model build process\n const specFile = joinPath(config.prepDir, 'spec.json')\n inputFiles.push(specFile)\n\n if (config.modelInputPaths && config.modelInputPaths.length > 0) {\n // Include the files that match the glob patterns in the config file.\n // Note that the folder-hash package supports glob patterns, but its\n // configuration is complicated, so it is easier if we resolve the\n // glob patterns here and pass each individual file to the\n // `hashElement` function.\n for (const globPath of config.modelInputPaths) {\n const paths = await glob(globPath, {\n cwd: config.rootDir,\n absolute: true,\n onlyFiles: true\n })\n inputFiles.push(...paths)\n }\n } else {\n // Only use the mdl files to compute the hash\n inputFiles.push(...config.modelFiles)\n }\n\n // Compute the hash of each input file and concatenate into a single string\n let hash = ''\n for (const inputFile of inputFiles) {\n const result = await hashElement(inputFile)\n hash += result.hash\n }\n\n return hash\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { basename } from 'path'\n\nimport chokidar from 'chokidar'\nimport { globSync, isDynamicPattern } from 'tinyglobby'\n\nimport { clearOverlay, log, logError } from '../../_shared/log'\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\nimport type { UserConfig } from '../../config/user-config'\nimport type { Plugin } from '../../plugin/plugin'\n\nimport type { BuildOnceOptions } from './build-once'\nimport { buildOnce } from './build-once'\n\nclass BuildState {\n readonly abortController = new AbortController()\n}\n\nexport function watch(config: ResolvedConfig, userConfig: UserConfig, plugins: Plugin[]): void {\n // Add a small delay so that if multiple files are changed at once (as is\n // often the case when switching branches), we batch them up and start the\n // the build after things settle down\n const delay = 150\n const changedPaths: Set<string> = new Set()\n\n // Keep track of the current build so that we only have one active at a time\n let currentBuildState: BuildState\n\n function performBuild() {\n clearOverlay()\n\n // Log the input files that have changed\n for (const path of changedPaths) {\n log('info', `Input file ${basename(path)} has been changed`)\n }\n\n // Clear the set of pending files\n changedPaths.clear()\n\n // Keep track of builds; if one is already in progress, abort it\n // before starting another one\n if (currentBuildState) {\n currentBuildState.abortController.abort()\n // TODO: Prevent aborted build from logging?\n currentBuildState = undefined\n }\n\n // Generate files and build the model\n currentBuildState = new BuildState()\n const buildOptions: BuildOnceOptions = {\n abortSignal: currentBuildState.abortController.signal\n }\n buildOnce(config, userConfig, plugins, buildOptions)\n .then(result => {\n // Log the error message in case of error result\n if (result.isErr()) {\n logError(result.error)\n }\n })\n .catch(e => {\n // Also catch thrown errors that may have not already been\n // handled by `buildOnce`\n logError(e)\n // log('info', 'Waiting for changes...')\n })\n .finally(() => {\n currentBuildState = undefined\n })\n }\n\n function scheduleBuild(changedPath: string) {\n // Only schedule the build if the set is currently empty\n const schedule = changedPaths.size === 0\n\n // Add the path to the set of changed files\n changedPaths.add(changedPath)\n\n if (schedule) {\n // Schedule the build to start after a delay\n setTimeout(() => {\n performBuild()\n }, delay)\n }\n }\n\n let watchPaths: string[]\n if (config.watchPaths && config.watchPaths.length > 0) {\n // Watch the configured files\n watchPaths = config.watchPaths\n } else {\n // Only watch the mdl files\n watchPaths = config.modelFiles\n }\n\n // The chokidar package no longer supports glob patterns, so we need to resolve\n // glob patterns first and then pass the resolved paths to chokidar\n const resolvedWatchPaths: string[] = []\n for (const watchPath of watchPaths) {\n if (isDynamicPattern(watchPath)) {\n // This is a glob pattern; resolve files that match the pattern\n const paths = globSync(watchPath, {\n // Watch paths are resolved relative to the project root directory\n cwd: config.rootDir,\n // Resolve to absolute paths\n absolute: true\n })\n resolvedWatchPaths.push(...paths)\n } else {\n // This is regular file or directory path; let chokidar resolve it\n resolvedWatchPaths.push(watchPath)\n }\n }\n\n // Watch the config and model files; if changes are detected, generate the specs\n // and rebuild the model if needed\n const watcher = chokidar.watch(resolvedWatchPaths, {\n // Watch paths are resolved relative to the project root directory\n cwd: config.rootDir,\n // XXX: Include a delay, otherwise on macOS we sometimes get multiple\n // change events when the csv file is saved just once\n awaitWriteFinish: {\n stabilityThreshold: 200\n }\n })\n watcher.on('change', path => {\n scheduleBuild(path)\n })\n}\n"],"mappings":";AAEA,SAAS,QAAQA,iBAAgB;AAGjC,SAAS,OAAAC,MAAK,MAAAC,WAAU;;;ACHxB,SAAS,YAAY,WAAW,iBAAiB;AACjD,SAAS,SAAS,YAAY,QAAQ,UAAU,UAAU,WAAW,mBAAmB;AACxF,SAAS,qBAAqB;AAG9B,SAAS,KAAK,UAAU;AAwBxB,eAAsB,WACpB,MACA,QACA,QACA,YACsC;AACtC,MAAI;AACJ,MAAI,OAAO,WAAW,UAAU;AAE9B,iBAAa;AAAA,EACf,OAAO;AAKL,QAAI;AACJ,QAAI,OAAO,WAAW,UAAU;AAC9B,mBAAa;AAAA,IACf,OAAO;AACL,mBAAa,SAAS,QAAQ,IAAI,GAAG,eAAe;AAAA,IACtD;AACA,QAAI;AACF,UAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,eAAO,IAAI,IAAI,MAAM,4BAA4B,UAAU,GAAG,CAAC;AAAA,MACjE;AACA,YAAM,gBAAgB,qBAAqB,UAAU;AACrD,YAAM,eAAe,MAAM,OAAO;AAClC,mBAAa,MAAM,aAAa,OAAO;AAAA,IACzC,SAAS,GAAG;AACV,aAAO,IAAI,IAAI,MAAM,+BAA+B,UAAU,MAAM,EAAE,OAAO,EAAE,CAAC;AAAA,IAClF;AAAA,EACF;AAGA,MAAI;AACF,UAAM,iBAAiB,kBAAkB,YAAY,MAAM,QAAQ,UAAU;AAC7E,WAAO,GAAG;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,SAAS,GAAG;AACV,WAAO,IAAI,CAAC;AAAA,EACd;AACF;AAaA,SAAS,kBACP,YACA,MACA,QACA,YACgB;AAChB,WAAS,gBAAgB,UAAkB,MAAoB;AAC7D,QAAI,CAAC,WAAW,IAAI,GAAG;AACrB,YAAM,IAAI,MAAM,kBAAkB,QAAQ,KAAK,IAAI,kBAAkB;AAAA,IACvE,WAAW,CAAC,UAAU,IAAI,EAAE,YAAY,GAAG;AACzC,YAAM,IAAI,MAAM,kBAAkB,QAAQ,KAAK,IAAI,sBAAsB;AAAA,IAC3E;AAAA,EACF;AAaA,MAAI;AACJ,MAAI,WAAW,SAAS;AAEtB,cAAU,YAAY,WAAW,OAAO;AACxC,oBAAgB,WAAW,OAAO;AAAA,EACpC,OAAO;AAEL,cAAU,QAAQ,IAAI;AAAA,EACxB;AAGA,MAAI;AACJ,MAAI,WAAW,SAAS;AAEtB,cAAU,YAAY,WAAW,OAAO;AAAA,EAC1C,OAAO;AAEL,cAAU,YAAY,SAAS,UAAU;AAAA,EAC3C;AACA,YAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAGtC,QAAM,iBAAiB,WAAW;AAClC,QAAM,aAAuB,CAAC;AAC9B,aAAW,iBAAiB,gBAAgB;AAC1C,UAAM,YAAY,YAAY,aAAa;AAC3C,QAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,YAAM,IAAI,MAAM,8BAA8B,SAAS,kBAAkB;AAAA,IAC3E;AACA,eAAW,KAAK,SAAS;AAAA,EAC3B;AAIA,MAAI;AACJ,MAAI,WAAW,mBAAmB,WAAW,gBAAgB,SAAS,GAAG;AACvE,sBAAkB,WAAW;AAAA,EAC/B,OAAO;AACL,sBAAkB;AAAA,EACpB;AACA,MAAI;AACJ,MAAI,WAAW,cAAc,WAAW,WAAW,SAAS,GAAG;AAC7D,iBAAa,WAAW;AAAA,EAC1B,OAAO;AACL,iBAAa;AAAA,EACf;AAGA,QAAM,eAAe,WAAW,aAAa;AAC7C,MAAI;AACJ,UAAQ,cAAc;AAAA,IACpB,KAAK;AACH,kBAAY;AACZ;AAAA,IACF,KAAK;AACH,kBAAY;AACZ;AAAA,IACF;AACE,YAAM,IAAI,MAAM,uEAAuE;AAAA,EAC3F;AAGA,MAAI;AACJ,MAAI,WAAW,gBAAgB;AAE7B,QAAI,WAAW,WAAW,cAAc,GAAG;AACzC,uBAAiB,WAAW;AAAA,IAC9B,OAAO;AACL,uBAAiB,YAAY,SAAS,WAAW,cAAc;AAAA,IACjE;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AASA,SAAS,qBAAqB,UAA0B;AACtD,QAAM,SAAS,QAAQ,cAAc,YAAY,GAAG,CAAC;AACrD,QAAM,UAAU,SAAS,QAAQ,QAAQ;AACzC,SAAO,QAAQ,WAAW,MAAM,GAAG;AACrC;;;AC/MA,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AAIjB,IAAM,eAA8B,oBAAI,IAAI,CAAC,SAAS,MAAM,CAAC;AAE7D,IAAI;AACJ,IAAI,iBAAiB;AACrB,IAAI,cAAc;AAQX,SAAS,gBAAgB,WAA6B;AAC3D,eAAa,MAAM;AACnB,aAAW,SAAS,WAAW;AAC7B,iBAAa,IAAI,KAAK;AAAA,EACxB;AACF;AASO,SAAS,eAAe,MAAc,SAAwB;AACnE,gBAAc;AACd,mBAAiB;AAIjB,gBAAc,aAAa,EAAE;AAC/B;AAQO,SAAS,IAAI,OAAiB,KAAmB;AACtD,MAAI,aAAa,IAAI,KAAK,GAAG;AAC3B,QAAI,UAAU,SAAS;AACrB,cAAQ,MAAM,KAAK,IAAI,GAAG,CAAC;AAC3B,mBAAa,GAAG;AAAA,IAClB,OAAO;AACL,cAAQ,IAAI,GAAG;AACf,mBAAa,GAAG;AAAA,IAClB;AAAA,EACF;AACF;AAOO,SAAS,SAAS,GAAgB;AAIvC,QAAM,QAAQ,EAAE,SAAS;AACzB,QAAM,aAAa,MAAM,MAAM,IAAI,EAAE,OAAO,OAAK,EAAE,MAAM,QAAQ,CAAC;AAClE,QAAM,QAAQ,WAAW,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAG9C,UAAQ,MAAM,KAAK,IAAI;AAAA,SAAY,EAAE,OAAO,EAAE,CAAC;AAC/C,UAAQ,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK;AAAA,CAAI,CAAC,CAAC;AAC9C,eAAa;AAAA,SAAY,EAAE,OAAO,IAAI,IAAI;AAC1C,eAAa,GAAG,KAAK;AAAA,GAAM,IAAI;AACjC;AAEA,SAAS,oBAA0B;AACjC,gBAAc,aAAa,WAAW;AACxC;AAEO,SAAS,eAAqB;AACnC,MAAI,CAAC,gBAAgB;AACnB;AAAA,EACF;AAEA,gBAAc;AACd,oBAAkB;AACpB;AAEA,IAAM,SAAS,SAAS,OAAO,CAAC;AAEzB,SAAS,aAAa,KAAa,QAAQ,OAAa;AAC7D,MAAI,CAAC,gBAAgB;AACnB;AAAA,EACF;AAEA,MAAI,OAAO;AACT,UAAM,+BAA+B,GAAG;AAAA,EAC1C;AACA,QAAM,UAAU,IAAI,QAAQ,OAAO,SAAS,EAAE,QAAQ,UAAU,MAAM;AACtE,MAAI,aAAa;AACf,mBAAe,QAAQ,OAAO;AAAA,EAChC,OAAO;AACL,kBAAc,GAAG,OAAO;AAAA,EAC1B;AACA,oBAAkB;AACpB;;;AC5GA,SAAS,cAAAC,aAAY,gBAAAC,eAAc,iBAAAC,sBAAqB;AACxD,SAAS,aAAAC,kBAAiB;AAC1B,SAAS,QAAQC,iBAAgB;AAGjC,SAAS,OAAAC,MAAK,MAAAC,WAAU;;;ACLxB,SAAS,sBAAsB;;;ACE/B,SAAS,aAAa;AAiCf,SAAS,WACd,KACA,SACA,MACA,aACA,MACwB;AACxB,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,aAAa,SAAS;AACxB,aAAO,IAAI,MAAM,OAAO,CAAC;AACzB;AAAA,IACF;AAEA,QAAI;AAEJ,UAAM,WAAW,CAAC,GAAWC,OAAM,UAAU;AAE3C,UAAI,cAAc,QAAW;AAC3B;AAAA,MACF;AACA,UAAIA,OAAM,UAAU,QAAQ,CAAC;AAAA,IAC/B;AAEA,UAAM,eAAe,MAAM;AACzB,UAAI,WAAW;AACb,YAAI,QAAQ,mCAAmC;AAC/C,kBAAU,KAAK,SAAS;AACxB,oBAAY;AAAA,MACd;AACA,aAAO,IAAI,MAAM,OAAO,CAAC;AAAA,IAC3B;AAGA,iBAAa,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAGnE,UAAM,iBAA2B,CAAC;AAClC,UAAM,iBAA2B,CAAC;AAClC,UAAM,aAAa,CAAC,KAAaA,SAAiB;AAChD,UAAI,iBAAiB;AACrB,UAAI,MAAM,wBAAwB,IAAI,KAAK,EAAE,WAAW,KAAK,oBAAoB,GAAG;AAClF,yBAAiB;AAAA,MACnB;AACA,UAAI,gBAAgB;AAClB,cAAM,QAAQ,IAAI,KAAK,EAAE,MAAM,IAAI;AACnC,mBAAW,QAAQ,OAAO;AACxB,mBAAS,KAAK,IAAI,IAAIA,IAAG;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAMA,gBAAY,MAAM,SAAS,MAAM;AAAA,MAC/B;AAAA,IACF,CAAC;AAED,cAAU,OAAO,GAAG,QAAQ,CAAC,SAAiB;AAC5C,YAAM,MAAM,KAAK,SAAS;AAC1B,UAAI,MAAM,kBAAkB,MAAM;AAChC,uBAAe,KAAK,GAAG;AAAA,MACzB;AACA,UAAI,MAAM,cAAc,OAAO;AAC7B,mBAAW,KAAK,KAAK;AAAA,MACvB;AAAA,IACF,CAAC;AACD,cAAU,OAAO,GAAG,QAAQ,CAAC,SAAiB;AAC5C,YAAM,MAAM,KAAK,SAAS;AAC1B,UAAI,MAAM,kBAAkB,MAAM;AAChC,uBAAe,KAAK,GAAG;AAAA,MACzB;AACA,UAAI,MAAM,cAAc,OAAO;AAC7B,mBAAW,KAAK,IAAI;AAAA,MACtB;AAAA,IACF,CAAC;AACD,cAAU,GAAG,SAAS,CAAAA,SAAO;AAC3B,eAAS,kBAAkBA,IAAG,IAAI,IAAI;AAAA,IACxC,CAAC;AACD,cAAU,GAAG,SAAS,CAAC,MAAM,WAAW;AAEtC,mBAAa,oBAAoB,SAAS,YAAY;AACtD,kBAAY;AAEZ,UAAI,QAAQ;AAEV;AAAA,MACF;AAEA,YAAM,gBAA+B;AAAA,QACnC,UAAU;AAAA,QACV;AAAA,QACA;AAAA,MACF;AAEA,UAAI,SAAS,GAAG;AAEd,gBAAQ,aAAa;AAAA,MACvB,WAAW,CAAC,QAAQ;AAElB,YAAI,MAAM,gBAAgB,MAAM;AAE9B,kBAAQ,aAAa;AAAA,QACvB,OAAO;AAEL,iBAAO,IAAI,MAAM,8BAA8B,IAAI,GAAG,CAAC;AAAA,QACzD;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;;;ADpIO,IAAM,eAAN,MAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKxB,YACkB,QACC,aACA,aACjB;AAHgB;AACC;AACA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQH,IAAI,OAAiB,KAAmB;AACtC,QAAI,OAAO,GAAG;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,kBAAkB,QAAgB,SAAiB,QAAgB,SAAyB;AAC1F,WAAO,KAAK,YAAY,kBAAkB,QAAQ,SAAS,QAAQ,OAAO;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,gBAAgB,QAAgB,QAAgB,UAAkB,SAAuB;AACvF,SAAK,YAAY,gBAAgB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,WAAW,KAAa,SAAiB,MAAgB,MAA+C;AACtG,WAAO,WAAW,KAAK,SAAS,MAAM,KAAK,aAAa,IAAI;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,eAAe,MAAsB;AACnC,WAAO,eAAe,IAAI;AAAA,EAC5B;AACF;;;AEjGA,SAAS,cAAc,cAAAC,aAAY,aAAAC,YAAW,cAAc,UAAU,iBAAAC,sBAAqB;AAC3F,SAAS,QAAQC,iBAAgB;AAW1B,IAAM,cAAN,MAAkB;AAAA,EAIvB,YAAY,SAAiB;AAF7B,SAAiB,cAA4B,CAAC;AAG5C,SAAK,gBAAgBC,UAAS,SAAS,QAAQ;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,kBAAkB,QAAgB,SAAiB,QAAgB,SAAyB;AAI1F,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAKA,QAAI,KAAK,YAAY,QAAQ,UAAU,IAAI,GAAG;AAC5C,WAAK,YAAY,KAAK,UAAU;AAAA,IAClC;AAGA,UAAM,YAAYA,UAAS,KAAK,eAAe,MAAM;AACrD,QAAI,CAACC,YAAW,SAAS,GAAG;AAC1B,MAAAC,WAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,IAC1C;AAEA,WAAOF,UAAS,WAAW,OAAO;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,gBAAgB,QAAgB,QAAgB,UAAkB,SAAuB;AAEvF,UAAM,iBAAiB,KAAK,kBAAkB,QAAQ,UAAU,QAAQ,QAAQ;AAGhF,IAAAG,eAAc,gBAAgB,OAAO;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,kBAAkB,QAAgB,SAAyB;AACzD,WAAOH,UAAS,KAAK,eAAe,QAAQ,OAAO;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,iBAAiB,QAAgB,SAA0B;AACzD,UAAM,cAAc,KAAK,kBAAkB,QAAQ,OAAO;AAC1D,WAAOC,YAAW,WAAW;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,sBAAsB,QAAgB,SAA0B;AAC9D,UAAM,IAAI,KAAK,YAAY,KAAK,CAAAG,OAAKA,GAAE,WAAW,UAAUA,GAAE,YAAY,OAAO;AACjF,QAAI,MAAM,QAAW;AACnB,aAAO;AAAA,IACT;AACA,UAAM,cAAcJ,UAAS,EAAE,QAAQ,EAAE,OAAO;AAChD,WAAOC,YAAW,WAAW;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAyB;AACvB,QAAI,QAAQ,qCAAqC;AAEjD,eAAW,KAAK,KAAK,aAAa;AAChC,WAAK,eAAe,CAAC;AAAA,IACvB;AAEA,QAAI,QAAQ,oBAAoB;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,eAAe,GAAwB;AAE7C,QAAI,CAACA,YAAW,EAAE,MAAM,GAAG;AACzB,MAAAC,WAAU,EAAE,QAAQ,EAAE,WAAW,KAAK,CAAC;AAAA,IACzC;AAIA,UAAM,cAAc,KAAK,kBAAkB,EAAE,QAAQ,EAAE,OAAO;AAC9D,UAAM,cAAcF,UAAS,EAAE,QAAQ,EAAE,OAAO;AAChD,UAAM,YAAY,YAAY,aAAa,WAAW;AACtD,QAAI,WAAW;AACb,UAAI,WAAW,aAAa,EAAE,OAAO,OAAO,WAAW,EAAE;AACzD,mBAAa,aAAa,WAAW;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AACF;AAKA,SAAS,YAAY,OAAe,OAAwB;AAC1D,MAAIC,YAAW,KAAK,KAAKA,YAAW,KAAK,GAAG;AAE1C,UAAM,QAAQ,SAAS,KAAK,EAAE;AAC9B,UAAM,QAAQ,SAAS,KAAK,EAAE;AAC9B,QAAI,UAAU,OAAO;AAEnB,aAAO;AAAA,IACT,OAAO;AAEL,YAAM,OAAO,aAAa,KAAK;AAC/B,YAAM,OAAO,aAAa,KAAK;AAC/B,aAAO,CAAC,KAAK,OAAO,IAAI;AAAA,IAC1B;AAAA,EACF,OAAO;AAEL,WAAO;AAAA,EACT;AACF;;;AC3LA,SAAS,UAAU,SAAS,UAAU,iBAAiB;AACvD,SAAS,UAAU,WAAAI,UAAS,QAAQC,iBAAgB;AAgBpD,eAAsB,cAAc,SAAuB,SAAkC;AAC3F,QAAM,SAAS,QAAQ;AACvB,MAAI,OAAO,WAAW,WAAW,GAAG;AAClC,QAAI,QAAQ,iEAAiE;AAC7E;AAAA,EACF;AAEA,MAAI,QAAQ,qBAAqB;AAEjC,QAAM,KAAK,YAAY,IAAI;AAG3B,QAAM,UAAU,OAAO;AAKvB,QAAM,aAAa,OAAO;AAG1B,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,eAAe;AACxB,YAAM,OAAO,cAAc,OAAO;AAAA,IACpC;AAAA,EACF;AACA,MAAI,OAAO,WAAW,WAAW,GAAG;AAElC,UAAM,cAAc,SAAS,YAAY,SAAS,OAAO,WAAW,CAAC,CAAC;AAAA,EACxE,OAAO;AAEL,UAAM,YAAY,SAAS,YAAY,SAAS,OAAO,UAAU;AAAA,EACnE;AACA,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,gBAAgB;AACzB,YAAM,UAAUC,UAAS,SAAS,eAAe;AACjD,UAAI,aAAa,MAAM,SAAS,SAAS,MAAM;AAC/C,mBAAa,MAAM,OAAO,eAAe,SAAS,UAAU;AAC5D,YAAM,UAAU,SAAS,UAAU;AAAA,IACrC;AAAA,EACF;AAGA,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,iBAAiB;AAC1B,YAAM,OAAO,gBAAgB,SAAS,OAAO,SAAS;AAAA,IACxD;AAAA,EACF;AACA,QAAM,aAAa,SAAS,OAAO,QAAQ,YAAY,OAAO;AAC9D,QAAM,oBAAoB,aAAa,OAAO,SAAS;AACvD,QAAM,oBAAoBA,UAAS,SAAS,SAAS,iBAAiB;AACtE,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,kBAAkB;AAC3B,UAAI,uBAAuB,MAAM,SAAS,mBAAmB,MAAM;AACnE,6BAAuB,MAAM,OAAO,iBAAiB,SAAS,OAAO,WAAW,oBAAoB;AACpG,YAAM,UAAU,mBAAmB,oBAAoB;AAAA,IACzD;AAAA,EACF;AAEA,MAAI,OAAO,cAAc,MAAM;AAO7B,UAAM,eAAe;AACrB,UAAM,qBAAqB,QAAQ,kBAAkB,SAAS,cAAc,SAAS,YAAY;AACjG,UAAM,SAAS,mBAAmB,kBAAkB;AAAA,EACtD;AAEA,MAAI,OAAO,gBAAgB;AAEzB,UAAM,qBAAqBA,UAAS,SAAS,SAAS,gBAAgB;AACtE,UAAM,YAAY;AAClB,UAAM,aAAa;AACnB,UAAM,SAASC,SAAQ,OAAO,cAAc;AAC5C,UAAM,UAAU,SAAS,OAAO,cAAc;AAC9C,UAAM,wBAAwB,QAAQ,kBAAkB,WAAW,YAAY,QAAQ,OAAO;AAC9F,UAAM,SAAS,oBAAoB,qBAAqB;AAAA,EAC1D;AAEA,QAAM,KAAK,YAAY,IAAI;AAC3B,QAAM,YAAY,KAAK,MAAM,KAAM,QAAQ,CAAC;AAC5C,MAAI,QAAQ,0BAA0B,OAAO,IAAI;AACnD;AAKA,eAAe,cACb,SACA,YACA,SACA,WACe;AACf,MAAI,WAAW,0BAA0B;AAGzC,QAAM,SAAS,WAAWD,UAAS,SAAS,eAAe,CAAC;AAG5D,QAAM,UAAU;AAChB,QAAM,OAAO,CAAC,YAAY,gBAAgB,eAAe;AACzD,QAAM,WAAW,MAAM,QAAQ,WAAW,SAAS,SAAS,MAAM;AAAA;AAAA;AAAA,IAGhE,aAAa;AAAA,EACf,CAAC;AACD,MAAI,SAAS,aAAa,GAAG;AAC3B,UAAM,IAAI,MAAM,sEAAsE,SAAS,QAAQ,GAAG;AAAA,EAC5G;AAGA,QAAM,SAASA,UAAS,SAAS,SAAS,eAAe,GAAGA,UAAS,SAAS,eAAe,CAAC;AAChG;AAKA,eAAe,YACb,SACA,YACA,SACA,YACe;AACf,MAAI,WAAW,0CAA0C;AAIzD,QAAM,UAAU;AAChB,QAAM,OAAiB,CAAC;AACxB,OAAK,KAAK,SAAS;AACnB,OAAK,KAAK,eAAe;AACzB,OAAK,KAAK,UAAU;AACpB,aAAW,QAAQ,YAAY;AAC7B,SAAK,KAAK,IAAI;AAAA,EAChB;AAIA,QAAM,SAAS,MAAM,QAAQ,WAAW,SAAS,SAAS,MAAM;AAAA,IAC9D,WAAW;AAAA,IACX,eAAe;AAAA,IACf,aAAa;AAAA,EACf,CAAC;AAGD,MAAI,gBAAgB;AACpB,aAAW,OAAO,OAAO,gBAAgB;AACvC,QAAI,IAAI,SAAS,OAAO,GAAG;AACzB,sBAAgB;AAChB;AAAA,IACF;AAAA,EACF;AACA,MAAI,eAAe;AACjB,QAAI,SAAS,uDAAuD;AACpE,eAAW,OAAO,OAAO,gBAAgB;AACvC,YAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,iBAAW,QAAQ,OAAO;AACxB,YAAI,SAAS,KAAK,IAAI,EAAE;AAAA,MAC1B;AAAA,IACF;AACA,UAAM,IAAI,MAAM,mEAAmE,OAAO,QAAQ,GAAG;AAAA,EACvG,WAAW,OAAO,aAAa,GAAG;AAChC,UAAM,IAAI,MAAM,mEAAmE,OAAO,QAAQ,GAAG;AAAA,EACvG;AAGA,QAAM,SAASA,UAAS,SAAS,SAAS,eAAe,GAAGA,UAAS,SAAS,eAAe,CAAC;AAChG;AAKA,eAAe,aAAa,SAAuB,QAAgB,YAAoB,SAAgC;AACrH,QAAM,YAAY,QAAQ,OAAO;AACjC,QAAM,gBAAgB,UAAU,YAAY;AAC5C,MAAI,WAAW,gBAAgB,aAAa,OAAO;AAInD,QAAM,UAAU;AAChB,QAAM,YAAY,eAAe,SAAS;AAC1C,QAAM,aAAa,CAAC,YAAY,WAAW,UAAU,UAAU,aAAa,WAAW;AACvF,QAAM,eAAe,MAAM,QAAQ,WAAW,SAAS,SAAS,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAM1E,aAAa;AAAA,EACf,CAAC;AACD,MAAI,aAAa,aAAa,GAAG;AAC/B,UAAM,IAAI;AAAA,MACR,sBAAsB,aAAa,8CAA8C,aAAa,QAAQ;AAAA,IACxG;AAAA,EACF;AAEA,MAAI,cAAc,KAAK;AAErB,UAAM,WAAWA,UAAS,SAAS,OAAO;AAC1C,UAAM,UAAUA,UAAS,QAAQ,OAAO,GAAG;AAC3C,UAAM,QAAQ,MAAM,QAAQ,OAAO;AACnC,UAAM,UAAU,CAAC;AACjB,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG;AAC9C,gBAAQ,KAAK,SAASA,UAAS,SAAS,IAAI,GAAGA,UAAS,UAAU,IAAI,CAAC,CAAC;AAAA,MAC1E;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,OAAO;AAAA,EAC3B;AACF;;;ACpOA,SAAS,QAAQE,iBAAgB;AACjC,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AAQrB,eAAsB,sBAAsB,QAAyC;AACnF,QAAM,aAAuB,CAAC;AAI9B,QAAM,WAAWA,UAAS,OAAO,SAAS,WAAW;AACrD,aAAW,KAAK,QAAQ;AAExB,MAAI,OAAO,mBAAmB,OAAO,gBAAgB,SAAS,GAAG;AAM/D,eAAW,YAAY,OAAO,iBAAiB;AAC7C,YAAM,QAAQ,MAAM,KAAK,UAAU;AAAA,QACjC,KAAK,OAAO;AAAA,QACZ,UAAU;AAAA,QACV,WAAW;AAAA,MACb,CAAC;AACD,iBAAW,KAAK,GAAG,KAAK;AAAA,IAC1B;AAAA,EACF,OAAO;AAEL,eAAW,KAAK,GAAG,OAAO,UAAU;AAAA,EACtC;AAGA,MAAI,OAAO;AACX,aAAW,aAAa,YAAY;AAClC,UAAM,SAAS,MAAM,YAAY,SAAS;AAC1C,YAAQ,OAAO;AAAA,EACjB;AAEA,SAAO;AACT;;;ALPA,eAAsB,UACpB,QACA,YACA,SACA,SACiC;AAEjC,QAAM,cAAc,IAAI,YAAY,OAAO,OAAO;AAClD,QAAM,UAAU,IAAI,aAAa,QAAQ,aAAa,QAAQ,WAAW;AACzE,QAAM,gBAAgBC,UAAS,OAAO,SAAS,gBAAgB;AAK/D,MAAI,YAAY;AAChB,MAAI;AAEF,UAAM,gBAAgB,MAAM,WAAW,UAAU,OAAO;AACxD,QAAI,kBAAkB,QAAW;AAC/B,aAAOC,KAAI,IAAI,MAAM,gCAAgC,CAAC;AAAA,IACxD;AAGA,UAAM,YAAY,iBAAiB,aAAa;AAGhD,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,aAAa;AACtB,cAAM,OAAO,YAAY,SAAS,SAAS;AAAA,MAC7C;AAAA,IACF;AAGA,UAAM,WAAW;AAAA,MACf,eAAe,UAAU;AAAA,MACzB,gBAAgB,UAAU;AAAA,MAC1B,kBAAkB,UAAU;AAAA,MAC5B,eAAe,UAAU;AAAA,MACzB,eAAe,UAAU;AAAA,MACzB,eAAe,UAAU;AAAA,MACzB,GAAG,UAAU;AAAA,IACf;AACA,UAAM,WAAWD,UAAS,OAAO,SAAS,WAAW;AACrD,UAAME,WAAU,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAG3D,QAAI;AACJ,QAAIC,YAAW,aAAa,GAAG;AAC7B,0BAAoBC,cAAa,eAAe,MAAM;AAAA,IACxD,OAAO;AACL,0BAAoB;AAAA,IACtB;AAIA,UAAM,iBAAiB,MAAM,sBAAsB,MAAM;AACzD,QAAI;AACJ,QAAI,QAAQ,kBAAkB,MAAM;AAClC,qBAAe;AAAA,IACjB,OAAO;AACL,YAAM,eAAe,mBAAmB;AACxC,qBAAe;AAAA,IACjB;AAEA,QAAI,cAAc;AAEhB,YAAM,cAAc,SAAS,OAAO;AAIpC,MAAAC,eAAc,eAAe,cAAc;AAAA,IAC7C,OAAO;AAEL,UAAI,QAAQ,oDAAoD;AAAA,IAClE;AASA,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,cAAc;AACvB,cAAM,kBAAkB,MAAM,OAAO,aAAa,SAAS,SAAS;AACpE,YAAI,CAAC,iBAAiB;AACpB,sBAAY;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAMA,gBAAY,iBAAiB;AAI7B,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,WAAW;AACpB,cAAM,kBAAkB,MAAM,OAAO,UAAU,SAAS,SAAS;AACjE,YAAI,CAAC,iBAAiB;AACpB,sBAAY;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,eAAe;AAGjC,UAAI,QAAQ,0BAA0B;AACtC,mBAAa;AAAA,IACf;AAAA,EACF,SAAS,GAAG;AAGV,QAAI,EAAE,YAAY,SAAS;AAEzB,MAAAA,eAAc,eAAe,EAAE;AAG/B,aAAOJ,KAAI,CAAC;AAAA,IACd;AAAA,EACF;AAEA,SAAOK,IAAG,SAAS;AACrB;AAOA,SAAS,iBAAiB,WAAyC;AACjE,MAAI;AACJ,MAAI;AACJ,MAAI,UAAU,OAAO,SAAS,GAAG;AAC/B,UAAM,OAAO,UAAU,OAAO,CAAC;AAC/B,QAAI,OAAO,SAAS,UAAU;AAE5B,sBAAgB,UAAU;AAC1B,mBAAa,cAAc,IAAI,aAAW;AACxC,eAAO;AAAA,UACL;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AAEL,mBAAa,UAAU;AACvB,sBAAgB,WAAW,IAAI,UAAQ,KAAK,OAAO;AAAA,IACrD;AAAA,EACF,OAAO;AAEL,oBAAgB,CAAC;AACjB,iBAAa,CAAC;AAAA,EAChB;AAEA,MAAI;AACJ,MAAI;AACJ,MAAI,UAAU,QAAQ,SAAS,GAAG;AAChC,UAAM,OAAO,UAAU,QAAQ,CAAC;AAChC,QAAI,OAAO,SAAS,UAAU;AAE5B,uBAAiB,UAAU;AAC3B,oBAAc,eAAe,IAAI,aAAW;AAC1C,eAAO;AAAA,UACL;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AAEL,oBAAc,UAAU;AACxB,uBAAiB,YAAY,IAAI,UAAQ,KAAK,OAAO;AAAA,IACvD;AAAA,EACF,OAAO;AAEL,qBAAiB,CAAC;AAClB,kBAAc,CAAC;AAAA,EACjB;AAEA,MAAI;AACJ,MAAI,UAAU,kBAAkB,QAAW;AACzC,oBAAgB,UAAU;AAAA,EAC5B,OAAO;AACL,oBAAgB;AAAA,EAClB;AAEA,MAAI;AACJ,MAAI,UAAU,kBAAkB,QAAW;AACzC,oBAAgB,UAAU;AAAA,EAC5B,OAAO;AACL,oBAAgB;AAAA,EAClB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,SAAS;AAAA,IACT,UAAU,UAAU,YAAY,CAAC;AAAA,IACjC,eAAe,UAAU,kBAAkB;AAAA,IAC3C;AAAA,IACA;AAAA,IACA,SAAS,UAAU;AAAA,EACrB;AACF;;;AMrPA,SAAS,YAAAC,iBAAgB;AAEzB,OAAO,cAAc;AACrB,SAAS,UAAU,wBAAwB;AAW3C,IAAM,aAAN,MAAiB;AAAA,EAAjB;AACE,SAAS,kBAAkB,IAAI,gBAAgB;AAAA;AACjD;AAEO,SAAS,MAAM,QAAwB,YAAwB,SAAyB;AAI7F,QAAM,QAAQ;AACd,QAAM,eAA4B,oBAAI,IAAI;AAG1C,MAAI;AAEJ,WAAS,eAAe;AACtB,iBAAa;AAGb,eAAW,QAAQ,cAAc;AAC/B,UAAI,QAAQ,cAAcC,UAAS,IAAI,CAAC,mBAAmB;AAAA,IAC7D;AAGA,iBAAa,MAAM;AAInB,QAAI,mBAAmB;AACrB,wBAAkB,gBAAgB,MAAM;AAExC,0BAAoB;AAAA,IACtB;AAGA,wBAAoB,IAAI,WAAW;AACnC,UAAM,eAAiC;AAAA,MACrC,aAAa,kBAAkB,gBAAgB;AAAA,IACjD;AACA,cAAU,QAAQ,YAAY,SAAS,YAAY,EAChD,KAAK,YAAU;AAEd,UAAI,OAAO,MAAM,GAAG;AAClB,iBAAS,OAAO,KAAK;AAAA,MACvB;AAAA,IACF,CAAC,EACA,MAAM,OAAK;AAGV,eAAS,CAAC;AAAA,IAEZ,CAAC,EACA,QAAQ,MAAM;AACb,0BAAoB;AAAA,IACtB,CAAC;AAAA,EACL;AAEA,WAAS,cAAc,aAAqB;AAE1C,UAAM,WAAW,aAAa,SAAS;AAGvC,iBAAa,IAAI,WAAW;AAE5B,QAAI,UAAU;AAEZ,iBAAW,MAAM;AACf,qBAAa;AAAA,MACf,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,MAAI;AACJ,MAAI,OAAO,cAAc,OAAO,WAAW,SAAS,GAAG;AAErD,iBAAa,OAAO;AAAA,EACtB,OAAO;AAEL,iBAAa,OAAO;AAAA,EACtB;AAIA,QAAM,qBAA+B,CAAC;AACtC,aAAW,aAAa,YAAY;AAClC,QAAI,iBAAiB,SAAS,GAAG;AAE/B,YAAM,QAAQ,SAAS,WAAW;AAAA;AAAA,QAEhC,KAAK,OAAO;AAAA;AAAA,QAEZ,UAAU;AAAA,MACZ,CAAC;AACD,yBAAmB,KAAK,GAAG,KAAK;AAAA,IAClC,OAAO;AAEL,yBAAmB,KAAK,SAAS;AAAA,IACnC;AAAA,EACF;AAIA,QAAM,UAAU,SAAS,MAAM,oBAAoB;AAAA;AAAA,IAEjD,KAAK,OAAO;AAAA;AAAA;AAAA,IAGZ,kBAAkB;AAAA,MAChB,oBAAoB;AAAA,IACtB;AAAA,EACF,CAAC;AACD,UAAQ,GAAG,UAAU,UAAQ;AAC3B,kBAAc,IAAI;AAAA,EACpB,CAAC;AACH;;;ATzEA,eAAsB,MAAM,MAAiB,SAA4D;AAEvG,QAAM,eAAe,MAAM,WAAW,MAAM,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,UAAU;AAC9F,MAAI,aAAa,MAAM,GAAG;AACxB,WAAOC,KAAI,aAAa,KAAK;AAAA,EAC/B;AACA,QAAM,EAAE,YAAY,eAAe,IAAI,aAAa;AAGpD,MAAI,QAAQ,cAAc,QAAW;AACnC,oBAAgB,QAAQ,SAAS;AAAA,EACnC;AAIA,QAAM,eAAeC,UAAS,eAAe,SAAS,eAAe;AACrE,QAAMC,kBAAiB,SAAS;AAChC,iBAAe,cAAcA,eAAc;AAE3C,MAAI;AAEF,UAAM,UAAU,WAAW,WAAW,CAAC;AACvC,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,MAAM;AACf,cAAM,OAAO,KAAK,cAAc;AAAA,MAClC;AAAA,IACF;AAEA,QAAI,SAAS,eAAe;AAI1B,YAAM,cAAc,MAAM,UAAU,gBAAgB,YAAY,SAAS,CAAC,CAAC;AAG3E,UAAI,YAAY,MAAM,GAAG;AACvB,eAAOF,KAAI,YAAY,KAAK;AAAA,MAC9B;AAGA,iBAAW,UAAU,SAAS;AAC5B,YAAI,OAAO,OAAO;AAChB,gBAAM,OAAO,MAAM,cAAc;AAAA,QACnC;AAAA,MACF;AAGA,YAAM,gBAAgB,YAAY,OAAO;AAIzC,aAAOG,IAAG,CAAC,CAAC;AAAA,IACd,OAAO;AAEL,YAAM,cAAc,MAAM,UAAU,gBAAgB,YAAY,SAAS,CAAC,CAAC;AAC3E,UAAI,YAAY,MAAM,GAAG;AACvB,eAAOH,KAAI,YAAY,KAAK;AAAA,MAC9B;AAOA,YAAM,sBAAsB,YAAY;AACxC,YAAM,WAAW,sBAAsB,IAAI;AAC3C,aAAOG,IAAG,EAAE,SAAS,CAAC;AAAA,IACxB;AAAA,EACF,SAAS,GAAG;AACV,WAAOH,KAAI,CAAC;AAAA,EACd;AACF;","names":["joinPath","err","ok","existsSync","readFileSync","writeFileSync","writeFile","joinPath","err","ok","err","existsSync","mkdirSync","writeFileSync","joinPath","joinPath","existsSync","mkdirSync","writeFileSync","f","dirname","joinPath","joinPath","dirname","joinPath","joinPath","err","writeFile","existsSync","readFileSync","writeFileSync","ok","basename","basename","err","joinPath","overlayEnabled","ok"]}
1
+ {"version":3,"sources":["../src/build/build.ts","../src/config/config-loader.ts","../src/_shared/log.ts","../src/build/impl/build-once.ts","../src/context/context.ts","../src/context/spawn-child.ts","../src/context/staged-files.ts","../src/build/impl/gen-model.ts","../src/build/impl/hash-files.ts","../src/build/impl/watch.ts","../src/build/impl/watch-paths.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport type { BuildMode } from '../_shared/mode'\n\nimport { loadConfig } from '../config/config-loader'\nimport type { UserConfig } from '../config/user-config'\nimport type { LogLevel } from '../_shared/log'\nimport { setOverlayFile, setActiveLevels } from '../_shared/log'\nimport { buildOnce } from './impl/build-once'\nimport { watch } from './impl/watch'\n\nexport interface BuildOptions {\n /** The path to an `sde.config.js` file, or a `UserConfig` object. */\n config?: string | UserConfig\n\n /**\n * The log levels to include. If undefined, the default 'info' and 'error' levels\n * will be active.\n */\n logLevels?: LogLevel[]\n\n /**\n * The path to the `@sdeverywhere/cli` package. This is currently only used to get\n * access to the files in the `src/c` directory.\n * @hidden This should be removed once we have tighter integration with the `cli` package.\n */\n sdeDir: string\n\n /**\n * The path to the `sde` command.\n * @hidden This should be removed once we have tighter integration with the `cli` package.\n */\n sdeCmdPath: string\n}\n\nexport interface BuildResult {\n /**\n * The exit code that should be set by the process. This will be undefined\n * if `mode` is 'development', indicating that the process should be kept alive.\n */\n exitCode?: number\n}\n\n/**\n * Initiate the build process, which can either be a single build if `mode` is\n * 'production', or a live development environment if `mode` is 'development'.\n *\n * @param mode The build mode.\n * @param options The build options.\n * @return An `ok` result if the build completed, otherwise an `err` result.\n */\nexport async function build(mode: BuildMode, options: BuildOptions): Promise<Result<BuildResult, Error>> {\n // Load the config\n const configResult = await loadConfig(mode, options.config, options.sdeDir, options.sdeCmdPath)\n if (configResult.isErr()) {\n return err(configResult.error)\n }\n const { userConfig, resolvedConfig } = configResult.value\n\n // Configure logging level\n if (options.logLevels !== undefined) {\n setActiveLevels(options.logLevels)\n }\n\n // Configure the overlay `messages.html` file, which is written under the\n // `sde-prep` directory. For production builds, this file will remain empty.\n const messagesPath = joinPath(resolvedConfig.prepDir, 'messages.html')\n const overlayEnabled = mode === 'development'\n setOverlayFile(messagesPath, overlayEnabled)\n\n try {\n // Initialize plugins\n const plugins = userConfig.plugins || []\n for (const plugin of plugins) {\n if (plugin.init) {\n await plugin.init(resolvedConfig)\n }\n }\n\n if (mode === 'development') {\n // Enable dev mode (which will rebuild when watched files are changed).\n // First run an initial build so that we have a baseline, and then\n // once that is complete, enable file watchers.\n const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {})\n // TODO: We should trap errors here and keep the dev process\n // running if the initial build fails\n if (buildResult.isErr()) {\n return err(buildResult.error)\n }\n\n // Allow plugins to set up watchers after the initial build completes\n for (const plugin of plugins) {\n if (plugin.watch) {\n await plugin.watch(resolvedConfig)\n }\n }\n\n // Watch for changes to the source/model/test files\n watch(resolvedConfig, userConfig, plugins)\n\n // Return a build result with undefined exit code, indicating that the\n // process should be kept alive\n return ok({})\n } else {\n // Run a single build\n const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {})\n if (buildResult.isErr()) {\n return err(buildResult.error)\n }\n\n // Configure the exit code depending on whether any plugins failed.\n // Currently we use the following exit code values:\n // 0 == build succeeded, AND all plugins succeeded\n // 1 == build failed (or a plugin reported a \"hard\" error)\n // 2 == build succeeded, BUT one or more plugins failed\n const allPluginsSucceeded = buildResult.value\n const exitCode = allPluginsSucceeded ? 0 : 2\n return ok({ exitCode })\n }\n } catch (e) {\n return err(e)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, lstatSync, mkdirSync } from 'fs'\nimport { dirname, isAbsolute, join as joinPath, relative, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport type { BuildMode } from '../_shared/mode'\nimport type { ResolvedConfig } from '../_shared/resolved-config'\n\nimport type { UserConfig } from './user-config'\n\nexport interface ConfigResult {\n userConfig: UserConfig\n resolvedConfig: ResolvedConfig\n}\n\n/**\n * Load a user-defined config file or a given `UserConfig` object. This validates\n * all paths and if successful, this returns a `ResolvedConfig`, otherwise returns\n * an error result.\n *\n * @param mode The build mode.\n * @param config The path to a config file, or a `UserConfig` object; if undefined,\n * this will look for a `sde.config.js` file in the current directory.\n * @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).\n * @param sdeCmdPath Temporary (the path to the `sde` command).\n * @return An `ok` result with the `ResolvedConfig`, otherwise an `err` result.\n */\nexport async function loadConfig(\n mode: BuildMode,\n config: string | UserConfig | undefined,\n sdeDir: string,\n sdeCmdPath: string\n): Promise<Result<ConfigResult, Error>> {\n let userConfig: UserConfig\n if (typeof config === 'object') {\n // Use the given `UserConfig` object\n userConfig = config\n } else {\n // Load the project-specific config. If no `--config` arg was specified\n // on the command line, look for a `sde.config.js` file in the current\n // directory, and failing that, use a default config.\n // TODO: Create a default config if no file found; for now, we just fail\n let configPath: string\n if (typeof config === 'string') {\n configPath = config\n } else {\n configPath = joinPath(process.cwd(), 'sde.config.js')\n }\n try {\n if (!existsSync(configPath)) {\n return err(new Error(`Cannot find config file '${configPath}'`))\n }\n const configRelPath = relativeToSourcePath(configPath)\n const configModule = await import(configRelPath)\n userConfig = await configModule.config()\n } catch (e) {\n return err(new Error(`Failed to load config file '${configPath}': ${e.message}`))\n }\n }\n\n // Resolve the config\n try {\n const resolvedConfig = resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath)\n return ok({\n userConfig,\n resolvedConfig\n })\n } catch (e) {\n return err(e)\n }\n}\n\n/**\n * Resolve the given user configuration by resolving all paths. This will create\n * the prep directory if it does not already exist. This will throw an error if\n * any other paths are invalid or do not exist.\n *\n * @param userConfig The user-defined configuration.\n * @param mode The active build mode.\n * @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).\n * @param sdeCmdPath Temporary (the path to the `sde` command).\n * @return The resolved configuration.\n */\nfunction resolveUserConfig(\n userConfig: UserConfig,\n mode: BuildMode,\n sdeDir: string,\n sdeCmdPath: string\n): ResolvedConfig {\n function expectDirectory(propName: string, path: string): void {\n if (!existsSync(path)) {\n throw new Error(`The configured ${propName} (${path}) does not exist`)\n } else if (!lstatSync(path).isDirectory()) {\n throw new Error(`The configured ${propName} (${path}) is not a directory`)\n }\n }\n\n // function expectFile(propName: string, path: string): void {\n // if (!existsSync(path)) {\n // throw new Error(`The configured ${propName} (${path}) does not exist`)\n // // TODO: Don't include the \"is file\" check for now; need to update this to\n // // handle symlinks\n // // } else if (!lstatSync(path).isFile()) {\n // // throw new Error(`The configured ${propName} (${path}) is not a file`)\n // }\n // }\n\n // Validate the root directory\n let rootDir: string\n if (userConfig.rootDir) {\n // Verify that the configured root directory exists\n rootDir = resolvePath(userConfig.rootDir)\n expectDirectory('rootDir', rootDir)\n } else {\n // Use the current working directory as the project root\n rootDir = process.cwd()\n }\n\n // Validate the prep directory\n let prepDir: string\n if (userConfig.prepDir) {\n // Resolve the configured prep directory\n prepDir = resolvePath(userConfig.prepDir)\n } else {\n // Create an 'sde-prep' directory under the configured root directory\n prepDir = resolvePath(rootDir, 'sde-prep')\n }\n mkdirSync(prepDir, { recursive: true })\n\n // Validate the model files\n const userModelFiles = userConfig.modelFiles\n const modelFiles: string[] = []\n for (const userModelFile of userModelFiles) {\n const modelFile = resolvePath(userModelFile)\n if (!existsSync(modelFile)) {\n throw new Error(`The configured model file (${modelFile}) does not exist`)\n }\n modelFiles.push(modelFile)\n }\n\n // TODO: Validate the watch paths; these are allowed to be globs, so need to\n // figure out the best way to resolve them\n let modelInputPaths: string[]\n if (userConfig.modelInputPaths && userConfig.modelInputPaths.length > 0) {\n modelInputPaths = userConfig.modelInputPaths\n } else {\n modelInputPaths = modelFiles\n }\n let watchPaths: string[]\n if (userConfig.watchPaths && userConfig.watchPaths.length > 0) {\n watchPaths = userConfig.watchPaths\n } else {\n watchPaths = modelFiles\n }\n\n // Validate the code generation format\n const rawGenFormat = userConfig.genFormat || 'js'\n let genFormat: 'js' | 'c'\n switch (rawGenFormat) {\n case 'js':\n genFormat = 'js'\n break\n case 'c':\n genFormat = 'c'\n break\n default:\n throw new Error(`The configured genFormat value is invalid; must be either 'js' or 'c'`)\n }\n\n // Validate the out listing file, if defined\n let outListingFile: string\n if (userConfig.outListingFile) {\n // Get the absolute path of the output file\n if (isAbsolute(userConfig.outListingFile)) {\n outListingFile = userConfig.outListingFile\n } else {\n outListingFile = resolvePath(rootDir, userConfig.outListingFile)\n }\n }\n\n return {\n mode,\n rootDir,\n prepDir,\n modelFiles,\n modelInputPaths,\n watchPaths,\n genFormat,\n outListingFile,\n sdeDir,\n sdeCmdPath\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 { writeFileSync } from 'fs'\nimport pico from 'picocolors'\n\nexport type LogLevel = 'error' | 'info' | 'verbose'\n\nconst activeLevels: Set<LogLevel> = new Set(['error', 'info'])\n\nlet overlayFile: string\nlet overlayEnabled = false\nlet overlayHtml = ''\n\n/**\n * Set the active logging levels. By default, only 'error' and 'info'\n * messages are emitted.\n *\n * @param logLevels The logging levels to include.\n */\nexport function setActiveLevels(logLevels: LogLevel[]): void {\n activeLevels.clear()\n for (const level of logLevels) {\n activeLevels.add(level)\n }\n}\n\n/**\n * Set the path to the `messages.html` file where overlay messages will be written.\n *\n * @param file The absolute path to the HTML file where messages will be written.\n * @param enabled Whether to write messages to the file; if false, the file will be\n * emptied and no further messages will be written.\n */\nexport function setOverlayFile(file: string, enabled: boolean): void {\n overlayFile = file\n overlayEnabled = enabled\n\n // Write an empty file by default; this will ensure that messages from a previous\n // build aren't included in the current build\n writeFileSync(overlayFile, '')\n}\n\n/**\n * Log a message to the console and/or overlay.\n *\n * @param level The logging level.\n * @param msg The message to emit.\n */\nexport function log(level: LogLevel, msg: string): void {\n if (activeLevels.has(level)) {\n if (level === 'error') {\n console.error(pico.red(msg))\n logToOverlay(msg)\n } else {\n console.log(msg)\n logToOverlay(msg)\n }\n }\n}\n\n/**\n * Log an error to the console and/or overlay.\n *\n * @param e The error to log.\n */\nexport function logError(e: Error): void {\n // Remove the first part of the stack trace (which contains the message)\n // so that we can control the formatting of the message separately, then\n // only include up to 3 lines of the stack to keep the log cleaner\n const stack = e.stack || ''\n const stackLines = stack.split('\\n').filter(s => s.match(/^\\s+at/))\n const trace = stackLines.slice(0, 3).join('\\n')\n\n // Log the error message followed by the stack trace\n console.error(pico.red(`\\nERROR: ${e.message}`))\n console.error(pico.dim(pico.red(`${trace}\\n`)))\n logToOverlay(`\\nERROR: ${e.message}`, true)\n logToOverlay(`${trace}\\n`, true)\n}\n\nfunction writeOverlayFiles(): void {\n writeFileSync(overlayFile, overlayHtml)\n}\n\nexport function clearOverlay(): void {\n if (!overlayEnabled) {\n return\n }\n\n overlayHtml = ''\n writeOverlayFiles()\n}\n\nconst indent = '&nbsp;'.repeat(4)\n\nexport function logToOverlay(msg: string, error = false): void {\n if (!overlayEnabled) {\n return\n }\n\n if (error) {\n msg = `<span class=\"overlay-error\">${msg}</span>`\n }\n const msgHtml = msg.replace(/\\n/g, '\\n<br/>').replace(/\\s{2}/g, indent)\n if (overlayHtml) {\n overlayHtml += `<br/>${msgHtml}`\n } else {\n overlayHtml = `${msgHtml}`\n }\n writeOverlayFiles()\n}\n\nexport function clearConsole(): void {\n // TODO: This is disabled for now; maybe re-enable it under an optional flag\n // console.log('\\x1Bc')\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync, readFileSync, writeFileSync } from 'fs'\nimport { writeFile } from 'fs/promises'\nimport { join as joinPath } from 'path'\n\nimport type { Result } from 'neverthrow'\nimport { err, ok } from 'neverthrow'\n\nimport { clearOverlay, log } from '../../_shared/log'\nimport type { InputSpec, ModelSpec, OutputSpec, ResolvedModelSpec, VarName } from '../../_shared/model-spec'\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\nimport type { UserConfig } from '../../config/user-config'\nimport { BuildContext } from '../../context/context'\nimport { StagedFiles } from '../../context/staged-files'\nimport type { Plugin } from '../../plugin/plugin'\n\nimport { generateModel } from './gen-model'\nimport { computeInputFilesHash } from './hash-files'\n\nexport interface BuildOnceOptions {\n forceModelGen?: boolean\n abortSignal?: AbortSignal\n}\n\n/**\n * Perform a single build.\n *\n * This will return an error if a build failure occurred, or if a plugin encounters\n * an error. Otherwise, it will return true if the build and all plugins\n * succeeded, or false if a plugin wants to report a \"soft\" failure (for example,\n * if the model check plugin reports failing checks).\n *\n * @param config The resolved build configuration.\n * @param plugins The configured plugins.\n * @param options Options specific to the build process.\n * @return An `ok` result with true if the build and all plugins succeeded, or false if\n * one or more plugins failed; otherwise, an `err` result if there was a hard error.\n */\nexport async function buildOnce(\n config: ResolvedConfig,\n userConfig: UserConfig,\n plugins: Plugin[],\n options: BuildOnceOptions\n): Promise<Result<boolean, Error>> {\n // Create the build context\n const stagedFiles = new StagedFiles(config.prepDir)\n const context = new BuildContext(config, stagedFiles, options.abortSignal)\n const modelHashPath = joinPath(config.prepDir, 'model-hash.txt')\n\n // Note that the entire body of this function is wrapped in a try/catch. Any\n // errors that are thrown by plugin functions or the core `generateModel`\n // function will be caught and handled as appropriate.\n let succeeded = true\n try {\n // Get the model spec from the config\n const userModelSpec = await userConfig.modelSpec(context)\n if (userModelSpec === undefined) {\n return err(new Error('The model spec must be defined'))\n }\n\n // Resolve the model spec\n const modelSpec = resolveModelSpec(userModelSpec)\n\n // Run plugins that implement `preGenerate`\n for (const plugin of plugins) {\n if (plugin.preGenerate) {\n await plugin.preGenerate(context, modelSpec)\n }\n }\n\n // Write the spec file\n const specJson = {\n inputVarNames: modelSpec.inputVarNames,\n outputVarNames: modelSpec.outputVarNames,\n externalDatfiles: modelSpec.datFiles,\n bundleListing: modelSpec.bundleListing,\n customConstants: modelSpec.customConstants || false,\n customLookups: modelSpec.customLookups || false,\n customOutputs: modelSpec.customOutputs || false,\n ...modelSpec.options\n }\n const specPath = joinPath(config.prepDir, 'spec.json')\n await writeFile(specPath, JSON.stringify(specJson, null, 2))\n\n // Read the hash from the last successful model build, if available\n let previousModelHash: string\n if (existsSync(modelHashPath)) {\n previousModelHash = readFileSync(modelHashPath, 'utf8')\n } else {\n previousModelHash = 'NONE'\n }\n\n // The code gen and Wasm build steps are time consuming, so we avoid rebuilding\n // it if the build input files are unchanged since the last successful build\n const inputFilesHash = await computeInputFilesHash(config)\n let needModelGen: boolean\n if (options.forceModelGen === true) {\n needModelGen = true\n } else {\n const hashMismatch = inputFilesHash !== previousModelHash\n needModelGen = hashMismatch\n }\n\n if (needModelGen) {\n // Generate the model\n await generateModel(context, plugins)\n\n // Save the hash of the input files, which can be used to determine if\n // we need to rebuild the model the next time\n writeFileSync(modelHashPath, inputFilesHash)\n } else {\n // Skip code generation\n log('info', 'Skipping model code generation; already up-to-date')\n }\n\n // Run plugins that implement `postGenerate`\n // TODO: For now we run all plugins even if one or more of them returns\n // false, which allows (in theory) for there to be multiple plugins that\n // run tests or checks as a post-build step. We should eventually make\n // this configurable so that a plugin can halt the build if it fails.\n // (Technically this is already possible if the plugin throws an error\n // instead of returning false, but maybe it should be more configurable.)\n for (const plugin of plugins) {\n if (plugin.postGenerate) {\n const pluginSucceeded = await plugin.postGenerate(context, modelSpec)\n if (!pluginSucceeded) {\n succeeded = false\n }\n }\n }\n\n // Copy staged files to their destination; this will only copy the staged\n // files if they are different than the existing destination files. We\n // copy the files in a batch like this so that hot module reload is only\n // triggered once at the end of the whole build process.\n stagedFiles.copyChangedFiles()\n\n // Run plugins that implement `postBuild` (this is specified to run after\n // the \"copy staged files\" step)\n for (const plugin of plugins) {\n if (plugin.postBuild) {\n const pluginSucceeded = await plugin.postBuild(context, modelSpec)\n if (!pluginSucceeded) {\n succeeded = false\n }\n }\n }\n\n if (config.mode === 'development') {\n // Hide the \"rebuilding\" message in the dev overlay in the app if the\n // build succeeded; otherwise keep the error message visible\n log('info', 'Waiting for changes...\\n')\n clearOverlay()\n }\n } catch (e) {\n // When a build is aborted, the error will have \"ABORT\" as the message,\n // in which case we can swallow the error; for actual errors, rethrow\n if (e.message !== 'ABORT') {\n // Clear the hash so that the model is rebuilt next time\n writeFileSync(modelHashPath, '')\n\n // Return an error result\n return err(e)\n }\n }\n\n return ok(succeeded)\n}\n\n/**\n * Convert a `ModelSpec` instance to a `ResolvedModelSpec` instance.\n *\n * @param modelSpec The `ModelSpec` instance returned by the `UserConfig`.\n */\nfunction resolveModelSpec(modelSpec: ModelSpec): ResolvedModelSpec {\n let inputVarNames: VarName[]\n let inputSpecs: InputSpec[]\n if (modelSpec.inputs.length > 0) {\n const item = modelSpec.inputs[0]\n if (typeof item === 'string') {\n // The array contains variable names; derive `InputSpec` instances\n inputVarNames = modelSpec.inputs as VarName[]\n inputSpecs = inputVarNames.map(varName => {\n return {\n varName\n }\n })\n } else {\n // The array contains `InputSpec` objects; derive variable names\n inputSpecs = modelSpec.inputs as InputSpec[]\n inputVarNames = inputSpecs.map(spec => spec.varName)\n }\n } else {\n // The inputs array is empty, so return empty arrays\n inputVarNames = []\n inputSpecs = []\n }\n\n let outputVarNames: VarName[]\n let outputSpecs: OutputSpec[]\n if (modelSpec.outputs.length > 0) {\n const item = modelSpec.outputs[0]\n if (typeof item === 'string') {\n // The array contains variable names; derive `OutputSpec` instances\n outputVarNames = modelSpec.outputs as VarName[]\n outputSpecs = outputVarNames.map(varName => {\n return {\n varName\n }\n })\n } else {\n // The array contains `OutputSpec` objects; derive variable names\n outputSpecs = modelSpec.outputs as OutputSpec[]\n outputVarNames = outputSpecs.map(spec => spec.varName)\n }\n } else {\n // The outputs array is empty, so return empty arrays\n outputVarNames = []\n outputSpecs = []\n }\n\n let customConstants: boolean | VarName[]\n if (modelSpec.customConstants !== undefined) {\n customConstants = modelSpec.customConstants\n } else {\n customConstants = false\n }\n\n let customLookups: boolean | VarName[]\n if (modelSpec.customLookups !== undefined) {\n customLookups = modelSpec.customLookups\n } else {\n customLookups = false\n }\n\n let customOutputs: boolean | VarName[]\n if (modelSpec.customOutputs !== undefined) {\n customOutputs = modelSpec.customOutputs\n } else {\n customOutputs = false\n }\n\n return {\n inputVarNames,\n inputs: inputSpecs,\n outputVarNames,\n outputs: outputSpecs,\n datFiles: modelSpec.datFiles || [],\n bundleListing: modelSpec.bundleListing === true,\n customConstants,\n customLookups,\n customOutputs,\n options: modelSpec.options\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { canonicalVarId } from '@sdeverywhere/parse'\nimport type { LogLevel } from '../_shared/log'\nimport { log } from '../_shared/log'\n\nimport type { ResolvedConfig } from '../_shared/resolved-config'\n\nimport type { ProcessOptions, ProcessOutput } from './spawn-child'\nimport { spawnChild } from './spawn-child'\nimport type { StagedFiles } from './staged-files'\n\n/**\n * Provides access to common functionality that is needed during the build process.\n * This is passed to most plugin functions.\n */\nexport class BuildContext {\n /**\n * @param config The resolved configuration.\n * @hidden\n */\n constructor(\n public readonly config: ResolvedConfig,\n private readonly stagedFiles: StagedFiles,\n private readonly abortSignal: AbortSignal | undefined\n ) {}\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n log(level, msg)\n }\n\n /**\n * Prepare for writing a file to the staged directory.\n *\n * This will add the path to the array of tracked files and will create the\n * staged directory if needed.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param srcFile The name of the file as written to the `staged` directory.\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param dstFile The name of the file as written to the destination directory.\n * @return The absolute path to the staged file.\n */\n prepareStagedFile(srcDir: string, srcFile: string, dstDir: string, dstFile: string): string {\n return this.stagedFiles.prepareStagedFile(srcDir, srcFile, dstDir, dstFile)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.stagedFiles.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n /**\n * Spawn a child process that runs the given command.\n *\n * @param cwd The directory in which the command will be executed.\n * @param command The command to execute.\n * @param args The arguments to pass to the command.\n * @param opts Additional options to configure the process.\n * @returns The output of the process.\n */\n spawnChild(cwd: string, command: string, args: string[], opts?: ProcessOptions): Promise<ProcessOutput> {\n return spawnChild(cwd, command, args, this.abortSignal, opts)\n }\n\n /**\n * Format a (subscripted or non-subscripted) model variable name into a canonical\n * identifier (with special characters converted to underscore, and subscript/dimension\n * parts separated by commas).\n *\n * @param name The name of the variable in the source model, e.g., `Variable name[DimA, B2]`.\n * @returns The canonical identifier for the given name, e.g., `_variable_name[_dima,_b2]`.\n */\n canonicalVarId(name: string): string {\n return canonicalVarId(name)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ChildProcess } from 'child_process'\n\nimport { spawn } from 'cross-spawn'\n\nimport { log } from '../_shared/log'\n\n/**\n * @hidden This isn't ready to be included in the public API just yet.\n */\nexport interface ProcessOptions {\n logOutput?: boolean\n ignoredMessageFilter?: string\n captureOutput?: boolean\n ignoreError?: boolean\n}\n\n/**\n * @hidden This isn't ready to be included in the public API just yet.\n */\nexport interface ProcessOutput {\n exitCode: number\n stdoutMessages: string[]\n stderrMessages: string[]\n}\n\n/**\n * Spawn a child process that runs the given command.\n *\n * @param cwd The directory in which the command will be executed.\n * @param command The command to execute.\n * @param args The arguments to pass to the command.\n * @param abortSignal The signal used to abort the process.\n * @param opts Additional options to configure the process.\n * @returns The output of the process.\n */\nexport function spawnChild(\n cwd: string,\n command: string,\n args: string[],\n abortSignal?: AbortSignal,\n opts?: ProcessOptions\n): Promise<ProcessOutput> {\n return new Promise((resolve, reject) => {\n if (abortSignal?.aborted) {\n reject(new Error('ABORT'))\n return\n }\n\n let childProc: ChildProcess\n\n const localLog = (s: string, err = false) => {\n // Don't log anything after the process has been killed\n if (childProc === undefined) {\n return\n }\n log(err ? 'error' : 'info', s)\n }\n\n const abortHandler = () => {\n if (childProc) {\n log('info', 'Killing existing build process...')\n childProc.kill('SIGKILL')\n childProc = undefined\n }\n reject(new Error('ABORT'))\n }\n\n // Kill the process if abort is requested\n abortSignal?.addEventListener('abort', abortHandler, { once: true })\n\n // Prepare for capturing output, if requested\n const stdoutMessages: string[] = []\n const stderrMessages: string[] = []\n const logMessage = (msg: string, err: boolean) => {\n let includeMessage = true\n if (opts?.ignoredMessageFilter && msg.trim().startsWith(opts.ignoredMessageFilter)) {\n includeMessage = false\n }\n if (includeMessage) {\n const lines = msg.trim().split('\\n')\n for (const line of lines) {\n localLog(` ${line}`, err)\n }\n }\n }\n\n // Spawn the (asynchronous) child process. Note that we are using `spawn`\n // from the `cross-spawn` package as an alternative to the built-in\n // `child_process` module, which doesn't handle spaces in command path\n // on Windows.\n childProc = spawn(command, args, {\n cwd\n })\n\n childProc.stdout.on('data', (data: Buffer) => {\n const msg = data.toString()\n if (opts?.captureOutput === true) {\n stdoutMessages.push(msg)\n }\n if (opts?.logOutput !== false) {\n logMessage(msg, false)\n }\n })\n childProc.stderr.on('data', (data: Buffer) => {\n const msg = data.toString()\n if (opts?.captureOutput === true) {\n stderrMessages.push(msg)\n }\n if (opts?.logOutput !== false) {\n logMessage(msg, true)\n }\n })\n childProc.on('error', err => {\n localLog(`Process error: ${err}`, true)\n })\n childProc.on('close', (code, signal) => {\n // Stop listening for abort events\n abortSignal?.removeEventListener('abort', abortHandler)\n childProc = undefined\n\n if (signal) {\n // The process was killed by a signal, so we don't need to print anything\n return\n }\n\n const processOutput: ProcessOutput = {\n exitCode: code,\n stdoutMessages,\n stderrMessages\n }\n\n if (code === 0) {\n // The process exited cleanly; resolve the promise\n resolve(processOutput)\n } else if (!signal) {\n // The process failed\n if (opts?.ignoreError === true) {\n // Resolve the promise (but with a non-zero exit code)\n resolve(processOutput)\n } else {\n // Reject the promise\n reject(new Error(`Child process failed (code=${code})`))\n }\n }\n })\n })\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport { log } from '../_shared/log'\n\ninterface StagedFile {\n srcDir: string\n srcFile: string\n dstDir: string\n dstFile: string\n}\n\nexport class StagedFiles {\n private readonly baseStagedDir: string\n private readonly stagedFiles: StagedFile[] = []\n\n constructor(prepDir: string) {\n this.baseStagedDir = joinPath(prepDir, 'staged')\n }\n\n /**\n * Prepare for writing a file to the staged directory.\n *\n * This will add the path to the array of tracked files and will create the\n * staged directory if needed.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param srcFile The name of the file as written to the `staged` directory.\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param dstFile The name of the file as written to the destination directory.\n * @return The absolute path to the staged file.\n */\n prepareStagedFile(srcDir: string, srcFile: string, dstDir: string, dstFile: string): string {\n // Add an entry to the array of staged files (only if an entry does not already\n // exist) so that we can copy the file to the destination directory when the build\n // process has completed\n const stagedFile = {\n srcDir,\n srcFile,\n dstDir,\n dstFile\n }\n // TODO: We allow there to be more than one entry for each source path to\n // support the case where a single source file gets copied to multiple\n // destination paths. But we should throw an error if the same destination\n // path is configured for different source paths.\n if (this.stagedFiles.indexOf(stagedFile) < 0) {\n this.stagedFiles.push(stagedFile)\n }\n\n // Create the directory underneath the staged directory if needed\n const stagedDir = joinPath(this.baseStagedDir, srcDir)\n if (!existsSync(stagedDir)) {\n mkdirSync(stagedDir, { recursive: true })\n }\n\n return joinPath(stagedDir, srcFile)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n // Add an entry to track the file and create the staged directory if needed\n const stagedFilePath = this.prepareStagedFile(srcDir, filename, dstDir, filename)\n\n // Write the file to the staged directory\n writeFileSync(stagedFilePath, content)\n }\n\n /**\n * Return the absolute path to the staged file for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n getStagedFilePath(srcDir: string, srcFile: string): string {\n return joinPath(this.baseStagedDir, srcDir, srcFile)\n }\n\n /**\n * Return true if the staged file exists for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n stagedFileExists(srcDir: string, srcFile: string): boolean {\n const fullSrcPath = this.getStagedFilePath(srcDir, srcFile)\n return existsSync(fullSrcPath)\n }\n\n /**\n * Return true if the destination file exists for the given source directory and file name.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file would be written initially (this must be a relative path).\n * @param srcFile The name of the file.\n */\n destinationFileExists(srcDir: string, srcFile: string): boolean {\n const f = this.stagedFiles.find(f => f.srcDir === srcDir && f.srcFile === srcFile)\n if (f === undefined) {\n return false\n }\n const fullDstPath = joinPath(f.dstDir, f.dstFile)\n return existsSync(fullDstPath)\n }\n\n /**\n * Copy staged files to their destination; this will only copy the staged\n * files if they are different than the existing destination files. We\n * copy the files in a batch like this so that hot module reload is only\n * triggered once at the end of the whole build process.\n */\n copyChangedFiles(): void {\n log('info', 'Copying changed files into place...')\n\n for (const f of this.stagedFiles) {\n this.copyStagedFile(f)\n }\n\n log('info', 'Done copying files')\n }\n\n /**\n * Copy a file from the `staged` directory to its destination. If the file already\n * exists in the destination directory and has the same contents as the source file,\n * the file will not be copied and this function will return false.\n *\n * @param f The staged file entry.\n */\n private copyStagedFile(f: StagedFile): boolean {\n // Create the destination directory, if needed\n if (!existsSync(f.dstDir)) {\n mkdirSync(f.dstDir, { recursive: true })\n }\n\n // If the destination file already exists and has the same contents as the source\n // file, we can skip copying it\n const fullSrcPath = this.getStagedFilePath(f.srcDir, f.srcFile)\n const fullDstPath = joinPath(f.dstDir, f.dstFile)\n const needsCopy = filesDiffer(fullSrcPath, fullDstPath)\n if (needsCopy) {\n log('verbose', ` Copying ${f.srcFile} to ${fullDstPath}`)\n copyFileSync(fullSrcPath, fullDstPath)\n }\n return needsCopy\n }\n}\n\n/**\n * Return true if both files exist at the given paths and have the same contents, false otherwise.\n */\nfunction filesDiffer(aPath: string, bPath: string): boolean {\n if (existsSync(aPath) && existsSync(bPath)) {\n // The files exist; see if they are different\n const aSize = statSync(aPath).size\n const bSize = statSync(bPath).size\n if (aSize !== bSize) {\n // The sizes are different, so the contents must be different\n return true\n } else {\n // The sizes are the same, so check the contents\n const aBuf = readFileSync(aPath)\n const bBuf = readFileSync(bPath)\n return !aBuf.equals(bBuf)\n }\n } else {\n // One or both files do not exist\n return true\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { copyFile, readdir, readFile, writeFile } from 'fs/promises'\nimport { basename, dirname, join as joinPath } from 'path'\n\nimport { log } from '../../_shared/log'\n\nimport type { BuildContext } from '../../context/context'\n\nimport type { Plugin } from '../../plugin/plugin'\n\n/**\n * Generate the model. This will run the core SDEverywhere code generation steps\n * and will also invoke the following plugin functions:\n * - `preProcessMdl`\n * - `postProcessMdl`\n * - `preGenerateC`\n * - `postGenerateC`\n */\nexport async function generateModel(context: BuildContext, plugins: Plugin[]): Promise<void> {\n const config = context.config\n if (config.modelFiles.length === 0) {\n log('info', 'No model input files specified, skipping model generation steps')\n return\n }\n\n log('info', 'Generating model...')\n\n const t0 = performance.now()\n\n // Use the defined prep directory\n const prepDir = config.prepDir\n\n // TODO: For now we assume the path is to the `main.js` file in the cli package;\n // this seems to work on both Unix and Windows, but we may need to revisit this\n // as part of removing the `sdeCmdPath` config hack\n const sdeCmdPath = config.sdeCmdPath\n\n // Process the mdl file(s)\n for (const plugin of plugins) {\n if (plugin.preProcessMdl) {\n await plugin.preProcessMdl(context)\n }\n }\n if (config.modelFiles.length === 1) {\n // Preprocess the single mdl file\n await preprocessMdl(context, sdeCmdPath, prepDir, config.modelFiles[0])\n } else {\n // Flatten and preprocess the multiple mdl files into a single mdl file\n await flattenMdls(context, sdeCmdPath, prepDir, config.modelFiles)\n }\n for (const plugin of plugins) {\n if (plugin.postProcessMdl) {\n const mdlPath = joinPath(prepDir, 'processed.mdl')\n let mdlContent = await readFile(mdlPath, 'utf8')\n mdlContent = await plugin.postProcessMdl(context, mdlContent)\n await writeFile(mdlPath, mdlContent)\n }\n }\n\n // Generate the JS or C file\n for (const plugin of plugins) {\n if (plugin.preGenerateCode) {\n await plugin.preGenerateCode(context, config.genFormat)\n }\n }\n await generateCode(context, config.sdeDir, sdeCmdPath, prepDir)\n const generatedCodeFile = `processed.${config.genFormat}`\n const generatedCodePath = joinPath(prepDir, 'build', generatedCodeFile)\n for (const plugin of plugins) {\n if (plugin.postGenerateCode) {\n let generatedCodeContent = await readFile(generatedCodePath, 'utf8')\n generatedCodeContent = await plugin.postGenerateCode(context, config.genFormat, generatedCodeContent)\n await writeFile(generatedCodePath, generatedCodeContent)\n }\n }\n\n if (config.genFormat === 'js') {\n // When generating JS code, copy the generated JS file to the `staged/model`\n // directory, because that's where plugin-worker expects to find it, but also\n // set it up to be copied to the `prepDir`, which is where other code expects\n // to find it\n // TODO: Maybe we can change plugin-worker to use the one in `prepDir`, and/or\n // add a build config setting to allow for customizing the output location\n const outputJsFile = 'generated-model.js'\n const stagedOutputJsPath = context.prepareStagedFile('model', outputJsFile, prepDir, outputJsFile)\n await copyFile(generatedCodePath, stagedOutputJsPath)\n }\n\n if (config.outListingFile) {\n // Copy the model listing file\n const srcListingJsonPath = joinPath(prepDir, 'build', 'processed.json')\n const stagedDir = 'model'\n const stagedFile = 'listing.json'\n const dstDir = dirname(config.outListingFile)\n const dstFile = basename(config.outListingFile)\n const stagedListingJsonPath = context.prepareStagedFile(stagedDir, stagedFile, dstDir, dstFile)\n await copyFile(srcListingJsonPath, stagedListingJsonPath)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n log('info', `Done generating model (${elapsed}s)`)\n}\n\n/**\n * Preprocess a single mdl file and copy the resulting `processed.mdl` to the prep directory.\n */\nasync function preprocessMdl(\n context: BuildContext,\n sdeCmdPath: string,\n prepDir: string,\n modelFile: string\n): Promise<void> {\n log('verbose', ' Preprocessing mdl file')\n\n // Copy the source file to the prep directory to make the next steps easier\n await copyFile(modelFile, joinPath(prepDir, 'processed.mdl'))\n\n // Use SDE to preprocess the model to strip anything that's not needed to build it\n const command = sdeCmdPath\n const args = ['generate', '--preprocess', 'processed.mdl']\n const ppOutput = await context.spawnChild(prepDir, command, args, {\n // The default error message from `spawnChild` is not very informative, so the\n // following allows us to throw our own error\n ignoreError: true\n })\n if (ppOutput.exitCode !== 0) {\n throw new Error(`Failed to preprocess mdl file: 'sde generate' command failed (code=${ppOutput.exitCode})`)\n }\n\n // Copy the processed file back to the prep directory\n await copyFile(joinPath(prepDir, 'build', 'processed.mdl'), joinPath(prepDir, 'processed.mdl'))\n}\n\n/**\n * Flatten multiple mdl files and copy the resulting `processed.mdl` to the prep directory.\n */\nasync function flattenMdls(\n context: BuildContext,\n sdeCmdPath: string,\n prepDir: string,\n modelFiles: string[]\n): Promise<void> {\n log('verbose', ' Flattening and preprocessing mdl files')\n\n // Use SDE to flatten the parent model and submodels into a single mdl file,\n // then preprocess to strip anything that's not needed to build the model\n const command = sdeCmdPath\n const args: string[] = []\n args.push('flatten')\n args.push('processed.mdl')\n args.push('--inputs')\n for (const path of modelFiles) {\n args.push(path)\n }\n\n // Disable logging by default; this will suppress flatten warnings, which are\n // sometimes unavoidable and not helpful\n const output = await context.spawnChild(prepDir, command, args, {\n logOutput: false,\n captureOutput: true,\n ignoreError: true\n })\n\n // Check for flattening errors\n let flattenErrors = false\n for (const msg of output.stderrMessages) {\n if (msg.includes('ERROR')) {\n flattenErrors = true\n break\n }\n }\n if (flattenErrors) {\n log('error', 'There were errors reported when flattening the model:')\n for (const msg of output.stderrMessages) {\n const lines = msg.split('\\n')\n for (const line of lines) {\n log('error', ` ${line}`)\n }\n }\n throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`)\n } else if (output.exitCode !== 0) {\n throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`)\n }\n\n // Copy the processed file back to the prep directory\n await copyFile(joinPath(prepDir, 'build', 'processed.mdl'), joinPath(prepDir, 'processed.mdl'))\n}\n\n/**\n * Generate a JS or C file from the `processed.mdl` file.\n */\nasync function generateCode(context: BuildContext, sdeDir: string, sdeCmdPath: string, prepDir: string): Promise<void> {\n const genFormat = context.config.genFormat\n const genFormatName = genFormat.toUpperCase()\n log('verbose', ` Generating ${genFormatName} code`)\n\n // Use SDE to generate both a JS/C version of the model (`--outformat`) AND a JSON list of all model\n // dimensions and variables (`--list`)\n const command = sdeCmdPath\n const outFormat = `--outformat=${genFormat}`\n const genCmdArgs = ['generate', outFormat, '--list', '--spec', 'spec.json', 'processed']\n const genCmdOutput = await context.spawnChild(prepDir, command, genCmdArgs, {\n // By default, ignore lines that start with \"WARNING: Data for\" since these are often harmless\n // TODO: Don't filter by default, but make it configurable\n // ignoredMessageFilter: 'WARNING: Data for'\n // The default error message from `spawnChild` is not very informative, so the\n // following allows us to throw our own error\n ignoreError: true\n })\n if (genCmdOutput.exitCode !== 0) {\n throw new Error(\n `Failed to generate ${genFormatName} code: 'sde generate' command failed (code=${genCmdOutput.exitCode})`\n )\n }\n\n if (genFormat === 'c') {\n // Copy SDE's supporting C files into the build directory\n const buildDir = joinPath(prepDir, 'build')\n const sdeCDir = joinPath(sdeDir, 'src', 'c')\n const files = await readdir(sdeCDir)\n const copyOps = []\n for (const file of files) {\n if (file.endsWith('.c') || file.endsWith('.h')) {\n copyOps.push(copyFile(joinPath(sdeCDir, file), joinPath(buildDir, file)))\n }\n }\n await Promise.all(copyOps)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\nimport { hashElement } from 'folder-hash'\nimport { glob } from 'tinyglobby'\n\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\n/**\n * Asynchronously compute the hash of the files that are inputs to the model\n * build process.\n */\nexport async function computeInputFilesHash(config: ResolvedConfig): Promise<string> {\n const inputFiles: string[] = []\n\n // Always include the `spec.json` file, since that is a primary input\n // to the model build process\n const specFile = joinPath(config.prepDir, 'spec.json')\n inputFiles.push(specFile)\n\n if (config.modelInputPaths && config.modelInputPaths.length > 0) {\n // Include the files that match the glob patterns in the config file.\n // Note that the folder-hash package supports glob patterns, but its\n // configuration is complicated, so it is easier if we resolve the\n // glob patterns here and pass each individual file to the\n // `hashElement` function.\n for (const globPath of config.modelInputPaths) {\n const paths = await glob(globPath, {\n cwd: config.rootDir,\n absolute: true,\n onlyFiles: true\n })\n inputFiles.push(...paths)\n }\n } else {\n // Only use the mdl files to compute the hash\n inputFiles.push(...config.modelFiles)\n }\n\n // Compute the hash of each input file and concatenate into a single string\n let hash = ''\n for (const inputFile of inputFiles) {\n const result = await hashElement(inputFile)\n hash += result.hash\n }\n\n return hash\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { basename } from 'path'\n\nimport { clearOverlay, log, logError } from '../../_shared/log'\nimport type { ResolvedConfig } from '../../_shared/resolved-config'\n\nimport type { UserConfig } from '../../config/user-config'\nimport type { Plugin } from '../../plugin/plugin'\n\nimport type { BuildOnceOptions } from './build-once'\nimport { buildOnce } from './build-once'\nimport { watchPaths } from './watch-paths'\n\nclass BuildState {\n readonly abortController = new AbortController()\n}\n\nexport function watch(config: ResolvedConfig, userConfig: UserConfig, plugins: Plugin[]): void {\n // Add a small delay so that if multiple files are changed at once (as is\n // often the case when switching branches), we batch them up and start the\n // the build after things settle down\n const delay = 150\n const changedPaths: Set<string> = new Set()\n\n // Keep track of the current build so that we only have one active at a time\n let currentBuildState: BuildState\n\n function performBuild() {\n clearOverlay()\n\n // Log the input files that have changed\n for (const path of changedPaths) {\n log('info', `Input file ${basename(path)} has been changed`)\n }\n\n // Clear the set of pending files\n changedPaths.clear()\n\n // Keep track of builds; if one is already in progress, abort it\n // before starting another one\n if (currentBuildState) {\n currentBuildState.abortController.abort()\n // TODO: Prevent aborted build from logging?\n currentBuildState = undefined\n }\n\n // Generate files and build the model\n currentBuildState = new BuildState()\n const buildOptions: BuildOnceOptions = {\n abortSignal: currentBuildState.abortController.signal\n }\n buildOnce(config, userConfig, plugins, buildOptions)\n .then(result => {\n // Log the error message in case of error result\n if (result.isErr()) {\n logError(result.error)\n }\n })\n .catch(e => {\n // Also catch thrown errors that may have not already been\n // handled by `buildOnce`\n logError(e)\n // log('info', 'Waiting for changes...')\n })\n .finally(() => {\n currentBuildState = undefined\n })\n }\n\n function scheduleBuild(changedPath: string) {\n // Only schedule the build if the set is currently empty\n const schedule = changedPaths.size === 0\n\n // Add the path to the set of changed files\n changedPaths.add(changedPath)\n\n if (schedule) {\n // Schedule the build to start after a delay\n setTimeout(() => {\n performBuild()\n }, delay)\n }\n }\n\n let watchPatterns: string[]\n if (config.watchPaths && config.watchPaths.length > 0) {\n // Watch the configured files\n watchPatterns = config.watchPaths\n } else {\n // Only watch the mdl files\n watchPatterns = config.modelFiles\n }\n\n // Watch the configured files; if changes are detected, generate the specs\n // and rebuild the model if needed\n watchPaths(watchPatterns, config.rootDir, path => {\n scheduleBuild(path)\n })\n}\n","// Copyright (c) 2025 Climate Interactive / New Venture Fund\n\nimport chokidar from 'chokidar'\nimport { globSync, isDynamicPattern } from 'tinyglobby'\n\n/**\n * Resolve watch paths, expanding glob patterns into concrete file paths.\n *\n * This function doesn't attempt to expand directories. This is intentional\n * as it allows chokidar to handle directory changes (added and removed files)\n * instead of it being passed a specific set of files when the watch is first\n * set up.\n *\n * @param patterns The watch paths to resolve (may include glob patterns).\n * @param cwd The current working directory to resolve paths relative to.\n * @returns An array of resolved paths. Non-glob paths are returned as-is,\n * while glob patterns are expanded to absolute paths of matching files.\n */\nexport function resolveWatchPaths(patterns: string[], cwd: string): string[] {\n const resolvedPaths: string[] = []\n\n // Separate regular file/directory paths from glob patterns\n const regularPaths: string[] = []\n const globPatterns: string[] = []\n for (const pattern of patterns) {\n if (isDynamicPattern(pattern)) {\n globPatterns.push(pattern)\n } else {\n regularPaths.push(pattern)\n }\n }\n\n // Add the regular paths as is (don't attempt to expand directories)\n resolvedPaths.push(...regularPaths)\n\n // Resolve the glob patterns; we pass these to tinyglobby all at once so that\n // it can handle negation involving multiple patterns\n if (globPatterns.length > 0) {\n const paths = globSync(globPatterns, {\n // Watch paths are resolved relative to the provided cwd\n cwd,\n // Resolve to absolute paths\n absolute: true\n })\n resolvedPaths.push(...paths)\n }\n\n return resolvedPaths\n}\n\n/**\n * Watch file paths and invoke callbacks when files are changed, added, or removed.\n *\n * This function sets up a file watcher using chokidar. Glob patterns in the paths\n * are resolved before being passed to chokidar (since chokidar no longer supports\n * glob patterns).\n *\n * @param patterns The paths to watch (may include glob patterns).\n * @param cwd The current working directory to resolve paths relative to.\n * @param onChange Callback invoked when a file is changed.\n * @param onReady Optional callback invoked when the watcher is ready.\n * @returns A cleanup function that closes the watcher.\n */\nexport function watchPaths(\n patterns: string[],\n cwd: string,\n onChange: (path: string) => void,\n onReady?: () => void\n): () => void {\n // The chokidar package no longer supports glob patterns, so we need to resolve\n // glob patterns first and then pass the resolved paths to chokidar\n const resolvedPathsToWatch = resolveWatchPaths(patterns, cwd)\n\n // Watch the resolved paths; if changes are detected, invoke the callbacks\n const watcher = chokidar.watch(resolvedPathsToWatch, {\n // Watch paths are resolved relative to the provided cwd\n cwd,\n // Ignore the initial add events when the watcher is created\n ignoreInitial: true,\n // Include a delay, otherwise on macOS we sometimes get multiple\n // change events when the file is saved just once\n awaitWriteFinish: {\n stabilityThreshold: 200\n }\n })\n watcher.on('change', path => {\n onChange(path)\n })\n watcher.on('add', path => {\n onChange(path)\n })\n watcher.on('unlink', path => {\n onChange(path)\n })\n if (onReady) {\n watcher.on('ready', onReady)\n }\n\n // Return cleanup function\n return () => {\n watcher.close()\n }\n}\n"],"mappings":"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","names":["joinPath","err","ok","watchPaths","existsSync","readFileSync","writeFileSync","writeFile","joinPath","err","ok","err","existsSync","mkdirSync","writeFileSync","joinPath","joinPath","existsSync","mkdirSync","writeFileSync","f","dirname","joinPath","joinPath","dirname","joinPath","joinPath","err","writeFile","existsSync","readFileSync","writeFileSync","ok","basename","basename","err","joinPath","overlayEnabled","ok"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/build",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "files": [
5
5
  "dist/**"
6
6
  ],