@sdeverywhere/build 0.3.4 → 0.3.5

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
@@ -121,6 +121,26 @@ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
121
121
  } else {
122
122
  watchPaths = modelFiles;
123
123
  }
124
+ const rawGenFormat = userConfig.genFormat || "js";
125
+ let genFormat;
126
+ switch (rawGenFormat) {
127
+ case "js":
128
+ genFormat = "js";
129
+ break;
130
+ case "c":
131
+ genFormat = "c";
132
+ break;
133
+ default:
134
+ throw new Error(`The configured genFormat value is invalid; must be either 'js' or 'c'`);
135
+ }
136
+ let outListingFile;
137
+ if (userConfig.outListingFile) {
138
+ if ((0, import_path.isAbsolute)(userConfig.outListingFile)) {
139
+ outListingFile = userConfig.outListingFile;
140
+ } else {
141
+ outListingFile = (0, import_path.resolve)(rootDir, userConfig.outListingFile);
142
+ }
143
+ }
124
144
  return {
125
145
  mode,
126
146
  rootDir,
@@ -128,6 +148,8 @@ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
128
148
  modelFiles,
129
149
  modelInputPaths,
130
150
  watchPaths,
151
+ genFormat,
152
+ outListingFile,
131
153
  sdeDir,
132
154
  sdeCmdPath
133
155
  };
@@ -545,18 +567,33 @@ async function generateModel(context, plugins) {
545
567
  }
546
568
  }
547
569
  for (const plugin of plugins) {
548
- if (plugin.preGenerateC) {
549
- await plugin.preGenerateC(context);
570
+ if (plugin.preGenerateCode) {
571
+ await plugin.preGenerateCode(context, config.genFormat);
550
572
  }
551
573
  }
552
- await generateC(context, config.sdeDir, sdeCmdPath, prepDir);
574
+ await generateCode(context, config.sdeDir, sdeCmdPath, prepDir);
575
+ const generatedCodeFile = `processed.${config.genFormat}`;
576
+ const generatedCodePath = (0, import_path3.join)(prepDir, "build", generatedCodeFile);
553
577
  for (const plugin of plugins) {
554
- if (plugin.postGenerateC) {
555
- const cPath = (0, import_path3.join)(prepDir, "build", "processed.c");
556
- let cContent = await (0, import_promises.readFile)(cPath, "utf8");
557
- cContent = await plugin.postGenerateC(context, cContent);
558
- await (0, import_promises.writeFile)(cPath, cContent);
559
- }
578
+ if (plugin.postGenerateCode) {
579
+ let generatedCodeContent = await (0, import_promises.readFile)(generatedCodePath, "utf8");
580
+ generatedCodeContent = await plugin.postGenerateCode(context, config.genFormat, generatedCodeContent);
581
+ await (0, import_promises.writeFile)(generatedCodePath, generatedCodeContent);
582
+ }
583
+ }
584
+ if (config.genFormat === "js") {
585
+ const outputJsFile = "generated-model.js";
586
+ const stagedOutputJsPath = context.prepareStagedFile("model", outputJsFile, prepDir, outputJsFile);
587
+ await (0, import_promises.copyFile)(generatedCodePath, stagedOutputJsPath);
588
+ }
589
+ if (config.outListingFile) {
590
+ const srcListingJsonPath = (0, import_path3.join)(prepDir, "build", "processed.json");
591
+ const stagedDir = "model";
592
+ const stagedFile = "listing.json";
593
+ const dstDir = (0, import_path3.dirname)(config.outListingFile);
594
+ const dstFile = (0, import_path3.basename)(config.outListingFile);
595
+ const stagedListingJsonPath = context.prepareStagedFile(stagedDir, stagedFile, dstDir, dstFile);
596
+ await (0, import_promises.copyFile)(srcListingJsonPath, stagedListingJsonPath);
560
597
  }
561
598
  const t1 = performance.now();
562
599
  const elapsed = ((t1 - t0) / 1e3).toFixed(1);
@@ -613,11 +650,14 @@ async function flattenMdls(context, sdeCmdPath, prepDir, modelFiles) {
613
650
  }
614
651
  await (0, import_promises.copyFile)((0, import_path3.join)(prepDir, "build", "processed.mdl"), (0, import_path3.join)(prepDir, "processed.mdl"));
615
652
  }
