@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.cjs +188 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +183 -22
- package/dist/index.d.ts +183 -22
- package/dist/index.js +192 -74
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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.
|
|
510
|
-
await plugin.
|
|
531
|
+
if (plugin.preGenerateCode) {
|
|
532
|
+
await plugin.preGenerateCode(context, config.genFormat);
|
|
511
533
|
}
|
|
512
534
|
}
|
|
513
|
-
await
|
|
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.
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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(`
|
|
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(`
|
|
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
|
|
571
|
-
|
|
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
|
|
574
|
-
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
680
|
+
const modelHashPath = joinPath5(config.prepDir, "model-hash.txt");
|
|
681
|
+
let succeeded = true;
|
|
624
682
|
try {
|
|
625
|
-
|
|
626
|
-
if (
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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 ${
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
912
|
+
watch(resolvedConfig, userConfig, plugins);
|
|
795
913
|
return ok3({});
|
|
796
914
|
} else {
|
|
797
|
-
const buildResult = await buildOnce(resolvedConfig, userConfig,
|
|
915
|
+
const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {});
|
|
798
916
|
if (buildResult.isErr()) {
|
|
799
917
|
return err3(buildResult.error);
|
|
800
918
|
}
|