@sdeverywhere/build 0.1.0

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 ADDED
@@ -0,0 +1,698 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+
18
+ // src/build/build.ts
19
+ import { join as joinPath6 } from "path";
20
+ import { err as err3, ok as ok3 } from "neverthrow";
21
+
22
+ // src/config/config-loader.ts
23
+ import { existsSync, lstatSync, mkdirSync } from "fs";
24
+ import { join as joinPath, resolve as resolvePath } from "path";
25
+ import { pathToFileURL } from "url";
26
+ import { err, ok } from "neverthrow";
27
+ async function loadConfig(mode, config, sdeDir, sdeCmdPath) {
28
+ let userConfig;
29
+ if (typeof config === "object") {
30
+ userConfig = config;
31
+ } else {
32
+ let configPath;
33
+ if (typeof config === "string") {
34
+ configPath = config;
35
+ } else {
36
+ configPath = joinPath(process.cwd(), "sde.config.js");
37
+ }
38
+ try {
39
+ if (!existsSync(configPath)) {
40
+ return err(new Error(`Cannot find config file '${configPath}'`));
41
+ }
42
+ const configUrl = pathToFileURL(configPath).toString();
43
+ const configModule = await import(configUrl);
44
+ userConfig = await configModule.config();
45
+ } catch (e) {
46
+ return err(new Error(`Failed to load config file '${configPath}': ${e.message}`));
47
+ }
48
+ }
49
+ try {
50
+ const resolvedConfig = resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath);
51
+ return ok({
52
+ userConfig,
53
+ resolvedConfig
54
+ });
55
+ } catch (e) {
56
+ return err(e);
57
+ }
58
+ }
59
+ function resolveUserConfig(userConfig, mode, sdeDir, sdeCmdPath) {
60
+ function expectDirectory(propName, path) {
61
+ if (!existsSync(path)) {
62
+ throw new Error(`The configured ${propName} (${path}) does not exist`);
63
+ } else if (!lstatSync(path).isDirectory()) {
64
+ throw new Error(`The configured ${propName} (${path}) is not a directory`);
65
+ }
66
+ }
67
+ let rootDir;
68
+ if (userConfig.rootDir) {
69
+ rootDir = resolvePath(userConfig.rootDir);
70
+ expectDirectory("rootDir", rootDir);
71
+ } else {
72
+ rootDir = process.cwd();
73
+ }
74
+ let prepDir;
75
+ if (userConfig.prepDir) {
76
+ prepDir = resolvePath(userConfig.prepDir);
77
+ } else {
78
+ prepDir = resolvePath(rootDir, "sde-prep");
79
+ }
80
+ mkdirSync(prepDir, { recursive: true });
81
+ const userModelFiles = userConfig.modelFiles;
82
+ const modelFiles = [];
83
+ for (const userModelFile of userModelFiles) {
84
+ const modelFile = resolvePath(userModelFile);
85
+ if (!existsSync(modelFile)) {
86
+ throw new Error(`The configured model file (${modelFile}) does not exist`);
87
+ }
88
+ modelFiles.push(modelFile);
89
+ }
90
+ let modelInputPaths;
91
+ if (userConfig.modelInputPaths && userConfig.modelInputPaths.length > 0) {
92
+ modelInputPaths = userConfig.modelInputPaths;
93
+ } else {
94
+ modelInputPaths = modelFiles;
95
+ }
96
+ let watchPaths;
97
+ if (userConfig.watchPaths && userConfig.watchPaths.length > 0) {
98
+ watchPaths = userConfig.watchPaths;
99
+ } else {
100
+ watchPaths = modelFiles;
101
+ }
102
+ return {
103
+ mode,
104
+ rootDir,
105
+ prepDir,
106
+ modelFiles,
107
+ modelInputPaths,
108
+ watchPaths,
109
+ sdeDir,
110
+ sdeCmdPath
111
+ };
112
+ }
113
+
114
+ // src/_shared/log.ts
115
+ import { writeFileSync } from "fs";
116
+ import pico from "picocolors";
117
+ var activeLevels = /* @__PURE__ */ new Set(["error", "info"]);
118
+ var overlayFile;
119
+ var overlayEnabled = false;
120
+ var overlayHtml = "";
121
+ function setActiveLevels(logLevels) {
122
+ activeLevels.clear();
123
+ for (const level of logLevels) {
124
+ activeLevels.add(level);
125
+ }
126
+ }
127
+ function setOverlayFile(file, enabled) {
128
+ overlayFile = file;
129
+ overlayEnabled = enabled;
130
+ writeFileSync(overlayFile, "");
131
+ }
132
+ function log(level, msg) {
133
+ if (activeLevels.has(level)) {
134
+ if (level === "error") {
135
+ console.error(pico.red(msg));
136
+ logToOverlay(msg);
137
+ } else {
138
+ console.log(msg);
139
+ logToOverlay(msg);
140
+ }
141
+ }
142
+ }
143
+ function logError(e) {
144
+ const stack = e.stack || "";
145
+ const stackLines = stack.split("\n").filter((s) => s.match(/^\s+at/));
146
+ const trace = stackLines.slice(0, 3).join("\n");
147
+ console.error(pico.red(`
148
+ ERROR: ${e.message}`));
149
+ console.error(pico.dim(pico.red(`${trace}
150
+ `)));
151
+ logToOverlay(`
152
+ ERROR: ${e.message}`, true);
153
+ logToOverlay(`${trace}
154
+ `, true);
155
+ }
156
+ function writeOverlayFiles() {
157
+ writeFileSync(overlayFile, overlayHtml);
158
+ }
159
+ function clearOverlay() {
160
+ if (!overlayEnabled) {
161
+ return;
162
+ }
163
+ overlayHtml = "";
164
+ writeOverlayFiles();
165
+ }
166
+ var indent = " ".repeat(4);
167
+ function logToOverlay(msg, error = false) {
168
+ if (!overlayEnabled) {
169
+ return;
170
+ }
171
+ if (error) {
172
+ msg = `<span class="overlay-error">${msg}</span>`;
173
+ }
174
+ const msgHtml = msg.replace(/\n/g, "\n<br/>").replace(/\s{2}/g, indent);
175
+ if (overlayHtml) {
176
+ overlayHtml += `<br/>${msgHtml}`;
177
+ } else {
178
+ overlayHtml = `${msgHtml}`;
179
+ }
180
+ writeOverlayFiles();
181
+ }
182
+
183
+ // src/build/impl/build-once.ts
184
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
185
+ import { writeFile as writeFile2 } from "fs/promises";
186
+ import { join as joinPath5 } from "path";
187
+ import { err as err2, ok as ok2 } from "neverthrow";
188
+
189
+ // src/context/spawn-child.ts
190
+ import { spawn } from "cross-spawn";
191
+ function spawnChild(cwd, command, args, abortSignal, opts) {
192
+ return new Promise((resolve, reject) => {
193
+ if (abortSignal == null ? void 0 : abortSignal.aborted) {
194
+ reject(new Error("ABORT"));
195
+ return;
196
+ }
197
+ let childProc;
198
+ const localLog = (s, err4 = false) => {
199
+ if (childProc === void 0) {
200
+ return;
201
+ }
202
+ log(err4 ? "error" : "info", s);
203
+ };
204
+ const abortHandler = () => {
205
+ if (childProc) {
206
+ log("info", "Killing existing build process...");
207
+ childProc.kill("SIGKILL");
208
+ childProc = void 0;
209
+ }
210
+ reject(new Error("ABORT"));
211
+ };
212
+ abortSignal == null ? void 0 : abortSignal.addEventListener("abort", abortHandler, { once: true });
213
+ const stdoutMessages = [];
214
+ const stderrMessages = [];
215
+ const logMessage = (msg, err4) => {
216
+ let includeMessage = true;
217
+ if ((opts == null ? void 0 : opts.ignoredMessageFilter) && msg.trim().startsWith(opts.ignoredMessageFilter)) {
218
+ includeMessage = false;
219
+ }
220
+ if (includeMessage) {
221
+ const lines = msg.trim().split("\n");
222
+ for (const line of lines) {
223
+ localLog(` ${line}`, err4);
224
+ }
225
+ }
226
+ };
227
+ childProc = spawn(command, args, {
228
+ cwd
229
+ });
230
+ childProc.stdout.on("data", (data) => {
231
+ const msg = data.toString();
232
+ if ((opts == null ? void 0 : opts.captureOutput) === true) {
233
+ stdoutMessages.push(msg);
234
+ }
235
+ if ((opts == null ? void 0 : opts.logOutput) !== false) {
236
+ logMessage(msg, false);
237
+ }
238
+ });
239
+ childProc.stderr.on("data", (data) => {
240
+ const msg = data.toString();
241
+ if ((opts == null ? void 0 : opts.captureOutput) === true) {
242
+ stderrMessages.push(msg);
243
+ }
244
+ if ((opts == null ? void 0 : opts.logOutput) !== false) {
245
+ logMessage(msg, true);
246
+ }
247
+ });
248
+ childProc.on("error", (err4) => {
249
+ localLog(`Process error: ${err4}`, true);
250
+ });
251
+ childProc.on("close", (code, signal) => {
252
+ abortSignal == null ? void 0 : abortSignal.removeEventListener("abort", abortHandler);
253
+ childProc = void 0;
254
+ if (signal) {
255
+ return;
256
+ }
257
+ const processOutput = {
258
+ exitCode: code,
259
+ stdoutMessages,
260
+ stderrMessages
261
+ };
262
+ if (code === 0) {
263
+ resolve(processOutput);
264
+ } else if (!signal) {
265
+ if ((opts == null ? void 0 : opts.ignoreError) === true) {
266
+ resolve(processOutput);
267
+ } else {
268
+ reject(new Error(`Child process failed (code=${code})`));
269
+ }
270
+ }
271
+ });
272
+ });
273
+ }
274
+
275
+ // src/context/context.ts
276
+ var BuildContext = class {
277
+ constructor(config, stagedFiles, abortSignal) {
278
+ this.config = config;
279
+ this.stagedFiles = stagedFiles;
280
+ this.abortSignal = abortSignal;
281
+ }
282
+ log(level, msg) {
283
+ log(level, msg);
284
+ }
285
+ prepareStagedFile(srcDir, srcFile, dstDir, dstFile) {
286
+ return this.stagedFiles.prepareStagedFile(srcDir, srcFile, dstDir, dstFile);
287
+ }
288
+ writeStagedFile(srcDir, dstDir, filename, content) {
289
+ this.stagedFiles.writeStagedFile(srcDir, dstDir, filename, content);
290
+ }
291
+ spawnChild(cwd, command, args, opts) {
292
+ return spawnChild(cwd, command, args, this.abortSignal, opts);
293
+ }
294
+ };
295
+
296
+ // src/context/staged-files.ts
297
+ import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, statSync, writeFileSync as writeFileSync2 } from "fs";
298
+ import { join as joinPath2 } from "path";
299
+ var StagedFiles = class {
300
+ constructor(prepDir) {
301
+ this.stagedFiles = [];
302
+ this.baseStagedDir = joinPath2(prepDir, "staged");
303
+ }
304
+ prepareStagedFile(srcDir, srcFile, dstDir, dstFile) {
305
+ const stagedFile = {
306
+ srcDir,
307
+ srcFile,
308
+ dstDir,
309
+ dstFile
310
+ };
311
+ if (this.stagedFiles.indexOf(stagedFile) < 0) {
312
+ this.stagedFiles.push(stagedFile);
313
+ }
314
+ const stagedDir = joinPath2(this.baseStagedDir, srcDir);
315
+ if (!existsSync2(stagedDir)) {
316
+ mkdirSync2(stagedDir, { recursive: true });
317
+ }
318
+ return joinPath2(stagedDir, srcFile);
319
+ }
320
+ writeStagedFile(srcDir, dstDir, filename, content) {
321
+ const stagedFilePath = this.prepareStagedFile(srcDir, filename, dstDir, filename);
322
+ writeFileSync2(stagedFilePath, content);
323
+ }
324
+ getStagedFilePath(srcDir, srcFile) {
325
+ return joinPath2(this.baseStagedDir, srcDir, srcFile);
326
+ }
327
+ stagedFileExists(srcDir, srcFile) {
328
+ const fullSrcPath = this.getStagedFilePath(srcDir, srcFile);
329
+ return existsSync2(fullSrcPath);
330
+ }
331
+ destinationFileExists(srcDir, srcFile) {
332
+ const f = this.stagedFiles.find((f2) => f2.srcDir === srcDir && f2.srcFile === srcFile);
333
+ if (f === void 0) {
334
+ return false;
335
+ }
336
+ const fullDstPath = joinPath2(f.dstDir, f.dstFile);
337
+ return existsSync2(fullDstPath);
338
+ }
339
+ copyChangedFiles() {
340
+ log("info", "Copying changed files into place...");
341
+ for (const f of this.stagedFiles) {
342
+ this.copyStagedFile(f);
343
+ }
344
+ log("info", "Done copying files");
345
+ }
346
+ copyStagedFile(f) {
347
+ if (!existsSync2(f.dstDir)) {
348
+ mkdirSync2(f.dstDir, { recursive: true });
349
+ }
350
+ const fullSrcPath = this.getStagedFilePath(f.srcDir, f.srcFile);
351
+ const fullDstPath = joinPath2(f.dstDir, f.dstFile);
352
+ const needsCopy = filesDiffer(fullSrcPath, fullDstPath);
353
+ if (needsCopy) {
354
+ log("verbose", ` Copying ${f.srcFile} to ${fullDstPath}`);
355
+ copyFileSync(fullSrcPath, fullDstPath);
356
+ }
357
+ return needsCopy;
358
+ }
359
+ };
360
+ function filesDiffer(aPath, bPath) {
361
+ if (existsSync2(aPath) && existsSync2(bPath)) {
362
+ const aSize = statSync(aPath).size;
363
+ const bSize = statSync(bPath).size;
364
+ if (aSize !== bSize) {
365
+ return true;
366
+ } else {
367
+ const aBuf = readFileSync(aPath);
368
+ const bBuf = readFileSync(bPath);
369
+ return !aBuf.equals(bBuf);
370
+ }
371
+ } else {
372
+ return true;
373
+ }
374
+ }
375
+
376
+ // src/build/impl/gen-model.ts
377
+ import { copyFile, readdir, readFile, writeFile } from "fs/promises";
378
+ import { join as joinPath3 } from "path";
379
+ async function generateModel(context, plugins) {
380
+ log("info", "Generating model...");
381
+ const t0 = performance.now();
382
+ const config = context.config;
383
+ const prepDir = config.prepDir;
384
+ const isWin = process.platform === "win32";
385
+ const sdeCmdPath = isWin ? `${config.sdeCmdPath}.cmd` : config.sdeCmdPath;
386
+ for (const plugin of plugins) {
387
+ if (plugin.preProcessMdl) {
388
+ await plugin.preProcessMdl(context);
389
+ }
390
+ }
391
+ if (config.modelFiles.length === 0) {
392
+ throw new Error("No model input files specified");
393
+ } else if (config.modelFiles.length === 1) {
394
+ await preprocessMdl(context, sdeCmdPath, prepDir, config.modelFiles[0]);
395
+ } else {
396
+ await flattenMdls(context, sdeCmdPath, prepDir, config.modelFiles);
397
+ }
398
+ for (const plugin of plugins) {
399
+ if (plugin.postProcessMdl) {
400
+ const mdlPath = joinPath3(prepDir, "processed.mdl");
401
+ let mdlContent = await readFile(mdlPath, "utf8");
402
+ mdlContent = await plugin.postProcessMdl(context, mdlContent);
403
+ await writeFile(mdlPath, mdlContent);
404
+ }
405
+ }
406
+ for (const plugin of plugins) {
407
+ if (plugin.preGenerateC) {
408
+ await plugin.preGenerateC(context);
409
+ }
410
+ }
411
+ await generateC(context, config.sdeDir, sdeCmdPath, prepDir);
412
+ for (const plugin of plugins) {
413
+ if (plugin.postGenerateC) {
414
+ const cPath = joinPath3(prepDir, "build", "processed.c");
415
+ let cContent = await readFile(cPath, "utf8");
416
+ cContent = await plugin.postGenerateC(context, cContent);
417
+ await writeFile(cPath, cContent);
418
+ }
419
+ }
420
+ const t1 = performance.now();
421
+ const elapsed = ((t1 - t0) / 1e3).toFixed(1);
422
+ log("info", `Done generating model (${elapsed}s)`);
423
+ }
424
+ async function preprocessMdl(context, sdeCmdPath, prepDir, modelFile) {
425
+ log("verbose", " Preprocessing mdl file");
426
+ await copyFile(modelFile, joinPath3(prepDir, "processed.mdl"));
427
+ const command = sdeCmdPath;
428
+ const args = ["generate", "--preprocess", "processed.mdl"];
429
+ await context.spawnChild(prepDir, command, args);
430
+ await copyFile(joinPath3(prepDir, "build", "processed.mdl"), joinPath3(prepDir, "processed.mdl"));
431
+ }
432
+ async function flattenMdls(context, sdeCmdPath, prepDir, modelFiles) {
433
+ log("verbose", " Flattening and preprocessing mdl files");
434
+ const command = sdeCmdPath;
435
+ const args = [];
436
+ args.push("flatten");
437
+ args.push("processed.mdl");
438
+ args.push("--inputs");
439
+ for (const path of modelFiles) {
440
+ args.push(path);
441
+ }
442
+ const output = await context.spawnChild(prepDir, command, args, {
443
+ logOutput: false,
444
+ captureOutput: true,
445
+ ignoreError: true
446
+ });
447
+ let flattenErrors = false;
448
+ for (const msg of output.stderrMessages) {
449
+ if (msg.includes("ERROR")) {
450
+ flattenErrors = true;
451
+ break;
452
+ }
453
+ }
454
+ if (flattenErrors) {
455
+ log("error", "There were errors reported when flattening the model:");
456
+ for (const msg of output.stderrMessages) {
457
+ const lines = msg.split("\n");
458
+ for (const line of lines) {
459
+ log("error", ` ${line}`);
460
+ }
461
+ }
462
+ throw new Error(`Flatten command failed (code=${output.exitCode})`);
463
+ } else if (output.exitCode !== 0) {
464
+ throw new Error(`Flatten command failed (code=${output.exitCode})`);
465
+ }
466
+ await copyFile(joinPath3(prepDir, "build", "processed.mdl"), joinPath3(prepDir, "processed.mdl"));
467
+ }
468
+ async function generateC(context, sdeDir, sdeCmdPath, prepDir) {
469
+ log("verbose", " Generating C code");
470
+ const command = sdeCmdPath;
471
+ const args = ["generate", "--genc", "--spec", "spec.json", "processed"];
472
+ await context.spawnChild(prepDir, command, args, {});
473
+ const buildDir = joinPath3(prepDir, "build");
474
+ const sdeCDir = joinPath3(sdeDir, "src", "c");
475
+ const files = await readdir(sdeCDir);
476
+ const copyOps = [];
477
+ for (const file of files) {
478
+ if (file.endsWith(".c") || file.endsWith(".h")) {
479
+ copyOps.push(copyFile(joinPath3(sdeCDir, file), joinPath3(buildDir, file)));
480
+ }
481
+ }
482
+ await Promise.all(copyOps);
483
+ }
484
+
485
+ // src/build/impl/hash-files.ts
486
+ import { join as joinPath4 } from "path";
487
+ import { hashElement } from "folder-hash";
488
+ import glob from "tiny-glob";
489
+ async function computeInputFilesHash(config) {
490
+ const inputFiles = [];
491
+ const specFile = joinPath4(config.prepDir, "spec.json");
492
+ inputFiles.push(specFile);
493
+ if (config.modelInputPaths && config.modelInputPaths.length > 0) {
494
+ for (const globPath of config.modelInputPaths) {
495
+ const paths = await glob(globPath, {
496
+ cwd: config.rootDir,
497
+ absolute: true,
498
+ filesOnly: true
499
+ });
500
+ inputFiles.push(...paths);
501
+ }
502
+ } else {
503
+ inputFiles.push(...config.modelFiles);
504
+ }
505
+ let hash = "";
506
+ for (const inputFile of inputFiles) {
507
+ const result = await hashElement(inputFile);
508
+ hash += result.hash;
509
+ }
510
+ return hash;
511
+ }
512
+
513
+ // src/build/impl/build-once.ts
514
+ async function buildOnce(config, userConfig, plugins, options) {
515
+ const stagedFiles = new StagedFiles(config.prepDir);
516
+ const context = new BuildContext(config, stagedFiles, options.abortSignal);
517
+ let modelSpec;
518
+ try {
519
+ modelSpec = await userConfig.modelSpec(context);
520
+ if (modelSpec === void 0) {
521
+ return err2(new Error("The model spec must be defined"));
522
+ }
523
+ } catch (e) {
524
+ return err2(e);
525
+ }
526
+ for (const plugin of plugins) {
527
+ if (plugin.preGenerate) {
528
+ plugin.preGenerate(context, modelSpec);
529
+ }
530
+ }
531
+ const specJson = __spreadValues({
532
+ inputVarNames: modelSpec.inputs.map((input) => input.varName),
533
+ outputVarNames: modelSpec.outputs.map((output) => output.varName),
534
+ externalDatfiles: modelSpec.datFiles
535
+ }, modelSpec.options);
536
+ const specPath = joinPath5(config.prepDir, "spec.json");
537
+ await writeFile2(specPath, JSON.stringify(specJson, null, 2));
538
+ const modelHashPath = joinPath5(config.prepDir, "model-hash.txt");
539
+ let previousModelHash;
540
+ if (existsSync3(modelHashPath)) {
541
+ previousModelHash = readFileSync2(modelHashPath, "utf8");
542
+ } else {
543
+ previousModelHash = "NONE";
544
+ }
545
+ const inputFilesHash = await computeInputFilesHash(config);
546
+ let needModelGen;
547
+ if (options.forceModelGen === true) {
548
+ needModelGen = true;
549
+ } else {
550
+ const hashMismatch = inputFilesHash !== previousModelHash;
551
+ needModelGen = hashMismatch;
552
+ }
553
+ let succeeded = true;
554
+ try {
555
+ if (needModelGen) {
556
+ await generateModel(context, plugins);
557
+ writeFileSync3(modelHashPath, inputFilesHash);
558
+ } else {
559
+ log("info", "Skipping model code generation; already up-to-date");
560
+ }
561
+ for (const plugin of plugins) {
562
+ if (plugin.postGenerate) {
563
+ const pluginSucceeded = await plugin.postGenerate(context, modelSpec);
564
+ if (!pluginSucceeded) {
565
+ succeeded = false;
566
+ }
567
+ }
568
+ }
569
+ stagedFiles.copyChangedFiles();
570
+ for (const plugin of plugins) {
571
+ if (plugin.postBuild) {
572
+ const pluginSucceeded = await plugin.postBuild(context, modelSpec);
573
+ if (!pluginSucceeded) {
574
+ succeeded = false;
575
+ }
576
+ }
577
+ }
578
+ if (config.mode === "development") {
579
+ log("info", "Waiting for changes...\n");
580
+ clearOverlay();
581
+ }
582
+ } catch (e) {
583
+ if (e.message !== "ABORT") {
584
+ writeFileSync3(modelHashPath, "");
585
+ return err2(e);
586
+ }
587
+ }
588
+ return ok2(succeeded);
589
+ }
590
+
591
+ // src/build/impl/watch.ts
592
+ import { basename } from "path";
593
+ import chokidar from "chokidar";
594
+ var BuildState = class {
595
+ constructor() {
596
+ this.abortController = new AbortController();
597
+ }
598
+ };
599
+ function watch(config, userConfig, plugins) {
600
+ const delay = 150;
601
+ const changedPaths = /* @__PURE__ */ new Set();
602
+ let currentBuildState;
603
+ function performBuild() {
604
+ clearOverlay();
605
+ for (const path of changedPaths) {
606
+ log("info", `Input file ${basename(path)} has been changed`);
607
+ }
608
+ changedPaths.clear();
609
+ if (currentBuildState) {
610
+ currentBuildState.abortController.abort();
611
+ currentBuildState = void 0;
612
+ }
613
+ currentBuildState = new BuildState();
614
+ const buildOptions = {
615
+ abortSignal: currentBuildState.abortController.signal
616
+ };
617
+ buildOnce(config, userConfig, plugins, buildOptions).catch((e) => {
618
+ logError(e);
619
+ }).finally(() => {
620
+ currentBuildState = void 0;
621
+ });
622
+ }
623
+ function scheduleBuild(changedPath) {
624
+ const schedule = changedPaths.size === 0;
625
+ changedPaths.add(changedPath);
626
+ if (schedule) {
627
+ setTimeout(() => {
628
+ performBuild();
629
+ }, delay);
630
+ }
631
+ }
632
+ let watchPaths;
633
+ if (config.watchPaths && config.watchPaths.length > 0) {
634
+ watchPaths = config.watchPaths;
635
+ } else {
636
+ watchPaths = config.modelFiles;
637
+ }
638
+ const watcher = chokidar.watch(watchPaths, {
639
+ cwd: config.rootDir,
640
+ awaitWriteFinish: {
641
+ stabilityThreshold: 200
642
+ }
643
+ });
644
+ watcher.on("change", (path) => {
645
+ scheduleBuild(path);
646
+ });
647
+ }
648
+
649
+ // src/build/build.ts
650
+ async function build(mode, options) {
651
+ const configResult = await loadConfig(mode, options.config, options.sdeDir, options.sdeCmdPath);
652
+ if (configResult.isErr()) {
653
+ return err3(configResult.error);
654
+ }
655
+ const { userConfig, resolvedConfig } = configResult.value;
656
+ if (options.logLevels !== void 0) {
657
+ setActiveLevels(options.logLevels);
658
+ }
659
+ const messagesPath = joinPath6(resolvedConfig.prepDir, "messages.html");
660
+ const overlayEnabled2 = mode === "development";
661
+ setOverlayFile(messagesPath, overlayEnabled2);
662
+ const plugins = userConfig.plugins || [];
663
+ for (const plugin of plugins) {
664
+ if (plugin.init) {
665
+ await plugin.init(resolvedConfig);
666
+ }
667
+ }
668
+ try {
669
+ const plugins2 = userConfig.plugins || [];
670
+ if (mode === "development") {
671
+ const buildResult = await buildOnce(resolvedConfig, userConfig, plugins2, {});
672
+ if (buildResult.isErr()) {
673
+ return err3(buildResult.error);
674
+ }
675
+ for (const plugin of plugins2) {
676
+ if (plugin.watch) {
677
+ await plugin.watch(resolvedConfig);
678
+ }
679
+ }
680
+ watch(resolvedConfig, userConfig, plugins2);
681
+ return ok3({});
682
+ } else {
683
+ const buildResult = await buildOnce(resolvedConfig, userConfig, plugins2, {});
684
+ if (buildResult.isErr()) {
685
+ return err3(buildResult.error);
686
+ }
687
+ const allPluginsSucceeded = buildResult.value;
688
+ const exitCode = allPluginsSucceeded ? 0 : 2;
689
+ return ok3({ exitCode });
690
+ }
691
+ } catch (e) {
692
+ return err3(e);
693
+ }
694
+ }
695
+ export {
696
+ build
697
+ };
698
+ //# sourceMappingURL=index.js.map