@typecad/cuttlefish 1.0.0-alpha.10 → 1.0.0-alpha.12
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/api/config.d.ts +11 -3
- package/dist/api/shared/framework-manifest.d.ts +85 -78
- package/dist/api/shared/framework-manifest.js +1 -0
- package/dist/api/shared/index.d.ts +2 -0
- package/dist/api/shared/index.js +1 -0
- package/dist/api/shared/spdx-licenses.d.ts +139 -0
- package/dist/api/shared/spdx-licenses.js +385 -0
- package/dist/cli.js +6 -0
- package/dist/config-loader.d.ts +8 -0
- package/dist/config-loader.js +192 -48
- package/dist/config-schema.d.ts +96 -89
- package/dist/config-schema.js +3 -0
- package/dist/create/board-spec.d.ts +122 -122
- package/dist/create/eslint-rules-template.js +6 -3
- package/dist/create/init-templates.js +9 -1
- package/dist/emit/compliance/rules.js +34 -0
- package/dist/emit/emitters/setup.js +15 -2
- package/dist/ir/feature-registry.js +22 -6
- package/dist/ir/program-analysis.d.ts +4 -2
- package/dist/ir/program-analysis.js +2 -4
- package/dist/platform/async-runtime.d.ts +1 -1
- package/dist/platform/async-runtime.js +12 -3
- package/dist/platform/generic-strategy.js +1 -1
- package/dist/transpile.js +55 -7
- package/dist/utils/cli.js +3 -2
- package/dist/utils/fs.d.ts +2 -0
- package/dist/utils/fs.js +16 -0
- package/package.json +5 -4
package/dist/config-loader.js
CHANGED
|
@@ -12,6 +12,13 @@ import ts from "typescript";
|
|
|
12
12
|
import { safeValidateConfig } from "./config-schema.js";
|
|
13
13
|
/** The filename we search for when walking up directories. */
|
|
14
14
|
const CONFIG_FILENAME = "cuttlefish.config.ts";
|
|
15
|
+
/** Top-level keys recognized by CuttlefishConfigSchema — used to warn about
|
|
16
|
+
* misspelled keys that the AST extraction would otherwise drop silently. */
|
|
17
|
+
const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
18
|
+
"entry", "target", "mcu", "board", "contract", "framework", "psram",
|
|
19
|
+
"output", "frameworkData", "include", "exclude", "test", "toolchain",
|
|
20
|
+
"console", "native", "zephyr", "display",
|
|
21
|
+
]);
|
|
15
22
|
/**
|
|
16
23
|
* Search upward from `startDir` for a file named `cuttlefish.config.ts`.
|
|
17
24
|
* Returns the absolute path on success, `undefined` if none is found.
|
|
@@ -30,15 +37,21 @@ export function findConfigFile(startDir) {
|
|
|
30
37
|
}
|
|
31
38
|
return undefined;
|
|
32
39
|
}
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// AST helpers — extract scalar values from a TS object literal
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
40
|
function unwrapTypeCast(node) {
|
|
37
41
|
let curr = node;
|
|
38
|
-
|
|
39
|
-
curr
|
|
42
|
+
for (;;) {
|
|
43
|
+
if (ts.isAsExpression(curr) || ts.isTypeAssertionExpression(curr) || ts.isParenthesizedExpression(curr)) {
|
|
44
|
+
curr = curr.expression;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// satisfies is TS ≥4.9 — guard for older typings, then cast for .expression.
|
|
48
|
+
const isSatisfies = ts.isSatisfiesExpression;
|
|
49
|
+
if (typeof isSatisfies === "function" && isSatisfies(curr)) {
|
|
50
|
+
curr = curr.expression;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
return curr;
|
|
40
54
|
}
|
|
41
|
-
return curr;
|
|
42
55
|
}
|
|
43
56
|
function getStringLiteral(node) {
|
|
44
57
|
const unwrapped = unwrapTypeCast(node);
|
|
@@ -55,20 +68,51 @@ function getScalarValue(node) {
|
|
|
55
68
|
if (ts.isNumericLiteral(unwrapped)) {
|
|
56
69
|
return Number(unwrapped.text);
|
|
57
70
|
}
|
|
71
|
+
// Negative (and explicitly positive) number literals parse as
|
|
72
|
+
// PrefixUnaryExpression — `reset: -1` used to be silently dropped.
|
|
73
|
+
if (ts.isPrefixUnaryExpression(unwrapped)
|
|
74
|
+
&& (unwrapped.operator === ts.SyntaxKind.MinusToken || unwrapped.operator === ts.SyntaxKind.PlusToken)
|
|
75
|
+
&& ts.isNumericLiteral(unwrapped.operand)) {
|
|
76
|
+
const magnitude = Number(unwrapped.operand.text);
|
|
77
|
+
return unwrapped.operator === ts.SyntaxKind.MinusToken ? -magnitude : magnitude;
|
|
78
|
+
}
|
|
58
79
|
if (unwrapped.kind === ts.SyntaxKind.TrueKeyword)
|
|
59
80
|
return true;
|
|
60
81
|
if (unwrapped.kind === ts.SyntaxKind.FalseKeyword)
|
|
61
82
|
return false;
|
|
62
83
|
return undefined;
|
|
63
84
|
}
|
|
85
|
+
/** True for `null` / `undefined` literals — inline literals, but not values
|
|
86
|
+
* the config shape supports; callers warn with an accurate message instead
|
|
87
|
+
* of the "variables/ternaries" text. */
|
|
88
|
+
function isNullishLiteral(node) {
|
|
89
|
+
const kind = unwrapTypeCast(node).kind;
|
|
90
|
+
return kind === ts.SyntaxKind.NullKeyword || kind === ts.SyntaxKind.UndefinedKeyword;
|
|
91
|
+
}
|
|
64
92
|
/**
|
|
65
93
|
* Walk an object literal and collect all scalar (string / number / boolean)
|
|
66
94
|
* property values into a flat dot-path map — exactly like board-resolver.ts.
|
|
67
95
|
*/
|
|
68
|
-
|
|
96
|
+
/** Property key text for Identifier/StringLiteral names, else undefined
|
|
97
|
+
* (SpreadAssignment has no name; computed keys are not static text). */
|
|
98
|
+
function propertyKeyName(prop) {
|
|
99
|
+
const name = prop.name;
|
|
100
|
+
if (!name)
|
|
101
|
+
return undefined;
|
|
102
|
+
return ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : undefined;
|
|
103
|
+
}
|
|
104
|
+
function describePropertyKey(prop) {
|
|
105
|
+
return propertyKeyName(prop) ?? "<unnamed>";
|
|
106
|
+
}
|
|
107
|
+
function walkObjectLiteral(obj, prefix, out, warn) {
|
|
69
108
|
for (const prop of obj.properties) {
|
|
70
|
-
if (!ts.isPropertyAssignment(prop))
|
|
109
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
110
|
+
if (warn) {
|
|
111
|
+
const label = prefix ? `${prefix}.${describePropertyKey(prop)}` : describePropertyKey(prop);
|
|
112
|
+
warn(`'${label}' uses ${ts.SyntaxKind[prop.kind]} syntax (shorthand/spread/method) — only property assignments are supported, ignored.`);
|
|
113
|
+
}
|
|
71
114
|
continue;
|
|
115
|
+
}
|
|
72
116
|
const key = ts.isIdentifier(prop.name)
|
|
73
117
|
? prop.name.text
|
|
74
118
|
: ts.isStringLiteral(prop.name)
|
|
@@ -78,13 +122,26 @@ function walkObjectLiteral(obj, prefix, out) {
|
|
|
78
122
|
continue;
|
|
79
123
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
80
124
|
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
81
|
-
walkObjectLiteral(prop.initializer, fullKey, out);
|
|
125
|
+
walkObjectLiteral(prop.initializer, fullKey, out, warn);
|
|
126
|
+
}
|
|
127
|
+
else if (ts.isArrayLiteralExpression(prop.initializer)) {
|
|
128
|
+
// Arrays are extracted by the dedicated array extractors
|
|
129
|
+
// (output.extraFlags, zephyr.cmakeArgs, native.libraries, …), not the
|
|
130
|
+
// flat scalar walk — skip here without warning.
|
|
82
131
|
}
|
|
83
132
|
else {
|
|
84
133
|
const value = getScalarValue(prop.initializer);
|
|
85
134
|
if (value !== undefined) {
|
|
86
135
|
out.set(fullKey, value);
|
|
87
136
|
}
|
|
137
|
+
else if (warn) {
|
|
138
|
+
// Message text must stay byte-identical to extractObjectAsRecord's —
|
|
139
|
+
// sections walked by both (native, display) rely on the warn-site
|
|
140
|
+
// dedup to print one warning per dropped value, not two.
|
|
141
|
+
warn(isNullishLiteral(prop.initializer)
|
|
142
|
+
? `'${fullKey}' is a null/undefined literal — not a supported config value, ignored.`
|
|
143
|
+
: `'${fullKey}' is not an inline literal (variables, ternaries, and template substitutions are not evaluated) — ignored.`);
|
|
144
|
+
}
|
|
88
145
|
}
|
|
89
146
|
}
|
|
90
147
|
}
|
|
@@ -124,20 +181,18 @@ function navigateToObjectProperty(obj, path) {
|
|
|
124
181
|
}
|
|
125
182
|
return undefined;
|
|
126
183
|
}
|
|
127
|
-
function extractStringArray(obj, path) {
|
|
184
|
+
function extractStringArray(obj, path, warn) {
|
|
185
|
+
const label = path.join(".");
|
|
128
186
|
const leaf = navigateToObjectProperty(obj, path);
|
|
129
|
-
if (!leaf || !ts.isArrayLiteralExpression(leaf))
|
|
187
|
+
if (!leaf || !ts.isArrayLiteralExpression(leaf)) {
|
|
188
|
+
if (leaf && warn)
|
|
189
|
+
warn(`'${label}' is not an inline array literal — ignored.`);
|
|
130
190
|
return undefined;
|
|
131
|
-
const result = [];
|
|
132
|
-
for (const elem of leaf.elements) {
|
|
133
|
-
const s = getStringLiteral(elem);
|
|
134
|
-
if (s === undefined)
|
|
135
|
-
return undefined;
|
|
136
|
-
result.push(s);
|
|
137
191
|
}
|
|
138
|
-
return
|
|
192
|
+
return extractStringArrayFromArrayLiteral(leaf, warn, label);
|
|
139
193
|
}
|
|
140
|
-
function extractStringRecord(obj, path) {
|
|
194
|
+
function extractStringRecord(obj, path, warn) {
|
|
195
|
+
const label = path.join(".");
|
|
141
196
|
const leaf = navigateToObjectProperty(obj, path);
|
|
142
197
|
if (!leaf || !ts.isObjectLiteralExpression(leaf))
|
|
143
198
|
return undefined;
|
|
@@ -153,8 +208,11 @@ function extractStringRecord(obj, path) {
|
|
|
153
208
|
if (!key)
|
|
154
209
|
continue;
|
|
155
210
|
const value = getStringLiteral(prop.initializer);
|
|
156
|
-
if (value === undefined)
|
|
211
|
+
if (value === undefined) {
|
|
212
|
+
if (warn)
|
|
213
|
+
warn(`'${label}.${key}' is not a string literal — the whole record is ignored.`);
|
|
157
214
|
return undefined;
|
|
215
|
+
}
|
|
158
216
|
result[key] = value;
|
|
159
217
|
}
|
|
160
218
|
if (Object.keys(result).length === 0)
|
|
@@ -165,11 +223,18 @@ function extractStringRecord(obj, path) {
|
|
|
165
223
|
* Recursively extract an object literal as Record<string, unknown>.
|
|
166
224
|
* Handles strings, numbers, booleans, string arrays, and nested objects.
|
|
167
225
|
*/
|
|
168
|
-
function extractObjectAsRecord(obj) {
|
|
226
|
+
function extractObjectAsRecord(obj, warn, prefix = "") {
|
|
169
227
|
const result = {};
|
|
170
228
|
for (const prop of obj.properties) {
|
|
171
|
-
if (!ts.isPropertyAssignment(prop))
|
|
229
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
230
|
+
if (warn) {
|
|
231
|
+
// Byte-identical to walkObjectLiteral's message for the same property
|
|
232
|
+
// so the warn-site dedup collapses the double walk into one warning.
|
|
233
|
+
const label = prefix ? `${prefix}.${describePropertyKey(prop)}` : describePropertyKey(prop);
|
|
234
|
+
warn(`'${label}' uses ${ts.SyntaxKind[prop.kind]} syntax (shorthand/spread/method) — only property assignments are supported, ignored.`);
|
|
235
|
+
}
|
|
172
236
|
continue;
|
|
237
|
+
}
|
|
173
238
|
const key = ts.isIdentifier(prop.name)
|
|
174
239
|
? prop.name.text
|
|
175
240
|
: ts.isStringLiteral(prop.name)
|
|
@@ -177,29 +242,40 @@ function extractObjectAsRecord(obj) {
|
|
|
177
242
|
: undefined;
|
|
178
243
|
if (!key)
|
|
179
244
|
continue;
|
|
245
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
180
246
|
const init = prop.initializer;
|
|
181
247
|
if (ts.isObjectLiteralExpression(init)) {
|
|
182
|
-
result[key] = extractObjectAsRecord(init);
|
|
248
|
+
result[key] = extractObjectAsRecord(init, warn, fullKey);
|
|
183
249
|
}
|
|
184
250
|
else if (ts.isArrayLiteralExpression(init)) {
|
|
185
|
-
const arr = extractStringArrayFromArrayLiteral(init);
|
|
251
|
+
const arr = extractStringArrayFromArrayLiteral(init, warn, fullKey);
|
|
186
252
|
if (arr)
|
|
187
253
|
result[key] = arr;
|
|
188
254
|
}
|
|
189
255
|
else {
|
|
190
256
|
const scalar = getScalarValue(init);
|
|
191
|
-
if (scalar !== undefined)
|
|
257
|
+
if (scalar !== undefined) {
|
|
192
258
|
result[key] = scalar;
|
|
259
|
+
}
|
|
260
|
+
else if (warn) {
|
|
261
|
+
// Keep byte-identical to walkObjectLiteral's message (see above).
|
|
262
|
+
warn(isNullishLiteral(init)
|
|
263
|
+
? `'${fullKey}' is a null/undefined literal — not a supported config value, ignored.`
|
|
264
|
+
: `'${fullKey}' is not an inline literal (variables, ternaries, and template substitutions are not evaluated) — ignored.`);
|
|
265
|
+
}
|
|
193
266
|
}
|
|
194
267
|
}
|
|
195
268
|
return result;
|
|
196
269
|
}
|
|
197
|
-
function extractStringArrayFromArrayLiteral(node) {
|
|
270
|
+
function extractStringArrayFromArrayLiteral(node, warn, label = "array") {
|
|
198
271
|
const result = [];
|
|
199
272
|
for (const elem of node.elements) {
|
|
200
273
|
const s = getStringLiteral(elem);
|
|
201
|
-
if (s === undefined)
|
|
274
|
+
if (s === undefined) {
|
|
275
|
+
if (warn)
|
|
276
|
+
warn(`'${label}' has a non-string-literal element — the whole array is ignored.`);
|
|
202
277
|
return undefined;
|
|
278
|
+
}
|
|
203
279
|
result.push(s);
|
|
204
280
|
}
|
|
205
281
|
return result;
|
|
@@ -207,7 +283,7 @@ function extractStringArrayFromArrayLiteral(node) {
|
|
|
207
283
|
/**
|
|
208
284
|
* Extract a top-level config section as a generic Record<string, unknown>.
|
|
209
285
|
*/
|
|
210
|
-
function extractFrameworkSection(obj, sectionName) {
|
|
286
|
+
function extractFrameworkSection(obj, sectionName, warn) {
|
|
211
287
|
for (const prop of obj.properties) {
|
|
212
288
|
if (!ts.isPropertyAssignment(prop))
|
|
213
289
|
continue;
|
|
@@ -218,9 +294,12 @@ function extractFrameworkSection(obj, sectionName) {
|
|
|
218
294
|
: undefined;
|
|
219
295
|
if (key !== sectionName)
|
|
220
296
|
continue;
|
|
221
|
-
if (!ts.isObjectLiteralExpression(prop.initializer))
|
|
297
|
+
if (!ts.isObjectLiteralExpression(prop.initializer)) {
|
|
298
|
+
if (warn)
|
|
299
|
+
warn(`'${sectionName}' section is not an inline object literal — ignored.`);
|
|
222
300
|
return undefined;
|
|
223
|
-
|
|
301
|
+
}
|
|
302
|
+
return extractObjectAsRecord(prop.initializer, warn, sectionName);
|
|
224
303
|
}
|
|
225
304
|
return undefined;
|
|
226
305
|
}
|
|
@@ -251,11 +330,14 @@ export function parseConfigFile(configPath) {
|
|
|
251
330
|
}
|
|
252
331
|
// export default config; (ExportAssignment with an identifier)
|
|
253
332
|
if (ts.isExportAssignment(stmt) && !stmt.isExportEquals) {
|
|
254
|
-
|
|
255
|
-
|
|
333
|
+
// Unwrap `export default { ... } satisfies CuttlefishConfig` /
|
|
334
|
+
// `as const` / parenthesized forms down to the underlying expression.
|
|
335
|
+
const expr = unwrapTypeCast(stmt.expression);
|
|
336
|
+
if (ts.isIdentifier(expr)) {
|
|
337
|
+
defaultExportName = expr.text;
|
|
256
338
|
}
|
|
257
|
-
else if (ts.isObjectLiteralExpression(
|
|
258
|
-
inlineDefaultObject =
|
|
339
|
+
else if (ts.isObjectLiteralExpression(expr)) {
|
|
340
|
+
inlineDefaultObject = expr;
|
|
259
341
|
}
|
|
260
342
|
}
|
|
261
343
|
}
|
|
@@ -263,16 +345,43 @@ export function parseConfigFile(configPath) {
|
|
|
263
345
|
let configObject = inlineDefaultObject;
|
|
264
346
|
if (!configObject && defaultExportName) {
|
|
265
347
|
const decl = variableDecls.get(defaultExportName);
|
|
266
|
-
if (decl?.initializer
|
|
267
|
-
|
|
348
|
+
if (decl?.initializer) {
|
|
349
|
+
// `const config = { ... } satisfies CuttlefishConfig` parses the
|
|
350
|
+
// initializer as a SatisfiesExpression — unwrap to the object literal
|
|
351
|
+
// so the whole config isn't silently ignored.
|
|
352
|
+
const init = unwrapTypeCast(decl.initializer);
|
|
353
|
+
if (ts.isObjectLiteralExpression(init)) {
|
|
354
|
+
configObject = init;
|
|
355
|
+
}
|
|
268
356
|
}
|
|
269
357
|
}
|
|
270
358
|
if (!configObject) {
|
|
271
359
|
return undefined;
|
|
272
360
|
}
|
|
361
|
+
// Collect drop warnings (values the AST-only parser can't evaluate) and
|
|
362
|
+
// print them once after parsing — silent drops here used to surface much
|
|
363
|
+
// later as missing -l flags / reverted defaults with no diagnostic.
|
|
364
|
+
const dropWarnings = [];
|
|
365
|
+
const seenWarnings = new Set();
|
|
366
|
+
const warn = (message) => {
|
|
367
|
+
const line = `${path.basename(configPath)}: ${message}`;
|
|
368
|
+
if (!seenWarnings.has(line)) {
|
|
369
|
+
seenWarnings.add(line);
|
|
370
|
+
dropWarnings.push(line);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
// Typos in top-level keys (e.g. `output.optmize`) were dropped before
|
|
374
|
+
// schema validation, so the strict schema never saw them — check the raw
|
|
375
|
+
// source keys against the known set directly.
|
|
376
|
+
for (const prop of configObject.properties) {
|
|
377
|
+
const key = propertyKeyName(prop);
|
|
378
|
+
if (key && !KNOWN_TOP_LEVEL_KEYS.has(key)) {
|
|
379
|
+
warn(`unknown top-level key '${key}' — misspelled or unsupported.`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
273
382
|
// Walk the object literal into a flat map.
|
|
274
383
|
const flat = new Map();
|
|
275
|
-
walkObjectLiteral(configObject, "", flat);
|
|
384
|
+
walkObjectLiteral(configObject, "", flat, warn);
|
|
276
385
|
// Map flat keys to the resolved config shape.
|
|
277
386
|
const resolved = { configPath };
|
|
278
387
|
const entry = flat.get("entry");
|
|
@@ -298,8 +407,11 @@ export function parseConfigFile(configPath) {
|
|
|
298
407
|
if (typeof framework === "string")
|
|
299
408
|
resolved.framework = framework;
|
|
300
409
|
const psram = flat.get("psram");
|
|
301
|
-
if (
|
|
410
|
+
if (typeof psram === "string") {
|
|
411
|
+
// Assign any string (including "") so the schema's PsramType enum rejects
|
|
412
|
+
// typos ('octal') with a validation error instead of silently dropping it.
|
|
302
413
|
resolved.psram = psram;
|
|
414
|
+
}
|
|
303
415
|
const outputFramework = flat.get("output.framework");
|
|
304
416
|
if (typeof outputFramework === "string")
|
|
305
417
|
resolved.outputFramework = outputFramework;
|
|
@@ -319,19 +431,19 @@ export function parseConfigFile(configPath) {
|
|
|
319
431
|
};
|
|
320
432
|
}
|
|
321
433
|
// Extract structured fields that the flat walker cannot handle.
|
|
322
|
-
const outputExtraFlags = extractStringArray(configObject, ["output", "extraFlags"]);
|
|
434
|
+
const outputExtraFlags = extractStringArray(configObject, ["output", "extraFlags"], warn);
|
|
323
435
|
if (outputExtraFlags)
|
|
324
436
|
resolved.outputExtraFlags = outputExtraFlags;
|
|
325
|
-
const outputDefines = extractStringRecord(configObject, ["output", "defines"]);
|
|
437
|
+
const outputDefines = extractStringRecord(configObject, ["output", "defines"], warn);
|
|
326
438
|
if (outputDefines)
|
|
327
439
|
resolved.outputDefines = outputDefines;
|
|
328
|
-
const nativeSection = extractFrameworkSection(configObject, "native");
|
|
440
|
+
const nativeSection = extractFrameworkSection(configObject, "native", warn);
|
|
329
441
|
if (nativeSection) {
|
|
330
442
|
resolved.frameworkConfig = nativeSection;
|
|
331
443
|
}
|
|
332
444
|
// Parse zephyr-specific config section.
|
|
333
|
-
const zephyrKconfig = extractStringRecord(configObject, ["zephyr", "kconfig"]);
|
|
334
|
-
const zephyrCmakeArgs = extractStringArray(configObject, ["zephyr", "cmakeArgs"]);
|
|
445
|
+
const zephyrKconfig = extractStringRecord(configObject, ["zephyr", "kconfig"], warn);
|
|
446
|
+
const zephyrCmakeArgs = extractStringArray(configObject, ["zephyr", "cmakeArgs"], warn);
|
|
335
447
|
const zephyrRunner = flat.get("zephyr.runner");
|
|
336
448
|
if (zephyrKconfig || zephyrCmakeArgs || typeof zephyrRunner === "string") {
|
|
337
449
|
resolved.zephyrConfig = {
|
|
@@ -341,7 +453,10 @@ export function parseConfigFile(configPath) {
|
|
|
341
453
|
};
|
|
342
454
|
}
|
|
343
455
|
// Parse display profile config (nested object with profile name, wiring, touch)
|
|
344
|
-
const displaySection = extractFrameworkSection(configObject, "display");
|
|
456
|
+
const displaySection = extractFrameworkSection(configObject, "display", warn);
|
|
457
|
+
// extractObjectAsRecord walks literals into a loose record; the shape is
|
|
458
|
+
// checked downstream (schema passthrough + display-profile resolution), so
|
|
459
|
+
// bridge it to the declared DisplayConfig type at this single boundary.
|
|
345
460
|
if (displaySection)
|
|
346
461
|
resolved.display = displaySection;
|
|
347
462
|
// Validate the parsed config against the Zod schema.
|
|
@@ -359,7 +474,9 @@ export function parseConfigFile(configPath) {
|
|
|
359
474
|
structuredForValidation.entry = resolved.entry;
|
|
360
475
|
if (resolved.framework)
|
|
361
476
|
structuredForValidation.framework = resolved.framework;
|
|
362
|
-
|
|
477
|
+
// `!== undefined` (not truthiness) so an empty-string psram reaches the
|
|
478
|
+
// PsramType enum and fails validation instead of vanishing.
|
|
479
|
+
if (resolved.psram !== undefined)
|
|
363
480
|
structuredForValidation.psram = resolved.psram;
|
|
364
481
|
if (resolved.outputFramework || resolved.outputOptimize || resolved.outputOutDir || resolved.outputExtraFlags || resolved.outputDefines) {
|
|
365
482
|
structuredForValidation.output = {
|
|
@@ -374,6 +491,10 @@ export function parseConfigFile(configPath) {
|
|
|
374
491
|
structuredForValidation.console = resolved.console;
|
|
375
492
|
if (resolved.zephyrConfig)
|
|
376
493
|
structuredForValidation.zephyr = resolved.zephyrConfig;
|
|
494
|
+
if (resolved.frameworkConfig)
|
|
495
|
+
structuredForValidation.native = resolved.frameworkConfig;
|
|
496
|
+
if (resolved.display)
|
|
497
|
+
structuredForValidation.display = resolved.display;
|
|
377
498
|
if (resolved.buildTarget) {
|
|
378
499
|
structuredForValidation.frameworkData = { buildTarget: resolved.buildTarget };
|
|
379
500
|
}
|
|
@@ -382,6 +503,12 @@ export function parseConfigFile(configPath) {
|
|
|
382
503
|
const errorMsg = validation.errors.map(e => ` - ${e}`).join("\n");
|
|
383
504
|
throw new Error(`Configuration validation failed for ${configPath}:\n${errorMsg}`);
|
|
384
505
|
}
|
|
506
|
+
if (dropWarnings.length > 0) {
|
|
507
|
+
console.warn(`⚠ ${configPath}: some values were ignored (the parser only evaluates inline literals):`);
|
|
508
|
+
for (const w of dropWarnings) {
|
|
509
|
+
console.warn(` ${w}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
385
512
|
return resolved;
|
|
386
513
|
}
|
|
387
514
|
/**
|
|
@@ -440,6 +567,23 @@ export function generateVirtualTypeDeclaration(config, platformDeclarations) {
|
|
|
440
567
|
" type Shared<T = unknown> = T;",
|
|
441
568
|
" type Mutable<T = unknown> = T;",
|
|
442
569
|
"",
|
|
570
|
+
" // SafeVariable: SEU-resistant storage. The transpiler lowers SafeVariable<number>",
|
|
571
|
+
" // to a C++ template with inverted-redundancy storage. Declared as an interface",
|
|
572
|
+
" // (not a type alias) so the TS type checker recognizes method calls.",
|
|
573
|
+
" // Arithmetic T only (integral or floating-point); string is rejected by a",
|
|
574
|
+
" // static_assert in the emitted C++ template.",
|
|
575
|
+
" interface SafeVariable<T = number> { set(value: T): void; get(): T; valid(): boolean; hasFault(): boolean; }",
|
|
576
|
+
" // SafeInt: chainable bounds-checked signed-integer arithmetic. The transpiler",
|
|
577
|
+
" // lowers SafeInt<number> to SafeInt<int32_t> (a C++ template with sticky-fault",
|
|
578
|
+
" // overflow detection). Signed integer T only — unsigned/bool/float/string are",
|
|
579
|
+
" // rejected by a static_assert in the emitted C++ template.",
|
|
580
|
+
" interface SafeInt<T = number> {",
|
|
581
|
+
" add(delta: T): SafeInt<T>; sub(delta: T): SafeInt<T>;",
|
|
582
|
+
" mul(factor: T): SafeInt<T>; divide(d: T): SafeInt<T>; mod(d: T): SafeInt<T>;",
|
|
583
|
+
" negate(): SafeInt<T>; absValue(): SafeInt<T>;",
|
|
584
|
+
" get(): T; hasFault(): boolean; valid(): boolean; reset(newValue: T): void;",
|
|
585
|
+
" }",
|
|
586
|
+
"",
|
|
443
587
|
" // C-style explicit number types recognized by the transpiler",
|
|
444
588
|
" type uint8_t = number;",
|
|
445
589
|
" type int8_t = number;",
|
|
@@ -480,9 +624,9 @@ export function generateVirtualTypeDeclaration(config, platformDeclarations) {
|
|
|
480
624
|
"",
|
|
481
625
|
"declare module '@typecad/board' {",
|
|
482
626
|
` ${boardExport}`,
|
|
483
|
-
" export type Owned<T =
|
|
484
|
-
" export type Shared<T =
|
|
485
|
-
" export type Mutable<T =
|
|
627
|
+
" export type Owned<T = unknown> = T;",
|
|
628
|
+
" export type Shared<T = unknown> = T;",
|
|
629
|
+
" export type Mutable<T = unknown> = T;",
|
|
486
630
|
"}",
|
|
487
631
|
"",
|
|
488
632
|
"export {};",
|