@typecad/expect 0.1.0-alpha.1
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 +345 -0
- package/dist/host/chain-collector.d.ts +48 -0
- package/dist/host/chain-collector.js +153 -0
- package/dist/host/cli.d.ts +2 -0
- package/dist/host/cli.js +185 -0
- package/dist/host/compiler.d.ts +27 -0
- package/dist/host/compiler.js +250 -0
- package/dist/host/config.d.ts +29 -0
- package/dist/host/config.js +218 -0
- package/dist/host/evaluator.d.ts +14 -0
- package/dist/host/evaluator.js +113 -0
- package/dist/host/finder.d.ts +12 -0
- package/dist/host/finder.js +70 -0
- package/dist/host/parser.d.ts +5 -0
- package/dist/host/parser.js +196 -0
- package/dist/host/preprocessor.d.ts +33 -0
- package/dist/host/preprocessor.js +115 -0
- package/dist/host/protocol-emitter.d.ts +29 -0
- package/dist/host/protocol-emitter.js +107 -0
- package/dist/host/reporter.d.ts +11 -0
- package/dist/host/reporter.js +200 -0
- package/dist/host/runner.d.ts +7 -0
- package/dist/host/runner.js +256 -0
- package/dist/host/serial.d.ts +28 -0
- package/dist/host/serial.js +129 -0
- package/dist/host/types.d.ts +121 -0
- package/dist/host/types.js +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/stubs.d.ts +17 -0
- package/dist/stubs.js +26 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +8 -0
- package/package.json +80 -0
package/dist/host/cli.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// @typecad/expect — CLI entry point
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// cuttlefish-test [options] [files...]
|
|
7
|
+
//
|
|
8
|
+
// Options:
|
|
9
|
+
// --port <port> Serial port (e.g. COM3, /dev/ttyACM0)
|
|
10
|
+
// --board <board> Board package override
|
|
11
|
+
// --build-target <id> Framework-specific build target override
|
|
12
|
+
// --baud <rate> Serial baud rate (default: 115200)
|
|
13
|
+
// --timeout <ms> Serial read timeout in ms (default: 30000)
|
|
14
|
+
// --include <glob> Test file glob pattern (repeatable)
|
|
15
|
+
// --exclude <glob> Test file glob pattern to skip (repeatable)
|
|
16
|
+
// --verbose Show debug serial output and assertion details
|
|
17
|
+
// --help Show this help
|
|
18
|
+
//
|
|
19
|
+
// If no files are given, discovers tests via config include patterns.
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
import { loadConfig } from './config.js';
|
|
22
|
+
import { run } from './runner.js';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
function parseArgs(argv) {
|
|
25
|
+
const result = { files: [] };
|
|
26
|
+
const args = argv.slice(2); // skip node + script
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
const arg = args[i];
|
|
29
|
+
switch (arg) {
|
|
30
|
+
case '--port':
|
|
31
|
+
case '-p':
|
|
32
|
+
result.port = args[++i];
|
|
33
|
+
break;
|
|
34
|
+
case '--board':
|
|
35
|
+
case '-b':
|
|
36
|
+
result.board = args[++i];
|
|
37
|
+
break;
|
|
38
|
+
case '--build-target':
|
|
39
|
+
result.buildTarget = args[++i];
|
|
40
|
+
break;
|
|
41
|
+
case '--baud': {
|
|
42
|
+
const val = args[++i];
|
|
43
|
+
const num = parseInt(val, 10);
|
|
44
|
+
if (isNaN(num)) {
|
|
45
|
+
console.error(`--baud requires a number, got "${val}"`);
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
result.baudRate = num;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case '--timeout':
|
|
52
|
+
case '-t': {
|
|
53
|
+
const val = args[++i];
|
|
54
|
+
const num = parseInt(val, 10);
|
|
55
|
+
if (isNaN(num)) {
|
|
56
|
+
console.error(`--timeout requires a number, got "${val}"`);
|
|
57
|
+
process.exit(2);
|
|
58
|
+
}
|
|
59
|
+
result.timeout = num;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case '--include':
|
|
63
|
+
case '-i':
|
|
64
|
+
if (!result.include)
|
|
65
|
+
result.include = [];
|
|
66
|
+
result.include.push(args[++i]);
|
|
67
|
+
break;
|
|
68
|
+
case '--exclude':
|
|
69
|
+
case '-x':
|
|
70
|
+
if (!result.exclude)
|
|
71
|
+
result.exclude = [];
|
|
72
|
+
result.exclude.push(args[++i]);
|
|
73
|
+
break;
|
|
74
|
+
case '--verbose':
|
|
75
|
+
case '-v':
|
|
76
|
+
result.verbose = true;
|
|
77
|
+
break;
|
|
78
|
+
case '--help':
|
|
79
|
+
case '-h':
|
|
80
|
+
result.help = true;
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
if (!arg.startsWith('-')) {
|
|
84
|
+
result.files.push(arg);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.error(`Unknown option: ${arg}`);
|
|
88
|
+
process.exit(2);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Help text
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
const HELP = `
|
|
98
|
+
\x1b[1m\x1b[36m cuttlefish-test\x1b[0m — Hardware test runner for TypeCAD
|
|
99
|
+
|
|
100
|
+
\x1b[1mUsage:\x1b[0m
|
|
101
|
+
cuttlefish-test [options] [files...]
|
|
102
|
+
|
|
103
|
+
\x1b[1mOptions:\x1b[0m
|
|
104
|
+
--port, -p <port> Serial port (e.g. COM4, /dev/ttyACM0)
|
|
105
|
+
--board, -b <board> Board package override
|
|
106
|
+
--build-target <id> Framework-specific build target override
|
|
107
|
+
--baud <rate> Serial baud rate (default: 115200)
|
|
108
|
+
--timeout, -t <ms> Serial read timeout (default: 30000)
|
|
109
|
+
--include, -i <glob> Test file pattern (repeatable)
|
|
110
|
+
--exclude, -x <glob> Test file pattern to skip (repeatable)
|
|
111
|
+
--verbose, -v Show debug serial output
|
|
112
|
+
--help, -h Show this help
|
|
113
|
+
|
|
114
|
+
\x1b[1mExamples:\x1b[0m
|
|
115
|
+
cuttlefish-test --port COM4
|
|
116
|
+
cuttlefish-test --port /dev/ttyACM0 tests/my-test.test.ts
|
|
117
|
+
cuttlefish-test -p COM4 -v
|
|
118
|
+
|
|
119
|
+
\x1b[1mConfiguration:\x1b[0m
|
|
120
|
+
Add a \`test\` section to your cuttlefish.config.ts:
|
|
121
|
+
|
|
122
|
+
const config = {
|
|
123
|
+
board: '@typecad/board-arduino-uno',
|
|
124
|
+
test: {
|
|
125
|
+
buildTarget: 'arduino:avr:uno',
|
|
126
|
+
port: 'COM4',
|
|
127
|
+
include: ['tests/**/*.test.ts'],
|
|
128
|
+
baudRate: 115200,
|
|
129
|
+
timeout: 30000,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
`.trim();
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Main
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
async function main() {
|
|
137
|
+
const args = parseArgs(process.argv);
|
|
138
|
+
if (args.help) {
|
|
139
|
+
console.log(HELP);
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
const projectRoot = process.cwd();
|
|
143
|
+
// Build config overrides from CLI args
|
|
144
|
+
const overrides = {};
|
|
145
|
+
if (args.port)
|
|
146
|
+
overrides.port = args.port;
|
|
147
|
+
if (args.board)
|
|
148
|
+
overrides.board = args.board;
|
|
149
|
+
if (args.buildTarget)
|
|
150
|
+
overrides.buildTarget = args.buildTarget;
|
|
151
|
+
if (args.baudRate)
|
|
152
|
+
overrides.baudRate = args.baudRate;
|
|
153
|
+
if (args.timeout)
|
|
154
|
+
overrides.timeout = args.timeout;
|
|
155
|
+
if (args.verbose)
|
|
156
|
+
overrides.verbose = args.verbose;
|
|
157
|
+
if (args.exclude)
|
|
158
|
+
overrides.exclude = args.exclude;
|
|
159
|
+
// If files are specified directly, use them as include patterns
|
|
160
|
+
if (args.files.length > 0) {
|
|
161
|
+
overrides.include = args.files.map(f => {
|
|
162
|
+
// Convert relative paths to be relative to project root
|
|
163
|
+
return path.isAbsolute(f) ? path.relative(projectRoot, f) : f;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else if (args.include) {
|
|
167
|
+
overrides.include = args.include;
|
|
168
|
+
}
|
|
169
|
+
// Load config
|
|
170
|
+
let config;
|
|
171
|
+
try {
|
|
172
|
+
config = loadConfig(projectRoot, overrides);
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
console.error(`\x1b[31m${e.message}\x1b[0m`);
|
|
176
|
+
process.exit(2);
|
|
177
|
+
}
|
|
178
|
+
// Run tests
|
|
179
|
+
const exitCode = await run(config);
|
|
180
|
+
process.exit(exitCode);
|
|
181
|
+
}
|
|
182
|
+
main().catch((e) => {
|
|
183
|
+
console.error(`\x1b[31mFatal error: ${e.message}\x1b[0m`);
|
|
184
|
+
process.exit(2);
|
|
185
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface CompileResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
sketchDir: string;
|
|
4
|
+
sketchPath: string;
|
|
5
|
+
output: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface UploadResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
output: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Transpile preprocessed TypeScript source to a C++ Arduino sketch.
|
|
15
|
+
*
|
|
16
|
+
* Writes the preprocessed source to a temp file, invokes the cuttlefish
|
|
17
|
+
* transpiler, and returns the path to the generated .ino file.
|
|
18
|
+
*/
|
|
19
|
+
export declare function transpileTestFile(preprocessedSource: string, originalFilePath: string, projectRoot: string, buildTarget: string): CompileResult;
|
|
20
|
+
/**
|
|
21
|
+
* Compile the Arduino sketch using arduino-cli.
|
|
22
|
+
*/
|
|
23
|
+
export declare function compileSketch(sketchDir: string, buildTarget: string): CompileResult;
|
|
24
|
+
/**
|
|
25
|
+
* Upload the compiled sketch to the board.
|
|
26
|
+
*/
|
|
27
|
+
export declare function uploadSketch(sketchDir: string, buildTarget: string, port: string): UploadResult;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/expect — Compiler
|
|
3
|
+
//
|
|
4
|
+
// Wraps the cuttlefish transpiler + arduino-cli compile/upload cycle.
|
|
5
|
+
// Takes preprocessed TypeScript source, transpiles to C++, compiles, uploads.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { parseConfigAST } from './config.js';
|
|
11
|
+
/**
|
|
12
|
+
* Transpile preprocessed TypeScript source to a C++ Arduino sketch.
|
|
13
|
+
*
|
|
14
|
+
* Writes the preprocessed source to a temp file, invokes the cuttlefish
|
|
15
|
+
* transpiler, and returns the path to the generated .ino file.
|
|
16
|
+
*/
|
|
17
|
+
export function transpileTestFile(preprocessedSource, originalFilePath, projectRoot, buildTarget) {
|
|
18
|
+
// Create a build directory for this test file
|
|
19
|
+
const baseName = path.basename(originalFilePath, '.test.ts').replace(/[^a-zA-Z0-9_]/g, '_');
|
|
20
|
+
const buildDir = path.join(projectRoot, '.build', 'expect', baseName);
|
|
21
|
+
try {
|
|
22
|
+
fs.rmSync(buildDir, { recursive: true, force: true });
|
|
23
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { success: false, sketchDir: buildDir, sketchPath: '', output: '', error: `Failed to create build dir: ${buildDir}` };
|
|
27
|
+
}
|
|
28
|
+
const rewrittenSource = rewriteRelativeImports(preprocessedSource, originalFilePath, buildDir);
|
|
29
|
+
// Write the preprocessed source as a .ts file
|
|
30
|
+
const tsPath = path.join(buildDir, `${baseName}.ts`);
|
|
31
|
+
fs.writeFileSync(tsPath, rewrittenSource, 'utf8');
|
|
32
|
+
// Invoke the cuttlefish transpiler
|
|
33
|
+
// We call it as a CLI command rather than importing to avoid coupling
|
|
34
|
+
const cuttlefishCmd = resolveCuttlefishCmd(projectRoot);
|
|
35
|
+
const useBuildMode = hasRelativeImports(rewrittenSource);
|
|
36
|
+
if (useBuildMode) {
|
|
37
|
+
writeBuildConfig(buildDir, projectRoot, path.basename(tsPath), buildTarget);
|
|
38
|
+
}
|
|
39
|
+
const result = spawnSync(process.execPath, useBuildMode
|
|
40
|
+
? [cuttlefishCmd, 'build', '--skip-type-check', '--force']
|
|
41
|
+
: [cuttlefishCmd, tsPath, '--skip-type-check', '--force'], {
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
cwd: useBuildMode ? buildDir : projectRoot,
|
|
44
|
+
timeout: 60000,
|
|
45
|
+
env: { ...process.env },
|
|
46
|
+
});
|
|
47
|
+
const transpileOutput = `${result.stdout ?? ''}\n${result.stderr ?? ''}`.trim();
|
|
48
|
+
if (result.status !== 0) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
sketchDir: buildDir,
|
|
52
|
+
sketchPath: '',
|
|
53
|
+
output: transpileOutput,
|
|
54
|
+
error: `Transpilation failed:\n${transpileOutput}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Find the generated .ino file
|
|
58
|
+
const outDir = findOutputDir(buildDir, baseName, projectRoot);
|
|
59
|
+
const inoPath = findInoFile(outDir);
|
|
60
|
+
if (!inoPath) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
sketchDir: outDir,
|
|
64
|
+
sketchPath: '',
|
|
65
|
+
output: transpileOutput,
|
|
66
|
+
error: `No .ino file found in ${outDir} after transpilation`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
sketchDir: path.dirname(inoPath),
|
|
72
|
+
sketchPath: inoPath,
|
|
73
|
+
output: transpileOutput,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Compile the Arduino sketch using arduino-cli.
|
|
78
|
+
*/
|
|
79
|
+
export function compileSketch(sketchDir, buildTarget) {
|
|
80
|
+
const result = spawnSync('arduino-cli', ['compile', '--fqbn', buildTarget, sketchDir], { encoding: 'utf8', timeout: 120000 });
|
|
81
|
+
const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`.trim();
|
|
82
|
+
return {
|
|
83
|
+
success: result.status === 0,
|
|
84
|
+
sketchDir,
|
|
85
|
+
sketchPath: '',
|
|
86
|
+
output,
|
|
87
|
+
error: result.status !== 0 ? `Compilation failed:\n${output}` : undefined,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Upload the compiled sketch to the board.
|
|
92
|
+
*/
|
|
93
|
+
export function uploadSketch(sketchDir, buildTarget, port) {
|
|
94
|
+
const result = spawnSync('arduino-cli', ['upload', '--fqbn', buildTarget, '--port', port, sketchDir], { encoding: 'utf8', timeout: 60000 });
|
|
95
|
+
const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`.trim();
|
|
96
|
+
return {
|
|
97
|
+
success: result.status === 0,
|
|
98
|
+
output,
|
|
99
|
+
error: result.status !== 0 ? `Upload failed:\n${output}` : undefined,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Internal
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
function resolveCuttlefishCmd(projectRoot) {
|
|
106
|
+
// Try to find cuttlefish CLI in the monorepo (current dir and parent dirs).
|
|
107
|
+
// We prefer the JS entry point over the .bin shims because the shell wrapper
|
|
108
|
+
// (no extension) cannot be passed to `node process.execPath` on Windows, and
|
|
109
|
+
// invoking it via the shell would require platform-specific handling.
|
|
110
|
+
let searchDir = projectRoot;
|
|
111
|
+
for (let i = 0; i < 5; i++) {
|
|
112
|
+
const candidates = [
|
|
113
|
+
path.join(searchDir, 'packages', 'cuttlefish', 'dist', 'cli.js'),
|
|
114
|
+
path.join(searchDir, 'node_modules', '@typecad', 'cuttlefish', 'dist', 'cli.js'),
|
|
115
|
+
path.join(searchDir, 'node_modules', 'cuttlefish', 'dist', 'cli.js'),
|
|
116
|
+
];
|
|
117
|
+
for (const c of candidates) {
|
|
118
|
+
if (fs.existsSync(c))
|
|
119
|
+
return c;
|
|
120
|
+
}
|
|
121
|
+
const parent = path.dirname(searchDir);
|
|
122
|
+
if (parent === searchDir)
|
|
123
|
+
break; // reached root
|
|
124
|
+
searchDir = parent;
|
|
125
|
+
}
|
|
126
|
+
// Fallback: assume it's on PATH
|
|
127
|
+
return 'cuttlefish';
|
|
128
|
+
}
|
|
129
|
+
function findOutputDir(buildDir, baseName, projectRoot) {
|
|
130
|
+
// The cuttlefish transpiler writes output next to the source by default,
|
|
131
|
+
// or to the configured outDir. Check common locations.
|
|
132
|
+
const candidates = [
|
|
133
|
+
buildDir,
|
|
134
|
+
path.join(buildDir, baseName),
|
|
135
|
+
path.join(buildDir, 'out', baseName),
|
|
136
|
+
path.join(buildDir, 'out'),
|
|
137
|
+
path.join(projectRoot, 'out'),
|
|
138
|
+
path.join(projectRoot, '.build', 'expect', baseName, baseName),
|
|
139
|
+
];
|
|
140
|
+
for (const c of candidates) {
|
|
141
|
+
if (fs.existsSync(c) && hasInoFile(c))
|
|
142
|
+
return c;
|
|
143
|
+
}
|
|
144
|
+
// Last resort: walk the buildDir tree recursively to find any .ino
|
|
145
|
+
const found = findInoFileRecursive(buildDir);
|
|
146
|
+
if (found)
|
|
147
|
+
return path.dirname(found);
|
|
148
|
+
return buildDir;
|
|
149
|
+
}
|
|
150
|
+
function hasInoFile(dir) {
|
|
151
|
+
if (!fs.existsSync(dir))
|
|
152
|
+
return false;
|
|
153
|
+
return fs.readdirSync(dir).some(f => f.endsWith('.ino'));
|
|
154
|
+
}
|
|
155
|
+
function findInoFile(dir) {
|
|
156
|
+
if (!fs.existsSync(dir))
|
|
157
|
+
return undefined;
|
|
158
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
159
|
+
if (entry.endsWith('.ino')) {
|
|
160
|
+
return path.join(dir, entry);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
function findInoFileRecursive(dir) {
|
|
166
|
+
if (!fs.existsSync(dir))
|
|
167
|
+
return undefined;
|
|
168
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
169
|
+
if (entry.name.endsWith('.ino')) {
|
|
170
|
+
return path.join(dir, entry.name);
|
|
171
|
+
}
|
|
172
|
+
if (entry.isDirectory()) {
|
|
173
|
+
const result = findInoFileRecursive(path.join(dir, entry.name));
|
|
174
|
+
if (result)
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
function hasRelativeImports(source) {
|
|
181
|
+
return /from\s+['"]\.\.?\//.test(source);
|
|
182
|
+
}
|
|
183
|
+
function rewriteRelativeImports(source, originalFilePath, buildDir) {
|
|
184
|
+
const originalDir = path.dirname(originalFilePath);
|
|
185
|
+
return source.replace(/from\s+(['"])(\.\.?\/[^'"]+)\1/g, (_match, quote, specifier) => {
|
|
186
|
+
const resolvedPath = path.resolve(originalDir, specifier);
|
|
187
|
+
let relativePath = path.relative(buildDir, resolvedPath).replace(/\\/g, '/');
|
|
188
|
+
if (!relativePath.startsWith('.')) {
|
|
189
|
+
relativePath = `./${relativePath}`;
|
|
190
|
+
}
|
|
191
|
+
return `from ${quote}${relativePath}${quote}`;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function writeBuildConfig(buildDir, projectRoot, entryFileName, buildTarget) {
|
|
195
|
+
const baseConfigPath = path.join(projectRoot, 'cuttlefish.config.ts');
|
|
196
|
+
const buildConfigPath = path.join(buildDir, 'cuttlefish.config.ts');
|
|
197
|
+
if (fs.existsSync(baseConfigPath)) {
|
|
198
|
+
// Parse base config via AST to extract scalar values, then inline them.
|
|
199
|
+
// This avoids spreads (...baseConfig) which the config loader cannot evaluate.
|
|
200
|
+
const baseValues = parseConfigAST(baseConfigPath);
|
|
201
|
+
const lines = [
|
|
202
|
+
`import type { CuttlefishConfig } from '@typecad/hal';`,
|
|
203
|
+
'',
|
|
204
|
+
'const config: CuttlefishConfig = {',
|
|
205
|
+
` entry: './${entryFileName}',`,
|
|
206
|
+
];
|
|
207
|
+
if (baseValues.target)
|
|
208
|
+
lines.push(` target: '${baseValues.target}',`);
|
|
209
|
+
if (baseValues.board)
|
|
210
|
+
lines.push(` board: '${baseValues.board}',`);
|
|
211
|
+
if (baseValues.framework)
|
|
212
|
+
lines.push(` framework: '${baseValues.framework}',`);
|
|
213
|
+
if (baseValues.frameworkData?.buildTarget)
|
|
214
|
+
lines.push(` frameworkData: { buildTarget: '${baseValues.frameworkData.buildTarget}' },`);
|
|
215
|
+
lines.push(' output: {');
|
|
216
|
+
if (baseValues.output?.framework)
|
|
217
|
+
lines.push(` framework: '${baseValues.output?.framework}',`);
|
|
218
|
+
if (baseValues.output?.optimize)
|
|
219
|
+
lines.push(` optimize: '${baseValues.output?.optimize}',`);
|
|
220
|
+
lines.push(` outDir: './out',`);
|
|
221
|
+
lines.push(' },');
|
|
222
|
+
if (baseValues.console?.baudRate) {
|
|
223
|
+
lines.push(' console: {');
|
|
224
|
+
lines.push(` baudRate: ${baseValues.console?.baudRate},`);
|
|
225
|
+
lines.push(' },');
|
|
226
|
+
}
|
|
227
|
+
lines.push('};');
|
|
228
|
+
lines.push('');
|
|
229
|
+
lines.push('export default config;');
|
|
230
|
+
lines.push('');
|
|
231
|
+
fs.writeFileSync(buildConfigPath, lines.join('\n'), 'utf8');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
fs.writeFileSync(buildConfigPath, [
|
|
235
|
+
`import type { CuttlefishConfig } from '@typecad/hal';`,
|
|
236
|
+
'',
|
|
237
|
+
'const config: CuttlefishConfig = {',
|
|
238
|
+
` entry: './${entryFileName}',`,
|
|
239
|
+
` target: 'avr',`,
|
|
240
|
+
` frameworkData: { buildTarget: '${buildTarget}' },`,
|
|
241
|
+
' output: {',
|
|
242
|
+
` framework: 'arduino',`,
|
|
243
|
+
` outDir: './out',`,
|
|
244
|
+
' },',
|
|
245
|
+
'};',
|
|
246
|
+
'',
|
|
247
|
+
'export default config;',
|
|
248
|
+
'',
|
|
249
|
+
].join('\n'), 'utf8');
|
|
250
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ResolvedConfig, TestConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load configuration from `cuttlefish.config.ts` in the given project root.
|
|
4
|
+
*
|
|
5
|
+
* Parses the config file via the TypeScript AST (no dynamic import) to
|
|
6
|
+
* extract scalar properties — consistent with how the cuttlefish CLI does it.
|
|
7
|
+
*
|
|
8
|
+
* @param projectRoot Absolute path to the project root.
|
|
9
|
+
* @param overrides CLI flag overrides for test config.
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadConfig(projectRoot: string, overrides?: Partial<TestConfig>): ResolvedConfig;
|
|
12
|
+
export interface RawConfig {
|
|
13
|
+
target?: string;
|
|
14
|
+
board?: string;
|
|
15
|
+
frameworkData?: {
|
|
16
|
+
buildTarget?: string;
|
|
17
|
+
};
|
|
18
|
+
framework?: string;
|
|
19
|
+
test?: Partial<TestConfig>;
|
|
20
|
+
output?: {
|
|
21
|
+
framework?: string;
|
|
22
|
+
optimize?: string;
|
|
23
|
+
outDir?: string;
|
|
24
|
+
};
|
|
25
|
+
console?: {
|
|
26
|
+
baudRate?: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare function parseConfigAST(configPath: string): RawConfig;
|