@typecad/expect 1.0.0-alpha.6 → 1.0.0-alpha.7
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/host/compiler.d.ts +10 -3
- package/dist/host/compiler.js +117 -10
- package/dist/host/preprocessor.d.ts +7 -0
- package/dist/host/preprocessor.js +12 -0
- package/dist/host/runner.js +8 -6
- package/package.json +3 -3
package/dist/host/compiler.d.ts
CHANGED
|
@@ -18,10 +18,17 @@ export interface UploadResult {
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function transpileTestFile(preprocessedSource: string, originalFilePath: string, projectRoot: string, buildTarget: string): CompileResult;
|
|
20
20
|
/**
|
|
21
|
-
* Compile the
|
|
21
|
+
* Compile the sketch. Detects the toolchain from the framework:
|
|
22
|
+
* - framework-esp32 → idf.py build (via framework-esp32's compileEspIdf)
|
|
23
|
+
* - everything else → arduino-cli compile
|
|
22
24
|
*/
|
|
23
|
-
export declare function compileSketch(sketchDir: string, buildTarget: string): CompileResult;
|
|
25
|
+
export declare function compileSketch(sketchDir: string, buildTarget: string, framework?: string): CompileResult;
|
|
24
26
|
/**
|
|
25
27
|
* Upload the compiled sketch to the board.
|
|
26
28
|
*/
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Upload firmware. Detects the toolchain from the framework:
|
|
31
|
+
* - framework-esp32 → idf.py flash
|
|
32
|
+
* - everything else → arduino-cli upload
|
|
33
|
+
*/
|
|
34
|
+
export declare function uploadSketch(sketchDir: string, buildTarget: string, port: string, framework?: string): UploadResult;
|
package/dist/host/compiler.js
CHANGED
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
10
11
|
import { parseConfigAST } from './config.js';
|
|
11
12
|
import { checkArduinoEnv } from '@typecad/arduino-cli';
|
|
13
|
+
// createRequire lets us synchronously require CommonJS modules from this ESM
|
|
14
|
+
// file. Used to load framework-esp32's compiled dist at runtime.
|
|
15
|
+
const require_ = createRequire(import.meta.url);
|
|
12
16
|
/** Format a check failure into the `error` field used by CompileResult/UploadResult. */
|
|
13
17
|
function formatEnvFailure(failure) {
|
|
14
18
|
const lines = [...failure.messages];
|
|
@@ -23,12 +27,35 @@ function formatEnvFailure(failure) {
|
|
|
23
27
|
* transpiler, and returns the path to the generated .ino file.
|
|
24
28
|
*/
|
|
25
29
|
export function transpileTestFile(preprocessedSource, originalFilePath, projectRoot, buildTarget) {
|
|
26
|
-
// Create a build directory for this test file
|
|
30
|
+
// Create a build directory for this test file.
|
|
31
|
+
// For idf.py targets, use a SHARED build directory across all test files —
|
|
32
|
+
// only main/main.cc changes between files, so idf.py can do incremental
|
|
33
|
+
// builds (ninja detects only the changed .cc and relinks). This turns the
|
|
34
|
+
// 2nd+ file from a ~130s full rebuild into a ~10-15s incremental build.
|
|
35
|
+
// For arduino-cli targets, use per-file directories (no incremental benefit).
|
|
27
36
|
const baseName = path.basename(originalFilePath, '.test.ts').replace(/[^a-zA-Z0-9_]/g, '_');
|
|
28
|
-
const
|
|
37
|
+
const isEsp32 = buildTarget === 'esp32' || buildTarget === 'esp32s3' || buildTarget === 'esp32c3' || buildTarget === 'esp32c6';
|
|
38
|
+
const buildDir = isEsp32
|
|
39
|
+
? path.join(projectRoot, '.build', 'expect', 'esp32_shared')
|
|
40
|
+
: path.join(projectRoot, '.build', 'expect', baseName);
|
|
29
41
|
try {
|
|
30
|
-
fs.
|
|
31
|
-
|
|
42
|
+
if (isEsp32 && fs.existsSync(buildDir)) {
|
|
43
|
+
// Shared ESP32 build dir: preserve it. The transpiler will overwrite
|
|
44
|
+
// the .ts source and regenerate main.cc; ninja does an incremental build.
|
|
45
|
+
// Only remove the old .ts file so the transpiler doesn't pick up stale ones.
|
|
46
|
+
const oldTsFiles = fs.readdirSync(buildDir).filter(f => f.endsWith('.ts'));
|
|
47
|
+
for (const f of oldTsFiles) {
|
|
48
|
+
try {
|
|
49
|
+
fs.unlinkSync(path.join(buildDir, f));
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Fresh dir (first run, or arduino-cli target).
|
|
56
|
+
fs.rmSync(buildDir, { recursive: true, force: true });
|
|
57
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
58
|
+
}
|
|
32
59
|
}
|
|
33
60
|
catch {
|
|
34
61
|
return { success: false, sketchDir: buildDir, sketchPath: '', output: '', error: `Failed to create build dir: ${buildDir}` };
|
|
@@ -82,9 +109,27 @@ export function transpileTestFile(preprocessedSource, originalFilePath, projectR
|
|
|
82
109
|
};
|
|
83
110
|
}
|
|
84
111
|
/**
|
|
85
|
-
* Compile the
|
|
112
|
+
* Compile the sketch. Detects the toolchain from the framework:
|
|
113
|
+
* - framework-esp32 → idf.py build (via framework-esp32's compileEspIdf)
|
|
114
|
+
* - everything else → arduino-cli compile
|
|
86
115
|
*/
|
|
87
|
-
export function compileSketch(sketchDir, buildTarget) {
|
|
116
|
+
export function compileSketch(sketchDir, buildTarget, framework) {
|
|
117
|
+
if (framework?.includes('framework-esp32')) {
|
|
118
|
+
return compileEspIdfSketch(sketchDir, buildTarget);
|
|
119
|
+
}
|
|
120
|
+
return compileArduinoSketch(sketchDir, buildTarget);
|
|
121
|
+
}
|
|
122
|
+
/** Resolve the framework-esp32 dist/toolchain directory from node_modules.
|
|
123
|
+
* Uses the main export entry point (dist/index.js) to find the dist dir,
|
|
124
|
+
* avoiding the package's exports field restriction on subpaths. */
|
|
125
|
+
function resolveFrameworkEsp32Dist() {
|
|
126
|
+
const indexPath = require_.resolve('@typecad/framework-esp32');
|
|
127
|
+
// indexPath = .../packages/framework-esp32/dist/index.js
|
|
128
|
+
// toolchain dir = .../packages/framework-esp32/dist/toolchain
|
|
129
|
+
return path.join(path.dirname(indexPath), 'toolchain');
|
|
130
|
+
}
|
|
131
|
+
/** Compile via arduino-cli (framework-arduino / framework-avr). */
|
|
132
|
+
function compileArduinoSketch(sketchDir, buildTarget) {
|
|
88
133
|
// Hard gate: verify arduino-cli + core before spawning.
|
|
89
134
|
{
|
|
90
135
|
const gate = checkArduinoEnv(buildTarget);
|
|
@@ -106,7 +151,68 @@ export function compileSketch(sketchDir, buildTarget) {
|
|
|
106
151
|
/**
|
|
107
152
|
* Upload the compiled sketch to the board.
|
|
108
153
|
*/
|
|
109
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Upload firmware. Detects the toolchain from the framework:
|
|
156
|
+
* - framework-esp32 → idf.py flash
|
|
157
|
+
* - everything else → arduino-cli upload
|
|
158
|
+
*/
|
|
159
|
+
export function uploadSketch(sketchDir, buildTarget, port, framework) {
|
|
160
|
+
if (framework?.includes('framework-esp32')) {
|
|
161
|
+
return uploadEspIdfSketch(sketchDir, buildTarget, port);
|
|
162
|
+
}
|
|
163
|
+
return uploadArduinoSketch(sketchDir, buildTarget, port);
|
|
164
|
+
}
|
|
165
|
+
/** Compile via idf.py build (framework-esp32). */
|
|
166
|
+
function compileEspIdfSketch(sketchDir, buildTarget) {
|
|
167
|
+
try {
|
|
168
|
+
const projectDir = path.basename(sketchDir) === 'main' ? path.dirname(sketchDir) : sketchDir;
|
|
169
|
+
const distDir = resolveFrameworkEsp32Dist();
|
|
170
|
+
const { compileEspIdf } = require_(path.join(distDir, 'compile.js'));
|
|
171
|
+
const result = compileEspIdf({
|
|
172
|
+
sourcePath: projectDir,
|
|
173
|
+
target: buildTarget || 'esp32',
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
success: result.success,
|
|
177
|
+
sketchDir: projectDir,
|
|
178
|
+
sketchPath: '',
|
|
179
|
+
output: result.output,
|
|
180
|
+
error: result.errorMessage,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
return {
|
|
185
|
+
success: false,
|
|
186
|
+
sketchDir,
|
|
187
|
+
sketchPath: '',
|
|
188
|
+
output: '',
|
|
189
|
+
error: `idf.py compile failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Upload via idf.py flash (framework-esp32). */
|
|
194
|
+
function uploadEspIdfSketch(sketchDir, buildTarget, port) {
|
|
195
|
+
try {
|
|
196
|
+
const projectDir = path.basename(sketchDir) === 'main' ? path.dirname(sketchDir) : sketchDir;
|
|
197
|
+
const distDir = resolveFrameworkEsp32Dist();
|
|
198
|
+
const { uploadEspIdf } = require_(path.join(distDir, 'upload.js'));
|
|
199
|
+
const result = uploadEspIdf(projectDir, port);
|
|
200
|
+
return {
|
|
201
|
+
success: result.success,
|
|
202
|
+
output: result.output,
|
|
203
|
+
error: result.errorMessage,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
output: '',
|
|
210
|
+
error: `idf.py flash failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/** Upload via arduino-cli (framework-arduino / framework-avr). */
|
|
215
|
+
function uploadArduinoSketch(sketchDir, buildTarget, port) {
|
|
110
216
|
// Hard gate: verify arduino-cli + core before spawning.
|
|
111
217
|
{
|
|
112
218
|
const gate = checkArduinoEnv(buildTarget);
|
|
@@ -174,13 +280,14 @@ function findOutputDir(buildDir, baseName, projectRoot) {
|
|
|
174
280
|
function hasInoFile(dir) {
|
|
175
281
|
if (!fs.existsSync(dir))
|
|
176
282
|
return false;
|
|
177
|
-
return fs.readdirSync(dir).some(f => f.endsWith('.ino'));
|
|
283
|
+
return fs.readdirSync(dir).some(f => f.endsWith('.ino') || f.endsWith('.cc'));
|
|
178
284
|
}
|
|
179
285
|
function findInoFile(dir) {
|
|
180
286
|
if (!fs.existsSync(dir))
|
|
181
287
|
return undefined;
|
|
182
288
|
for (const entry of fs.readdirSync(dir)) {
|
|
183
|
-
|
|
289
|
+
// Accept .ino (Arduino) and .cc (ESP-IDF main.cc) as entry files.
|
|
290
|
+
if (entry.endsWith('.ino') || entry.endsWith('.cc')) {
|
|
184
291
|
return path.join(dir, entry);
|
|
185
292
|
}
|
|
186
293
|
}
|
|
@@ -190,7 +297,7 @@ function findInoFileRecursive(dir) {
|
|
|
190
297
|
if (!fs.existsSync(dir))
|
|
191
298
|
return undefined;
|
|
192
299
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
193
|
-
if (entry.name.endsWith('.ino')) {
|
|
300
|
+
if (entry.name.endsWith('.ino') || entry.name.endsWith('.cc')) {
|
|
194
301
|
return path.join(dir, entry.name);
|
|
195
302
|
}
|
|
196
303
|
if (entry.isDirectory()) {
|
|
@@ -17,6 +17,13 @@ export interface OutputShim {
|
|
|
17
17
|
export declare const serialShim: OutputShim;
|
|
18
18
|
/** AVR native UART shim: routes through framework-avr's _uart_* helpers. */
|
|
19
19
|
export declare const avrUartShim: OutputShim;
|
|
20
|
+
/** ESP-IDF shim: emits console.debug (print, no newline) and console.log
|
|
21
|
+
* (println, with newline) calls. The transpiler's transformConsoleCall lowers
|
|
22
|
+
* these to printf for framework-esp32. Using different console methods lets
|
|
23
|
+
* the lowering distinguish "partial line" (print/debug) from "end of line"
|
|
24
|
+
* (println/log) — critical for the [TC:EXPECT:...] protocol format which
|
|
25
|
+
* spans multiple print calls on a single line. */
|
|
26
|
+
export declare const espIdfShim: OutputShim;
|
|
20
27
|
export interface PreprocessorOptions {
|
|
21
28
|
/** Wrap string literals in Arduino F() macro to save SRAM on AVR. */
|
|
22
29
|
isAvr?: boolean;
|
|
@@ -38,6 +38,18 @@ export const avrUartShim = {
|
|
|
38
38
|
println: (e) => `_uart_println_expr(${e})`,
|
|
39
39
|
delay: '_native_delay_ms(1000)',
|
|
40
40
|
};
|
|
41
|
+
/** ESP-IDF shim: emits console.debug (print, no newline) and console.log
|
|
42
|
+
* (println, with newline) calls. The transpiler's transformConsoleCall lowers
|
|
43
|
+
* these to printf for framework-esp32. Using different console methods lets
|
|
44
|
+
* the lowering distinguish "partial line" (print/debug) from "end of line"
|
|
45
|
+
* (println/log) — critical for the [TC:EXPECT:...] protocol format which
|
|
46
|
+
* spans multiple print calls on a single line. */
|
|
47
|
+
export const espIdfShim = {
|
|
48
|
+
begin: '',
|
|
49
|
+
print: (e) => `console.debug(${e})`,
|
|
50
|
+
println: (e) => `console.log(${e})`,
|
|
51
|
+
delay: 'Timing.delay(1000)',
|
|
52
|
+
};
|
|
41
53
|
/**
|
|
42
54
|
* Preprocess a test file's TypeScript source.
|
|
43
55
|
*
|
package/dist/host/runner.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { findTestFiles } from './finder.js';
|
|
10
|
-
import { preprocess, serialShim, avrUartShim } from './preprocessor.js';
|
|
10
|
+
import { preprocess, serialShim, avrUartShim, espIdfShim } from './preprocessor.js';
|
|
11
11
|
import { transpileTestFile, compileSketch, uploadSketch } from './compiler.js';
|
|
12
12
|
import { readSerialOutput } from './serial.js';
|
|
13
13
|
import { parseProtocolLines } from './parser.js';
|
|
@@ -84,7 +84,9 @@ async function processTestFile(filePath, config) {
|
|
|
84
84
|
try {
|
|
85
85
|
preprocessed = preprocess(source, path.basename(filePath), {
|
|
86
86
|
isAvr: config.target === 'avr' || config.target === 'megaavr',
|
|
87
|
-
shim: config.framework === '@typecad/framework-avr' ? avrUartShim
|
|
87
|
+
shim: config.framework === '@typecad/framework-avr' ? avrUartShim
|
|
88
|
+
: config.framework?.includes('framework-esp32') ? espIdfShim
|
|
89
|
+
: serialShim,
|
|
88
90
|
});
|
|
89
91
|
}
|
|
90
92
|
catch (e) {
|
|
@@ -96,15 +98,15 @@ async function processTestFile(filePath, config) {
|
|
|
96
98
|
if (!transpileResult.success) {
|
|
97
99
|
return errorResult(filePath, transpileResult.error ?? 'Transpilation failed', startTime);
|
|
98
100
|
}
|
|
99
|
-
// Step 3: Compile
|
|
101
|
+
// Step 3: Compile (arduino-cli or idf.py depending on framework)
|
|
100
102
|
console.log(` ${DIM}compiling...${RESET}`);
|
|
101
|
-
const compileResult = compileSketch(transpileResult.sketchDir, config.buildTarget);
|
|
103
|
+
const compileResult = compileSketch(transpileResult.sketchDir, config.buildTarget, config.framework);
|
|
102
104
|
if (!compileResult.success) {
|
|
103
105
|
return errorResult(filePath, compileResult.error ?? 'Compilation failed', startTime);
|
|
104
106
|
}
|
|
105
|
-
// Step 4: Upload
|
|
107
|
+
// Step 4: Upload (arduino-cli or idf.py depending on framework)
|
|
106
108
|
console.log(` ${DIM}uploading to ${config.test.port}...${RESET}`);
|
|
107
|
-
const uploadResult = uploadSketch(transpileResult.sketchDir, config.buildTarget, config.test.port);
|
|
109
|
+
const uploadResult = uploadSketch(transpileResult.sketchDir, config.buildTarget, config.test.port, config.framework);
|
|
108
110
|
if (!uploadResult.success) {
|
|
109
111
|
return errorResult(filePath, uploadResult.error ?? 'Upload failed', startTime);
|
|
110
112
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typecad/expect",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.7",
|
|
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,10 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"typescript": "^5.7.3",
|
|
42
42
|
"serialport": "^12.0.0",
|
|
43
|
-
"@typecad/arduino-cli": "1.0.0-alpha.
|
|
43
|
+
"@typecad/arduino-cli": "1.0.0-alpha.7"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@typecad/cuttlefish": "1.0.0-alpha.
|
|
46
|
+
"@typecad/cuttlefish": "1.0.0-alpha.7",
|
|
47
47
|
"@types/node": "^22.10.7"
|
|
48
48
|
},
|
|
49
49
|
"license": "MIT",
|