@sdeverywhere/build 0.3.3 → 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.js CHANGED
@@ -4,7 +4,7 @@ import { err as err3, ok as ok3 } from "neverthrow";
4
4
 
5
5
  // src/config/config-loader.ts
6
6
  import { existsSync, lstatSync, mkdirSync } from "fs";
7
- import { dirname, join as joinPath, relative, resolve as resolvePath } from "path";
7
+ import { dirname, isAbsolute, join as joinPath, relative, resolve as resolvePath } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
  import { err, ok } from "neverthrow";
10
10
  async function loadConfig(mode, config, sdeDir, sdeCmdPath) {
@@ -82,6 +82,26 @@ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
82
82
  } else {
83
83
  watchPaths = modelFiles;
84
84
  }
85
+ const rawGenFormat = userConfig.genFormat || "js";
86
+ let genFormat;
87
+ switch (rawGenFormat) {
88
+ case "js":
89
+ genFormat = "js";
90
+ break;
91
+ case "c":
92
+ genFormat = "c";
93
+ break;
94
+ default:
95
+ throw new Error(`The configured genFormat value is invalid; must be either 'js' or 'c'`);
96
+ }
97
+ let outListingFile;
98
+ if (userConfig.outListingFile) {
99
+ if (isAbsolute(userConfig.outListingFile)) {
100
+ outListingFile = userConfig.outListingFile;
101
+ } else {
102
+ outListingFile = resolvePath(rootDir, userConfig.outListingFile);
103
+ }
104
+ }
85
105
  return {
86
106
  mode,
87
107
  rootDir,
@@ -89,6 +109,8 @@ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
89
109
  modelFiles,
90
110
  modelInputPaths,
91
111
  watchPaths,
112
+ genFormat,
113
+ outListingFile,
92
114
  sdeDir,
93
115
  sdeCmdPath
94
116
  };
@@ -476,7 +498,7 @@ function filesDiffer(aPath, bPath) {
476
498
 
477
499
  // src/build/impl/gen-model.ts
478
500
  import { copyFile, readdir, readFile, writeFile } from "fs/promises";
479
- import { join as joinPath3 } from "path";
501
+ import { basename, dirname as dirname2, join as joinPath3 } from "path";
480
502
  async function generateModel(context, plugins) {
481
503
  const config = context.config;
482
504
  if (config.modelFiles.length === 0) {
@@ -506,18 +528,33 @@ async function generateModel(context, plugins) {
506
528
  }
507
529
  }
508
530
  for (const plugin of plugins) {
509
- if (plugin.preGenerateC) {
510
- await plugin.preGenerateC(context);
531
+ if (plugin.preGenerateCode) {
532
+ await plugin.preGenerateCode(context, config.genFormat);
511
533
  }
512
534
  }
513
- await generateC(context, config.sdeDir, sdeCmdPath, prepDir);
535
+ await generateCode(context, config.sdeDir, sdeCmdPath, prepDir);
536
+ const generatedCodeFile = `processed.${config.genFormat}`;
537
+ const generatedCodePath = joinPath3(prepDir, "build", generatedCodeFile);
514
538
  for (const plugin of plugins) {
515
- if (plugin.postGenerateC) {
516
- const cPath = joinPath3(prepDir, "build", "processed.c");
517
- let cContent = await readFile(cPath, "utf8");
518
- cContent = await plugin.postGenerateC(context, cContent);
519
- await writeFile(cPath, cContent);
520
- }
539
+ if (plugin.postGenerateCode) {
540
+ let generatedCodeContent = await readFile(generatedCodePath, "utf8");
541
+ generatedCodeContent = await plugin.postGenerateCode(context, config.genFormat, generatedCodeContent);
542
+ await writeFile(generatedCodePath, generatedCodeContent);
543
+ }
544
+ }
545
+ if (config.genFormat === "js") {
546
+ const outputJsFile = "generated-model.js";
547
+ const stagedOutputJsPath = context.prepareStagedFile("model", outputJsFile, prepDir, outputJsFile);
548
+ await copyFile(generatedCodePath, stagedOutputJsPath);
549
+ }
550
+ if (config.outListingFile) {
551
+ const srcListingJsonPath = joinPath3(prepDir, "build", "processed.json");
552
+ const stagedDir = "model";
553
+ const stagedFile = "listing.json";
554
+ const dstDir = dirname2(config.outListingFile);
555
+ const dstFile = basename(config.outListingFile);
556
+ const stagedListingJsonPath = context.prepareStagedFile(stagedDir, stagedFile, dstDir, dstFile);
557
+ await copyFile(srcListingJsonPath, stagedListingJsonPath);
521
558
  }
522
559
  const t1 = performance.now();
523
560
  const elapsed = ((t1 - t0) / 1e3).toFixed(1);
@@ -528,7 +565,14 @@ async function preprocessMdl(context, sdeCmdPath, prepDir, modelFile) {
528
565
  await copyFile(modelFile, joinPath3(prepDir, "processed.mdl"));
529
566
  const command = sdeCmdPath;
530
567
  const args = ["generate", "--preprocess", "processed.mdl"];
531
- await context.spawnChild(prepDir, command, args);
568
+ const ppOutput = await context.spawnChild(prepDir, command, args, {
569
+ // The default error message from `spawnChild` is not very informative, so the
570
+ // following allows us to throw our own error
571
+ ignoreError: true
572
+ });
573
+ if (ppOutput.exitCode !== 0) {
574
+ throw new Error(`Failed to preprocess mdl file: 'sde generate' command failed (code=${ppOutput.exitCode})`);
575
+ }
532
576
  await copyFile(joinPath3(prepDir, "build", "processed.mdl"), joinPath3(prepDir, "processed.mdl"));
533
577
  }
534
578
  async function flattenMdls(context, sdeCmdPath, prepDir, modelFiles) {
@@ -561,31 +605,44 @@ async function flattenMdls(context, sdeCmdPath, prepDir, modelFiles) {
561
605
  log("error", ` ${line}`);
562
606
  }
563
607
  }
564
- throw new Error(`Flatten command failed (code=${output.exitCode})`);
608
+ throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`);
565
609
  } else if (output.exitCode !== 0) {
566
- throw new Error(`Flatten command failed (code=${output.exitCode})`);
610
+ throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`);
567
611
  }
568
612
  await copyFile(joinPath3(prepDir, "build", "processed.mdl"), joinPath3(prepDir, "processed.mdl"));
569
613
  }
