@tsplc/compiler 0.1.0 → 0.2.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/cli.js +718 -62
- package/dist/cli.js.map +9 -6
- package/dist/project.js +22 -16
- package/dist/project.js.map +3 -3
- package/package.json +6 -5
- package/project/cli.ts +35 -12
- package/project/plant.ts +437 -0
- package/project/project.ts +34 -20
package/dist/cli.js
CHANGED
|
@@ -71,11 +71,11 @@ var buildServer = async (config, root) => {
|
|
|
71
71
|
|
|
72
72
|
// packages/compiler/project/cli.ts
|
|
73
73
|
import { parseArgs } from "util";
|
|
74
|
-
import { resolve as
|
|
74
|
+
import { resolve as resolve6 } from "path";
|
|
75
75
|
import {
|
|
76
76
|
buildTargetFor,
|
|
77
|
-
controllerFor,
|
|
78
|
-
loadTsplcConfig as
|
|
77
|
+
controllerFor as controllerFor2,
|
|
78
|
+
loadTsplcConfig as loadTsplcConfig3,
|
|
79
79
|
rawBuildConfig,
|
|
80
80
|
simulationConfig
|
|
81
81
|
} from "@tsplc/config";
|
|
@@ -84,17 +84,30 @@ import {
|
|
|
84
84
|
configureDocker,
|
|
85
85
|
configureRuntime,
|
|
86
86
|
inspectController,
|
|
87
|
-
installLibraries as
|
|
87
|
+
installLibraries as installLibraries3,
|
|
88
88
|
loadSources,
|
|
89
89
|
resolveConfig,
|
|
90
90
|
runProject,
|
|
91
91
|
updateController
|
|
92
92
|
} from "@tsplc/build";
|
|
93
|
-
import { checkHostConfig as
|
|
94
|
-
import { formatDiagnostic, hasErrors as
|
|
95
|
-
import { deviceTree as
|
|
96
|
-
import { loadHardware as
|
|
93
|
+
import { checkHostConfig as checkHostConfig3 } from "@tsplc/compiler";
|
|
94
|
+
import { formatDiagnostic, hasErrors as hasErrors4 } from "@tsplc/compiler/ir";
|
|
95
|
+
import { deviceTree as deviceTree3 } from "@tsplc/typegen";
|
|
96
|
+
import { loadHardware as loadHardware3, resolveIo as resolveIo3 } from "@tsplc/typegen/hardware";
|
|
97
|
+
import { generateProject as generateProject2 } from "@tsplc/typegen";
|
|
98
|
+
|
|
99
|
+
// packages/compiler/project/plant.ts
|
|
100
|
+
import { basename, relative, resolve as resolve4, sep } from "node:path";
|
|
101
|
+
import { createInterface } from "node:readline/promises";
|
|
102
|
+
import { pathToFileURL } from "node:url";
|
|
103
|
+
import {
|
|
104
|
+
AgentOperationError,
|
|
105
|
+
createAgentClient
|
|
106
|
+
} from "@tsplc/agent/client";
|
|
107
|
+
import { controllerFor } from "@tsplc/config";
|
|
108
|
+
import { hasErrors as hasErrors2 } from "@tsplc/compiler/ir";
|
|
97
109
|
import { generateProject } from "@tsplc/typegen";
|
|
110
|
+
import { z } from "zod";
|
|
98
111
|
|
|
99
112
|
// packages/compiler/project/project.ts
|
|
100
113
|
import { copyFile, mkdir as mkdir3, mkdtemp as mkdtemp2, realpath, rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -402,6 +415,17 @@ async function checkProject(file = "tsplc.config.ts", options = {}) {
|
|
|
402
415
|
]
|
|
403
416
|
};
|
|
404
417
|
}
|
|
418
|
+
async function engineeringInputs(shared, configFile, bundle, frozen) {
|
|
419
|
+
const root = dirname3(configFile);
|
|
420
|
+
const tree = await deviceTree(shared, await loadHardware(shared, root));
|
|
421
|
+
return {
|
|
422
|
+
libraries: await installLibraries(shared.dependencies, root, { frozen }),
|
|
423
|
+
deviceTree: tree && {
|
|
424
|
+
...tree,
|
|
425
|
+
mappings: bundle.ioMappings.map(({ address, variable }) => ({ address, variable }))
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
405
429
|
async function buildProject(file = "tsplc.config.ts", options = {}) {
|
|
406
430
|
let staging;
|
|
407
431
|
try {
|
|
@@ -421,11 +445,14 @@ async function buildProject(file = "tsplc.config.ts", options = {}) {
|
|
|
421
445
|
if (hasErrors(bundle.diagnostics)) {
|
|
422
446
|
return { diagnostics: bundle.diagnostics };
|
|
423
447
|
}
|
|
424
|
-
const
|
|
448
|
+
const shared = checked.shared;
|
|
449
|
+
const engineering = shared && (!options.emitOnly || options.forController) ? await engineeringInputs(shared, configFile, bundle, options.frozenLockfile) : undefined;
|
|
450
|
+
const controllerConfig = shared?.build ? codesysBuildConfig(checked.plan, bundle, {
|
|
425
451
|
configFile,
|
|
426
452
|
sourceDir: join3(previous.output, "st"),
|
|
427
|
-
outDir: resolve2(dirname3(configFile),
|
|
428
|
-
controller: options.controller
|
|
453
|
+
outDir: resolve2(dirname3(configFile), shared.build.outDir),
|
|
454
|
+
controller: options.controller,
|
|
455
|
+
...engineering
|
|
429
456
|
}) : undefined;
|
|
430
457
|
await mkdir3(previous.root, { recursive: true });
|
|
431
458
|
staging = await mkdtemp2(join3(previous.root, "stage-"));
|
|
@@ -447,23 +474,15 @@ async function buildProject(file = "tsplc.config.ts", options = {}) {
|
|
|
447
474
|
}
|
|
448
475
|
let vendor;
|
|
449
476
|
if (!options.emitOnly) {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
frozen: options.frozenLockfile
|
|
453
|
-
}) : undefined;
|
|
454
|
-
await engineeringDirectory(engineering, configFile);
|
|
455
|
-
const tree = checked.shared && await deviceTree(checked.shared, await loadHardware(checked.shared, dirname3(configFile)));
|
|
477
|
+
const outDir = shared?.build ? resolve2(dirname3(configFile), shared.build.outDir ?? "build") : join3(previous.root, "engineering");
|
|
478
|
+
await engineeringDirectory(outDir, configFile);
|
|
456
479
|
try {
|
|
457
480
|
vendor = await buildCodesys(checked.plan, bundle, {
|
|
458
481
|
configFile,
|
|
459
482
|
sourceDir: join3(staging, "st"),
|
|
460
|
-
outDir
|
|
461
|
-
libraries,
|
|
483
|
+
outDir,
|
|
462
484
|
controller: options.controller,
|
|
463
|
-
|
|
464
|
-
...tree,
|
|
465
|
-
mappings: bundle.ioMappings.map(({ address, variable }) => ({ address, variable }))
|
|
466
|
-
}
|
|
485
|
+
...engineering
|
|
467
486
|
});
|
|
468
487
|
} catch (cause) {
|
|
469
488
|
return { diagnostics: diagnoseCodesys(cause, bundle) };
|
|
@@ -502,12 +521,627 @@ async function buildProject(file = "tsplc.config.ts", options = {}) {
|
|
|
502
521
|
}
|
|
503
522
|
}
|
|
504
523
|
}
|
|
524
|
+
|
|
525
|
+
// packages/compiler/project/server.ts
|
|
526
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
527
|
+
import { mkdtemp as mkdtemp3, mkdir as mkdir4, rm as rm4, rename as rename3 } from "node:fs/promises";
|
|
528
|
+
import { dirname as dirname4, resolve as resolve3, join as join4 } from "node:path";
|
|
529
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
530
|
+
import ts2 from "typescript";
|
|
531
|
+
import { typeCheckOnly as typeCheckOnly2 } from "@tsplc/compiler";
|
|
532
|
+
var resolveServerModule2 = (root, name) => name.startsWith(".") || name.startsWith("/") || existsSync2(resolve3(root, name)) ? resolve3(root, name) : createRequire2(join4(root, "package.json")).resolve(name);
|
|
533
|
+
var kebabCase2 = (name) => name.replace(/([a-z0-9])([A-Z])|([A-Z])([A-Z][a-z])/g, "$1$3-$2$4").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
534
|
+
var buildServer2 = async (config, root) => {
|
|
535
|
+
const settings = config.server;
|
|
536
|
+
if (!settings?.runtime)
|
|
537
|
+
throw new Error("Configure server.runtime to build an executable.");
|
|
538
|
+
const runtime = settings.runtime;
|
|
539
|
+
const entry = resolveServerModule2(root, runtime.entry);
|
|
540
|
+
const windows = runtime.target ? runtime.target.includes("windows") : process.platform === "win32";
|
|
541
|
+
const outFile = resolve3(root, "build/server", `${kebabCase2(config.name)}-services${windows ? ".exe" : ""}`);
|
|
542
|
+
const tsconfig = ts2.findConfigFile(dirname4(entry), ts2.sys.fileExists);
|
|
543
|
+
if (!tsconfig)
|
|
544
|
+
throw new Error("Server entry requires a tsconfig.json.");
|
|
545
|
+
const read = ts2.readConfigFile(tsconfig, ts2.sys.readFile);
|
|
546
|
+
if (read.error)
|
|
547
|
+
throw new Error(ts2.flattenDiagnosticMessageText(read.error.messageText, `
|
|
548
|
+
`));
|
|
549
|
+
const parsed = ts2.parseJsonConfigFileContent(read.config, ts2.sys, dirname4(tsconfig));
|
|
550
|
+
const program = ts2.createProgram({
|
|
551
|
+
rootNames: [entry, ...parsed.fileNames.filter((file) => /\.d\.[cm]?ts$/.test(file))],
|
|
552
|
+
options: { ...parsed.options, ...typeCheckOnly2 },
|
|
553
|
+
projectReferences: parsed.projectReferences
|
|
554
|
+
});
|
|
555
|
+
const diagnostics = [...parsed.errors, ...ts2.getPreEmitDiagnostics(program)];
|
|
556
|
+
if (diagnostics.length)
|
|
557
|
+
throw new Error(ts2.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
558
|
+
getCanonicalFileName: (path) => path,
|
|
559
|
+
getCurrentDirectory: () => root,
|
|
560
|
+
getNewLine: () => `
|
|
561
|
+
`
|
|
562
|
+
}));
|
|
563
|
+
await mkdir4(dirname4(outFile), { recursive: true });
|
|
564
|
+
const staging = await mkdtemp3(join4(dirname4(outFile), ".tsplc-server-"));
|
|
565
|
+
try {
|
|
566
|
+
const binary = join4(staging, "server");
|
|
567
|
+
const result = await Bun.build({
|
|
568
|
+
entrypoints: [entry],
|
|
569
|
+
env: "disable",
|
|
570
|
+
compile: {
|
|
571
|
+
outfile: binary,
|
|
572
|
+
...runtime.target ? { target: runtime.target } : {},
|
|
573
|
+
autoloadDotenv: false,
|
|
574
|
+
autoloadBunfig: false,
|
|
575
|
+
autoloadTsconfig: false,
|
|
576
|
+
autoloadPackageJson: false
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
if (!result.success)
|
|
580
|
+
throw new AggregateError(result.logs, "Server executable build failed.");
|
|
581
|
+
await rename3(windows ? `${binary}.exe` : binary, outFile);
|
|
582
|
+
return {
|
|
583
|
+
server: {
|
|
584
|
+
executable: outFile,
|
|
585
|
+
target: runtime.target ?? `bun-${process.platform}-${process.arch}`
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
} finally {
|
|
589
|
+
await rm4(staging, { recursive: true, force: true });
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// packages/compiler/project/plant.ts
|
|
594
|
+
var plantCommands = new Set([
|
|
595
|
+
"deploy",
|
|
596
|
+
"target",
|
|
597
|
+
"status",
|
|
598
|
+
"login",
|
|
599
|
+
"logout",
|
|
600
|
+
"vars",
|
|
601
|
+
"read",
|
|
602
|
+
"write",
|
|
603
|
+
"force",
|
|
604
|
+
"unforce",
|
|
605
|
+
"start",
|
|
606
|
+
"stop",
|
|
607
|
+
"reset"
|
|
608
|
+
]);
|
|
609
|
+
var ask = async (question) => {
|
|
610
|
+
const lines = createInterface({ input: process.stdin, output: process.stdout });
|
|
611
|
+
try {
|
|
612
|
+
return (await lines.question(question)).trim();
|
|
613
|
+
} finally {
|
|
614
|
+
lines.close();
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
var askSecret = (question) => new Promise((accept, reject) => {
|
|
618
|
+
if (!process.stdin.isTTY) {
|
|
619
|
+
reject(new Error("The controller password needs an interactive terminal."));
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
process.stdin.setRawMode(true);
|
|
623
|
+
process.stdin.setEncoding("utf8");
|
|
624
|
+
process.stdin.resume();
|
|
625
|
+
process.stdout.write(question);
|
|
626
|
+
let secret = "";
|
|
627
|
+
const finish = () => {
|
|
628
|
+
process.stdin.off("data", read);
|
|
629
|
+
process.stdin.setRawMode(false);
|
|
630
|
+
process.stdin.pause();
|
|
631
|
+
process.stdout.write(`
|
|
632
|
+
`);
|
|
633
|
+
};
|
|
634
|
+
const read = (text) => {
|
|
635
|
+
for (const character of text) {
|
|
636
|
+
if (character === "\r" || character === `
|
|
637
|
+
`) {
|
|
638
|
+
finish();
|
|
639
|
+
accept(secret);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (character === "\x03") {
|
|
643
|
+
finish();
|
|
644
|
+
reject(new Error("Cancelled."));
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
secret = character === "" ? secret.slice(0, -1) : secret + character;
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
process.stdin.on("data", read);
|
|
651
|
+
});
|
|
652
|
+
var choose = async (question, items, label) => {
|
|
653
|
+
items.forEach((item, index) => console.log(` ${index + 1}. ${label(item)}`));
|
|
654
|
+
while (true) {
|
|
655
|
+
const item = items[Number(await ask(`${question} [1-${items.length}]: `)) - 1];
|
|
656
|
+
if (item !== undefined)
|
|
657
|
+
return item;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
var confirm = async (question, flags) => flags.yes || /^y(es)?$/i.test(await ask(`${question} [y/N]: `));
|
|
661
|
+
var askCredentials = async () => ({
|
|
662
|
+
username: await ask("Controller username: "),
|
|
663
|
+
password: await askSecret("Controller password: ")
|
|
664
|
+
});
|
|
665
|
+
var progress = (message) => console.log(` ${message}`);
|
|
666
|
+
var connect = () => {
|
|
667
|
+
const environment = z.object({
|
|
668
|
+
TSPLC_AGENT_URL: z.url({
|
|
669
|
+
error: "Set TSPLC_AGENT_URL to the plant agent, such as http://line-pc:4850"
|
|
670
|
+
}),
|
|
671
|
+
TSPLC_AGENT_TOKEN: z.string({ error: "Set TSPLC_AGENT_TOKEN to the plant agent's token" })
|
|
672
|
+
}).parse(process.env);
|
|
673
|
+
return createAgentClient({
|
|
674
|
+
url: environment.TSPLC_AGENT_URL,
|
|
675
|
+
token: environment.TSPLC_AGENT_TOKEN
|
|
676
|
+
});
|
|
677
|
+
};
|
|
678
|
+
var selectTarget = async (agent) => {
|
|
679
|
+
const interfaces = await agent.interfaces();
|
|
680
|
+
if (!interfaces.length)
|
|
681
|
+
throw new Error("The plant PC has no IPv4 network.");
|
|
682
|
+
const network = interfaces.length === 1 && interfaces[0] ? interfaces[0] : await choose("Which network is the controller on?", interfaces, (item) => `${item.name} ${item.address}/${item.prefixLength}`);
|
|
683
|
+
console.log(`Scanning ${network.address}/${network.prefixLength} for CODESYS controllers…`);
|
|
684
|
+
const found = await agent.discover(network.name);
|
|
685
|
+
const manual = { address: "", port: 11740, hostname: null };
|
|
686
|
+
const choice = await choose("Which controller?", [...found, manual], (item) => item === manual ? "Enter an address" : `${item.address}${item.hostname ? ` (${item.hostname})` : ""}`);
|
|
687
|
+
const host = choice === manual ? await ask("Controller address: ") : choice.address;
|
|
688
|
+
return agent.setTarget({ host, port: choice.port });
|
|
689
|
+
};
|
|
690
|
+
var contractVariables = async (config, root) => {
|
|
691
|
+
if (!config.typegen)
|
|
692
|
+
return [];
|
|
693
|
+
const contract = z.object({
|
|
694
|
+
symbols: z.array(z.object({ name: z.string(), gvl: z.string(), type: z.string() })),
|
|
695
|
+
typeFields: z.record(z.string(), z.array(z.object({ field: z.string(), type: z.string() })))
|
|
696
|
+
}).parse(await import(pathToFileURL(resolve4(root, config.typegen.dir, "plc.ts")).href));
|
|
697
|
+
return contract.symbols.flatMap(({ name, gvl, type }) => {
|
|
698
|
+
const path = `${gvl}.${name}`;
|
|
699
|
+
const fields = contract.typeFields[type];
|
|
700
|
+
if (!fields)
|
|
701
|
+
return [{ path, type }];
|
|
702
|
+
return fields.map((field) => ({
|
|
703
|
+
path: field.field === "" ? path : `${path}${field.field.startsWith("[") ? "" : "."}${field.field}`,
|
|
704
|
+
type: field.type
|
|
705
|
+
}));
|
|
706
|
+
});
|
|
707
|
+
};
|
|
708
|
+
var prepareRelease = async (file, loaded, flags) => {
|
|
709
|
+
const { config, root } = loaded;
|
|
710
|
+
if (!config.compiler)
|
|
711
|
+
throw new Error("tsplc deploy deploys tsPLC programs; configure compiler.");
|
|
712
|
+
const emitted = await buildProject(file, {
|
|
713
|
+
emitOnly: true,
|
|
714
|
+
forController: true,
|
|
715
|
+
frozenLockfile: flags["frozen-lockfile"],
|
|
716
|
+
loaded,
|
|
717
|
+
buildTarget: "production",
|
|
718
|
+
controller: controllerFor(config, "deploy")
|
|
719
|
+
});
|
|
720
|
+
if (hasErrors2(emitted.diagnostics))
|
|
721
|
+
return { diagnostics: emitted.diagnostics };
|
|
722
|
+
const settings = emitted.controllerConfig;
|
|
723
|
+
if (!settings)
|
|
724
|
+
throw new Error("Configure build settings for deployment.");
|
|
725
|
+
await generateProject(file, { loaded, targets: await projectTargets(file, { loaded }) });
|
|
726
|
+
const portable = (path) => path.split(sep).join("/");
|
|
727
|
+
const libraries = (settings.libraries ?? []).map((library) => ({
|
|
728
|
+
...library,
|
|
729
|
+
release: `libraries/${library.sha256}-${basename(library.path)}`
|
|
730
|
+
}));
|
|
731
|
+
const files = [
|
|
732
|
+
{ path: "template.project", content: Bun.file(settings.template) },
|
|
733
|
+
...libraries.map((library) => ({ path: library.release, content: Bun.file(library.path) }))
|
|
734
|
+
];
|
|
735
|
+
for await (const source of new Bun.Glob("**/*").scan({ cwd: settings.sourceDir, dot: true }))
|
|
736
|
+
files.push({
|
|
737
|
+
path: `st/${portable(source)}`,
|
|
738
|
+
content: Bun.file(resolve4(settings.sourceDir, source))
|
|
739
|
+
});
|
|
740
|
+
let server;
|
|
741
|
+
if (config.server?.runtime) {
|
|
742
|
+
const { executable } = (await buildServer2(config, root)).server;
|
|
743
|
+
server = `server/${basename(executable)}`;
|
|
744
|
+
files.push({ path: server, content: Bun.file(executable) });
|
|
745
|
+
}
|
|
746
|
+
const manifest = {
|
|
747
|
+
config: {
|
|
748
|
+
io: {},
|
|
749
|
+
deviceTree: settings.deviceTree,
|
|
750
|
+
controller: settings.controller,
|
|
751
|
+
libraryReferences: settings.libraryReferences,
|
|
752
|
+
libraries: libraries.map(({ release, ...library }) => ({ ...library, path: release })),
|
|
753
|
+
task: settings.task,
|
|
754
|
+
tasks: settings.tasks,
|
|
755
|
+
removeObjects: settings.removeObjects,
|
|
756
|
+
symbols: settings.symbols,
|
|
757
|
+
timeouts: settings.timeouts,
|
|
758
|
+
template: "template.project",
|
|
759
|
+
sourceDir: "st",
|
|
760
|
+
entry: `st/${portable(relative(settings.sourceDir, settings.entry))}`
|
|
761
|
+
},
|
|
762
|
+
variables: await contractVariables(config, root),
|
|
763
|
+
server
|
|
764
|
+
};
|
|
765
|
+
return { release: { manifest, files } };
|
|
766
|
+
};
|
|
767
|
+
var deploy = async (agent, file, loaded, flags) => {
|
|
768
|
+
const status = await agent.status();
|
|
769
|
+
if (status.login)
|
|
770
|
+
throw new Error("A login is open; log out before deploying: tsplc logout");
|
|
771
|
+
const target = !status.target || flags.select ? await selectTarget(agent) : status.target;
|
|
772
|
+
console.log(`Building the release for ${target.host}…`);
|
|
773
|
+
const { release, diagnostics } = await prepareRelease(file, loaded, flags);
|
|
774
|
+
if (!release)
|
|
775
|
+
return { diagnostics };
|
|
776
|
+
if (flags.download && !await confirm("A full download stops the running application while it loads. Continue?", flags))
|
|
777
|
+
throw new Error("Cancelled.");
|
|
778
|
+
const credentials = await askCredentials();
|
|
779
|
+
const settings = {
|
|
780
|
+
credentials,
|
|
781
|
+
download: flags.download ?? false,
|
|
782
|
+
initializeUser: flags["initialize-user"] ?? false
|
|
783
|
+
};
|
|
784
|
+
console.log(release.manifest.server ? "Deploying the server and the program…" : "Deploying the program…");
|
|
785
|
+
try {
|
|
786
|
+
return await agent.deploy(release, settings, progress);
|
|
787
|
+
} catch (error) {
|
|
788
|
+
if (!(error instanceof AgentOperationError && error.downloadRequired) || !await confirm(`${error.message}. A full download stops the running application while it loads. Download now?`, flags))
|
|
789
|
+
throw error;
|
|
790
|
+
return agent.deploy(release, { ...settings, download: true }, progress);
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
var ensureLogin = async (agent) => {
|
|
794
|
+
if ((await agent.status()).login)
|
|
795
|
+
return;
|
|
796
|
+
const credentials = await askCredentials();
|
|
797
|
+
console.log("Logging in. CODESYS takes a few minutes to start; the login stays open until tsplc logout.");
|
|
798
|
+
await agent.login(credentials, progress);
|
|
799
|
+
};
|
|
800
|
+
var variableChildren = (variables, prefix) => {
|
|
801
|
+
const children = new Map;
|
|
802
|
+
const start = prefix.toLowerCase();
|
|
803
|
+
for (const { path, type } of variables) {
|
|
804
|
+
const lower = path.toLowerCase();
|
|
805
|
+
if (prefix && !(lower === start || lower.startsWith(`${start}.`) || lower.startsWith(`${start}[`)))
|
|
806
|
+
continue;
|
|
807
|
+
const rest = path.slice(prefix.length).replace(/^\./, "");
|
|
808
|
+
if (rest === "")
|
|
809
|
+
return [{ name: path, type, leaves: 1 }];
|
|
810
|
+
const name = /^(\[[^\]]*\]|[^.[]+)/.exec(rest)?.[0] ?? rest;
|
|
811
|
+
const child = children.get(name) ?? { leaves: 0 };
|
|
812
|
+
children.set(name, { type: rest === name ? type : undefined, leaves: child.leaves + 1 });
|
|
813
|
+
}
|
|
814
|
+
const indexes = [...children.keys()].filter((name) => /^\[\d+\]$/.test(name)).map((name) => Number(name.slice(1, -1)));
|
|
815
|
+
const listed = [...children].filter(([name]) => !/^\[\d+\]$/.test(name));
|
|
816
|
+
if (indexes.length) {
|
|
817
|
+
const first = children.get(`[${Math.min(...indexes)}]`);
|
|
818
|
+
listed.unshift([`[${Math.min(...indexes)}..${Math.max(...indexes)}]`, first ?? { leaves: 0 }]);
|
|
819
|
+
}
|
|
820
|
+
return listed.map(([name, child]) => ({ name, ...child }));
|
|
821
|
+
};
|
|
822
|
+
var printLogin = (result) => {
|
|
823
|
+
if ("values" in result) {
|
|
824
|
+
for (const [expression, value] of Object.entries(result.values))
|
|
825
|
+
console.log(`${expression} = ${value}`);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
console.log(`Application ${result.state}`);
|
|
829
|
+
if (result.forced.length)
|
|
830
|
+
console.log(`Forced: ${result.forced.join(", ")}`);
|
|
831
|
+
};
|
|
832
|
+
var runPlantCommand = async (command, args, flags, project) => {
|
|
833
|
+
const agent = connect();
|
|
834
|
+
const expect = (count, usage) => {
|
|
835
|
+
if (args.length !== count)
|
|
836
|
+
throw new Error(`Usage: tsplc ${command} ${usage}`);
|
|
837
|
+
};
|
|
838
|
+
const send = async (loginCommand) => {
|
|
839
|
+
await ensureLogin(agent);
|
|
840
|
+
printLogin(await agent.command(loginCommand));
|
|
841
|
+
};
|
|
842
|
+
switch (command) {
|
|
843
|
+
case "deploy": {
|
|
844
|
+
expect(0, "[--download] [--select] [--initialize-user]");
|
|
845
|
+
const deployed = await deploy(agent, project.file, await project.load(), flags);
|
|
846
|
+
if ("diagnostics" in deployed)
|
|
847
|
+
return deployed;
|
|
848
|
+
console.log(`Deployed release ${deployed.id} (${deployed.operation}).`);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
case "target": {
|
|
852
|
+
expect(0, "");
|
|
853
|
+
const target = await selectTarget(agent);
|
|
854
|
+
console.log(`Deployments now go to ${target.host}:${target.port}.`);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
case "status": {
|
|
858
|
+
expect(0, "");
|
|
859
|
+
const status = await agent.status();
|
|
860
|
+
console.log(`Controller ${status.target ? `${status.target.host}:${status.target.port}` : "not selected (tsplc target)"}`);
|
|
861
|
+
console.log(`Release ${status.release ? `${status.release.id}, ${status.release.operation} at ${status.release.deployedAt}` : "none"}`);
|
|
862
|
+
console.log(`Server ${status.server ? `${status.server.running ? "running" : "restarting"} (${status.server.executable})` : "none"}`);
|
|
863
|
+
console.log(`Login ${status.login ? `open; application ${status.login.state}${status.login.forced.length ? `; forced: ${status.login.forced.join(", ")}` : ""}` : "closed"}`);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
case "login":
|
|
867
|
+
expect(0, "");
|
|
868
|
+
await ensureLogin(agent);
|
|
869
|
+
printLogin(await agent.command({ command: "state" }));
|
|
870
|
+
return;
|
|
871
|
+
case "logout": {
|
|
872
|
+
expect(0, "");
|
|
873
|
+
const forced = (await agent.status()).login?.forced ?? [];
|
|
874
|
+
if (forced.length && !await confirm(`Forced: ${forced.join(", ")}. Forces stay on the controller after logout. Log out anyway?`, flags))
|
|
875
|
+
throw new Error("Cancelled.");
|
|
876
|
+
await agent.logout();
|
|
877
|
+
console.log("Logged out.");
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
case "vars": {
|
|
881
|
+
if (args.length > 1)
|
|
882
|
+
throw new Error("Usage: tsplc vars [path]");
|
|
883
|
+
const variables = await agent.variables();
|
|
884
|
+
const children = variableChildren(variables, args[0] ?? "");
|
|
885
|
+
if (!children.length)
|
|
886
|
+
throw new Error(`No published variable ${args[0]}.`);
|
|
887
|
+
for (const child of children)
|
|
888
|
+
console.log(`${child.name.padEnd(32)} ${child.type ?? `${child.leaves} variables`}`);
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
case "read": {
|
|
892
|
+
const [first, ...rest] = args;
|
|
893
|
+
if (!first)
|
|
894
|
+
throw new Error("Usage: tsplc read <expression>...");
|
|
895
|
+
return send({ command: "read", expressions: [first, ...rest] });
|
|
896
|
+
}
|
|
897
|
+
case "write":
|
|
898
|
+
case "force": {
|
|
899
|
+
const [expression, value, ...extra] = args;
|
|
900
|
+
if (!expression || !value || extra.length)
|
|
901
|
+
throw new Error(`Usage: tsplc ${command} <expression> <value>`);
|
|
902
|
+
return send({ command, values: { [expression]: value } });
|
|
903
|
+
}
|
|
904
|
+
case "unforce":
|
|
905
|
+
return send({ command: "unforce", expressions: args, restore: flags.restore ?? false });
|
|
906
|
+
case "start":
|
|
907
|
+
expect(0, "");
|
|
908
|
+
return send({ command: "start" });
|
|
909
|
+
case "stop":
|
|
910
|
+
expect(0, "");
|
|
911
|
+
if (!await confirm("Stop the application on the controller?", flags))
|
|
912
|
+
throw new Error("Cancelled.");
|
|
913
|
+
return send({ command: "stop" });
|
|
914
|
+
case "reset": {
|
|
915
|
+
expect(1, "warm|cold");
|
|
916
|
+
const kind = z.enum(["warm", "cold"]).parse(args[0]);
|
|
917
|
+
if (!await confirm(`Reset the application (${kind})? It stops and reinitializes its variables.`, flags))
|
|
918
|
+
throw new Error("Cancelled.");
|
|
919
|
+
return send({ command: "reset", kind: kind === "warm" ? "Warm" : "Cold" });
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
// packages/compiler/project/project.ts
|
|
925
|
+
import { copyFile as copyFile2, mkdir as mkdir5, mkdtemp as mkdtemp4, realpath as realpath2, rm as rm5, writeFile as writeFile3 } from "node:fs/promises";
|
|
926
|
+
import { dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
|
|
927
|
+
import {
|
|
928
|
+
buildCodesys as buildCodesys2,
|
|
929
|
+
codesysBuildConfig as codesysBuildConfig2,
|
|
930
|
+
diagnoseCodesys as diagnoseCodesys2,
|
|
931
|
+
emitCodesys as emitCodesys2,
|
|
932
|
+
ioTargets as ioTargets2,
|
|
933
|
+
publicationSelectors as publicationSelectors2,
|
|
934
|
+
planCodesys as planCodesys2
|
|
935
|
+
} from "@tsplc/compiler/codesys";
|
|
936
|
+
import { analyze as analyze2, checkHostConfig as checkHostConfig2 } from "@tsplc/compiler";
|
|
937
|
+
import {
|
|
938
|
+
compilerConfig as compilerConfig2,
|
|
939
|
+
loadTsplcConfig as loadTsplcConfig2
|
|
940
|
+
} from "@tsplc/config";
|
|
941
|
+
import { deviceTree as deviceTree2, loadHardware as loadHardware2, resolveIo as resolveIo2 } from "@tsplc/typegen";
|
|
942
|
+
import { installLibraries as installLibraries2 } from "@tsplc/build";
|
|
943
|
+
import { hasErrors as hasErrors3, normalizeModule as normalizeModule2 } from "@tsplc/compiler/ir";
|
|
944
|
+
var failure2 = (origin, code, cause) => ({
|
|
945
|
+
origin,
|
|
946
|
+
code,
|
|
947
|
+
severity: "error",
|
|
948
|
+
message: cause instanceof Error ? cause.message : String(cause)
|
|
949
|
+
});
|
|
950
|
+
async function loadProject2(file, loaded, buildTarget) {
|
|
951
|
+
let configFile = resolve5(file);
|
|
952
|
+
try {
|
|
953
|
+
configFile = await realpath2(configFile);
|
|
954
|
+
const diagnostics = checkHostConfig2(configFile);
|
|
955
|
+
if (hasErrors3(diagnostics))
|
|
956
|
+
return { configFile, diagnostics };
|
|
957
|
+
const shared = (loaded ?? await loadTsplcConfig2(configFile)).config;
|
|
958
|
+
const root = dirname5(configFile);
|
|
959
|
+
const config = compilerConfig2(shared, resolveIo2(shared, await loadHardware2(shared, root)), buildTarget);
|
|
960
|
+
return {
|
|
961
|
+
configFile,
|
|
962
|
+
shared,
|
|
963
|
+
config: { ...config, ...sourceInput2(config, root) },
|
|
964
|
+
diagnostics
|
|
965
|
+
};
|
|
966
|
+
} catch (cause) {
|
|
967
|
+
return { configFile, diagnostics: [failure2("config", "PLC5003", cause)] };
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function sourceInput2(compiler, root) {
|
|
971
|
+
return {
|
|
972
|
+
tsconfig: resolve5(root, compiler.tsconfig),
|
|
973
|
+
programs: Object.fromEntries(Object.entries(compiler.programs).map(([name, entry]) => [
|
|
974
|
+
name,
|
|
975
|
+
{ ...entry, module: resolve5(root, entry.module) }
|
|
976
|
+
]))
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
async function projectTargets2(file = "tsplc.config.ts", options = {}) {
|
|
980
|
+
const configFile = await realpath2(resolve5(file));
|
|
981
|
+
const { config } = options.loaded ?? await loadTsplcConfig2(configFile);
|
|
982
|
+
if (!config.compiler)
|
|
983
|
+
return;
|
|
984
|
+
const source = analyze2({
|
|
985
|
+
...sourceInput2(config.compiler, dirname5(configFile)),
|
|
986
|
+
services: config.server
|
|
987
|
+
});
|
|
988
|
+
const core = source.module && normalizeModule2(source.module).core;
|
|
989
|
+
return core ? { io: ioTargets2(core), publication: publicationSelectors2(core) } : undefined;
|
|
990
|
+
}
|
|
991
|
+
async function checkProject2(file = "tsplc.config.ts", options = {}) {
|
|
992
|
+
const loaded = await loadProject2(file, options.loaded, options.buildTarget);
|
|
993
|
+
if (!loaded.config) {
|
|
994
|
+
return loaded;
|
|
995
|
+
}
|
|
996
|
+
const config = loaded.config;
|
|
997
|
+
if (config.target.kind !== "codesys") {
|
|
998
|
+
return {
|
|
999
|
+
...loaded,
|
|
1000
|
+
diagnostics: [failure2("backend", "PLC7001", `Unsupported target: ${config.target.kind}`)]
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
const source = analyze2({
|
|
1004
|
+
tsconfig: config.tsconfig,
|
|
1005
|
+
programs: config.programs,
|
|
1006
|
+
services: loaded.shared?.server
|
|
1007
|
+
});
|
|
1008
|
+
if (!source.module) {
|
|
1009
|
+
return { ...loaded, diagnostics: source.diagnostics };
|
|
1010
|
+
}
|
|
1011
|
+
const normalized = normalizeModule2(source.module);
|
|
1012
|
+
if (!normalized.core) {
|
|
1013
|
+
return { ...loaded, diagnostics: normalized.diagnostics };
|
|
1014
|
+
}
|
|
1015
|
+
const tasks = config.tasks.map((task) => ({
|
|
1016
|
+
name: task.name,
|
|
1017
|
+
intervalNanoseconds: task.schedule.interval.nanoseconds,
|
|
1018
|
+
priority: task.priority,
|
|
1019
|
+
programs: task.programs
|
|
1020
|
+
}));
|
|
1021
|
+
const planned = planCodesys2(normalized.core, { tasks }, config.target.options);
|
|
1022
|
+
return {
|
|
1023
|
+
...loaded,
|
|
1024
|
+
...planned,
|
|
1025
|
+
diagnostics: [
|
|
1026
|
+
...loaded.diagnostics,
|
|
1027
|
+
...source.diagnostics,
|
|
1028
|
+
...normalized.diagnostics,
|
|
1029
|
+
...planned.diagnostics
|
|
1030
|
+
]
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
async function engineeringInputs2(shared, configFile, bundle, frozen) {
|
|
1034
|
+
const root = dirname5(configFile);
|
|
1035
|
+
const tree = await deviceTree2(shared, await loadHardware2(shared, root));
|
|
1036
|
+
return {
|
|
1037
|
+
libraries: await installLibraries2(shared.dependencies, root, { frozen }),
|
|
1038
|
+
deviceTree: tree && {
|
|
1039
|
+
...tree,
|
|
1040
|
+
mappings: bundle.ioMappings.map(({ address, variable }) => ({ address, variable }))
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async function buildProject2(file = "tsplc.config.ts", options = {}) {
|
|
1045
|
+
let staging;
|
|
1046
|
+
try {
|
|
1047
|
+
const configFile = await realpath2(resolve5(file));
|
|
1048
|
+
const previous = await owned(configFile, true);
|
|
1049
|
+
if (previous.unknown.length) {
|
|
1050
|
+
throw new Error(`Output contains unrelated files: ${previous.unknown.join(", ")}`);
|
|
1051
|
+
}
|
|
1052
|
+
const checked = await checkProject2(configFile, {
|
|
1053
|
+
loaded: options.loaded,
|
|
1054
|
+
buildTarget: options.buildTarget
|
|
1055
|
+
});
|
|
1056
|
+
if (!checked.plan || !checked.config) {
|
|
1057
|
+
return { diagnostics: checked.diagnostics };
|
|
1058
|
+
}
|
|
1059
|
+
const bundle = emitCodesys2(checked.plan);
|
|
1060
|
+
if (hasErrors3(bundle.diagnostics)) {
|
|
1061
|
+
return { diagnostics: bundle.diagnostics };
|
|
1062
|
+
}
|
|
1063
|
+
const shared = checked.shared;
|
|
1064
|
+
const engineering = shared && (!options.emitOnly || options.forController) ? await engineeringInputs2(shared, configFile, bundle, options.frozenLockfile) : undefined;
|
|
1065
|
+
const controllerConfig = shared?.build ? codesysBuildConfig2(checked.plan, bundle, {
|
|
1066
|
+
configFile,
|
|
1067
|
+
sourceDir: join5(previous.output, "st"),
|
|
1068
|
+
outDir: resolve5(dirname5(configFile), shared.build.outDir),
|
|
1069
|
+
controller: options.controller,
|
|
1070
|
+
...engineering
|
|
1071
|
+
}) : undefined;
|
|
1072
|
+
await mkdir5(previous.root, { recursive: true });
|
|
1073
|
+
staging = await mkdtemp4(join5(previous.root, "stage-"));
|
|
1074
|
+
const artifacts = [
|
|
1075
|
+
...bundle.artifacts,
|
|
1076
|
+
{ path: "mappings.json", content: `${JSON.stringify(bundle.mappings, null, 2)}
|
|
1077
|
+
` },
|
|
1078
|
+
{ path: "io-symbols.json", content: `${JSON.stringify(bundle.ioSymbols, null, 2)}
|
|
1079
|
+
` },
|
|
1080
|
+
{ path: "publication.json", content: `${JSON.stringify(bundle.publication, null, 2)}
|
|
1081
|
+
` },
|
|
1082
|
+
{ path: "config.json", content: `${JSON.stringify(checked.config, null, 2)}
|
|
1083
|
+
` }
|
|
1084
|
+
];
|
|
1085
|
+
for (const artifact of artifacts) {
|
|
1086
|
+
const path = join5(staging, "st", artifact.path);
|
|
1087
|
+
await mkdir5(dirname5(path), { recursive: true });
|
|
1088
|
+
await writeFile3(path, artifact.content);
|
|
1089
|
+
}
|
|
1090
|
+
let vendor;
|
|
1091
|
+
if (!options.emitOnly) {
|
|
1092
|
+
const outDir = shared?.build ? resolve5(dirname5(configFile), shared.build.outDir ?? "build") : join5(previous.root, "engineering");
|
|
1093
|
+
await engineeringDirectory(outDir, configFile);
|
|
1094
|
+
try {
|
|
1095
|
+
vendor = await buildCodesys2(checked.plan, bundle, {
|
|
1096
|
+
configFile,
|
|
1097
|
+
sourceDir: join5(staging, "st"),
|
|
1098
|
+
outDir,
|
|
1099
|
+
controller: options.controller,
|
|
1100
|
+
...engineering
|
|
1101
|
+
});
|
|
1102
|
+
} catch (cause) {
|
|
1103
|
+
return { diagnostics: diagnoseCodesys2(cause, bundle) };
|
|
1104
|
+
}
|
|
1105
|
+
const vendorArtifacts = [
|
|
1106
|
+
["Application.project", vendor.project],
|
|
1107
|
+
["Application.app", vendor.output],
|
|
1108
|
+
["Application.crc", vendor.checksum]
|
|
1109
|
+
];
|
|
1110
|
+
for (const [name, path] of vendorArtifacts) {
|
|
1111
|
+
if (!path)
|
|
1112
|
+
continue;
|
|
1113
|
+
const destination = join5(staging, "vendor", name);
|
|
1114
|
+
await mkdir5(dirname5(destination), { recursive: true });
|
|
1115
|
+
await copyFile2(path, destination);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
const status = vendor ? "vendor-compiled" : "emitted";
|
|
1119
|
+
await publishArtifacts(staging, previous, {
|
|
1120
|
+
configFile,
|
|
1121
|
+
status,
|
|
1122
|
+
...vendor && { vendor: { application: vendor.application, tasks: vendor.tasks } }
|
|
1123
|
+
});
|
|
1124
|
+
staging = undefined;
|
|
1125
|
+
return {
|
|
1126
|
+
directory: previous.output,
|
|
1127
|
+
status,
|
|
1128
|
+
...controllerConfig && { controllerConfig },
|
|
1129
|
+
diagnostics: [...checked.diagnostics, ...bundle.diagnostics]
|
|
1130
|
+
};
|
|
1131
|
+
} catch (cause) {
|
|
1132
|
+
return { diagnostics: [failure2("config", "PLC5004", cause)] };
|
|
1133
|
+
} finally {
|
|
1134
|
+
if (staging) {
|
|
1135
|
+
await rm5(staging, { recursive: true, force: true });
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
505
1139
|
async function cleanProject(file = "tsplc.config.ts") {
|
|
506
1140
|
try {
|
|
507
|
-
await cleanArtifacts(await
|
|
1141
|
+
await cleanArtifacts(await realpath2(resolve5(file)));
|
|
508
1142
|
return { diagnostics: [] };
|
|
509
1143
|
} catch (cause) {
|
|
510
|
-
return { diagnostics: [
|
|
1144
|
+
return { diagnostics: [failure2("config", "PLC5004", cause)] };
|
|
511
1145
|
}
|
|
512
1146
|
}
|
|
513
1147
|
|
|
@@ -527,14 +1161,32 @@ async function main() {
|
|
|
527
1161
|
plc: { type: "boolean" },
|
|
528
1162
|
server: { type: "boolean" },
|
|
529
1163
|
simulate: { type: "boolean" },
|
|
530
|
-
runtime: { type: "boolean" }
|
|
1164
|
+
runtime: { type: "boolean" },
|
|
1165
|
+
download: { type: "boolean" },
|
|
1166
|
+
restore: { type: "boolean" },
|
|
1167
|
+
select: { type: "boolean" },
|
|
1168
|
+
yes: { type: "boolean", short: "y" }
|
|
531
1169
|
}
|
|
532
1170
|
});
|
|
533
1171
|
const command = positionals[0], file = values.config ?? "tsplc.config.ts";
|
|
534
1172
|
if (!command || values.help) {
|
|
535
|
-
console.log(
|
|
1173
|
+
console.log(`tsplc install|typegen|check|build|run|download|online-change|inspect|clean|configure [--config tsplc.config.ts] [--plc|--server] [--simulate] [--runtime] [--emit-only] [--format json] [--read symbol]
|
|
1174
|
+
|
|
1175
|
+
Plant agent (TSPLC_AGENT_URL, TSPLC_AGENT_TOKEN):
|
|
1176
|
+
tsplc deploy [--download] [--select] [--initialize-user]
|
|
1177
|
+
tsplc target | status | login | logout
|
|
1178
|
+
tsplc vars [path] | read <expression>... | write <expression> <value>
|
|
1179
|
+
tsplc force <expression> <value> | unforce [expression]... [--restore]
|
|
1180
|
+
tsplc start | stop | reset warm|cold (-y skips confirmations)`);
|
|
536
1181
|
return 0;
|
|
537
1182
|
}
|
|
1183
|
+
if (plantCommands.has(command)) {
|
|
1184
|
+
const failed = await runPlantCommand(command, positionals.slice(1), values, {
|
|
1185
|
+
file,
|
|
1186
|
+
load: () => loadTsplcConfig3(file)
|
|
1187
|
+
});
|
|
1188
|
+
return failed ? report(failed, values.format) : 0;
|
|
1189
|
+
}
|
|
538
1190
|
if (positionals.length > 1)
|
|
539
1191
|
throw new Error("Specify one command.");
|
|
540
1192
|
if (![
|
|
@@ -578,51 +1230,51 @@ async function main() {
|
|
|
578
1230
|
result = await cleanProject(file);
|
|
579
1231
|
} else {
|
|
580
1232
|
if (["check", "build", "run", "download", "online-change"].includes(command)) {
|
|
581
|
-
const diagnostics =
|
|
582
|
-
if (
|
|
1233
|
+
const diagnostics = checkHostConfig3(resolve6(file));
|
|
1234
|
+
if (hasErrors4(diagnostics))
|
|
583
1235
|
throw new DiagnosticFailure({ diagnostics });
|
|
584
1236
|
}
|
|
585
|
-
const loaded = await
|
|
1237
|
+
const loaded = await loadTsplcConfig3(file);
|
|
586
1238
|
const { config, configFile, root } = loaded;
|
|
587
1239
|
const target = values.simulate ? { ...loaded, config: simulationConfig(config) } : loaded;
|
|
588
1240
|
if (command === "install")
|
|
589
1241
|
result = {
|
|
590
|
-
libraries: await
|
|
1242
|
+
libraries: await installLibraries3(config.dependencies, root, {
|
|
591
1243
|
frozen: values["frozen-lockfile"]
|
|
592
1244
|
})
|
|
593
1245
|
};
|
|
594
1246
|
else if (command === "typegen") {
|
|
595
|
-
const targets = await
|
|
1247
|
+
const targets = await projectTargets2(file, { loaded });
|
|
596
1248
|
if (config.compiler && !values.check) {
|
|
597
|
-
await
|
|
598
|
-
const emitted = await
|
|
599
|
-
if (
|
|
1249
|
+
await generateProject2(file, { hardwareOnly: true, loaded, targets });
|
|
1250
|
+
const emitted = await buildProject2(file, { emitOnly: true, loaded });
|
|
1251
|
+
if (hasErrors4(emitted.diagnostics))
|
|
600
1252
|
throw new DiagnosticFailure(emitted);
|
|
601
1253
|
}
|
|
602
|
-
result = await
|
|
1254
|
+
result = await generateProject2(file, { check: values.check, loaded, targets });
|
|
603
1255
|
} else if (command === "check") {
|
|
604
|
-
result = config.compiler ? await
|
|
1256
|
+
result = config.compiler ? await checkProject2(file, { loaded }) : {
|
|
605
1257
|
objects: (await loadSources(resolveConfig({
|
|
606
1258
|
...rawBuildConfig(config),
|
|
607
|
-
io:
|
|
1259
|
+
io: resolveIo3(config, await loadHardware3(config, root))
|
|
608
1260
|
}, configFile))).objects.length
|
|
609
1261
|
};
|
|
610
1262
|
} else if (command === "build" && values.server) {
|
|
611
1263
|
result = await buildServer(config, root);
|
|
612
1264
|
} else if (command === "build" || command === "run") {
|
|
613
1265
|
if (config.compiler) {
|
|
614
|
-
const built = await
|
|
1266
|
+
const built = await buildProject2(file, {
|
|
615
1267
|
emitOnly: values["emit-only"],
|
|
616
1268
|
frozenLockfile: values["frozen-lockfile"],
|
|
617
1269
|
loaded: target,
|
|
618
|
-
controller:
|
|
1270
|
+
controller: controllerFor2(config, command),
|
|
619
1271
|
buildTarget: buildTargetFor(command)
|
|
620
1272
|
});
|
|
621
|
-
if (
|
|
1273
|
+
if (hasErrors4(built.diagnostics))
|
|
622
1274
|
throw new DiagnosticFailure(built);
|
|
623
|
-
await
|
|
1275
|
+
await generateProject2(file, {
|
|
624
1276
|
loaded,
|
|
625
|
-
targets: await
|
|
1277
|
+
targets: await projectTargets2(file, { loaded })
|
|
626
1278
|
});
|
|
627
1279
|
result = built;
|
|
628
1280
|
} else {
|
|
@@ -631,18 +1283,18 @@ async function main() {
|
|
|
631
1283
|
if (!config.build)
|
|
632
1284
|
throw new Error("Configure build settings.");
|
|
633
1285
|
const build = rawBuildConfig(config);
|
|
634
|
-
const libraries = await
|
|
1286
|
+
const libraries = await installLibraries3(config.dependencies, root, {
|
|
635
1287
|
frozen: values["frozen-lockfile"]
|
|
636
1288
|
});
|
|
637
|
-
const hardware = await
|
|
1289
|
+
const hardware = await loadHardware3(target.config, root);
|
|
638
1290
|
result = await buildSt(resolveConfig({
|
|
639
1291
|
...build,
|
|
640
1292
|
libraries,
|
|
641
|
-
io:
|
|
642
|
-
deviceTree: await
|
|
643
|
-
controller:
|
|
1293
|
+
io: resolveIo3(target.config, hardware),
|
|
1294
|
+
deviceTree: await deviceTree3(target.config, hardware),
|
|
1295
|
+
controller: controllerFor2(config, command)
|
|
644
1296
|
}, configFile));
|
|
645
|
-
await
|
|
1297
|
+
await generateProject2(file, { hardwareOnly: !config.typegen, loaded });
|
|
646
1298
|
}
|
|
647
1299
|
if (command === "build" && !values.plc && !values["emit-only"] && config.server?.runtime) {
|
|
648
1300
|
const backend = await buildServer(config, root);
|
|
@@ -656,31 +1308,35 @@ async function main() {
|
|
|
656
1308
|
} else if (command === "inspect") {
|
|
657
1309
|
if (!config.build)
|
|
658
1310
|
throw new Error("Configure build settings and controller connection.");
|
|
659
|
-
result = await inspectController(resolveConfig({ ...config.build, controller:
|
|
1311
|
+
result = await inspectController(resolveConfig({ ...config.build, controller: controllerFor2(config, command) }, configFile), values.read);
|
|
660
1312
|
} else if (["download", "online-change"].includes(command)) {
|
|
661
1313
|
if (!config.build)
|
|
662
1314
|
throw new Error("Configure build settings and controller connection.");
|
|
663
1315
|
let settings;
|
|
664
1316
|
if (config.compiler) {
|
|
665
|
-
const emitted = await
|
|
1317
|
+
const emitted = await buildProject2(file, {
|
|
666
1318
|
emitOnly: true,
|
|
1319
|
+
forController: true,
|
|
1320
|
+
frozenLockfile: values["frozen-lockfile"],
|
|
667
1321
|
loaded: target,
|
|
668
1322
|
buildTarget: buildTargetFor(command)
|
|
669
1323
|
});
|
|
670
|
-
if (
|
|
1324
|
+
if (hasErrors4(emitted.diagnostics))
|
|
671
1325
|
throw new DiagnosticFailure(emitted);
|
|
672
1326
|
if (!emitted.controllerConfig)
|
|
673
1327
|
throw new Error("Configure build settings for deployment.");
|
|
674
1328
|
settings = { ...emitted.controllerConfig, io: {} };
|
|
675
1329
|
} else
|
|
676
|
-
settings =
|
|
677
|
-
...
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1330
|
+
settings = {
|
|
1331
|
+
...resolveConfig({
|
|
1332
|
+
...rawBuildConfig(config),
|
|
1333
|
+
io: resolveIo3(target.config, await loadHardware3(target.config, root))
|
|
1334
|
+
}, configFile),
|
|
1335
|
+
libraries: await installLibraries3(config.dependencies, root, {
|
|
1336
|
+
frozen: values["frozen-lockfile"]
|
|
1337
|
+
})
|
|
1338
|
+
};
|
|
1339
|
+
result = await updateController({ ...settings, controller: controllerFor2(config, command) }, command, {
|
|
684
1340
|
initializeUser: values["initialize-user"],
|
|
685
1341
|
read: values.read
|
|
686
1342
|
});
|
|
@@ -706,7 +1362,7 @@ class DiagnosticFailure extends Error {
|
|
|
706
1362
|
}
|
|
707
1363
|
function report(result, format) {
|
|
708
1364
|
const diagnostics = result && typeof result === "object" && "diagnostics" in result ? result.diagnostics : [];
|
|
709
|
-
const ok = !
|
|
1365
|
+
const ok = !hasErrors4(diagnostics);
|
|
710
1366
|
if (format === "json")
|
|
711
1367
|
console.log(JSON.stringify({ ok, ...result && typeof result === "object" ? result : { result } }));
|
|
712
1368
|
else {
|
|
@@ -736,5 +1392,5 @@ try {
|
|
|
736
1392
|
}
|
|
737
1393
|
}
|
|
738
1394
|
|
|
739
|
-
//# debugId=
|
|
1395
|
+
//# debugId=2A9AAF31224302AE64756E2164756E21
|
|
740
1396
|
//# sourceMappingURL=cli.js.map
|