616
- async function generateC(context, sdeDir, sdeCmdPath, prepDir) {
617
- log("verbose", " Generating C code");
653
+ async function generateCode(context, sdeDir, sdeCmdPath, prepDir) {
654
+ const genFormat = context.config.genFormat;
655
+ const genFormatName = genFormat.toUpperCase();
656
+ log("verbose", ` Generating ${genFormatName} code`);
618
657
  const command = sdeCmdPath;
619
- const gencArgs = ["generate", "--genc", "--list", "--spec", "spec.json", "processed"];
620
- const gencOutput = await context.spawnChild(prepDir, command, gencArgs, {
658
+ const outFormat = `--outformat=${genFormat}`;
659
+ const genCmdArgs = ["generate", outFormat, "--list", "--spec", "spec.json", "processed"];
660
+ const genCmdOutput = await context.spawnChild(prepDir, command, genCmdArgs, {
621
661
  // By default, ignore lines that start with "WARNING: Data for" since these are often harmless
622
662
  // TODO: Don't filter by default, but make it configurable
623
663
  // ignoredMessageFilter: 'WARNING: Data for'
@@ -625,19 +665,23 @@ async function generateC(context, sdeDir, sdeCmdPath, prepDir) {
625
665
  // following allows us to throw our own error
626
666
  ignoreError: true
627
667
  });
628
- if (gencOutput.exitCode !== 0) {
629
- throw new Error(`Failed to generate C code: 'sde generate' command failed (code=${gencOutput.exitCode})`);
630
- }
631
- const buildDir = (0, import_path3.join)(prepDir, "build");
632
- const sdeCDir = (0, import_path3.join)(sdeDir, "src", "c");
633
- const files = await (0, import_promises.readdir)(sdeCDir);
634
- const copyOps = [];
635
- for (const file of files) {
636
- if (file.endsWith(".c") || file.endsWith(".h")) {
637
- copyOps.push((0, import_promises.copyFile)((0, import_path3.join)(sdeCDir, file), (0, import_path3.join)(buildDir, file)));
668
+ if (genCmdOutput.exitCode !== 0) {
669
+ throw new Error(
670
+ `Failed to generate ${genFormatName} code: 'sde generate' command failed (code=${genCmdOutput.exitCode})`
671
+ );
672
+ }
673
+ if (genFormat === "c") {
674
+ const buildDir = (0, import_path3.join)(prepDir, "build");
675
+ const sdeCDir = (0, import_path3.join)(sdeDir, "src", "c");
676
+ const files = await (0, import_promises.readdir)(sdeCDir);
677
+ const copyOps = [];
678
+ for (const file of files) {
679
+ if (file.endsWith(".c") || file.endsWith(".h")) {
680
+ copyOps.push((0, import_promises.copyFile)((0, import_path3.join)(sdeCDir, file), (0, import_path3.join)(buildDir, file)));
681
+ }
638
682
  }
683
+ await Promise.all(copyOps);
639
684
  }
640
- await Promise.all(copyOps);
641
685
  }
642
686
 
643
687
  // src/build/impl/hash-files.ts
@@ -675,19 +719,23 @@ async function buildOnce(config, userConfig, plugins, options) {
675
719
  const modelHashPath = (0, import_path5.join)(config.prepDir, "model-hash.txt");
676
720
  let succeeded = true;
677
721
  try {
678
- const modelSpec = await userConfig.modelSpec(context);
679
- if (modelSpec === void 0) {
722
+ const userModelSpec = await userConfig.modelSpec(context);
723
+ if (userModelSpec === void 0) {
680
724
  return (0, import_neverthrow2.err)(new Error("The model spec must be defined"));
681
725
  }
726
+ const modelSpec = resolveModelSpec(userModelSpec);
682
727
  for (const plugin of plugins) {
683
728
  if (plugin.preGenerate) {
684
729
  await plugin.preGenerate(context, modelSpec);
685
730
  }
686
731
  }
687
732
  const specJson = {
688
- inputVarNames: modelSpec.inputs.map((input) => input.varName),
689
- outputVarNames: modelSpec.outputs.map((output) => output.varName),
733
+ inputVarNames: modelSpec.inputVarNames,
734
+ outputVarNames: modelSpec.outputVarNames,
690
735
  externalDatfiles: modelSpec.datFiles,
736
+ bundleListing: modelSpec.bundleListing,
737
+ customLookups: modelSpec.customLookups,
738
+ customOutputs: modelSpec.customOutputs,
691
739
  ...modelSpec.options
692
740
  };
693
741
  const specPath = (0, import_path5.join)(config.prepDir, "spec.json");
@@ -741,6 +789,69 @@ async function buildOnce(config, userConfig, plugins, options) {
741
789
  }
742
790
  return (0, import_neverthrow2.ok)(succeeded);
743
791
  }
792
+ function resolveModelSpec(modelSpec) {
793
+ let inputVarNames;
794
+ let inputSpecs;
795
+ if (modelSpec.inputs.length > 0) {
796
+ const item = modelSpec.inputs[0];
797
+ if (typeof item === "string") {
798
+ inputVarNames = modelSpec.inputs;
799
+ inputSpecs = inputVarNames.map((varName) => {
800
+ return {
801
+ varName
802
+ };
803
+ });
804
+ } else {
805
+ inputSpecs = modelSpec.inputs;
806
+ inputVarNames = inputSpecs.map((spec) => spec.varName);
807
+ }
808
+ } else {
809
+ inputVarNames = [];
810
+ inputSpecs = [];
811
+ }
812
+ let outputVarNames;
813
+ let outputSpecs;
814
+ if (modelSpec.outputs.length > 0) {
815
+ const item = modelSpec.outputs[0];
816
+ if (typeof item === "string") {
817
+ outputVarNames = modelSpec.outputs;
818
+ outputSpecs = outputVarNames.map((varName) => {
819
+ return {
820
+ varName
821
+ };
822
+ });
823
+ } else {
824
+ outputSpecs = modelSpec.outputs;
825
+ outputVarNames = outputSpecs.map((spec) => spec.varName);
826
+ }
827
+ } else {
828
+ outputVarNames = [];
829
+ outputSpecs = [];
830
+ }
831
+ let customLookups;
832
+ if (modelSpec.customLookups !== void 0) {
833
+ customLookups = modelSpec.customLookups;
834
+ } else {
835
+ customLookups = false;
836
+ }
837
+ let customOutputs;
838
+ if (modelSpec.customOutputs !== void 0) {
839
+ customOutputs = modelSpec.customOutputs;
840
+ } else {
841
+ customOutputs = false;
842
+ }
843
+ return {
844
+ inputVarNames,
845
+ inputs: inputSpecs,
846
+ outputVarNames,
847
+ outputs: outputSpecs,
848
+ datFiles: modelSpec.datFiles || [],
849
+ bundleListing: modelSpec.bundleListing === true,
850
+ customLookups,
851
+ customOutputs,
852
+ options: modelSpec.options
853
+ };
854
+ }
744
855
 
745
856
  // src/build/impl/watch.ts
746
857
  var import_path6 = require("path");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/tsup@7.2.0_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/spawn-child.ts","../src/context/context.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 } 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.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, 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 return {\n mode,\n rootDir,\n prepDir,\n modelFiles,\n modelInputPaths,\n watchPaths,\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 { 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 modelSpec = await userConfig.modelSpec(context)\n if (modelSpec === undefined) {\n return err(new Error('The model spec must be defined'))\n }\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.inputs.map(input => input.varName),\n outputVarNames: modelSpec.outputs.map(output => output.varName),\n externalDatfiles: modelSpec.datFiles,\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","// 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 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","// 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 { 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 C file\n for (const plugin of plugins) {\n if (plugin.preGenerateC) {\n await plugin.preGenerateC(context)\n }\n }\n await generateC(context, config.sdeDir, sdeCmdPath, prepDir)\n for (const plugin of plugins) {\n if (plugin.postGenerateC) {\n const cPath = joinPath(prepDir, 'build', 'processed.c')\n let cContent = await readFile(cPath, 'utf8')\n cContent = await plugin.postGenerateC(context, cContent)\n await writeFile(cPath, cContent)\n }\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 C file from the `processed.mdl` file.\n */\nasync function generateC(context: BuildContext, sdeDir: string, sdeCmdPath: string, prepDir: string): Promise<void> {\n log('verbose', ' Generating C code')\n\n // Use SDE to generate both a C version of the model (`--genc`) AND a JSON list of all model\n // dimensions and variables (`--list`)\n const command = sdeCmdPath\n const gencArgs = ['generate', '--genc', '--list', '--spec', 'spec.json', 'processed']\n const gencOutput = await context.spawnChild(prepDir, command, gencArgs, {\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 (gencOutput.exitCode !== 0) {\n throw new Error(`Failed to generate C code: 'sde generate' command failed (code=${gencOutput.exitCode})`)\n }\n\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","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { join as joinPath } from 'path'\nimport { hashElement } from 'folder-hash'\nimport glob from 'tiny-glob'\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 filesOnly: 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'\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 // 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(watchPaths, {\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","glob","joinPath","import_path","chokidar","joinPath","overlayEnabled"]}
1
+ {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/tsup@7.2.0_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/spawn-child.ts","../src/context/context.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.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 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 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","// 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 'tiny-glob'\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 filesOnly: 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'\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 // 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(watchPaths, {\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","glob","joinPath","import_path","chokidar","joinPath","overlayEnabled"]}