@tsplc/config 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +6 -2
- package/dist/build.js +472 -0
- package/dist/build.js.map +13 -0
- package/dist/index.js +574 -0
- package/dist/index.js.map +13 -0
- package/dist/schema.js +554 -0
- package/dist/schema.js.map +12 -0
- package/package.json +51 -4
- package/src/build-schema.ts +245 -0
- package/src/build.ts +80 -0
- package/src/compiler.ts +168 -0
- package/src/index.ts +2 -0
- package/src/loader.ts +20 -0
- package/src/schema.ts +408 -0
- package/templates/README.md +17 -0
- package/templates/default.project +0 -0
- package/templates/provenance.json +8 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
// packages/config/src/schema.ts
|
|
2
|
+
import { z as z3 } from "zod";
|
|
3
|
+
|
|
4
|
+
// packages/config/src/build-schema.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var commandArgumentSchema = z.string().refine((value) => value.trim().length > 0, "Command arguments must not be empty").refine((value) => !/["\r\n\0]/.test(value), "Command arguments must not contain quotes, line breaks, or null bytes");
|
|
7
|
+
var codesysTimeouts = {
|
|
8
|
+
build: 20 * 60 * 1000,
|
|
9
|
+
hostCommand: 15000,
|
|
10
|
+
runtime: 60000,
|
|
11
|
+
startup: 20 * 60 * 1000,
|
|
12
|
+
stop: 1e4,
|
|
13
|
+
terminate: 5000
|
|
14
|
+
};
|
|
15
|
+
var positiveMilliseconds = z.number().int().positive();
|
|
16
|
+
var pathSchema = z.string().trim().nonempty();
|
|
17
|
+
var wineSchema = z.strictObject({
|
|
18
|
+
prefix: pathSchema,
|
|
19
|
+
binary: pathSchema.default("wine"),
|
|
20
|
+
pathBinary: pathSchema.default("winepath"),
|
|
21
|
+
debug: z.string().default("-all"),
|
|
22
|
+
nativeExit: z.boolean().default(false)
|
|
23
|
+
});
|
|
24
|
+
var localCodesysSchema = z.strictObject({
|
|
25
|
+
executable: commandArgumentSchema,
|
|
26
|
+
profile: commandArgumentSchema,
|
|
27
|
+
wine: wineSchema.optional(),
|
|
28
|
+
commandProcessor: pathSchema.default("cmd.exe")
|
|
29
|
+
});
|
|
30
|
+
var iecName = z.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/);
|
|
31
|
+
var cyclicTasksSchema = z.array(z.strictObject({
|
|
32
|
+
name: iecName,
|
|
33
|
+
intervalNanoseconds: z.string().regex(/^[1-9][0-9]*$/).refine((value) => {
|
|
34
|
+
if (!/^[1-9][0-9]*$/.test(value)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const ns = BigInt(value);
|
|
38
|
+
return ns % 1000000n === 0n && ns <= 4294967295000000n;
|
|
39
|
+
}, "Cyclic tasks require an exact positive TIME interval in milliseconds"),
|
|
40
|
+
priority: z.number().int().min(0).max(31),
|
|
41
|
+
programs: z.array(iecName).nonempty()
|
|
42
|
+
})).nonempty().superRefine((tasks, context) => {
|
|
43
|
+
const names = new Set, programs = new Set;
|
|
44
|
+
tasks.forEach((task, index) => {
|
|
45
|
+
if (names.has(task.name.toLowerCase())) {
|
|
46
|
+
context.addIssue({
|
|
47
|
+
code: "custom",
|
|
48
|
+
path: [index, "name"],
|
|
49
|
+
message: "Task names must be unique"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
names.add(task.name.toLowerCase());
|
|
53
|
+
task.programs.forEach((program, position) => {
|
|
54
|
+
if (programs.has(program.toLowerCase())) {
|
|
55
|
+
context.addIssue({
|
|
56
|
+
code: "custom",
|
|
57
|
+
path: [index, "programs", position],
|
|
58
|
+
message: "A program can be scheduled only once"
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
programs.add(program.toLowerCase());
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
var ioBindingOptions = {
|
|
66
|
+
inverted: z.boolean().optional(),
|
|
67
|
+
shared: z.boolean().optional()
|
|
68
|
+
};
|
|
69
|
+
var directIoBindingSchema = z.strictObject({
|
|
70
|
+
address: z.string().regex(/^%[IQ](?:X\d+\.[0-7]|[BWD]\d+)$/),
|
|
71
|
+
type: z.string().optional(),
|
|
72
|
+
...ioBindingOptions
|
|
73
|
+
});
|
|
74
|
+
var ethernetSchema = z.strictObject({
|
|
75
|
+
interface: z.string().nonempty().optional(),
|
|
76
|
+
address: z.ipv4().optional(),
|
|
77
|
+
subnet: z.ipv4().optional(),
|
|
78
|
+
gateway: z.ipv4().optional()
|
|
79
|
+
});
|
|
80
|
+
var processImageSchema = z.strictObject({
|
|
81
|
+
address: z.string().regex(/^%[IQ]B\d+$/),
|
|
82
|
+
size: z.number().int().positive()
|
|
83
|
+
});
|
|
84
|
+
var deviceTreeSchema = z.strictObject({
|
|
85
|
+
ethernet: ethernetSchema.default({}),
|
|
86
|
+
devices: z.array(z.strictObject({
|
|
87
|
+
name: iecName,
|
|
88
|
+
eds: z.string().nonempty(),
|
|
89
|
+
address: z.ipv4().optional(),
|
|
90
|
+
rpiMicroseconds: z.number().int().positive().optional(),
|
|
91
|
+
inputs: processImageSchema.optional(),
|
|
92
|
+
outputs: processImageSchema.optional(),
|
|
93
|
+
modbus: z.strictObject({
|
|
94
|
+
port: z.number().int().positive(),
|
|
95
|
+
registers: z.array(z.strictObject({
|
|
96
|
+
name: iecName,
|
|
97
|
+
offset: z.number().int().nonnegative(),
|
|
98
|
+
value: z.string().regex(/^%QW\d+$/),
|
|
99
|
+
write: z.string().regex(/^%QX\d+\.[0-7]$/),
|
|
100
|
+
status: z.string().regex(/^%IB\d+$/),
|
|
101
|
+
requests: z.string().regex(/^%ID\d+$/)
|
|
102
|
+
}))
|
|
103
|
+
}).optional(),
|
|
104
|
+
comments: z.array(z.strictObject({
|
|
105
|
+
address: z.string().regex(/^%[IQ](?:X\d+\.[0-7]|B\d+)$/),
|
|
106
|
+
text: z.string().nonempty()
|
|
107
|
+
})).default([])
|
|
108
|
+
})).nonempty(),
|
|
109
|
+
mappings: z.array(z.strictObject({
|
|
110
|
+
address: z.string().regex(/^%[IQ](?:X\d+\.[0-7]|B\d+)$/),
|
|
111
|
+
variable: z.string().nonempty()
|
|
112
|
+
})).default([])
|
|
113
|
+
});
|
|
114
|
+
var controllerSchema = z.strictObject({
|
|
115
|
+
type: z.number().int().positive(),
|
|
116
|
+
id: z.string().regex(/^[0-9A-F]{4} [0-9A-F]{4}$/),
|
|
117
|
+
version: z.string().regex(/^\d+\.\d+\.\d+\.\d+$/)
|
|
118
|
+
});
|
|
119
|
+
var controllerTargetSchema = controllerSchema.pick({ version: true }).extend({
|
|
120
|
+
target: z.enum(["linux-sl", "virtual-linux-arm64-sl"])
|
|
121
|
+
});
|
|
122
|
+
var controllerDevices = {
|
|
123
|
+
"linux-sl": { type: 4102, id: "0000 0005" },
|
|
124
|
+
"virtual-linux-arm64-sl": { type: 4096, id: "0000 1008" }
|
|
125
|
+
};
|
|
126
|
+
var configSchema = z.strictObject({
|
|
127
|
+
io: z.record(z.string(), directIoBindingSchema).optional(),
|
|
128
|
+
deviceTree: deviceTreeSchema.optional(),
|
|
129
|
+
controller: controllerSchema.optional(),
|
|
130
|
+
libraryReferences: z.array(z.string().nonempty()).default([]),
|
|
131
|
+
libraries: z.array(z.strictObject({
|
|
132
|
+
title: z.string(),
|
|
133
|
+
company: z.string().optional(),
|
|
134
|
+
version: z.string().optional(),
|
|
135
|
+
namespace: z.string().optional(),
|
|
136
|
+
path: pathSchema,
|
|
137
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
138
|
+
url: z.string().optional()
|
|
139
|
+
})).optional(),
|
|
140
|
+
docker: z.strictObject({
|
|
141
|
+
cpuSet: z.string().regex(/^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$/).optional()
|
|
142
|
+
}).optional(),
|
|
143
|
+
template: pathSchema.optional(),
|
|
144
|
+
sourceDir: pathSchema.default("src"),
|
|
145
|
+
entry: pathSchema.default("src/Main.st"),
|
|
146
|
+
outDir: pathSchema.default("build"),
|
|
147
|
+
task: z.string().nonempty().default("MainTask"),
|
|
148
|
+
tasks: cyclicTasksSchema.optional(),
|
|
149
|
+
removeObjects: z.array(z.string().nonempty()).default([]),
|
|
150
|
+
symbols: z.boolean().default(false),
|
|
151
|
+
online: z.strictObject({
|
|
152
|
+
network: pathSchema.default("bridge"),
|
|
153
|
+
gateway: z.strictObject({
|
|
154
|
+
host: pathSchema,
|
|
155
|
+
port: z.number().int().min(1).max(65535).default(1217)
|
|
156
|
+
}),
|
|
157
|
+
device: z.strictObject({
|
|
158
|
+
host: pathSchema,
|
|
159
|
+
port: z.number().int().min(1).max(65535).default(11740)
|
|
160
|
+
}),
|
|
161
|
+
credentials: z.strictObject({
|
|
162
|
+
usernameEnv: z.string().regex(/^[A-Za-z_][A-Za-z_0-9]*$/),
|
|
163
|
+
passwordEnv: z.string().regex(/^[A-Za-z_][A-Za-z_0-9]*$/)
|
|
164
|
+
}).optional()
|
|
165
|
+
}).optional(),
|
|
166
|
+
timeouts: z.strictObject({
|
|
167
|
+
startup: positiveMilliseconds.default(codesysTimeouts.startup),
|
|
168
|
+
build: positiveMilliseconds.default(codesysTimeouts.build),
|
|
169
|
+
stop: positiveMilliseconds.default(codesysTimeouts.stop),
|
|
170
|
+
terminate: positiveMilliseconds.default(codesysTimeouts.terminate),
|
|
171
|
+
hostCommand: positiveMilliseconds.default(codesysTimeouts.hostCommand),
|
|
172
|
+
runtime: positiveMilliseconds.default(codesysTimeouts.runtime)
|
|
173
|
+
}).prefault({})
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// packages/config/src/compiler.ts
|
|
177
|
+
import { z as z2 } from "zod";
|
|
178
|
+
var durationSchema = z2.object({
|
|
179
|
+
kind: z2.literal("duration"),
|
|
180
|
+
nanoseconds: z2.string().regex(/^[1-9][0-9]*$/)
|
|
181
|
+
}).strict();
|
|
182
|
+
function duration(value, scale) {
|
|
183
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
184
|
+
throw new Error("Task duration must be finite and positive.");
|
|
185
|
+
}
|
|
186
|
+
const nanoseconds = value * scale;
|
|
187
|
+
if (!Number.isSafeInteger(nanoseconds)) {
|
|
188
|
+
throw new Error("Task duration must be an exact, safe number of nanoseconds.");
|
|
189
|
+
}
|
|
190
|
+
return { kind: "duration", nanoseconds: String(nanoseconds) };
|
|
191
|
+
}
|
|
192
|
+
var ns = (value) => duration(value, 1);
|
|
193
|
+
var ms = (value) => duration(value, 1e6);
|
|
194
|
+
var seconds = (value) => duration(value, 1e9);
|
|
195
|
+
var sourceExportSchema = z2.object({ module: z2.string().min(1), export: z2.string().min(1) }).strict();
|
|
196
|
+
var taskSchema = z2.object({
|
|
197
|
+
name: z2.string().min(1),
|
|
198
|
+
schedule: z2.object({ kind: z2.literal("cyclic"), interval: durationSchema }).strict(),
|
|
199
|
+
priority: z2.number().int().nonnegative(),
|
|
200
|
+
programs: z2.array(z2.string().min(1)).min(1)
|
|
201
|
+
}).strict();
|
|
202
|
+
var targetSchema = z2.object({
|
|
203
|
+
kind: z2.string().min(1),
|
|
204
|
+
options: z2.unknown().refine((value) => value !== undefined, { message: "Target options are required." })
|
|
205
|
+
}).strict();
|
|
206
|
+
var common = {
|
|
207
|
+
name: z2.string().min(1),
|
|
208
|
+
tsconfig: z2.string().min(1),
|
|
209
|
+
tasks: z2.array(taskSchema).min(1),
|
|
210
|
+
target: targetSchema
|
|
211
|
+
};
|
|
212
|
+
var entryConfigSchema = z2.object({ ...common, entry: sourceExportSchema }).strict();
|
|
213
|
+
var programsConfigSchema = z2.object({ ...common, programs: z2.record(z2.string().min(1), sourceExportSchema) }).strict();
|
|
214
|
+
function definePlcConfig(config) {
|
|
215
|
+
return config;
|
|
216
|
+
}
|
|
217
|
+
function validateConfig(value) {
|
|
218
|
+
const multiplePrograms = typeof value === "object" && value !== null && Object.hasOwn(value, "programs") && !Object.hasOwn(value, "entry");
|
|
219
|
+
const parsed = (multiplePrograms ? programsConfigSchema : entryConfigSchema).safeParse(value);
|
|
220
|
+
if (!parsed.success) {
|
|
221
|
+
return {
|
|
222
|
+
diagnostics: parsed.error.issues.map((issue) => ({
|
|
223
|
+
origin: "config",
|
|
224
|
+
code: "PLC5001",
|
|
225
|
+
severity: "error",
|
|
226
|
+
message: `${issue.path.join(".") || "config"}: ${issue.message}`
|
|
227
|
+
}))
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const { data } = parsed;
|
|
231
|
+
const programs = "entry" in data ? { main: data.entry } : data.programs;
|
|
232
|
+
const diagnostics = [];
|
|
233
|
+
const report = (message) => {
|
|
234
|
+
diagnostics.push({ origin: "config", code: "PLC5002", severity: "error", message });
|
|
235
|
+
};
|
|
236
|
+
if (Object.keys(programs).length === 0) {
|
|
237
|
+
report("At least one program is required.");
|
|
238
|
+
}
|
|
239
|
+
const taskNames = new Set;
|
|
240
|
+
const assigned = new Set;
|
|
241
|
+
for (const task of data.tasks) {
|
|
242
|
+
if (taskNames.has(task.name)) {
|
|
243
|
+
report(`Duplicate task name: ${task.name}.`);
|
|
244
|
+
}
|
|
245
|
+
taskNames.add(task.name);
|
|
246
|
+
for (const program of task.programs) {
|
|
247
|
+
if (!Object.hasOwn(programs, program)) {
|
|
248
|
+
report(`Task ${task.name} references unknown program ${program}.`);
|
|
249
|
+
}
|
|
250
|
+
if (assigned.has(program)) {
|
|
251
|
+
report(`Program ${program} must belong to exactly one task.`);
|
|
252
|
+
}
|
|
253
|
+
assigned.add(program);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (const program of Object.keys(programs)) {
|
|
257
|
+
if (!assigned.has(program)) {
|
|
258
|
+
report(`Program ${program} is not assigned to a task.`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (diagnostics.length) {
|
|
262
|
+
return { diagnostics };
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
config: {
|
|
266
|
+
name: data.name,
|
|
267
|
+
tsconfig: data.tsconfig,
|
|
268
|
+
programs,
|
|
269
|
+
tasks: data.tasks,
|
|
270
|
+
target: data.target
|
|
271
|
+
},
|
|
272
|
+
diagnostics
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// packages/config/src/schema.ts
|
|
277
|
+
var httpOptionsSchema = z3.strictObject({
|
|
278
|
+
timeoutMs: z3.number().int().positive().max(60000).default(2000),
|
|
279
|
+
credentials: z3.array(z3.strictObject({
|
|
280
|
+
originEnv: z3.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/),
|
|
281
|
+
bearerTokenEnv: z3.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/)
|
|
282
|
+
})).default([])
|
|
283
|
+
}).default({ timeoutMs: 2000, credentials: [] });
|
|
284
|
+
var ioBindingSchema = z3.union([
|
|
285
|
+
directIoBindingSchema,
|
|
286
|
+
z3.strictObject({
|
|
287
|
+
device: z3.string().min(1),
|
|
288
|
+
channel: z3.string().min(1),
|
|
289
|
+
...ioBindingOptions
|
|
290
|
+
})
|
|
291
|
+
]);
|
|
292
|
+
var assemblySchema = z3.strictObject({
|
|
293
|
+
assembly: z3.number().int().nonnegative().optional(),
|
|
294
|
+
size: z3.number().int().positive().optional(),
|
|
295
|
+
address: z3.string().regex(/^%[IQ]B\d+$/).optional()
|
|
296
|
+
});
|
|
297
|
+
var hardwareDeviceSchema = z3.strictObject({
|
|
298
|
+
file: z3.string().min(1),
|
|
299
|
+
connection: z3.string().min(1).optional(),
|
|
300
|
+
inputs: assemblySchema.optional(),
|
|
301
|
+
outputs: assemblySchema.optional(),
|
|
302
|
+
modbus: z3.strictObject({
|
|
303
|
+
port: z3.number().int().min(1).max(65535).default(502),
|
|
304
|
+
registers: z3.record(z3.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/), z3.number().int().min(0).max(65535))
|
|
305
|
+
}).optional(),
|
|
306
|
+
statusSymbols: z3.record(z3.string(), z3.string()).optional(),
|
|
307
|
+
network: z3.strictObject({
|
|
308
|
+
address: z3.ipv4().optional(),
|
|
309
|
+
rpi: durationSchema.refine((rpi) => BigInt(rpi.nanoseconds) % 1000n === 0n, "An RPI must be a whole number of microseconds.").optional()
|
|
310
|
+
}).optional()
|
|
311
|
+
});
|
|
312
|
+
var dependencySchema = z3.union([
|
|
313
|
+
z3.string().regex(/^\d+\.\d+\.\d+\.\d+$/),
|
|
314
|
+
z3.strictObject({
|
|
315
|
+
version: z3.string().regex(/^\d+\.\d+\.\d+\.\d+$/),
|
|
316
|
+
company: z3.string().optional(),
|
|
317
|
+
namespace: z3.string().optional()
|
|
318
|
+
}),
|
|
319
|
+
z3.strictObject({ path: z3.string().min(1), namespace: z3.string().optional() })
|
|
320
|
+
]);
|
|
321
|
+
var taskSchema2 = z3.strictObject({
|
|
322
|
+
name: z3.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/),
|
|
323
|
+
schedule: z3.strictObject({ kind: z3.literal("cyclic"), interval: durationSchema }),
|
|
324
|
+
priority: z3.number().int().min(0).max(31),
|
|
325
|
+
programs: z3.array(z3.string().min(1)).nonempty()
|
|
326
|
+
});
|
|
327
|
+
var monitorSchema = z3.strictObject({
|
|
328
|
+
endpoint: z3.string().url(),
|
|
329
|
+
namespaceUri: z3.string().optional(),
|
|
330
|
+
symbols: z3.array(z3.string().min(1)).optional(),
|
|
331
|
+
securityMode: z3.enum(["None", "Sign", "SignAndEncrypt"]).default("SignAndEncrypt"),
|
|
332
|
+
securityPolicy: z3.enum(["None", "Basic256Sha256"]).optional(),
|
|
333
|
+
certificateDirectory: z3.string().min(1).default(".tsplc/opcua-pki"),
|
|
334
|
+
username: z3.string().min(1).optional(),
|
|
335
|
+
passwordEnv: z3.string().regex(/^[A-Za-z_][A-Za-z_0-9]*$/).optional()
|
|
336
|
+
}).superRefine((monitor, context) => {
|
|
337
|
+
if (monitor.securityPolicy && monitor.securityMode === "None" !== (monitor.securityPolicy === "None"))
|
|
338
|
+
context.addIssue({
|
|
339
|
+
code: "custom",
|
|
340
|
+
path: ["securityPolicy"],
|
|
341
|
+
message: "None mode requires None policy; secure modes require Basic256Sha256."
|
|
342
|
+
});
|
|
343
|
+
if (monitor.passwordEnv && !monitor.username)
|
|
344
|
+
context.addIssue({
|
|
345
|
+
code: "custom",
|
|
346
|
+
path: ["username"],
|
|
347
|
+
message: "Configure username when passwordEnv is used."
|
|
348
|
+
});
|
|
349
|
+
if (monitor.username && monitor.securityMode === "None")
|
|
350
|
+
context.addIssue({
|
|
351
|
+
code: "custom",
|
|
352
|
+
path: ["securityMode"],
|
|
353
|
+
message: "Username authentication requires a secure OPC-UA connection."
|
|
354
|
+
});
|
|
355
|
+
}).transform((monitor) => ({
|
|
356
|
+
...monitor,
|
|
357
|
+
securityPolicy: monitor.securityPolicy ?? (monitor.securityMode === "None" ? "None" : "Basic256Sha256")
|
|
358
|
+
}));
|
|
359
|
+
var typegenSchema = z3.strictObject({
|
|
360
|
+
types: z3.union([z3.string(), z3.array(z3.string())]).optional(),
|
|
361
|
+
globals: z3.union([z3.string(), z3.array(z3.string())]).optional(),
|
|
362
|
+
dir: z3.string().default("generated"),
|
|
363
|
+
declarations: z3.boolean().default(true),
|
|
364
|
+
ioTypes: z3.boolean().default(true)
|
|
365
|
+
});
|
|
366
|
+
var buildTargetSchema = z3.enum(["production", "development"]);
|
|
367
|
+
var publicationSchema = z3.strictObject({
|
|
368
|
+
production: z3.array(z3.string().nonempty()),
|
|
369
|
+
development: z3.array(z3.string().nonempty())
|
|
370
|
+
});
|
|
371
|
+
var hmiTarget = z3.string().regex(/^\w+(\.\w+|\[\d+\])+$/);
|
|
372
|
+
var hmiControlSchema = z3.discriminatedUnion("kind", [
|
|
373
|
+
z3.strictObject({
|
|
374
|
+
kind: z3.literal("button"),
|
|
375
|
+
label: z3.string().nonempty(),
|
|
376
|
+
target: hmiTarget,
|
|
377
|
+
tone: z3.enum(["start", "stop"]).optional(),
|
|
378
|
+
confirm: z3.boolean().optional()
|
|
379
|
+
}),
|
|
380
|
+
z3.strictObject({
|
|
381
|
+
kind: z3.literal("toggle"),
|
|
382
|
+
label: z3.string().nonempty(),
|
|
383
|
+
target: hmiTarget
|
|
384
|
+
}),
|
|
385
|
+
z3.strictObject({
|
|
386
|
+
kind: z3.literal("slider"),
|
|
387
|
+
label: z3.string().nonempty(),
|
|
388
|
+
target: hmiTarget,
|
|
389
|
+
min: z3.number().int(),
|
|
390
|
+
max: z3.number().int()
|
|
391
|
+
})
|
|
392
|
+
]);
|
|
393
|
+
var hmiSchema = z3.strictObject({
|
|
394
|
+
title: z3.string().nonempty(),
|
|
395
|
+
controls: z3.array(hmiControlSchema).default([]),
|
|
396
|
+
conveyors: z3.array(z3.strictObject({
|
|
397
|
+
label: z3.string().nonempty(),
|
|
398
|
+
positions: z3.number().int().positive(),
|
|
399
|
+
diverters: z3.array(z3.number().int().nonnegative()).default([])
|
|
400
|
+
})).default([])
|
|
401
|
+
});
|
|
402
|
+
var tsplcConfigSchema = z3.strictObject({
|
|
403
|
+
name: z3.string().default("Application"),
|
|
404
|
+
backend: z3.literal("codesys"),
|
|
405
|
+
publish: publicationSchema.optional(),
|
|
406
|
+
server: z3.strictObject({
|
|
407
|
+
baseUrlEnv: z3.string().optional(),
|
|
408
|
+
transport: z3.enum(["http", "opcua"]).default("http"),
|
|
409
|
+
runtime: z3.strictObject({
|
|
410
|
+
entry: z3.string(),
|
|
411
|
+
target: z3.enum([
|
|
412
|
+
"bun-linux-x64",
|
|
413
|
+
"bun-linux-arm64",
|
|
414
|
+
"bun-darwin-x64",
|
|
415
|
+
"bun-darwin-arm64",
|
|
416
|
+
"bun-windows-x64"
|
|
417
|
+
]).optional()
|
|
418
|
+
}).optional()
|
|
419
|
+
}).optional(),
|
|
420
|
+
compiler: z3.strictObject({
|
|
421
|
+
tsconfig: z3.string().default("tsconfig.json"),
|
|
422
|
+
programs: z3.record(z3.string(), z3.strictObject({ module: z3.string(), export: z3.string() }))
|
|
423
|
+
}).optional(),
|
|
424
|
+
tasks: z3.array(taskSchema2).nonempty().optional(),
|
|
425
|
+
build: configSchema.extend({ task: configSchema.shape.task.unwrap().optional() }).optional(),
|
|
426
|
+
codesys: z3.strictObject({
|
|
427
|
+
http: httpOptionsSchema.optional(),
|
|
428
|
+
programNames: z3.record(z3.string(), z3.string()).optional(),
|
|
429
|
+
deviceTree: z3.strictObject({ ethernet: ethernetSchema.optional() }).optional(),
|
|
430
|
+
controllers: z3.strictObject({
|
|
431
|
+
production: controllerTargetSchema.optional(),
|
|
432
|
+
development: controllerTargetSchema.optional()
|
|
433
|
+
}).optional()
|
|
434
|
+
}).optional(),
|
|
435
|
+
eds: z3.union([z3.string(), z3.array(z3.string())]).default("eds"),
|
|
436
|
+
devices: z3.record(z3.string(), hardwareDeviceSchema).default({}),
|
|
437
|
+
io: z3.record(z3.string(), ioBindingSchema).default({}),
|
|
438
|
+
dependencies: z3.record(z3.string(), dependencySchema).default({}),
|
|
439
|
+
typegen: typegenSchema.optional(),
|
|
440
|
+
monitor: monitorSchema.optional(),
|
|
441
|
+
hmi: hmiSchema.optional()
|
|
442
|
+
});
|
|
443
|
+
var buildTargetFor = (command) => ["run", "download", "online-change", "inspect"].includes(command) ? "development" : "production";
|
|
444
|
+
var controllerFor = (config, command) => {
|
|
445
|
+
const controller = config.codesys?.controllers?.[buildTargetFor(command)];
|
|
446
|
+
return controller && { ...controllerDevices[controller.target], version: controller.version };
|
|
447
|
+
};
|
|
448
|
+
var simulationConfig = (config) => ({
|
|
449
|
+
...config,
|
|
450
|
+
eds: [],
|
|
451
|
+
devices: {},
|
|
452
|
+
io: {},
|
|
453
|
+
...config.codesys && {
|
|
454
|
+
codesys: {
|
|
455
|
+
...config.codesys,
|
|
456
|
+
deviceTree: undefined
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
function defineConfig(config) {
|
|
461
|
+
return config;
|
|
462
|
+
}
|
|
463
|
+
function rawBuildConfig(config) {
|
|
464
|
+
if (config.compiler)
|
|
465
|
+
throw new Error("Use the compiler build plan for TypeScript projects.");
|
|
466
|
+
if (!config.build)
|
|
467
|
+
throw new Error("Configure build settings.");
|
|
468
|
+
if (!config.tasks)
|
|
469
|
+
return config.build;
|
|
470
|
+
if (config.build.tasks || config.build.task !== undefined)
|
|
471
|
+
throw new Error("Configure the raw ST schedule in tasks or build.tasks/build.task, not both.");
|
|
472
|
+
return {
|
|
473
|
+
...config.build,
|
|
474
|
+
tasks: cyclicTasksSchema.parse(config.tasks.map((task) => ({
|
|
475
|
+
name: task.name,
|
|
476
|
+
intervalNanoseconds: task.schedule.interval.nanoseconds,
|
|
477
|
+
priority: task.priority,
|
|
478
|
+
programs: task.programs
|
|
479
|
+
})))
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function compilerConfig(config, io, buildTarget = "production") {
|
|
483
|
+
if (!config.compiler || !config.tasks)
|
|
484
|
+
throw new Error("This project uses raw Structured Text; no TypeScript compiler is configured.");
|
|
485
|
+
const { deviceTree: _deviceTree, controllers: _controllers, ...codesys } = config.codesys ?? {};
|
|
486
|
+
const value = {
|
|
487
|
+
name: config.name,
|
|
488
|
+
tsconfig: config.compiler.tsconfig,
|
|
489
|
+
programs: config.compiler.programs,
|
|
490
|
+
tasks: config.tasks,
|
|
491
|
+
target: {
|
|
492
|
+
kind: config.backend,
|
|
493
|
+
options: {
|
|
494
|
+
...codesys,
|
|
495
|
+
publish: config.publish,
|
|
496
|
+
buildTarget,
|
|
497
|
+
io: Object.fromEntries(Object.entries(io).map(([name, binding]) => [
|
|
498
|
+
name,
|
|
499
|
+
{
|
|
500
|
+
kind: "direct",
|
|
501
|
+
...binding,
|
|
502
|
+
...mappedChannel(config, name, binding) && { mapped: true }
|
|
503
|
+
}
|
|
504
|
+
])),
|
|
505
|
+
engineering: {
|
|
506
|
+
kind: "v3.5",
|
|
507
|
+
...config.build ? { build: compilerBuildOptions(config.build) } : {}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
const validated = validateConfig(value);
|
|
513
|
+
if (!validated.config)
|
|
514
|
+
throw new Error(validated.diagnostics.map((item) => item.message).join(`
|
|
515
|
+
`));
|
|
516
|
+
return validated.config;
|
|
517
|
+
}
|
|
518
|
+
function mappedChannel(config, name, binding) {
|
|
519
|
+
const source = config.io[name];
|
|
520
|
+
return config.codesys?.deviceTree !== undefined && source !== undefined && "device" in source && !binding.inverted && !binding.shared && /^%[IQ][XB]/.test(binding.address) && !/^ARRAY/i.test(binding.type ?? "");
|
|
521
|
+
}
|
|
522
|
+
function compilerBuildOptions(build) {
|
|
523
|
+
const {
|
|
524
|
+
sourceDir: _source,
|
|
525
|
+
entry: _entry,
|
|
526
|
+
outDir: _out,
|
|
527
|
+
task: _task,
|
|
528
|
+
tasks: _tasks,
|
|
529
|
+
...settings
|
|
530
|
+
} = build;
|
|
531
|
+
return settings;
|
|
532
|
+
}
|
|
533
|
+
// packages/config/src/loader.ts
|
|
534
|
+
import { readFile } from "node:fs/promises";
|
|
535
|
+
import { dirname, resolve } from "node:path";
|
|
536
|
+
import { createJiti } from "jiti";
|
|
537
|
+
async function loadTsplcConfig(file = "tsplc.config.ts") {
|
|
538
|
+
const configFile = resolve(file);
|
|
539
|
+
await readFile(configFile);
|
|
540
|
+
const jiti = createJiti(import.meta.url, {
|
|
541
|
+
moduleCache: false,
|
|
542
|
+
fsCache: false,
|
|
543
|
+
interopDefault: false,
|
|
544
|
+
tryNative: false
|
|
545
|
+
});
|
|
546
|
+
const module = await jiti.import(configFile);
|
|
547
|
+
const config = tsplcConfigSchema.parse(module.default ?? module.config);
|
|
548
|
+
if (config.compiler && !config.tasks)
|
|
549
|
+
throw new Error("TypeScript programs require a tasks configuration.");
|
|
550
|
+
return { configFile, root: dirname(configFile), config };
|
|
551
|
+
}
|
|
552
|
+
export {
|
|
553
|
+
buildTargetFor,
|
|
554
|
+
buildTargetSchema,
|
|
555
|
+
compilerConfig,
|
|
556
|
+
controllerFor,
|
|
557
|
+
defineConfig,
|
|
558
|
+
definePlcConfig,
|
|
559
|
+
hmiSchema,
|
|
560
|
+
httpOptionsSchema,
|
|
561
|
+
loadTsplcConfig,
|
|
562
|
+
ms,
|
|
563
|
+
ns,
|
|
564
|
+
publicationSchema,
|
|
565
|
+
rawBuildConfig,
|
|
566
|
+
seconds,
|
|
567
|
+
simulationConfig,
|
|
568
|
+
tsplcConfigSchema,
|
|
569
|
+
typegenSchema,
|
|
570
|
+
validateConfig
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
//# debugId=35D15C1A44FCA22C64756E2164756E21
|
|
574
|
+
//# sourceMappingURL=index.js.map
|