@sdeverywhere/build 0.3.17 → 0.3.18
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.d.ts +493 -483
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +884 -959
- package/dist/index.js.map +1 -1
- package/package.json +5 -7
- package/dist/index.cjs +0 -1055
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -603
package/dist/index.js
CHANGED
|
@@ -1,1015 +1,940 @@
|
|
|
1
|
-
|
|
2
|
-
import { join as joinPath6 } from "path";
|
|
3
|
-
import { err as err3, ok as ok3 } from "neverthrow";
|
|
4
|
-
|
|
5
|
-
// src/config/config-loader.ts
|
|
6
|
-
import { existsSync, lstatSync, mkdirSync } from "fs";
|
|
7
|
-
import { dirname, isAbsolute, join as joinPath, relative, resolve as resolvePath } from "path";
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
1
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
|
|
9
2
|
import { err, ok } from "neverthrow";
|
|
3
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import pico from "picocolors";
|
|
6
|
+
import { copyFile, readFile, readdir, writeFile } from "fs/promises";
|
|
7
|
+
import { canonicalVarId } from "@sdeverywhere/parse";
|
|
8
|
+
import { spawn } from "cross-spawn";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { createReadStream } from "node:fs";
|
|
11
|
+
import { basename as basename$1, join as join$1 } from "node:path";
|
|
12
|
+
import { pipeline } from "node:stream/promises";
|
|
13
|
+
import { glob, globSync, isDynamicPattern } from "tinyglobby";
|
|
14
|
+
import chokidar from "chokidar";
|
|
15
|
+
//#region src/config/config-loader.ts
|
|
16
|
+
/**
|
|
17
|
+
* Load a user-defined config file or a given `UserConfig` object. This validates
|
|
18
|
+
* all paths and if successful, this returns a `ResolvedConfig`, otherwise returns
|
|
19
|
+
* an error result.
|
|
20
|
+
*
|
|
21
|
+
* @param mode The build mode.
|
|
22
|
+
* @param config The path to a config file, or a `UserConfig` object; if undefined,
|
|
23
|
+
* this will look for a `sde.config.js` file in the current directory.
|
|
24
|
+
* @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).
|
|
25
|
+
* @param sdeCmdPath Temporary (the path to the `sde` command).
|
|
26
|
+
* @return An `ok` result with the `ResolvedConfig`, otherwise an `err` result.
|
|
27
|
+
*/
|
|
10
28
|
async function loadConfig(mode, config, sdeDir, sdeCmdPath) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const resolvedConfig = resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath);
|
|
34
|
-
return ok({
|
|
35
|
-
userConfig,
|
|
36
|
-
resolvedConfig
|
|
37
|
-
});
|
|
38
|
-
} catch (e) {
|
|
39
|
-
return err(e);
|
|
40
|
-
}
|
|
29
|
+
let userConfig;
|
|
30
|
+
if (typeof config === "object") userConfig = config;
|
|
31
|
+
else {
|
|
32
|
+
let configPath;
|
|
33
|
+
if (typeof config === "string") configPath = config;
|
|
34
|
+
else configPath = join(process.cwd(), "sde.config.js");
|
|
35
|
+
try {
|
|
36
|
+
if (!existsSync(configPath)) return err(/* @__PURE__ */ new Error(`Cannot find config file '${configPath}'`));
|
|
37
|
+
userConfig = await (await import(relativeToSourcePath(configPath))).config();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return err(/* @__PURE__ */ new Error(`Failed to load config file '${configPath}': ${e.message}`));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const resolvedConfig = resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath);
|
|
44
|
+
return ok({
|
|
45
|
+
userConfig,
|
|
46
|
+
resolvedConfig
|
|
47
|
+
});
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return err(e);
|
|
50
|
+
}
|
|
41
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the given user configuration by resolving all paths. This will create
|
|
54
|
+
* the prep directory if it does not already exist. This will throw an error if
|
|
55
|
+
* any other paths are invalid or do not exist.
|
|
56
|
+
*
|
|
57
|
+
* @param userConfig The user-defined configuration.
|
|
58
|
+
* @param mode The active build mode.
|
|
59
|
+
* @param sdeDir Temporary (the path to the `@sdeverywhere/cli` package).
|
|
60
|
+
* @param sdeCmdPath Temporary (the path to the `sde` command).
|
|
61
|
+
* @return The resolved configuration.
|
|
62
|
+
*/
|
|
42
63
|
function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
mode,
|
|
107
|
-
rootDir,
|
|
108
|
-
prepDir,
|
|
109
|
-
modelFiles,
|
|
110
|
-
modelInputPaths,
|
|
111
|
-
watchPaths: watchPaths2,
|
|
112
|
-
genFormat,
|
|
113
|
-
outListingFile,
|
|
114
|
-
sdeDir,
|
|
115
|
-
sdeCmdPath
|
|
116
|
-
};
|
|
64
|
+
function expectDirectory(propName, path) {
|
|
65
|
+
if (!existsSync(path)) throw new Error(`The configured ${propName} (${path}) does not exist`);
|
|
66
|
+
else if (!lstatSync(path).isDirectory()) throw new Error(`The configured ${propName} (${path}) is not a directory`);
|
|
67
|
+
}
|
|
68
|
+
let rootDir;
|
|
69
|
+
if (userConfig.rootDir) {
|
|
70
|
+
rootDir = resolve(userConfig.rootDir);
|
|
71
|
+
expectDirectory("rootDir", rootDir);
|
|
72
|
+
} else rootDir = process.cwd();
|
|
73
|
+
let prepDir;
|
|
74
|
+
if (userConfig.prepDir) prepDir = resolve(userConfig.prepDir);
|
|
75
|
+
else prepDir = resolve(rootDir, "sde-prep");
|
|
76
|
+
mkdirSync(prepDir, { recursive: true });
|
|
77
|
+
const userModelFiles = userConfig.modelFiles;
|
|
78
|
+
const modelFiles = [];
|
|
79
|
+
for (const userModelFile of userModelFiles) {
|
|
80
|
+
const modelFile = resolve(userModelFile);
|
|
81
|
+
if (!existsSync(modelFile)) throw new Error(`The configured model file (${modelFile}) does not exist`);
|
|
82
|
+
modelFiles.push(modelFile);
|
|
83
|
+
}
|
|
84
|
+
let modelInputPaths;
|
|
85
|
+
if (userConfig.modelInputPaths && userConfig.modelInputPaths.length > 0) modelInputPaths = userConfig.modelInputPaths;
|
|
86
|
+
else modelInputPaths = modelFiles;
|
|
87
|
+
let watchPaths;
|
|
88
|
+
if (userConfig.watchPaths && userConfig.watchPaths.length > 0) watchPaths = userConfig.watchPaths;
|
|
89
|
+
else watchPaths = modelFiles;
|
|
90
|
+
const rawGenFormat = userConfig.genFormat || "js";
|
|
91
|
+
let genFormat;
|
|
92
|
+
switch (rawGenFormat) {
|
|
93
|
+
case "js":
|
|
94
|
+
genFormat = "js";
|
|
95
|
+
break;
|
|
96
|
+
case "c":
|
|
97
|
+
genFormat = "c";
|
|
98
|
+
break;
|
|
99
|
+
default: throw new Error(`The configured genFormat value is invalid; must be either 'js' or 'c'`);
|
|
100
|
+
}
|
|
101
|
+
let outListingFile;
|
|
102
|
+
if (userConfig.outListingFile) {
|
|
103
|
+
if (isAbsolute(userConfig.outListingFile)) outListingFile = userConfig.outListingFile;
|
|
104
|
+
else outListingFile = resolve(rootDir, userConfig.outListingFile);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
mode,
|
|
108
|
+
rootDir,
|
|
109
|
+
prepDir,
|
|
110
|
+
modelFiles,
|
|
111
|
+
modelInputPaths,
|
|
112
|
+
watchPaths,
|
|
113
|
+
genFormat,
|
|
114
|
+
outListingFile,
|
|
115
|
+
sdeDir,
|
|
116
|
+
sdeCmdPath
|
|
117
|
+
};
|
|
117
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Return a Unix-style path (e.g. '../../foo.js') that is relative to the directory of
|
|
121
|
+
* the current source file. This can be used to construct a path that is safe for
|
|
122
|
+
* dynamic import on either Unix or Windows.
|
|
123
|
+
*
|
|
124
|
+
* @param filePath The path to make relative.
|
|
125
|
+
*/
|
|
118
126
|
function relativeToSourcePath(filePath) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return relPath.replaceAll("\\", "/");
|
|
127
|
+
const srcDir = dirname(fileURLToPath(import.meta.url));
|
|
128
|
+
return relative(srcDir, filePath).replaceAll("\\", "/");
|
|
122
129
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/_shared/log.ts
|
|
132
|
+
const activeLevels = /* @__PURE__ */ new Set(["error", "info"]);
|
|
133
|
+
let overlayFile;
|
|
134
|
+
let overlayEnabled = false;
|
|
135
|
+
let overlayHtml = "";
|
|
136
|
+
/**
|
|
137
|
+
* Set the active logging levels. By default, only 'error' and 'info'
|
|
138
|
+
* messages are emitted.
|
|
139
|
+
*
|
|
140
|
+
* @param logLevels The logging levels to include.
|
|
141
|
+
*/
|
|
131
142
|
function setActiveLevels(logLevels) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
activeLevels.add(level);
|
|
135
|
-
}
|
|
143
|
+
activeLevels.clear();
|
|
144
|
+
for (const level of logLevels) activeLevels.add(level);
|
|
136
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Set the path to the `messages.html` file where overlay messages will be written.
|
|
148
|
+
*
|
|
149
|
+
* @param file The absolute path to the HTML file where messages will be written.
|
|
150
|
+
* @param enabled Whether to write messages to the file; if false, the file will be
|
|
151
|
+
* emptied and no further messages will be written.
|
|
152
|
+
*/
|
|
137
153
|
function setOverlayFile(file, enabled) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
overlayFile = file;
|
|
155
|
+
overlayEnabled = enabled;
|
|
156
|
+
writeFileSync(overlayFile, "");
|
|
141
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Log a message to the console and/or overlay.
|
|
160
|
+
*
|
|
161
|
+
* @param level The logging level.
|
|
162
|
+
* @param msg The message to emit.
|
|
163
|
+
*/
|
|
142
164
|
function log(level, msg) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
165
|
+
if (activeLevels.has(level)) {
|
|
166
|
+
if (level === "error") {
|
|
167
|
+
console.error(pico.red(msg));
|
|
168
|
+
logToOverlay(msg);
|
|
169
|
+
} else {
|
|
170
|
+
console.log(msg);
|
|
171
|
+
logToOverlay(msg);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
152
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Log an error to the console and/or overlay.
|
|
177
|
+
*
|
|
178
|
+
* @param e The error to log.
|
|
179
|
+
*/
|
|
153
180
|
function logError(e) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
console.error(pico.dim(pico.red(`${trace}
|
|
160
|
-
`)));
|
|
161
|
-
logToOverlay(`
|
|
162
|
-
ERROR: ${e.message}`, true);
|
|
163
|
-
logToOverlay(`${trace}
|
|
164
|
-
`, true);
|
|
181
|
+
const trace = (e.stack || "").split("\n").filter((s) => s.match(/^\s+at/)).slice(0, 3).join("\n");
|
|
182
|
+
console.error(pico.red(`\nERROR: ${e.message}`));
|
|
183
|
+
console.error(pico.dim(pico.red(`${trace}\n`)));
|
|
184
|
+
logToOverlay(`\nERROR: ${e.message}`, true);
|
|
185
|
+
logToOverlay(`${trace}\n`, true);
|
|
165
186
|
}
|
|
166
187
|
function writeOverlayFiles() {
|
|
167
|
-
|
|
188
|
+
writeFileSync(overlayFile, overlayHtml);
|
|
168
189
|
}
|
|
169
190
|
function clearOverlay() {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
overlayHtml = "";
|
|
174
|
-
writeOverlayFiles();
|
|
191
|
+
if (!overlayEnabled) return;
|
|
192
|
+
overlayHtml = "";
|
|
193
|
+
writeOverlayFiles();
|
|
175
194
|
}
|
|
176
|
-
|
|
195
|
+
const indent = " ".repeat(4);
|
|
177
196
|
function logToOverlay(msg, error = false) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const msgHtml = msg.replace(/\n/g, "\n<br/>").replace(/\s{2}/g, indent);
|
|
185
|
-
if (overlayHtml) {
|
|
186
|
-
overlayHtml += `<br/>${msgHtml}`;
|
|
187
|
-
} else {
|
|
188
|
-
overlayHtml = `${msgHtml}`;
|
|
189
|
-
}
|
|
190
|
-
writeOverlayFiles();
|
|
197
|
+
if (!overlayEnabled) return;
|
|
198
|
+
if (error) msg = `<span class="overlay-error">${msg}</span>`;
|
|
199
|
+
const msgHtml = msg.replace(/\n/g, "\n<br/>").replace(/\s{2}/g, indent);
|
|
200
|
+
if (overlayHtml) overlayHtml += `<br/>${msgHtml}`;
|
|
201
|
+
else overlayHtml = `${msgHtml}`;
|
|
202
|
+
writeOverlayFiles();
|
|
191
203
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/context/spawn-child.ts
|
|
206
|
+
/**
|
|
207
|
+
* Spawn a child process that runs the given command.
|
|
208
|
+
*
|
|
209
|
+
* @param cwd The directory in which the command will be executed.
|
|
210
|
+
* @param command The command to execute.
|
|
211
|
+
* @param args The arguments to pass to the command.
|
|
212
|
+
* @param abortSignal The signal used to abort the process.
|
|
213
|
+
* @param opts Additional options to configure the process.
|
|
214
|
+
* @returns The output of the process.
|
|
215
|
+
*/
|
|
204
216
|
function spawnChild(cwd, command, args, abortSignal, opts) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
childProc.on("close", (code, signal) => {
|
|
265
|
-
abortSignal?.removeEventListener("abort", abortHandler);
|
|
266
|
-
childProc = void 0;
|
|
267
|
-
if (signal) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const processOutput = {
|
|
271
|
-
exitCode: code,
|
|
272
|
-
stdoutMessages,
|
|
273
|
-
stderrMessages
|
|
274
|
-
};
|
|
275
|
-
if (code === 0) {
|
|
276
|
-
resolve(processOutput);
|
|
277
|
-
} else if (!signal) {
|
|
278
|
-
if (opts?.ignoreError === true) {
|
|
279
|
-
resolve(processOutput);
|
|
280
|
-
} else {
|
|
281
|
-
reject(new Error(`Child process failed (code=${code})`));
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
});
|
|
217
|
+
return new Promise((resolve, reject) => {
|
|
218
|
+
if (abortSignal?.aborted) {
|
|
219
|
+
reject(/* @__PURE__ */ new Error("ABORT"));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
let childProc;
|
|
223
|
+
const localLog = (s, err = false) => {
|
|
224
|
+
if (childProc === void 0) return;
|
|
225
|
+
log(err ? "error" : "info", s);
|
|
226
|
+
};
|
|
227
|
+
const abortHandler = () => {
|
|
228
|
+
if (childProc) {
|
|
229
|
+
log("info", "Killing existing build process...");
|
|
230
|
+
childProc.kill("SIGKILL");
|
|
231
|
+
childProc = void 0;
|
|
232
|
+
}
|
|
233
|
+
reject(/* @__PURE__ */ new Error("ABORT"));
|
|
234
|
+
};
|
|
235
|
+
abortSignal?.addEventListener("abort", abortHandler, { once: true });
|
|
236
|
+
const stdoutMessages = [];
|
|
237
|
+
const stderrMessages = [];
|
|
238
|
+
const logMessage = (msg, err) => {
|
|
239
|
+
let includeMessage = true;
|
|
240
|
+
if (opts?.ignoredMessageFilter && msg.trim().startsWith(opts.ignoredMessageFilter)) includeMessage = false;
|
|
241
|
+
if (includeMessage) {
|
|
242
|
+
const lines = msg.trim().split("\n");
|
|
243
|
+
for (const line of lines) localLog(` ${line}`, err);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
childProc = spawn(command, args, { cwd });
|
|
247
|
+
childProc.stdout.on("data", (data) => {
|
|
248
|
+
const msg = data.toString();
|
|
249
|
+
if (opts?.captureOutput === true) stdoutMessages.push(msg);
|
|
250
|
+
if (opts?.logOutput !== false) logMessage(msg, false);
|
|
251
|
+
});
|
|
252
|
+
childProc.stderr.on("data", (data) => {
|
|
253
|
+
const msg = data.toString();
|
|
254
|
+
if (opts?.captureOutput === true) stderrMessages.push(msg);
|
|
255
|
+
if (opts?.logOutput !== false) logMessage(msg, true);
|
|
256
|
+
});
|
|
257
|
+
childProc.on("error", (err) => {
|
|
258
|
+
localLog(`Process error: ${err}`, true);
|
|
259
|
+
});
|
|
260
|
+
childProc.on("close", (code, signal) => {
|
|
261
|
+
abortSignal?.removeEventListener("abort", abortHandler);
|
|
262
|
+
childProc = void 0;
|
|
263
|
+
if (signal) return;
|
|
264
|
+
const processOutput = {
|
|
265
|
+
exitCode: code,
|
|
266
|
+
stdoutMessages,
|
|
267
|
+
stderrMessages
|
|
268
|
+
};
|
|
269
|
+
if (code === 0) resolve(processOutput);
|
|
270
|
+
else if (!signal) {
|
|
271
|
+
if (opts?.ignoreError === true) resolve(processOutput);
|
|
272
|
+
else reject(/* @__PURE__ */ new Error(`Child process failed (code=${code})`));
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
286
276
|
}
|
|
287
|
-
|
|
288
|
-
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/context/context.ts
|
|
279
|
+
/**
|
|
280
|
+
* Provides access to common functionality that is needed during the build process.
|
|
281
|
+
* This is passed to most plugin functions.
|
|
282
|
+
*/
|
|
289
283
|
var BuildContext = class {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
284
|
+
/**
|
|
285
|
+
* @param config The resolved configuration.
|
|
286
|
+
* @hidden
|
|
287
|
+
*/
|
|
288
|
+
constructor(config, stagedFiles, abortSignal) {
|
|
289
|
+
this.config = config;
|
|
290
|
+
this.stagedFiles = stagedFiles;
|
|
291
|
+
this.abortSignal = abortSignal;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Log a message to the console and/or the in-browser overlay panel.
|
|
295
|
+
*
|
|
296
|
+
* @param level The log level (verbose, info, error).
|
|
297
|
+
* @param msg The message.
|
|
298
|
+
*/
|
|
299
|
+
log(level, msg) {
|
|
300
|
+
log(level, msg);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Prepare for writing a file to the staged directory.
|
|
304
|
+
*
|
|
305
|
+
* This will add the path to the array of tracked files and will create the
|
|
306
|
+
* staged directory if needed.
|
|
307
|
+
*
|
|
308
|
+
* @param srcDir The directory underneath the configured `staged` directory where
|
|
309
|
+
* the file will be written (this must be a relative path).
|
|
310
|
+
* @param srcFile The name of the file as written to the `staged` directory.
|
|
311
|
+
* @param dstDir The absolute path to the destination directory where the staged
|
|
312
|
+
* file will be copied when the build has completed.
|
|
313
|
+
* @param dstFile The name of the file as written to the destination directory.
|
|
314
|
+
* @return The absolute path to the staged file.
|
|
315
|
+
*/
|
|
316
|
+
prepareStagedFile(srcDir, srcFile, dstDir, dstFile) {
|
|
317
|
+
return this.stagedFiles.prepareStagedFile(srcDir, srcFile, dstDir, dstFile);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Write a file to the staged directory.
|
|
321
|
+
*
|
|
322
|
+
* This file will be copied (along with other staged files) into the destination
|
|
323
|
+
* directory only after the build process has completed. Copying all staged files
|
|
324
|
+
* at once helps improve the local development experience by making it so that
|
|
325
|
+
* live reloading tools only need to refresh once instead of every time a build
|
|
326
|
+
* file is written.
|
|
327
|
+
*
|
|
328
|
+
* @param srcDir The directory underneath the configured `staged` directory where
|
|
329
|
+
* the file will be written (this must be a relative path).
|
|
330
|
+
* @param dstDir The absolute path to the destination directory where the staged
|
|
331
|
+
* file will be copied when the build has completed.
|
|
332
|
+
* @param filename The name of the file.
|
|
333
|
+
* @param content The file content.
|
|
334
|
+
*/
|
|
335
|
+
writeStagedFile(srcDir, dstDir, filename, content) {
|
|
336
|
+
this.stagedFiles.writeStagedFile(srcDir, dstDir, filename, content);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Spawn a child process that runs the given command.
|
|
340
|
+
*
|
|
341
|
+
* @param cwd The directory in which the command will be executed.
|
|
342
|
+
* @param command The command to execute.
|
|
343
|
+
* @param args The arguments to pass to the command.
|
|
344
|
+
* @param opts Additional options to configure the process.
|
|
345
|
+
* @returns The output of the process.
|
|
346
|
+
*/
|
|
347
|
+
spawnChild(cwd, command, args, opts) {
|
|
348
|
+
return spawnChild(cwd, command, args, this.abortSignal, opts);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Format a (subscripted or non-subscripted) model variable name into a canonical
|
|
352
|
+
* identifier (with special characters converted to underscore, and subscript/dimension
|
|
353
|
+
* parts separated by commas).
|
|
354
|
+
*
|
|
355
|
+
* @param name The name of the variable in the source model, e.g., `Variable name[DimA, B2]`.
|
|
356
|
+
* @returns The canonical identifier for the given name, e.g., `_variable_name[_dima,_b2]`.
|
|
357
|
+
*/
|
|
358
|
+
canonicalVarId(name) {
|
|
359
|
+
return canonicalVarId(name);
|
|
360
|
+
}
|
|
367
361
|
};
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
371
|
-
import { join as joinPath2 } from "path";
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region src/context/staged-files.ts
|
|
372
364
|
var StagedFiles = class {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
const fullSrcPath = this.getStagedFilePath(f.srcDir, f.srcFile);
|
|
488
|
-
const fullDstPath = joinPath2(f.dstDir, f.dstFile);
|
|
489
|
-
const needsCopy = filesDiffer(fullSrcPath, fullDstPath);
|
|
490
|
-
if (needsCopy) {
|
|
491
|
-
log("verbose", ` Copying ${f.srcFile} to ${fullDstPath}`);
|
|
492
|
-
copyFileSync(fullSrcPath, fullDstPath);
|
|
493
|
-
}
|
|
494
|
-
return needsCopy;
|
|
495
|
-
}
|
|
365
|
+
constructor(prepDir) {
|
|
366
|
+
this.stagedFiles = [];
|
|
367
|
+
this.baseStagedDir = join(prepDir, "staged");
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Prepare for writing a file to the staged directory.
|
|
371
|
+
*
|
|
372
|
+
* This will add the path to the array of tracked files and will create the
|
|
373
|
+
* staged directory if needed.
|
|
374
|
+
*
|
|
375
|
+
* @param srcDir The directory underneath the configured `staged` directory where
|
|
376
|
+
* the file will be written (this must be a relative path).
|
|
377
|
+
* @param srcFile The name of the file as written to the `staged` directory.
|
|
378
|
+
* @param dstDir The absolute path to the destination directory where the staged
|
|
379
|
+
* file will be copied when the build has completed.
|
|
380
|
+
* @param dstFile The name of the file as written to the destination directory.
|
|
381
|
+
* @return The absolute path to the staged file.
|
|
382
|
+
*/
|
|
383
|
+
prepareStagedFile(srcDir, srcFile, dstDir, dstFile) {
|
|
384
|
+
const stagedFile = {
|
|
385
|
+
srcDir,
|
|
386
|
+
srcFile,
|
|
387
|
+
dstDir,
|
|
388
|
+
dstFile
|
|
389
|
+
};
|
|
390
|
+
if (this.stagedFiles.indexOf(stagedFile) < 0) this.stagedFiles.push(stagedFile);
|
|
391
|
+
const stagedDir = join(this.baseStagedDir, srcDir);
|
|
392
|
+
if (!existsSync(stagedDir)) mkdirSync(stagedDir, { recursive: true });
|
|
393
|
+
return join(stagedDir, srcFile);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Write a file to the staged directory.
|
|
397
|
+
*
|
|
398
|
+
* This file will be copied (along with other staged files) into the destination
|
|
399
|
+
* directory only after the build process has completed. Copying all staged files
|
|
400
|
+
* at once helps improve the local development experience by making it so that
|
|
401
|
+
* live reloading tools only need to refresh once instead of every time a build
|
|
402
|
+
* file is written.
|
|
403
|
+
*
|
|
404
|
+
* @param srcDir The directory underneath the configured `staged` directory where
|
|
405
|
+
* the file will be written (this must be a relative path).
|
|
406
|
+
* @param dstDir The absolute path to the destination directory where the staged
|
|
407
|
+
* file will be copied when the build has completed.
|
|
408
|
+
* @param filename The name of the file.
|
|
409
|
+
* @param content The file content.
|
|
410
|
+
*/
|
|
411
|
+
writeStagedFile(srcDir, dstDir, filename, content) {
|
|
412
|
+
const stagedFilePath = this.prepareStagedFile(srcDir, filename, dstDir, filename);
|
|
413
|
+
writeFileSync(stagedFilePath, content);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Return the absolute path to the staged file for the given source directory and file name.
|
|
417
|
+
*
|
|
418
|
+
* @param srcDir The directory underneath the configured `staged` directory where
|
|
419
|
+
* the file would be written initially (this must be a relative path).
|
|
420
|
+
* @param srcFile The name of the file.
|
|
421
|
+
*/
|
|
422
|
+
getStagedFilePath(srcDir, srcFile) {
|
|
423
|
+
return join(this.baseStagedDir, srcDir, srcFile);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Return true if the staged file exists for the given source directory and file name.
|
|
427
|
+
*
|
|
428
|
+
* @param srcDir The directory underneath the configured `staged` directory where
|
|
429
|
+
* the file would be written initially (this must be a relative path).
|
|
430
|
+
* @param srcFile The name of the file.
|
|
431
|
+
*/
|
|
432
|
+
stagedFileExists(srcDir, srcFile) {
|
|
433
|
+
const fullSrcPath = this.getStagedFilePath(srcDir, srcFile);
|
|
434
|
+
return existsSync(fullSrcPath);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Return true if the destination file exists for the given source directory and file name.
|
|
438
|
+
*
|
|
439
|
+
* @param srcDir The directory underneath the configured `staged` directory where
|
|
440
|
+
* the file would be written initially (this must be a relative path).
|
|
441
|
+
* @param srcFile The name of the file.
|
|
442
|
+
*/
|
|
443
|
+
destinationFileExists(srcDir, srcFile) {
|
|
444
|
+
const f = this.stagedFiles.find((f) => f.srcDir === srcDir && f.srcFile === srcFile);
|
|
445
|
+
if (f === void 0) return false;
|
|
446
|
+
const fullDstPath = join(f.dstDir, f.dstFile);
|
|
447
|
+
return existsSync(fullDstPath);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Copy staged files to their destination; this will only copy the staged
|
|
451
|
+
* files if they are different than the existing destination files. We
|
|
452
|
+
* copy the files in a batch like this so that hot module reload is only
|
|
453
|
+
* triggered once at the end of the whole build process.
|
|
454
|
+
*/
|
|
455
|
+
copyChangedFiles() {
|
|
456
|
+
log("info", "Copying changed files into place...");
|
|
457
|
+
for (const f of this.stagedFiles) this.copyStagedFile(f);
|
|
458
|
+
log("info", "Done copying files");
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Copy a file from the `staged` directory to its destination. If the file already
|
|
462
|
+
* exists in the destination directory and has the same contents as the source file,
|
|
463
|
+
* the file will not be copied and this function will return false.
|
|
464
|
+
*
|
|
465
|
+
* @param f The staged file entry.
|
|
466
|
+
*/
|
|
467
|
+
copyStagedFile(f) {
|
|
468
|
+
if (!existsSync(f.dstDir)) mkdirSync(f.dstDir, { recursive: true });
|
|
469
|
+
const fullSrcPath = this.getStagedFilePath(f.srcDir, f.srcFile);
|
|
470
|
+
const fullDstPath = join(f.dstDir, f.dstFile);
|
|
471
|
+
const needsCopy = filesDiffer(fullSrcPath, fullDstPath);
|
|
472
|
+
if (needsCopy) {
|
|
473
|
+
log("verbose", ` Copying ${f.srcFile} to ${fullDstPath}`);
|
|
474
|
+
copyFileSync(fullSrcPath, fullDstPath);
|
|
475
|
+
}
|
|
476
|
+
return needsCopy;
|
|
477
|
+
}
|
|
496
478
|
};
|
|
479
|
+
/**
|
|
480
|
+
* Return true if both files exist at the given paths and have the same contents, false otherwise.
|
|
481
|
+
*/
|
|
497
482
|
function filesDiffer(aPath, bPath) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
return !aBuf.equals(bBuf);
|
|
507
|
-
}
|
|
508
|
-
} else {
|
|
509
|
-
return true;
|
|
510
|
-
}
|
|
483
|
+
if (existsSync(aPath) && existsSync(bPath)) {
|
|
484
|
+
if (statSync(aPath).size !== statSync(bPath).size) return true;
|
|
485
|
+
else {
|
|
486
|
+
const aBuf = readFileSync(aPath);
|
|
487
|
+
const bBuf = readFileSync(bPath);
|
|
488
|
+
return !aBuf.equals(bBuf);
|
|
489
|
+
}
|
|
490
|
+
} else return true;
|
|
511
491
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/build/impl/gen-model.ts
|
|
494
|
+
/**
|
|
495
|
+
* Generate the model. This will run the core SDEverywhere code generation steps
|
|
496
|
+
* and will also invoke the following plugin functions:
|
|
497
|
+
* - `preProcessMdl`
|
|
498
|
+
* - `postProcessMdl`
|
|
499
|
+
* - `preGenerateC`
|
|
500
|
+
* - `postGenerateC`
|
|
501
|
+
*/
|
|
516
502
|
async function generateModel(context, plugins) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (config.genFormat === "js") {
|
|
560
|
-
const outputJsFile = "generated-model.js";
|
|
561
|
-
const stagedOutputJsPath = context.prepareStagedFile("model", outputJsFile, prepDir, outputJsFile);
|
|
562
|
-
await copyFile(generatedCodePath, stagedOutputJsPath);
|
|
563
|
-
}
|
|
564
|
-
if (config.outListingFile) {
|
|
565
|
-
const srcListingJsonPath = joinPath3(prepDir, "build", "processed.json");
|
|
566
|
-
const stagedDir = "model";
|
|
567
|
-
const stagedFile = "listing.json";
|
|
568
|
-
const dstDir = dirname2(config.outListingFile);
|
|
569
|
-
const dstFile = basename(config.outListingFile);
|
|
570
|
-
const stagedListingJsonPath = context.prepareStagedFile(stagedDir, stagedFile, dstDir, dstFile);
|
|
571
|
-
await copyFile(srcListingJsonPath, stagedListingJsonPath);
|
|
572
|
-
}
|
|
573
|
-
const t1 = performance.now();
|
|
574
|
-
const elapsed = ((t1 - t0) / 1e3).toFixed(1);
|
|
575
|
-
log("info", `Done generating model (${elapsed}s)`);
|
|
503
|
+
const config = context.config;
|
|
504
|
+
if (config.modelFiles.length === 0) {
|
|
505
|
+
log("info", "No model input files specified, skipping model generation steps");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
log("info", "Generating model...");
|
|
509
|
+
const t0 = performance.now();
|
|
510
|
+
const prepDir = config.prepDir;
|
|
511
|
+
const sdeCmdPath = config.sdeCmdPath;
|
|
512
|
+
for (const plugin of plugins) if (plugin.preProcessMdl) await plugin.preProcessMdl(context);
|
|
513
|
+
if (config.modelFiles.length === 1) await preprocessMdl(context, sdeCmdPath, prepDir, config.modelFiles[0]);
|
|
514
|
+
else await flattenMdls(context, sdeCmdPath, prepDir, config.modelFiles);
|
|
515
|
+
for (const plugin of plugins) if (plugin.postProcessMdl) {
|
|
516
|
+
const mdlPath = join(prepDir, "processed.mdl");
|
|
517
|
+
let mdlContent = await readFile(mdlPath, "utf8");
|
|
518
|
+
mdlContent = await plugin.postProcessMdl(context, mdlContent);
|
|
519
|
+
await writeFile(mdlPath, mdlContent);
|
|
520
|
+
}
|
|
521
|
+
for (const plugin of plugins) if (plugin.preGenerateCode) await plugin.preGenerateCode(context, config.genFormat);
|
|
522
|
+
await generateCode(context, config.sdeDir, sdeCmdPath, prepDir);
|
|
523
|
+
const generatedCodeFile = `processed.${config.genFormat}`;
|
|
524
|
+
const generatedCodePath = join(prepDir, "build", generatedCodeFile);
|
|
525
|
+
for (const plugin of plugins) if (plugin.postGenerateCode) {
|
|
526
|
+
let generatedCodeContent = await readFile(generatedCodePath, "utf8");
|
|
527
|
+
generatedCodeContent = await plugin.postGenerateCode(context, config.genFormat, generatedCodeContent);
|
|
528
|
+
await writeFile(generatedCodePath, generatedCodeContent);
|
|
529
|
+
}
|
|
530
|
+
if (config.genFormat === "js") {
|
|
531
|
+
const outputJsFile = "generated-model.js";
|
|
532
|
+
const stagedOutputJsPath = context.prepareStagedFile("model", outputJsFile, prepDir, outputJsFile);
|
|
533
|
+
await copyFile(generatedCodePath, stagedOutputJsPath);
|
|
534
|
+
}
|
|
535
|
+
if (config.outListingFile) {
|
|
536
|
+
const srcListingJsonPath = join(prepDir, "build", "processed.json");
|
|
537
|
+
const stagedDir = "model";
|
|
538
|
+
const stagedFile = "listing.json";
|
|
539
|
+
const dstDir = dirname(config.outListingFile);
|
|
540
|
+
const dstFile = basename(config.outListingFile);
|
|
541
|
+
const stagedListingJsonPath = context.prepareStagedFile(stagedDir, stagedFile, dstDir, dstFile);
|
|
542
|
+
await copyFile(srcListingJsonPath, stagedListingJsonPath);
|
|
543
|
+
}
|
|
544
|
+
log("info", `Done generating model (${((performance.now() - t0) / 1e3).toFixed(1)}s)`);
|
|
576
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* Preprocess a single mdl file and copy the resulting `processed.mdl` to the prep directory.
|
|
548
|
+
*/
|
|
577
549
|
async function preprocessMdl(context, sdeCmdPath, prepDir, modelFile) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
throw new Error(`Failed to preprocess mdl file: 'sde generate' command failed (code=${ppOutput.exitCode})`);
|
|
589
|
-
}
|
|
590
|
-
await copyFile(joinPath3(prepDir, "build", "processed.mdl"), joinPath3(prepDir, "processed.mdl"));
|
|
550
|
+
log("verbose", " Preprocessing mdl file");
|
|
551
|
+
await copyFile(modelFile, join(prepDir, "processed.mdl"));
|
|
552
|
+
const command = sdeCmdPath;
|
|
553
|
+
const ppOutput = await context.spawnChild(prepDir, command, [
|
|
554
|
+
"generate",
|
|
555
|
+
"--preprocess",
|
|
556
|
+
"processed.mdl"
|
|
557
|
+
], { ignoreError: true });
|
|
558
|
+
if (ppOutput.exitCode !== 0) throw new Error(`Failed to preprocess mdl file: 'sde generate' command failed (code=${ppOutput.exitCode})`);
|
|
559
|
+
await copyFile(join(prepDir, "build", "processed.mdl"), join(prepDir, "processed.mdl"));
|
|
591
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Flatten multiple mdl files and copy the resulting `processed.mdl` to the prep directory.
|
|
563
|
+
*/
|
|
592
564
|
async function flattenMdls(context, sdeCmdPath, prepDir, modelFiles) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
log("error", ` ${line}`);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`);
|
|
623
|
-
} else if (output.exitCode !== 0) {
|
|
624
|
-
throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`);
|
|
625
|
-
}
|
|
626
|
-
await copyFile(joinPath3(prepDir, "build", "processed.mdl"), joinPath3(prepDir, "processed.mdl"));
|
|
565
|
+
log("verbose", " Flattening and preprocessing mdl files");
|
|
566
|
+
const command = sdeCmdPath;
|
|
567
|
+
const args = [];
|
|
568
|
+
args.push("flatten");
|
|
569
|
+
args.push("processed.mdl");
|
|
570
|
+
args.push("--inputs");
|
|
571
|
+
for (const path of modelFiles) args.push(path);
|
|
572
|
+
const output = await context.spawnChild(prepDir, command, args, {
|
|
573
|
+
logOutput: false,
|
|
574
|
+
captureOutput: true,
|
|
575
|
+
ignoreError: true
|
|
576
|
+
});
|
|
577
|
+
let flattenErrors = false;
|
|
578
|
+
for (const msg of output.stderrMessages) if (msg.includes("ERROR")) {
|
|
579
|
+
flattenErrors = true;
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
if (flattenErrors) {
|
|
583
|
+
log("error", "There were errors reported when flattening the model:");
|
|
584
|
+
for (const msg of output.stderrMessages) {
|
|
585
|
+
const lines = msg.split("\n");
|
|
586
|
+
for (const line of lines) log("error", ` ${line}`);
|
|
587
|
+
}
|
|
588
|
+
throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`);
|
|
589
|
+
} else if (output.exitCode !== 0) throw new Error(`Failed to flatten mdl files: 'sde flatten' command failed (code=${output.exitCode})`);
|
|
590
|
+
await copyFile(join(prepDir, "build", "processed.mdl"), join(prepDir, "processed.mdl"));
|
|
627
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* Generate a JS or C file from the `processed.mdl` file.
|
|
594
|
+
*/
|
|
628
595
|
async function generateCode(context, sdeDir, sdeCmdPath, prepDir) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
for (const file of files) {
|
|
655
|
-
if (file.endsWith(".c") || file.endsWith(".h")) {
|
|
656
|
-
copyOps.push(copyFile(joinPath3(sdeCDir, file), joinPath3(buildDir, file)));
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
await Promise.all(copyOps);
|
|
660
|
-
}
|
|
596
|
+
const genFormat = context.config.genFormat;
|
|
597
|
+
const genFormatName = genFormat.toUpperCase();
|
|
598
|
+
log("verbose", ` Generating ${genFormatName} code`);
|
|
599
|
+
const dataDir = dirname(context.config.modelFiles[0]);
|
|
600
|
+
const command = sdeCmdPath;
|
|
601
|
+
const genCmdArgs = [
|
|
602
|
+
"generate",
|
|
603
|
+
`--outformat=${genFormat}`,
|
|
604
|
+
"--list",
|
|
605
|
+
"--spec",
|
|
606
|
+
"spec.json",
|
|
607
|
+
"--datadir",
|
|
608
|
+
dataDir,
|
|
609
|
+
"processed"
|
|
610
|
+
];
|
|
611
|
+
const genCmdOutput = await context.spawnChild(prepDir, command, genCmdArgs, { ignoreError: true });
|
|
612
|
+
if (genCmdOutput.exitCode !== 0) throw new Error(`Failed to generate ${genFormatName} code: 'sde generate' command failed (code=${genCmdOutput.exitCode})`);
|
|
613
|
+
if (genFormat === "c") {
|
|
614
|
+
const buildDir = join(prepDir, "build");
|
|
615
|
+
const sdeCDir = join(sdeDir, "src", "c");
|
|
616
|
+
const files = await readdir(sdeCDir);
|
|
617
|
+
const copyOps = [];
|
|
618
|
+
for (const file of files) if (file.endsWith(".c") || file.endsWith(".h")) copyOps.push(copyFile(join(sdeCDir, file), join(buildDir, file)));
|
|
619
|
+
await Promise.all(copyOps);
|
|
620
|
+
}
|
|
661
621
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
import { glob } from "tinyglobby";
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/build/impl/hash-files.ts
|
|
624
|
+
/**
|
|
625
|
+
* Asynchronously compute the hash of the files that are inputs to the model
|
|
626
|
+
* build process.
|
|
627
|
+
*/
|
|
669
628
|
async function computeInputFilesHash(config) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
let hash = "";
|
|
686
|
-
for (const inputFile of inputFiles) {
|
|
687
|
-
hash += await hashFile(inputFile);
|
|
688
|
-
}
|
|
689
|
-
return hash;
|
|
629
|
+
const inputFiles = [];
|
|
630
|
+
const specFile = join$1(config.prepDir, "spec.json");
|
|
631
|
+
inputFiles.push(specFile);
|
|
632
|
+
if (config.modelInputPaths && config.modelInputPaths.length > 0) for (const globPath of config.modelInputPaths) {
|
|
633
|
+
const paths = await glob(globPath, {
|
|
634
|
+
cwd: config.rootDir,
|
|
635
|
+
absolute: true,
|
|
636
|
+
onlyFiles: true
|
|
637
|
+
});
|
|
638
|
+
inputFiles.push(...paths);
|
|
639
|
+
}
|
|
640
|
+
else inputFiles.push(...config.modelFiles);
|
|
641
|
+
let hash = "";
|
|
642
|
+
for (const inputFile of inputFiles) hash += await hashFile(inputFile);
|
|
643
|
+
return hash;
|
|
690
644
|
}
|
|
645
|
+
/**
|
|
646
|
+
* Asynchronously compute the hash of a single file. The returned hash covers the
|
|
647
|
+
* base name of the file followed by its contents, so that renaming a file changes
|
|
648
|
+
* the hash even if its contents are unchanged.
|
|
649
|
+
*
|
|
650
|
+
* @param file The absolute path of the file to hash.
|
|
651
|
+
* @returns The base64-encoded SHA-1 digest for the file.
|
|
652
|
+
*/
|
|
691
653
|
async function hashFile(file) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
654
|
+
const hash = createHash("sha1");
|
|
655
|
+
hash.update(basename$1(file));
|
|
656
|
+
await pipeline(createReadStream(file), hash);
|
|
657
|
+
return hash.digest("base64");
|
|
696
658
|
}
|
|
697
|
-
|
|
698
|
-
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region src/build/impl/build-once.ts
|
|
661
|
+
/**
|
|
662
|
+
* Perform a single build.
|
|
663
|
+
*
|
|
664
|
+
* This will return an error if a build failure occurred, or if a plugin encounters
|
|
665
|
+
* an error. Otherwise, it will return true if the build and all plugins
|
|
666
|
+
* succeeded, or false if a plugin wants to report a "soft" failure (for example,
|
|
667
|
+
* if the model check plugin reports failing checks).
|
|
668
|
+
*
|
|
669
|
+
* @param config The resolved build configuration.
|
|
670
|
+
* @param plugins The configured plugins.
|
|
671
|
+
* @param options Options specific to the build process.
|
|
672
|
+
* @return An `ok` result with true if the build and all plugins succeeded, or false if
|
|
673
|
+
* one or more plugins failed; otherwise, an `err` result if there was a hard error.
|
|
674
|
+
*/
|
|
699
675
|
async function buildOnce(config, userConfig, plugins, options) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (!pluginSucceeded) {
|
|
754
|
-
succeeded = false;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
stagedFiles.copyChangedFiles();
|
|
759
|
-
for (const plugin of plugins) {
|
|
760
|
-
if (plugin.postBuild) {
|
|
761
|
-
const pluginSucceeded = await plugin.postBuild(context, modelSpec);
|
|
762
|
-
if (!pluginSucceeded) {
|
|
763
|
-
succeeded = false;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
if (config.mode === "development") {
|
|
768
|
-
log("info", "Waiting for changes...\n");
|
|
769
|
-
clearOverlay();
|
|
770
|
-
}
|
|
771
|
-
} catch (e) {
|
|
772
|
-
if (e.message !== "ABORT") {
|
|
773
|
-
writeFileSync3(modelHashPath, "");
|
|
774
|
-
return err2(e);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
return ok2(succeeded);
|
|
676
|
+
const stagedFiles = new StagedFiles(config.prepDir);
|
|
677
|
+
const context = new BuildContext(config, stagedFiles, options.abortSignal);
|
|
678
|
+
const modelHashPath = join(config.prepDir, "model-hash.txt");
|
|
679
|
+
let succeeded = true;
|
|
680
|
+
try {
|
|
681
|
+
const userModelSpec = await userConfig.modelSpec(context);
|
|
682
|
+
if (userModelSpec === void 0) return err(/* @__PURE__ */ new Error("The model spec must be defined"));
|
|
683
|
+
const modelSpec = resolveModelSpec(userModelSpec);
|
|
684
|
+
for (const plugin of plugins) if (plugin.preGenerate) await plugin.preGenerate(context, modelSpec);
|
|
685
|
+
const specJson = {
|
|
686
|
+
inputVarNames: modelSpec.inputVarNames,
|
|
687
|
+
outputVarNames: modelSpec.outputVarNames,
|
|
688
|
+
datFiles: modelSpec.datFiles,
|
|
689
|
+
bundleListing: modelSpec.bundleListing,
|
|
690
|
+
customConstants: modelSpec.customConstants,
|
|
691
|
+
customLookups: modelSpec.customLookups,
|
|
692
|
+
customOutputs: modelSpec.customOutputs,
|
|
693
|
+
directData: modelSpec.directData,
|
|
694
|
+
dimensionFamilies: modelSpec.dimensionFamilies,
|
|
695
|
+
specialSeparationDims: modelSpec.specialSeparationDims,
|
|
696
|
+
separateAllVarsWithDims: modelSpec.separateAllVarsWithDims
|
|
697
|
+
};
|
|
698
|
+
const specPath = join(config.prepDir, "spec.json");
|
|
699
|
+
await writeFile(specPath, JSON.stringify(specJson, null, 2));
|
|
700
|
+
let previousModelHash;
|
|
701
|
+
if (existsSync(modelHashPath)) previousModelHash = readFileSync(modelHashPath, "utf8");
|
|
702
|
+
else previousModelHash = "NONE";
|
|
703
|
+
const inputFilesHash = await computeInputFilesHash(config);
|
|
704
|
+
let needModelGen;
|
|
705
|
+
if (options.forceModelGen === true) needModelGen = true;
|
|
706
|
+
else needModelGen = inputFilesHash !== previousModelHash;
|
|
707
|
+
if (needModelGen) {
|
|
708
|
+
await generateModel(context, plugins);
|
|
709
|
+
writeFileSync(modelHashPath, inputFilesHash);
|
|
710
|
+
} else log("info", "Skipping model code generation; already up-to-date");
|
|
711
|
+
for (const plugin of plugins) if (plugin.postGenerate) {
|
|
712
|
+
if (!await plugin.postGenerate(context, modelSpec)) succeeded = false;
|
|
713
|
+
}
|
|
714
|
+
stagedFiles.copyChangedFiles();
|
|
715
|
+
for (const plugin of plugins) if (plugin.postBuild) {
|
|
716
|
+
if (!await plugin.postBuild(context, modelSpec)) succeeded = false;
|
|
717
|
+
}
|
|
718
|
+
if (config.mode === "development") {
|
|
719
|
+
log("info", "Waiting for changes...\n");
|
|
720
|
+
clearOverlay();
|
|
721
|
+
}
|
|
722
|
+
} catch (e) {
|
|
723
|
+
if (e.message !== "ABORT") {
|
|
724
|
+
writeFileSync(modelHashPath, "");
|
|
725
|
+
return err(e);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return ok(succeeded);
|
|
778
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* Convert a `ModelSpec` instance to a `ResolvedModelSpec` instance.
|
|
732
|
+
*
|
|
733
|
+
* @param userModelSpec The `ModelSpec` instance returned by the `UserConfig`.
|
|
734
|
+
*/
|
|
779
735
|
function resolveModelSpec(userModelSpec) {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
return {
|
|
839
|
-
// Carry through the properties (for example, `directData` and `specialSeparationDims`)
|
|
840
|
-
// that are shared with the compile package and don't need to be resolved here
|
|
841
|
-
...modelSpec,
|
|
842
|
-
inputVarNames,
|
|
843
|
-
inputs: inputSpecs,
|
|
844
|
-
outputVarNames,
|
|
845
|
-
outputs: outputSpecs,
|
|
846
|
-
datFiles: modelSpec.datFiles || [],
|
|
847
|
-
bundleListing: modelSpec.bundleListing === true,
|
|
848
|
-
customConstants,
|
|
849
|
-
customLookups,
|
|
850
|
-
customOutputs
|
|
851
|
-
};
|
|
736
|
+
const { options, ...configuredProps } = userModelSpec;
|
|
737
|
+
const modelSpec = {
|
|
738
|
+
...options,
|
|
739
|
+
...configuredProps
|
|
740
|
+
};
|
|
741
|
+
let inputVarNames;
|
|
742
|
+
let inputSpecs;
|
|
743
|
+
if (modelSpec.inputs.length > 0) {
|
|
744
|
+
if (typeof modelSpec.inputs[0] === "string") {
|
|
745
|
+
inputVarNames = modelSpec.inputs;
|
|
746
|
+
inputSpecs = inputVarNames.map((varName) => {
|
|
747
|
+
return { varName };
|
|
748
|
+
});
|
|
749
|
+
} else {
|
|
750
|
+
inputSpecs = modelSpec.inputs;
|
|
751
|
+
inputVarNames = inputSpecs.map((spec) => spec.varName);
|
|
752
|
+
}
|
|
753
|
+
} else {
|
|
754
|
+
inputVarNames = [];
|
|
755
|
+
inputSpecs = [];
|
|
756
|
+
}
|
|
757
|
+
let outputVarNames;
|
|
758
|
+
let outputSpecs;
|
|
759
|
+
if (modelSpec.outputs.length > 0) {
|
|
760
|
+
if (typeof modelSpec.outputs[0] === "string") {
|
|
761
|
+
outputVarNames = modelSpec.outputs;
|
|
762
|
+
outputSpecs = outputVarNames.map((varName) => {
|
|
763
|
+
return { varName };
|
|
764
|
+
});
|
|
765
|
+
} else {
|
|
766
|
+
outputSpecs = modelSpec.outputs;
|
|
767
|
+
outputVarNames = outputSpecs.map((spec) => spec.varName);
|
|
768
|
+
}
|
|
769
|
+
} else {
|
|
770
|
+
outputVarNames = [];
|
|
771
|
+
outputSpecs = [];
|
|
772
|
+
}
|
|
773
|
+
let customConstants;
|
|
774
|
+
if (modelSpec.customConstants !== void 0) customConstants = modelSpec.customConstants;
|
|
775
|
+
else customConstants = false;
|
|
776
|
+
let customLookups;
|
|
777
|
+
if (modelSpec.customLookups !== void 0) customLookups = modelSpec.customLookups;
|
|
778
|
+
else customLookups = false;
|
|
779
|
+
let customOutputs;
|
|
780
|
+
if (modelSpec.customOutputs !== void 0) customOutputs = modelSpec.customOutputs;
|
|
781
|
+
else customOutputs = false;
|
|
782
|
+
return {
|
|
783
|
+
...modelSpec,
|
|
784
|
+
inputVarNames,
|
|
785
|
+
inputs: inputSpecs,
|
|
786
|
+
outputVarNames,
|
|
787
|
+
outputs: outputSpecs,
|
|
788
|
+
datFiles: modelSpec.datFiles || [],
|
|
789
|
+
bundleListing: modelSpec.bundleListing === true,
|
|
790
|
+
customConstants,
|
|
791
|
+
customLookups,
|
|
792
|
+
customOutputs
|
|
793
|
+
};
|
|
852
794
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region src/build/impl/watch-paths.ts
|
|
797
|
+
/**
|
|
798
|
+
* Resolve watch paths, expanding glob patterns into concrete file paths.
|
|
799
|
+
*
|
|
800
|
+
* This function doesn't attempt to expand directories. This is intentional
|
|
801
|
+
* as it allows chokidar to handle directory changes (added and removed files)
|
|
802
|
+
* instead of it being passed a specific set of files when the watch is first
|
|
803
|
+
* set up.
|
|
804
|
+
*
|
|
805
|
+
* @param patterns The watch paths to resolve (may include glob patterns).
|
|
806
|
+
* @param cwd The current working directory to resolve paths relative to.
|
|
807
|
+
* @returns An array of resolved paths. Non-glob paths are returned as-is,
|
|
808
|
+
* while glob patterns are expanded to absolute paths of matching files.
|
|
809
|
+
*/
|
|
860
810
|
function resolveWatchPaths(patterns, cwd) {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
cwd,
|
|
876
|
-
// Resolve to absolute paths
|
|
877
|
-
absolute: true
|
|
878
|
-
});
|
|
879
|
-
resolvedPaths.push(...paths);
|
|
880
|
-
}
|
|
881
|
-
return resolvedPaths;
|
|
811
|
+
const resolvedPaths = [];
|
|
812
|
+
const regularPaths = [];
|
|
813
|
+
const globPatterns = [];
|
|
814
|
+
for (const pattern of patterns) if (isDynamicPattern(pattern)) globPatterns.push(pattern);
|
|
815
|
+
else regularPaths.push(pattern);
|
|
816
|
+
resolvedPaths.push(...regularPaths);
|
|
817
|
+
if (globPatterns.length > 0) {
|
|
818
|
+
const paths = globSync(globPatterns, {
|
|
819
|
+
cwd,
|
|
820
|
+
absolute: true
|
|
821
|
+
});
|
|
822
|
+
resolvedPaths.push(...paths);
|
|
823
|
+
}
|
|
824
|
+
return resolvedPaths;
|
|
882
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Watch file paths and invoke callbacks when files are changed, added, or removed.
|
|
828
|
+
*
|
|
829
|
+
* This function sets up a file watcher using chokidar. Glob patterns in the paths
|
|
830
|
+
* are resolved before being passed to chokidar (since chokidar no longer supports
|
|
831
|
+
* glob patterns).
|
|
832
|
+
*
|
|
833
|
+
* @param patterns The paths to watch (may include glob patterns).
|
|
834
|
+
* @param cwd The current working directory to resolve paths relative to.
|
|
835
|
+
* @param onChange Callback invoked when a file is changed.
|
|
836
|
+
* @param onReady Optional callback invoked when the watcher is ready.
|
|
837
|
+
* @returns A cleanup function that closes the watcher.
|
|
838
|
+
*/
|
|
883
839
|
function watchPaths(patterns, cwd, onChange, onReady) {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
onChange(path);
|
|
904
|
-
});
|
|
905
|
-
if (onReady) {
|
|
906
|
-
watcher.on("ready", onReady);
|
|
907
|
-
}
|
|
908
|
-
return () => {
|
|
909
|
-
watcher.close();
|
|
910
|
-
};
|
|
840
|
+
const resolvedPathsToWatch = resolveWatchPaths(patterns, cwd);
|
|
841
|
+
const watcher = chokidar.watch(resolvedPathsToWatch, {
|
|
842
|
+
cwd,
|
|
843
|
+
ignoreInitial: true,
|
|
844
|
+
awaitWriteFinish: { stabilityThreshold: 200 }
|
|
845
|
+
});
|
|
846
|
+
watcher.on("change", (path) => {
|
|
847
|
+
onChange(path);
|
|
848
|
+
});
|
|
849
|
+
watcher.on("add", (path) => {
|
|
850
|
+
onChange(path);
|
|
851
|
+
});
|
|
852
|
+
watcher.on("unlink", (path) => {
|
|
853
|
+
onChange(path);
|
|
854
|
+
});
|
|
855
|
+
if (onReady) watcher.on("ready", onReady);
|
|
856
|
+
return () => {
|
|
857
|
+
watcher.close();
|
|
858
|
+
};
|
|
911
859
|
}
|
|
912
|
-
|
|
913
|
-
|
|
860
|
+
//#endregion
|
|
861
|
+
//#region src/build/impl/watch.ts
|
|
914
862
|
var BuildState = class {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
863
|
+
constructor() {
|
|
864
|
+
this.abortController = new AbortController();
|
|
865
|
+
}
|
|
918
866
|
};
|
|
919
867
|
function watch(config, userConfig, plugins) {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
}, delay);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
let watchPatterns;
|
|
957
|
-
if (config.watchPaths && config.watchPaths.length > 0) {
|
|
958
|
-
watchPatterns = config.watchPaths;
|
|
959
|
-
} else {
|
|
960
|
-
watchPatterns = config.modelFiles;
|
|
961
|
-
}
|
|
962
|
-
watchPaths(watchPatterns, config.rootDir, (path) => {
|
|
963
|
-
scheduleBuild(path);
|
|
964
|
-
});
|
|
868
|
+
const delay = 150;
|
|
869
|
+
const changedPaths = /* @__PURE__ */ new Set();
|
|
870
|
+
let currentBuildState;
|
|
871
|
+
function performBuild() {
|
|
872
|
+
clearOverlay();
|
|
873
|
+
for (const path of changedPaths) log("info", `Input file ${basename(path)} has been changed`);
|
|
874
|
+
changedPaths.clear();
|
|
875
|
+
if (currentBuildState) {
|
|
876
|
+
currentBuildState.abortController.abort();
|
|
877
|
+
currentBuildState = void 0;
|
|
878
|
+
}
|
|
879
|
+
currentBuildState = new BuildState();
|
|
880
|
+
buildOnce(config, userConfig, plugins, { abortSignal: currentBuildState.abortController.signal }).then((result) => {
|
|
881
|
+
if (result.isErr()) logError(result.error);
|
|
882
|
+
}).catch((e) => {
|
|
883
|
+
logError(e);
|
|
884
|
+
}).finally(() => {
|
|
885
|
+
currentBuildState = void 0;
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
function scheduleBuild(changedPath) {
|
|
889
|
+
const schedule = changedPaths.size === 0;
|
|
890
|
+
changedPaths.add(changedPath);
|
|
891
|
+
if (schedule) setTimeout(() => {
|
|
892
|
+
performBuild();
|
|
893
|
+
}, delay);
|
|
894
|
+
}
|
|
895
|
+
let watchPatterns;
|
|
896
|
+
if (config.watchPaths && config.watchPaths.length > 0) watchPatterns = config.watchPaths;
|
|
897
|
+
else watchPatterns = config.modelFiles;
|
|
898
|
+
watchPaths(watchPatterns, config.rootDir, (path) => {
|
|
899
|
+
scheduleBuild(path);
|
|
900
|
+
});
|
|
965
901
|
}
|
|
966
|
-
|
|
967
|
-
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region src/build/build.ts
|
|
904
|
+
/**
|
|
905
|
+
* Initiate the build process, which can either be a single build if `mode` is
|
|
906
|
+
* 'production', or a live development environment if `mode` is 'development'.
|
|
907
|
+
*
|
|
908
|
+
* @param mode The build mode.
|
|
909
|
+
* @param options The build options.
|
|
910
|
+
* @return An `ok` result if the build completed, otherwise an `err` result.
|
|
911
|
+
*/
|
|
968
912
|
async function build(mode, options) {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
for (const plugin of plugins) {
|
|
993
|
-
if (plugin.watch) {
|
|
994
|
-
await plugin.watch(resolvedConfig);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
watch(resolvedConfig, userConfig, plugins);
|
|
998
|
-
return ok3({});
|
|
999
|
-
} else {
|
|
1000
|
-
const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {});
|
|
1001
|
-
if (buildResult.isErr()) {
|
|
1002
|
-
return err3(buildResult.error);
|
|
1003
|
-
}
|
|
1004
|
-
const allPluginsSucceeded = buildResult.value;
|
|
1005
|
-
const exitCode = allPluginsSucceeded ? 0 : 2;
|
|
1006
|
-
return ok3({ exitCode });
|
|
1007
|
-
}
|
|
1008
|
-
} catch (e) {
|
|
1009
|
-
return err3(e);
|
|
1010
|
-
}
|
|
913
|
+
const configResult = await loadConfig(mode, options.config, options.sdeDir, options.sdeCmdPath);
|
|
914
|
+
if (configResult.isErr()) return err(configResult.error);
|
|
915
|
+
const { userConfig, resolvedConfig } = configResult.value;
|
|
916
|
+
if (options.logLevels !== void 0) setActiveLevels(options.logLevels);
|
|
917
|
+
setOverlayFile(join(resolvedConfig.prepDir, "messages.html"), mode === "development");
|
|
918
|
+
try {
|
|
919
|
+
const plugins = userConfig.plugins || [];
|
|
920
|
+
for (const plugin of plugins) if (plugin.init) await plugin.init(resolvedConfig);
|
|
921
|
+
if (mode === "development") {
|
|
922
|
+
const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {});
|
|
923
|
+
if (buildResult.isErr()) return err(buildResult.error);
|
|
924
|
+
for (const plugin of plugins) if (plugin.watch) await plugin.watch(resolvedConfig);
|
|
925
|
+
watch(resolvedConfig, userConfig, plugins);
|
|
926
|
+
return ok({});
|
|
927
|
+
} else {
|
|
928
|
+
const buildResult = await buildOnce(resolvedConfig, userConfig, plugins, {});
|
|
929
|
+
if (buildResult.isErr()) return err(buildResult.error);
|
|
930
|
+
const exitCode = buildResult.value ? 0 : 2;
|
|
931
|
+
return ok({ exitCode });
|
|
932
|
+
}
|
|
933
|
+
} catch (e) {
|
|
934
|
+
return err(e);
|
|
935
|
+
}
|
|
1011
936
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
937
|
+
//#endregion
|
|
938
|
+
export { build };
|
|
939
|
+
|
|
1015
940
|
//# sourceMappingURL=index.js.map
|