570
- async function generateC(context, sdeDir, sdeCmdPath, prepDir) {
571
- log("verbose", " Generating C code");
614
+ async function generateCode(context, sdeDir, sdeCmdPath, prepDir) {
615
+ const genFormat = context.config.genFormat;
616
+ const genFormatName = genFormat.toUpperCase();
617
+ log("verbose", ` Generating ${genFormatName} code`);
572
618
  const command = sdeCmdPath;
573
- const gencArgs = ["generate", "--genc", "--list", "--spec", "spec.json", "processed"];
574
- await context.spawnChild(prepDir, command, gencArgs, {
619
+ const outFormat = `--outformat=${genFormat}`;
620
+ const genCmdArgs = ["generate", outFormat, "--list", "--spec", "spec.json", "processed"];
621
+ const genCmdOutput = await context.spawnChild(prepDir, command, genCmdArgs, {
575
622
  // By default, ignore lines that start with "WARNING: Data for" since these are often harmless
576
623
  // TODO: Don't filter by default, but make it configurable
577
624
  // ignoredMessageFilter: 'WARNING: Data for'
625
+ // The default error message from `spawnChild` is not very informative, so the
626
+ // following allows us to throw our own error
627
+ ignoreError: true
578
628
  });
579
- const buildDir = joinPath3(prepDir, "build");
580
- const sdeCDir = joinPath3(sdeDir, "src", "c");
581
- const files = await readdir(sdeCDir);
582
- const copyOps = [];
583
- for (const file of files) {
584
- if (file.endsWith(".c") || file.endsWith(".h")) {
585
- copyOps.push(copyFile(joinPath3(sdeCDir, file), joinPath3(buildDir, file)));
629
+ if (genCmdOutput.exitCode !== 0) {
630
+ throw new Error(
631
+ `Failed to generate ${genFormatName} code: 'sde generate' command failed (code=${genCmdOutput.exitCode})`
632
+ );
633
+ }
634
+ if (genFormat === "c") {
635
+ const buildDir = joinPath3(prepDir, "build");
636
+ const sdeCDir = joinPath3(sdeDir, "src", "c");
637
+ const files = await readdir(sdeCDir);
638
+ const copyOps = [];
639
+ for (const file of files) {
640
+ if (file.endsWith(".c") || file.endsWith(".h")) {
641
+ copyOps.push(copyFile(joinPath3(sdeCDir, file), joinPath3(buildDir, file)));
642
+ }
586
643
  }
644
+ await Promise.all(copyOps);
587
645
  }
588
- await Promise.all(copyOps);
589
646
  }
590
647
 
591
648
  // src/build/impl/hash-files.ts
@@ -620,45 +677,44 @@ async function computeInputFilesHash(config) {
620
677
  async function buildOnce(config, userConfig, plugins, options) {
621
678
  const stagedFiles = new StagedFiles(config.prepDir);
622
679
  const context = new BuildContext(config, stagedFiles, options.abortSignal);
623
- let modelSpec;
680
+ const modelHashPath = joinPath5(config.prepDir, "model-hash.txt");
681
+ let succeeded = true;
624
682
  try {
625
- modelSpec = await userConfig.modelSpec(context);
626
- if (modelSpec === void 0) {
683
+ const userModelSpec = await userConfig.modelSpec(context);
684
+ if (userModelSpec === void 0) {
627
685
  return err2(new Error("The model spec must be defined"));
628
686
  }
629
- } catch (e) {
630
- return err2(e);
631
- }
632
- for (const plugin of plugins) {
633
- if (plugin.preGenerate) {
634
- plugin.preGenerate(context, modelSpec);
687
+ const modelSpec = resolveModelSpec(userModelSpec);
688
+ for (const plugin of plugins) {
689
+ if (plugin.preGenerate) {
690
+ await plugin.preGenerate(context, modelSpec);
691
+ }
692
+ }
693
+ const specJson = {
694
+ inputVarNames: modelSpec.inputVarNames,
695
+ outputVarNames: modelSpec.outputVarNames,
696
+ externalDatfiles: modelSpec.datFiles,
697
+ bundleListing: modelSpec.bundleListing,
698
+ customLookups: modelSpec.customLookups,
699
+ customOutputs: modelSpec.customOutputs,
700
+ ...modelSpec.options
701
+ };
702
+ const specPath = joinPath5(config.prepDir, "spec.json");
703
+ await writeFile2(specPath, JSON.stringify(specJson, null, 2));
704
+ let previousModelHash;
705
+ if (existsSync3(modelHashPath)) {
706
+ previousModelHash = readFileSync2(modelHashPath, "utf8");
707
+ } else {
708
+ previousModelHash = "NONE";
709
+ }
710
+ const inputFilesHash = await computeInputFilesHash(config);
711
+ let needModelGen;
712
+ if (options.forceModelGen === true) {
713
+ needModelGen = true;
714
+ } else {
715
+ const hashMismatch = inputFilesHash !== previousModelHash;
716
+ needModelGen = hashMismatch;
635
717
  }
636
- }
637
- const specJson = {
638
- inputVarNames: modelSpec.inputs.map((input) => input.varName),
639
- outputVarNames: modelSpec.outputs.map((output) => output.varName),
640
- externalDatfiles: modelSpec.datFiles,
641
- ...modelSpec.options
642
- };
643
- const specPath = joinPath5(config.prepDir, "spec.json");
644
- await writeFile2(specPath, JSON.stringify(specJson, null, 2));
645
- const modelHashPath = joinPath5(config.prepDir, "model-hash.txt");
646
- let previousModelHash;
647
- if (existsSync3(modelHashPath)) {
648
- previousModelHash = readFileSync2(modelHashPath, "utf8");
649
- } else {
650
- previousModelHash = "NONE";
651
- }
652
- const inputFilesHash = await computeInputFilesHash(config);
653
- let needModelGen;
654
- if (options.forceModelGen === true) {
655
- needModelGen = true;
656
- } else {
657
- const hashMismatch = inputFilesHash !== previousModelHash;
658
- needModelGen = hashMismatch;
659
- }
660
- let succeeded = true;
661
- try {
662
718
  if (needModelGen) {
663
719
  await generateModel(context, plugins);
664
720
  writeFileSync3(modelHashPath, inputFilesHash);
@@ -694,9 +750,72 @@ async function buildOnce(config, userConfig, plugins, options) {
694
750
  }
695
751
  return ok2(succeeded);
696
752
  }
753
+ function resolveModelSpec(modelSpec) {
754
+ let inputVarNames;
755
+ let inputSpecs;
756
+ if (modelSpec.inputs.length > 0) {
757
+ const item = modelSpec.inputs[0];
758
+ if (typeof item === "string") {
759
+ inputVarNames = modelSpec.inputs;
760
+ inputSpecs = inputVarNames.map((varName) => {
761
+ return {
762
+ varName
763
+ };
764
+ });
765
+ } else {
766
+ inputSpecs = modelSpec.inputs;
767
+ inputVarNames = inputSpecs.map((spec) => spec.varName);
768
+ }
769
+ } else {
770
+ inputVarNames = [];
771
+ inputSpecs = [];
772
+ }
773
+ let outputVarNames;
774
+ let outputSpecs;
775
+ if (modelSpec.outputs.length > 0) {
776
+ const item = modelSpec.outputs[0];
777
+ if (typeof item === "string") {
778
+ outputVarNames = modelSpec.outputs;
779
+ outputSpecs = outputVarNames.map((varName) => {
780
+ return {
781
+ varName
782
+ };
783
+ });
784
+ } else {
785
+ outputSpecs = modelSpec.outputs;
786
+ outputVarNames = outputSpecs.map((spec) => spec.varName);
787
+ }
788
+ } else {
789
+ outputVarNames = [];
790
+ outputSpecs = [];
791
+ }
792
+ let customLookups;
793
+ if (modelSpec.customLookups !== void 0) {
794
+ customLookups = modelSpec.customLookups;
795
+ } else {
796
+ customLookups = false;
797
+ }
798
+ let customOutputs;
799
+ if (modelSpec.customOutputs !== void 0) {
800
+ customOutputs = modelSpec.customOutputs;
801
+ } else {
802
+ customOutputs = false;
803
+ }
804
+ return {
805
+ inputVarNames,
806
+ inputs: inputSpecs,
807
+ outputVarNames,
808
+ outputs: outputSpecs,
809
+ datFiles: modelSpec.datFiles || [],
810
+ bundleListing: modelSpec.bundleListing === true,
811
+ customLookups,
812
+ customOutputs,
813
+ options: modelSpec.options
814
+ };
815
+ }
697
816
 
698
817
  // src/build/impl/watch.ts
699
- import { basename } from "path";
818
+ import { basename as basename2 } from "path";
700
819
  import chokidar from "chokidar";
701
820
  var BuildState = class {
702
821
  constructor() {
@@ -710,7 +829,7 @@ function watch(config, userConfig, plugins) {
710
829
  function performBuild() {
711
830
  clearOverlay();
712
831
  for (const path of changedPaths) {
713
- log("info", `Input file ${basename(path)} has been changed`);
832
+ log("info", `Input file ${basename2(path)} has been changed`);
714
833
  }
715
834
  changedPaths.clear();
716
835
  if (currentBuildState) {
@@ -773,28 +892,27 @@ async function build(mode, options) {
773
892
  const messagesPath = joinPath6(resolvedConfig.prepDir, "messages.html");
774
893
  const overlayEnabled2 = mode === "development";
775
894
  setOverlayFile(messagesPath, overlayEnabled2);
776
- const plugins = userConfig.plugins || [];
777
- for (const plugin of plugins) {
778
- if (plugin.init) {
779
- await plugin.init(resolvedConfig);
780
- }
781
- }
782
895
  try {
783
- const plugins2 = userConfig.plugins || [];
896
+ const plugins = userConfig.plugins || [];
897
+ for (const plugin of plugins) {
898
+ if (plugin.init) {
899
+ await plugin.init(resolvedConfig);
900
+ }
901
+ }
784
902
  if (mode === "development") {
785
- const buildResult = await buildOnce(resolvedConfig, userConfig, plugins2, {});
903
+ const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {});
786
904
  if (buildResult.isErr()) {
787
905
  return err3(buildResult.error);
788
906
  }
789
- for (const plugin of plugins2) {
907
+ for (const plugin of plugins) {
790
908
  if (plugin.watch) {
791
909
  await plugin.watch(resolvedConfig);
792
910
  }
793
911
  }
794
- watch(resolvedConfig, userConfig, plugins2);
912
+ watch(resolvedConfig, userConfig, plugins);
795
913
  return ok3({});
796
914
  } else {
797
- const buildResult = await buildOnce(resolvedConfig, userConfig, plugins2, {});
915
+ const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {});
798
916
  if (buildResult.isErr()) {
799
917
  return err3(buildResult.error);
800
918
  }