@typecad/expect 1.0.0-alpha.11 → 1.0.0-alpha.13

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.
@@ -377,8 +377,6 @@ function writeBuildConfig(buildDir, projectRoot, entryFileName, buildTarget, con
377
377
  lines.push(' output: {');
378
378
  if (baseValues.output?.framework)
379
379
  lines.push(` framework: '${baseValues.output?.framework}',`);
380
- if (baseValues.output?.optimize)
381
- lines.push(` optimize: '${baseValues.output?.optimize}',`);
382
380
  lines.push(` outDir: './out',`);
383
381
  lines.push(' },');
384
382
  if (baseValues.console?.baudRate) {
@@ -24,7 +24,6 @@ export interface RawConfig {
24
24
  test?: Partial<TestConfig>;
25
25
  output?: {
26
26
  framework?: string;
27
- optimize?: string;
28
27
  outDir?: string;
29
28
  };
30
29
  console?: {
@@ -79,6 +79,60 @@ function findConfigFile(projectRoot) {
79
79
  ];
80
80
  return candidates.find(c => fs.existsSync(c));
81
81
  }
82
+ /** Unwrap `as const` / `satisfies T` / parenthesized wrappers so initializers
83
+ * like `const config = {...} satisfies CuttlefishConfig` still parse. */
84
+ function unwrapExpr(node) {
85
+ let curr = node;
86
+ for (;;) {
87
+ if (ts.isAsExpression(curr) || ts.isTypeAssertionExpression(curr) || ts.isParenthesizedExpression(curr)) {
88
+ curr = curr.expression;
89
+ continue;
90
+ }
91
+ // satisfies is TS ≥4.9 — guard for older typings, then cast for .expression.
92
+ const isSatisfies = ts.isSatisfiesExpression;
93
+ if (typeof isSatisfies === 'function' && isSatisfies(curr)) {
94
+ curr = curr.expression;
95
+ continue;
96
+ }
97
+ return curr;
98
+ }
99
+ }
100
+ /** Property key as plain text — accepts both `target:` and `'target':` forms. */
101
+ function propName(prop) {
102
+ const name = prop.name;
103
+ if (name && (ts.isIdentifier(name) || ts.isStringLiteral(name)))
104
+ return name.text;
105
+ return undefined;
106
+ }
107
+ /** String-literal (or plain template-literal) text, through as-cast wrappers. */
108
+ function stringLikeText(node) {
109
+ const curr = unwrapExpr(node);
110
+ return (ts.isStringLiteral(curr) || ts.isNoSubstitutionTemplateLiteral(curr))
111
+ ? curr.text
112
+ : undefined;
113
+ }
114
+ /** Numeric literal via Number() so hex (0x38), decimals, and separators parse
115
+ * correctly — parseInt(text, 10) silently corrupted hex values to 0. */
116
+ function numericValue(node) {
117
+ const curr = unwrapExpr(node);
118
+ if (ts.isNumericLiteral(curr))
119
+ return Number(curr.text);
120
+ if (ts.isPrefixUnaryExpression(curr)
121
+ && (curr.operator === ts.SyntaxKind.MinusToken || curr.operator === ts.SyntaxKind.PlusToken)
122
+ && ts.isNumericLiteral(curr.operand)) {
123
+ const magnitude = Number(curr.operand.text);
124
+ return curr.operator === ts.SyntaxKind.MinusToken ? -magnitude : magnitude;
125
+ }
126
+ return undefined;
127
+ }
128
+ function boolValue(node) {
129
+ const curr = unwrapExpr(node);
130
+ if (curr.kind === ts.SyntaxKind.TrueKeyword)
131
+ return true;
132
+ if (curr.kind === ts.SyntaxKind.FalseKeyword)
133
+ return false;
134
+ return undefined;
135
+ }
82
136
  export function parseConfigAST(configPath) {
83
137
  const text = fs.readFileSync(configPath, 'utf8');
84
138
  const sf = ts.createSourceFile(configPath, text, ts.ScriptTarget.Latest, true);
@@ -87,155 +141,212 @@ export function parseConfigAST(configPath) {
87
141
  // `const config: CuttlefishConfig = { ... };`
88
142
  if (ts.isVariableStatement(stmt)) {
89
143
  for (const decl of stmt.declarationList.declarations) {
90
- if (decl.initializer && ts.isObjectLiteralExpression(decl.initializer)) {
91
- extractConfigProperties(decl.initializer, result);
144
+ const init = decl.initializer ? unwrapExpr(decl.initializer) : undefined;
145
+ if (init && ts.isObjectLiteralExpression(init)) {
146
+ extractConfigProperties(init, result);
92
147
  }
93
148
  }
94
149
  }
95
150
  // `export default { ... };`
96
- if (ts.isExportAssignment(stmt) && ts.isObjectLiteralExpression(stmt.expression)) {
97
- extractConfigProperties(stmt.expression, result);
151
+ if (ts.isExportAssignment(stmt)) {
152
+ const expr = unwrapExpr(stmt.expression);
153
+ if (ts.isObjectLiteralExpression(expr)) {
154
+ extractConfigProperties(expr, result);
155
+ }
98
156
  }
99
157
  }
100
158
  return result;
101
159
  }
102
160
  function extractConfigProperties(obj, out) {
103
161
  for (const prop of obj.properties) {
104
- if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
162
+ if (!ts.isPropertyAssignment(prop))
163
+ continue;
164
+ const name = propName(prop);
165
+ if (!name)
105
166
  continue;
106
- const name = prop.name.text;
107
167
  switch (name) {
108
- case 'target':
109
- if (ts.isStringLiteral(prop.initializer))
110
- out.target = prop.initializer.text;
168
+ case 'target': {
169
+ const v = stringLikeText(prop.initializer);
170
+ if (v !== undefined)
171
+ out.target = v;
111
172
  break;
112
- case 'board':
113
- if (ts.isStringLiteral(prop.initializer))
114
- out.board = prop.initializer.text;
173
+ }
174
+ case 'board': {
175
+ const v = stringLikeText(prop.initializer);
176
+ if (v !== undefined)
177
+ out.board = v;
115
178
  break;
116
- case 'mcu':
117
- if (ts.isStringLiteral(prop.initializer))
118
- out.mcu = prop.initializer.text;
179
+ }
180
+ case 'mcu': {
181
+ const v = stringLikeText(prop.initializer);
182
+ if (v !== undefined)
183
+ out.mcu = v;
119
184
  break;
120
- case 'frameworkData':
121
- if (ts.isObjectLiteralExpression(prop.initializer)) {
185
+ }
186
+ case 'frameworkData': {
187
+ const init = unwrapExpr(prop.initializer);
188
+ if (ts.isObjectLiteralExpression(init)) {
122
189
  out.frameworkData = {};
123
- for (const fProp of prop.initializer.properties) {
124
- if (ts.isPropertyAssignment(fProp) && ts.isIdentifier(fProp.name) && fProp.name.text === 'buildTarget') {
125
- if (ts.isStringLiteral(fProp.initializer))
126
- out.frameworkData.buildTarget = fProp.initializer.text;
190
+ for (const fProp of init.properties) {
191
+ if (ts.isPropertyAssignment(fProp) && propName(fProp) === 'buildTarget') {
192
+ const v = stringLikeText(fProp.initializer);
193
+ if (v !== undefined)
194
+ out.frameworkData.buildTarget = v;
127
195
  }
128
196
  }
129
197
  }
130
198
  break;
131
- case 'framework':
132
- if (ts.isStringLiteral(prop.initializer))
133
- out.framework = prop.initializer.text;
199
+ }
200
+ case 'framework': {
201
+ const v = stringLikeText(prop.initializer);
202
+ if (v !== undefined)
203
+ out.framework = v;
134
204
  break;
135
- case 'toolchain':
136
- if (ts.isObjectLiteralExpression(prop.initializer)) {
205
+ }
206
+ case 'toolchain': {
207
+ const init = unwrapExpr(prop.initializer);
208
+ if (ts.isObjectLiteralExpression(init)) {
137
209
  out.toolchain = {};
138
- for (const tProp of prop.initializer.properties) {
139
- if (ts.isPropertyAssignment(tProp) && tProp.name.getText() === 'type' && ts.isStringLiteral(tProp.initializer)) {
140
- out.toolchain.type = tProp.initializer.text;
210
+ for (const tProp of init.properties) {
211
+ if (ts.isPropertyAssignment(tProp) && propName(tProp) === 'type') {
212
+ const v = stringLikeText(tProp.initializer);
213
+ if (v !== undefined)
214
+ out.toolchain.type = v;
141
215
  }
142
216
  }
143
217
  }
144
218
  break;
145
- case 'zephyr':
219
+ }
220
+ case 'zephyr': {
146
221
  // Parse the zephyr section (kconfig, runner, cmakeArgs) as a generic
147
222
  // object so it can be passed through to the Zephyr toolchain.
148
- if (ts.isObjectLiteralExpression(prop.initializer)) {
223
+ const init = unwrapExpr(prop.initializer);
224
+ if (ts.isObjectLiteralExpression(init)) {
149
225
  out.zephyr = {};
150
- for (const zProp of prop.initializer.properties) {
151
- if (ts.isPropertyAssignment(zProp)) {
152
- const key = zProp.name.getText();
153
- if (ts.isObjectLiteralExpression(zProp.initializer)) {
154
- // kconfig: { 'CONFIG_X': 'y' }
155
- const sub = {};
156
- for (const subProp of zProp.initializer.properties) {
157
- if (ts.isPropertyAssignment(subProp) && ts.isStringLiteral(subProp.initializer)) {
158
- sub[subProp.name.getText().replace(/['"]/g, '')] = subProp.initializer.text;
159
- }
160
- }
161
- out.zephyr[key] = sub;
162
- }
163
- else if (ts.isStringLiteral(zProp.initializer)) {
164
- out.zephyr[key] = zProp.initializer.text;
226
+ for (const zProp of init.properties) {
227
+ if (!ts.isPropertyAssignment(zProp))
228
+ continue;
229
+ const key = propName(zProp);
230
+ if (!key)
231
+ continue;
232
+ const zInit = unwrapExpr(zProp.initializer);
233
+ if (ts.isObjectLiteralExpression(zInit)) {
234
+ // kconfig: { 'CONFIG_X': 'y' }
235
+ const sub = {};
236
+ for (const subProp of zInit.properties) {
237
+ if (!ts.isPropertyAssignment(subProp))
238
+ continue;
239
+ const subKey = propName(subProp);
240
+ const v = stringLikeText(subProp.initializer);
241
+ if (subKey !== undefined && v !== undefined)
242
+ sub[subKey] = v;
165
243
  }
244
+ out.zephyr[key] = sub;
245
+ }
246
+ else {
247
+ const v = stringLikeText(zInit);
248
+ if (v !== undefined)
249
+ out.zephyr[key] = v;
166
250
  }
167
251
  }
168
252
  }
169
253
  break;
170
- case 'test':
171
- if (ts.isObjectLiteralExpression(prop.initializer)) {
172
- out.test = extractTestConfig(prop.initializer);
254
+ }
255
+ case 'test': {
256
+ const init = unwrapExpr(prop.initializer);
257
+ if (ts.isObjectLiteralExpression(init)) {
258
+ out.test = extractTestConfig(init);
173
259
  }
174
260
  break;
175
- case 'output':
176
- if (ts.isObjectLiteralExpression(prop.initializer)) {
177
- out.output = extractOutputConfig(prop.initializer);
261
+ }
262
+ case 'output': {
263
+ const init = unwrapExpr(prop.initializer);
264
+ if (ts.isObjectLiteralExpression(init)) {
265
+ out.output = extractOutputConfig(init);
178
266
  }
179
267
  break;
180
- case 'console':
181
- if (ts.isObjectLiteralExpression(prop.initializer)) {
182
- out.console = extractConsoleConfig(prop.initializer);
268
+ }
269
+ case 'console': {
270
+ const init = unwrapExpr(prop.initializer);
271
+ if (ts.isObjectLiteralExpression(init)) {
272
+ out.console = extractConsoleConfig(init);
183
273
  }
184
274
  break;
275
+ }
185
276
  }
186
277
  }
187
278
  }
188
279
  function extractTestConfig(obj) {
189
280
  const result = {};
190
281
  for (const prop of obj.properties) {
191
- if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
282
+ if (!ts.isPropertyAssignment(prop))
283
+ continue;
284
+ const name = propName(prop);
285
+ if (!name)
192
286
  continue;
193
- const name = prop.name.text;
194
287
  switch (name) {
195
- case 'port':
196
- if (ts.isStringLiteral(prop.initializer))
197
- result.port = prop.initializer.text;
288
+ case 'port': {
289
+ const v = stringLikeText(prop.initializer);
290
+ if (v !== undefined)
291
+ result.port = v;
198
292
  break;
199
- case 'baudRate':
200
- if (ts.isNumericLiteral(prop.initializer))
201
- result.baudRate = parseInt(prop.initializer.text, 10);
293
+ }
294
+ case 'baudRate': {
295
+ const v = numericValue(prop.initializer);
296
+ if (v !== undefined)
297
+ result.baudRate = v;
202
298
  break;
203
- case 'timeout':
204
- if (ts.isNumericLiteral(prop.initializer))
205
- result.timeout = parseInt(prop.initializer.text, 10);
299
+ }
300
+ case 'timeout': {
301
+ const v = numericValue(prop.initializer);
302
+ if (v !== undefined)
303
+ result.timeout = v;
304
+ break;
305
+ }
306
+ case 'serialOpenDelay': {
307
+ const v = numericValue(prop.initializer);
308
+ if (v !== undefined)
309
+ result.serialOpenDelay = v;
206
310
  break;
207
- case 'serialOpenDelay':
208
- if (ts.isNumericLiteral(prop.initializer))
209
- result.serialOpenDelay = parseInt(prop.initializer.text, 10);
311
+ }
312
+ case 'buildTarget': {
313
+ const v = stringLikeText(prop.initializer);
314
+ if (v !== undefined)
315
+ result.buildTarget = v;
210
316
  break;
211
- case 'buildTarget':
212
- if (ts.isStringLiteral(prop.initializer))
213
- result.buildTarget = prop.initializer.text;
317
+ }
318
+ case 'resetAfterOpen': {
319
+ const v = boolValue(prop.initializer);
320
+ if (v !== undefined)
321
+ result.resetAfterOpen = v;
214
322
  break;
215
- case 'resetAfterOpen':
216
- if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword)
217
- result.resetAfterOpen = true;
218
- if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword)
219
- result.resetAfterOpen = false;
323
+ }
324
+ case 'verbose': {
325
+ const v = boolValue(prop.initializer);
326
+ if (v !== undefined)
327
+ result.verbose = v;
220
328
  break;
221
- case 'board':
222
- if (ts.isStringLiteral(prop.initializer))
223
- result.board = prop.initializer.text;
329
+ }
330
+ case 'board': {
331
+ const v = stringLikeText(prop.initializer);
332
+ if (v !== undefined)
333
+ result.board = v;
224
334
  break;
335
+ }
225
336
  case 'include':
226
- if (ts.isArrayLiteralExpression(prop.initializer)) {
227
- result.include = prop.initializer.elements
228
- .filter(ts.isStringLiteral)
229
- .map(el => el.text);
230
- }
231
- break;
232
- case 'exclude':
233
- if (ts.isArrayLiteralExpression(prop.initializer)) {
234
- result.exclude = prop.initializer.elements
235
- .filter(ts.isStringLiteral)
236
- .map(el => el.text);
337
+ case 'exclude': {
338
+ const init = unwrapExpr(prop.initializer);
339
+ if (ts.isArrayLiteralExpression(init)) {
340
+ const values = init.elements
341
+ .map(el => stringLikeText(el))
342
+ .filter((v) => v !== undefined);
343
+ if (name === 'include')
344
+ result.include = values;
345
+ else
346
+ result.exclude = values;
237
347
  }
238
348
  break;
349
+ }
239
350
  }
240
351
  }
241
352
  return result;
@@ -243,25 +354,30 @@ function extractTestConfig(obj) {
243
354
  function extractOutputConfig(obj) {
244
355
  const result = {};
245
356
  for (const prop of obj.properties) {
246
- if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
357
+ if (!ts.isPropertyAssignment(prop))
358
+ continue;
359
+ const name = propName(prop);
360
+ if (!name)
361
+ continue;
362
+ const v = stringLikeText(prop.initializer);
363
+ if (v === undefined)
247
364
  continue;
248
- const name = prop.name.text;
249
- if (name === 'framework' && ts.isStringLiteral(prop.initializer))
250
- result.framework = prop.initializer.text;
251
- if (name === 'optimize' && ts.isStringLiteral(prop.initializer))
252
- result.optimize = prop.initializer.text;
253
- if (name === 'outDir' && ts.isStringLiteral(prop.initializer))
254
- result.outDir = prop.initializer.text;
365
+ if (name === 'framework')
366
+ result.framework = v;
367
+ if (name === 'outDir')
368
+ result.outDir = v;
255
369
  }
256
370
  return result;
257
371
  }
258
372
  function extractConsoleConfig(obj) {
259
373
  const result = {};
260
374
  for (const prop of obj.properties) {
261
- if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
375
+ if (!ts.isPropertyAssignment(prop))
262
376
  continue;
263
- if (prop.name.text === 'baudRate' && ts.isNumericLiteral(prop.initializer)) {
264
- result.baudRate = parseInt(prop.initializer.text, 10);
377
+ if (propName(prop) === 'baudRate') {
378
+ const v = numericValue(prop.initializer);
379
+ if (v !== undefined)
380
+ result.baudRate = v;
265
381
  }
266
382
  }
267
383
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typecad/expect",
3
- "version": "1.0.0-alpha.11",
3
+ "version": "1.0.0-alpha.13",
4
4
  "description": "Hardware test framework for TypeCAD — vitest-style assertions over serial",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,10 +40,13 @@
40
40
  "dependencies": {
41
41
  "typescript": "^5.7.3",
42
42
  "serialport": "^12.0.0",
43
- "@typecad/arduino-cli": "1.0.0-alpha.11"
43
+ "@typecad/arduino-cli": "1.0.0-alpha.13",
44
+ "@typecad/hal": "1.0.0-alpha.13"
45
+ },
46
+ "optionalDependencies": {
47
+ "@typecad/framework-zephyr": "1.0.0-alpha.13"
44
48
  },
45
49
  "devDependencies": {
46
- "@typecad/cuttlefish": "1.0.0-alpha.11",
47
50
  "@types/node": "^22.10.7"
48
51
  },
49
52
  "license": "MIT",