@typecad/cuttlefish 1.0.0-alpha.6 → 1.0.0-alpha.8

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.
Files changed (187) hide show
  1. package/dist/api/config.d.ts +32 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/schema/types.d.ts +15 -0
  4. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  5. package/dist/api/shared/async-runtime-static.js +69 -24
  6. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  7. package/dist/api/shared/async-symbol-detector.js +140 -0
  8. package/dist/api/shared/async-types.d.ts +24 -0
  9. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  10. package/dist/api/shared/coop-scheduler.js +149 -0
  11. package/dist/api/shared/display-adapter.d.ts +4 -3
  12. package/dist/api/shared/display-adapter.js +15 -118
  13. package/dist/api/shared/display-adapters/sdl.js +38 -31
  14. package/dist/api/shared/display-profile.d.ts +47 -25
  15. package/dist/api/shared/display-profile.js +31 -164
  16. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  17. package/dist/api/shared/framework-manifest-registry.js +27 -0
  18. package/dist/api/shared/framework-manifest.d.ts +581 -0
  19. package/dist/api/shared/framework-manifest.js +182 -0
  20. package/dist/api/shared/glcdfont.d.ts +12 -0
  21. package/dist/api/shared/glcdfont.js +124 -0
  22. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  23. package/dist/api/shared/hal-op-ir.d.ts +755 -1
  24. package/dist/api/shared/hal-op-ir.js +129 -1
  25. package/dist/api/shared/index.d.ts +20 -2
  26. package/dist/api/shared/index.js +25 -1
  27. package/dist/api/shared/ir-core.d.ts +4 -0
  28. package/dist/api/shared/ir-declarations.d.ts +6 -0
  29. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  30. package/dist/api/shared/native-display-op-resolver.js +64 -0
  31. package/dist/api/shared/platform-strategy.d.ts +113 -2
  32. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  33. package/dist/api/shared/promise-runtime.d.ts +1 -1
  34. package/dist/api/shared/promise-runtime.js +95 -13
  35. package/dist/api/shared/toolchain-types.d.ts +17 -0
  36. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  37. package/dist/api/shared/validate-framework-manifest.js +494 -0
  38. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  39. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  40. package/dist/api/shared/worker-runtime.d.ts +69 -0
  41. package/dist/api/shared/worker-runtime.js +163 -0
  42. package/dist/cli.js +186 -82
  43. package/dist/config-loader.d.ts +7 -2
  44. package/dist/config-loader.js +23 -3
  45. package/dist/config-schema.d.ts +112 -70
  46. package/dist/config-schema.js +14 -0
  47. package/dist/contract/board-generator.d.ts +17 -0
  48. package/dist/contract/board-generator.js +57 -0
  49. package/dist/contract/contract-parser.d.ts +217 -0
  50. package/dist/contract/contract-parser.js +224 -0
  51. package/dist/contract/index.d.ts +22 -0
  52. package/dist/contract/index.js +68 -0
  53. package/dist/create/board-codegen.js +4 -4
  54. package/dist/create/board-generators.js +4 -5
  55. package/dist/create/board-spec.d.ts +72 -75
  56. package/dist/create/board-spec.js +0 -1
  57. package/dist/create/init-scaffold.d.ts +9 -2
  58. package/dist/create/init-scaffold.js +0 -14
  59. package/dist/create/init-templates.d.ts +2 -0
  60. package/dist/create/init-templates.js +74 -8
  61. package/dist/create/init-wizard.js +31 -7
  62. package/dist/debug/preprocessor.js +178 -39
  63. package/dist/debug/types.d.ts +23 -0
  64. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  65. package/dist/diagnostics/mermaid-builder.js +34 -24
  66. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  67. package/dist/emit/compliance/arxml-writer.js +34 -0
  68. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  69. package/dist/emit/compliance/compliance-context.js +113 -0
  70. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  71. package/dist/emit/compliance/deviation-ledger.js +47 -0
  72. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  73. package/dist/emit/compliance/deviation-writer.js +37 -0
  74. package/dist/emit/compliance/index.d.ts +7 -0
  75. package/dist/emit/compliance/index.js +6 -0
  76. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  77. package/dist/emit/compliance/rule-engine.js +101 -0
  78. package/dist/emit/compliance/rules.d.ts +16 -0
  79. package/dist/emit/compliance/rules.js +191 -0
  80. package/dist/emit/compliance/types.d.ts +59 -0
  81. package/dist/emit/compliance/types.js +8 -0
  82. package/dist/emit/cpp-emitter.js +4 -3
  83. package/dist/emit/emitters/class-emitter.js +6 -1
  84. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  85. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  86. package/dist/emit/emitters/line-appender.js +19 -0
  87. package/dist/emit/emitters/line-marker.d.ts +38 -0
  88. package/dist/emit/emitters/line-marker.js +39 -0
  89. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  90. package/dist/emit/emitters/output-finalizer.js +101 -12
  91. package/dist/emit/emitters/setup.d.ts +17 -0
  92. package/dist/emit/emitters/setup.js +257 -36
  93. package/dist/emit/emitters/top-level-prep.js +8 -0
  94. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  95. package/dist/emit/emitters/ui-emitter.js +33 -9
  96. package/dist/emit/expression-renderer.d.ts +1 -1
  97. package/dist/emit/expression-renderer.js +91 -6
  98. package/dist/emit/route-hal-op.js +18 -5
  99. package/dist/emit/snprintf-helpers.js +15 -4
  100. package/dist/emit/statement-renderer.d.ts +10 -0
  101. package/dist/emit/statement-renderer.js +34 -5
  102. package/dist/emit/utils/async-state-machine.js +221 -125
  103. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  104. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  105. package/dist/framework-package.js +2 -0
  106. package/dist/framework-registry.d.ts +17 -0
  107. package/dist/ir/adc-range-validation.js +14 -1
  108. package/dist/ir/build-ir-state.d.ts +1 -0
  109. package/dist/ir/build-ir-state.js +14 -0
  110. package/dist/ir/build-ir.js +9 -5
  111. package/dist/ir/call-graph.js +16 -0
  112. package/dist/ir/expression-to-ir.js +164 -0
  113. package/dist/ir/feature-registry.js +7 -25
  114. package/dist/ir/function-builder.js +22 -0
  115. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  116. package/dist/ir/hal/hal-emitter.js +50 -18
  117. package/dist/ir/hal/hal-parser.d.ts +6 -0
  118. package/dist/ir/hal/hal-parser.js +81 -0
  119. package/dist/ir/hal/hal-plugins.js +723 -1
  120. package/dist/ir/identifier-collector.js +35 -0
  121. package/dist/ir/interrupt-analysis.d.ts +5 -1
  122. package/dist/ir/interrupt-analysis.js +8 -16
  123. package/dist/ir/network-validation.d.ts +4 -0
  124. package/dist/ir/network-validation.js +184 -0
  125. package/dist/ir/ownership-analysis.js +20 -1
  126. package/dist/ir/pin-capability-validation.js +37 -0
  127. package/dist/ir/pin-mode-validation.d.ts +2 -2
  128. package/dist/ir/pin-mode-validation.js +49 -18
  129. package/dist/ir/program-analysis.d.ts +65 -0
  130. package/dist/ir/program-analysis.js +318 -2
  131. package/dist/ir/render-expr.js +11 -0
  132. package/dist/ir/timing-validation.d.ts +6 -1
  133. package/dist/ir/timing-validation.js +60 -13
  134. package/dist/ir/transformers/call-statement.js +105 -0
  135. package/dist/ir/transformers/expressions.js +62 -0
  136. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  137. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  138. package/dist/ir/transformers/namespace-methods.js +17 -12
  139. package/dist/ir/transformers/ui-reactive.js +2 -2
  140. package/dist/ir/transformers/variables.js +92 -3
  141. package/dist/ir/type-resolution.js +18 -0
  142. package/dist/ir/ui-element-auto-wire.js +7 -5
  143. package/dist/ir/utils/map-statements.d.ts +4 -0
  144. package/dist/ir/utils/map-statements.js +79 -0
  145. package/dist/ir/validation-orchestrator.js +9 -2
  146. package/dist/ir/worker-analysis.d.ts +10 -0
  147. package/dist/ir/worker-analysis.js +261 -0
  148. package/dist/libdef/c-to-decl.d.ts +27 -0
  149. package/dist/libdef/c-to-decl.js +397 -0
  150. package/dist/libdef/component-decls.d.ts +2 -0
  151. package/dist/libdef/component-decls.js +6 -0
  152. package/dist/libdef/component-discovery.d.ts +43 -0
  153. package/dist/libdef/component-discovery.js +83 -0
  154. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  155. package/dist/libdef/cpp-to-decl.js +72 -0
  156. package/dist/libdef/registry.js +5 -2
  157. package/dist/lint-cache.d.ts +59 -0
  158. package/dist/lint-cache.js +257 -0
  159. package/dist/orchestrator/graph-builder.js +14 -9
  160. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  161. package/dist/platform/coop-scheduler-runtime.js +41 -0
  162. package/dist/platform/generic-strategy.d.ts +15 -3
  163. package/dist/platform/generic-strategy.js +49 -4
  164. package/dist/safety/safety-bridge.d.ts +11 -0
  165. package/dist/safety/safety-bridge.js +48 -0
  166. package/dist/safety/sidecar-bridge.d.ts +5 -0
  167. package/dist/safety/sidecar-bridge.js +22 -0
  168. package/dist/safety-hook.d.ts +79 -0
  169. package/dist/safety-hook.js +35 -0
  170. package/dist/testing.d.ts +7 -1
  171. package/dist/testing.js +8 -1
  172. package/dist/transpile.d.ts +3 -0
  173. package/dist/transpile.js +175 -41
  174. package/dist/types.d.ts +13 -2
  175. package/dist/ui-hook.d.ts +17 -3
  176. package/dist/utils/cli.js +78 -4
  177. package/dist/utils/fs.d.ts +13 -0
  178. package/dist/utils/fs.js +50 -0
  179. package/package.json +24 -5
  180. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  181. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  182. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  183. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  184. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  185. package/dist/api/shared/display-adapters/st7796.js +0 -132
  186. package/dist/licenses.d.ts +0 -185
  187. package/dist/licenses.js +0 -963
@@ -0,0 +1,163 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared worker-offload runtime C++ code generator
3
+ //
4
+ // Generates the generic worker pool + the __tc_worker_submit / __tc_worker_done
5
+ // contract that generalizes the one-off ESP32 HTTP offload pattern
6
+ // (framework-esp32/src/lowering/http.ts: the __tc_http_send_task /
7
+ // __tc_http_send_start / __tc_http_done trio). The pool and contract live here;
8
+ // the per-framework spawn/signal/poll PRIMITIVES are injected via the strategy
9
+ // hooks workerSpawnLines / workerSignalDoneExpr / workerIsDoneExpr.
10
+ //
11
+ // Design (the safety property, per the approved plan):
12
+ // - The worker runtime is request-in / poll-out: the worker function receives
13
+ // its inputs by value and exposes outputs only through the opaque handle.
14
+ // - No mutex/semaphore is added to the IR. The worker-isolation analyzer
15
+ // (worker-analysis.ts) makes shared mutable state a transpile-time error.
16
+ // - STL-free: fixed-size static slot table, function-pointer + void*. Links
17
+ // on minimal-libc targets (Zephyr).
18
+ //
19
+ // The memory-barrier contract is load-bearing on dual-core targets: the signal
20
+ // primitive (workerSignalDoneExpr) MUST issue a full barrier so the consumer
21
+ // observes the worker's preceding output writes once it observes completion.
22
+ // ESP32 xTaskNotifyGive and Zephyr k_sem_give both satisfy this; bare volatile
23
+ // does NOT across cores.
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Generate the worker-offload runtime C++ for a framework that supplies a
27
+ * backing. Returns null if `backing` is null (unsupported on this framework).
28
+ *
29
+ * The emitted code defines, for each slot i in [0, poolSize):
30
+ * - a worker trampoline __tc_worker_fn_i(void* arg) that runs the recorded
31
+ * fn and then signals completion via signalDoneExpr,
32
+ * - __tc_worker_submit(i, fn, arg) — records the waiter, clears done, spawns,
33
+ * - __tc_worker_done(i) — poll predicate.
34
+ *
35
+ * AUTOSAR C++14: fixed-width types, static_cast, final structs, no STL, no heap.
36
+ */
37
+ export function generateWorkerRuntime(backing, options = {}) {
38
+ if (!backing)
39
+ return null;
40
+ const poolSize = options.poolSize ?? 4;
41
+ const stackSize = options.stackSize ?? 8192;
42
+ const lines = [];
43
+ lines.push(`// ── Worker offload runtime (generalized request-in / poll-out) ──────────────`);
44
+ lines.push(`// Slot table + __tc_worker_submit/done contract. Per-framework primitives`);
45
+ lines.push(`// (spawn/signal/poll) are injected by the framework strategy. STL-free so`);
46
+ lines.push(`// this links on minimal-libc targets. See worker-runtime.ts.`);
47
+ lines.push(`namespace typecad_worker {`);
48
+ lines.push(` using WorkerFn = void (*)(void*);`);
49
+ lines.push(``);
50
+ lines.push(` struct WorkerSlot final {`);
51
+ lines.push(` volatile bool done;`);
52
+ lines.push(` void* waiter; // framework-specific waiter handle (opaque)`);
53
+ lines.push(` WorkerFn fn;`);
54
+ lines.push(` void* arg;`);
55
+ lines.push(` };`);
56
+ lines.push(``);
57
+ // Per-slot static instances.
58
+ for (let i = 0; i < poolSize; i++) {
59
+ lines.push(` static WorkerSlot __slot_${i};`);
60
+ }
61
+ lines.push(``);
62
+ // Forward-declare the per-slot trampolines so backings that need to bind a
63
+ // named handler to one (e.g. Zephyr K_WORK_DEFINE(_work_i, handler_i)) can
64
+ // reference it before the trampoline definition appears below.
65
+ for (let i = 0; i < poolSize; i++) {
66
+ lines.push(` static void __tc_worker_fn_${i}(void* arg);`);
67
+ }
68
+ lines.push(``);
69
+ // Per-slot backing declarations (e.g. Zephyr K_SEM_DEFINE + K_WORK_DEFINE +
70
+ // a work handler that forwards to the trampoline). Emitted BEFORE the
71
+ // trampoline definitions so the trampoline bodies can reference per-slot
72
+ // objects declared here (e.g. the completion semaphore used by the signal
73
+ // expression). Backings must only forward-declare, not define, the
74
+ // trampoline itself.
75
+ if (backing.slotDeclarations) {
76
+ for (let i = 0; i < poolSize; i++) {
77
+ const trampolineName = `__tc_worker_fn_${i}`;
78
+ for (const d of backing.slotDeclarations(i, trampolineName)) {
79
+ lines.push(` ${d}`);
80
+ }
81
+ }
82
+ lines.push(``);
83
+ }
84
+ // Per-slot worker trampolines: run fn(arg), then signal completion.
85
+ for (let i = 0; i < poolSize; i++) {
86
+ const signal = backing.signalDoneExpr(i);
87
+ lines.push(` // Worker trampoline for slot ${i}.`);
88
+ lines.push(` static void __tc_worker_fn_${i}(void* arg) {`);
89
+ lines.push(` // The shared runtime recorded fn/waiter before spawn; run it.`);
90
+ lines.push(` if (__slot_${i}.fn != nullptr) { __slot_${i}.fn(arg); }`);
91
+ lines.push(` __slot_${i}.done = true;`);
92
+ lines.push(` ${signal}; // barrier: makes the fn's output writes visible`);
93
+ lines.push(` }`);
94
+ lines.push(``);
95
+ }
96
+ // submit / done per slot.
97
+ for (let i = 0; i < poolSize; i++) {
98
+ const trampolineName = `__tc_worker_fn_${i}`;
99
+ const spawn = backing.spawnLines(i, trampolineName, `__slot_${i}.waiter`);
100
+ lines.push(` static inline void __tc_worker_submit_${i}(WorkerFn fn, void* arg) {`);
101
+ lines.push(` __slot_${i}.done = false;`);
102
+ lines.push(` __slot_${i}.fn = fn;`);
103
+ lines.push(` __slot_${i}.arg = arg;`);
104
+ lines.push(` // waiter is set by the framework spawn primitive below.`);
105
+ for (const s of spawn)
106
+ lines.push(` ${s}`);
107
+ lines.push(` }`);
108
+ lines.push(``);
109
+ const poll = backing.isDoneExpr(i);
110
+ lines.push(` static inline bool __tc_worker_done_${i}(void) {`);
111
+ lines.push(` if (__slot_${i}.done) { return true; } // fast path`);
112
+ lines.push(` if (${poll}) { __slot_${i}.done = true; return true; }`);
113
+ lines.push(` return false;`);
114
+ lines.push(` }`);
115
+ lines.push(``);
116
+ }
117
+ lines.push(`} // namespace typecad_worker`);
118
+ lines.push(``);
119
+ // Global dispatch aliases so HAL emit (worker.submit / worker.done) resolves.
120
+ lines.push(`// Global dispatch: select the per-slot submit/done by handle id.`);
121
+ lines.push(`inline void __tc_worker_submit(int32_t handle, typecad_worker::WorkerFn fn, void* arg) {`);
122
+ lines.push(` switch (handle) {`);
123
+ for (let i = 0; i < poolSize; i++) {
124
+ lines.push(` case ${i}: typecad_worker::__tc_worker_submit_${i}(fn, arg); return;`);
125
+ }
126
+ lines.push(` default: break; // out-of-range handle: no-op`);
127
+ lines.push(` }`);
128
+ lines.push(`}`);
129
+ lines.push(`inline bool __tc_worker_done(int32_t handle) {`);
130
+ lines.push(` switch (handle) {`);
131
+ for (let i = 0; i < poolSize; i++) {
132
+ lines.push(` case ${i}: return typecad_worker::__tc_worker_done_${i}();`);
133
+ }
134
+ lines.push(` default: return true; // out-of-range handle: treat as already done`);
135
+ lines.push(` }`);
136
+ lines.push(`}`);
137
+ return lines.join("\n");
138
+ }
139
+ /**
140
+ * Resolve a HAL worker.* op to C++ against the shared contract.
141
+ * Returns undefined for ops the caller did not lower (unsupported).
142
+ *
143
+ * Note: the worker function itself (fnRef) and its argument (argRef) come from
144
+ * the IR as flat-scalar strings (per the hal-op-ir flat-fields contract). The
145
+ * lowering emits a straight call into __tc_worker_submit / __tc_worker_done.
146
+ */
147
+ export function lowerWorkerOp(op) {
148
+ const o = op;
149
+ switch (op.operation) {
150
+ case 'worker.submit': {
151
+ const handle = Number(o.handleId);
152
+ const fn = String(o.fnRef);
153
+ const arg = o.argRef != null ? String(o.argRef) : 'nullptr';
154
+ return { code: `__tc_worker_submit(static_cast<int32_t>(${handle}), ${fn}, ${arg});` };
155
+ }
156
+ case 'worker.done': {
157
+ const handle = Number(o.handleId);
158
+ return { expression: `__tc_worker_done(static_cast<int32_t>(${handle}))` };
159
+ }
160
+ default:
161
+ return undefined;
162
+ }
163
+ }
package/dist/cli.js CHANGED
@@ -5,22 +5,22 @@ import { parseCommandLine, printHelp } from "./utils/cli.js";
5
5
  import { scaffoldProject, printInitNextSteps, KNOWN_TARGETS } from "./create/index.js";
6
6
  import { runInitWizard } from "./create/index.js";
7
7
  import { generateLibraryDefinitions, transpileFile } from "./transpile.js";
8
- import { generateDecl, generateDeclsForDirectory } from "./libdef/cpp-to-decl.js";
8
+ import { generateDecl, generateDeclsForDirectory, generateComponentDeclsForProject } from "./libdef/cpp-to-decl.js";
9
9
  import { mapCppLocationToTs, readSourceMap, resolveMapPath } from "./mapping/source-map.js";
10
10
  import { compileSource, uploadFirmware, monitorDevice } from "./platform/toolchain.js";
11
11
  import { resolveStrategy } from "./platform/registry.js";
12
12
  import { loadFrameworkPackage } from "./framework-package.js";
13
13
  import { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
14
14
  import { loadCuttlefishConfig, generateVirtualTypeDeclaration } from "./config-loader.js";
15
+ import { generateContractBoard } from "./contract/index.js";
15
16
  import { requireUIHook, hasUIHook } from "./ui-hook.js";
16
17
  import { loadUIEngine } from "./ui/ui-bridge.js";
18
+ import { loadSafetyEngine } from "./safety/safety-bridge.js";
17
19
  import { runWatch, discoverWatchDirs } from "./watch.js";
18
20
  import { runExpectTests, assertTypeScriptInput, printDiagnostics, printMappedCompileErrors } from "./cli-utils.js";
19
21
  import { runPreviewServer } from "./preview/server.js";
20
22
  import * as ui from "./utils/ui.js";
21
23
  import chalk from "chalk";
22
- import { checkArduinoEnv } from "@typecad/arduino-cli";
23
- import { runLicensesPresenter } from "./licenses.js";
24
24
  function hasFatalDiagnostics(result) {
25
25
  return result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
26
26
  }
@@ -33,6 +33,27 @@ function displayConfigForTranspile(config) {
33
33
  }
34
34
  return display;
35
35
  }
36
+ /**
37
+ * Apply the PSRAM config to the Arduino build: append the `PSRAM={opi|quad}`
38
+ * menu option to the FQBN (so the Arduino core's psramFound()/ps_malloc work
39
+ * at runtime — they're gated on the board menu option, not just the define)
40
+ * and add `-DBOARD_HAS_PSRAM` to the defines (so the framework's PSRAM canvas
41
+ * allocator is compiled in). No-op when psram is unset or the target isn't an
42
+ * Arduino FQBN (the PSRAM= option is Arduino-core-specific).
43
+ */
44
+ function applyPsramToArduinoBuild(buildTarget, defines, psram) {
45
+ if (!psram || !buildTarget || !buildTarget.includes(':')) {
46
+ return { buildTarget, defines: defines ?? {} };
47
+ }
48
+ // Arduino FQBN config options follow the board:option as `key=value` pairs.
49
+ // Append PSRAM= if not already present (don't clobber an explicit override).
50
+ const psramOpt = `PSRAM=${psram}`;
51
+ const adjustedTarget = buildTarget.includes('PSRAM=') ? buildTarget : `${buildTarget}:${psramOpt}`;
52
+ const adjustedDefines = { ...(defines ?? {}) };
53
+ if (!('BOARD_HAS_PSRAM' in adjustedDefines))
54
+ adjustedDefines.BOARD_HAS_PSRAM = '';
55
+ return { buildTarget: adjustedTarget, defines: adjustedDefines };
56
+ }
36
57
  async function handleCreate(options) {
37
58
  const targetId = options.target ?? options.board;
38
59
  const hasTarget = !!targetId;
@@ -43,13 +64,16 @@ async function handleCreate(options) {
43
64
  throw new Error(`Unknown target '${targetId}'. Available targets:\n${available}`);
44
65
  }
45
66
  const framework = options.framework ?? target.framework;
46
- const frameworkPackage = target.isNative
47
- ? target.frameworkPackage
48
- : framework === 'avr'
49
- ? '@typecad/framework-avr'
50
- : framework === 'arduino'
51
- ? '@typecad/framework-arduino'
52
- : `@typecad/framework-${framework}`;
67
+ const frameworkPackage = target.frameworkPackage
68
+ ?? (framework ? `@typecad/framework-${framework}` : undefined);
69
+ if (!frameworkPackage) {
70
+ throw new Error(`No framework package resolved for target '${target.id}'. ` +
71
+ `Pass --framework <name> or install a @typecad/framework-* package.`);
72
+ }
73
+ // Derive the short framework id from the package name (e.g.
74
+ // @typecad/framework-arduino → arduino) when neither the option nor the
75
+ // target supplied one. KNOWN_TARGETS embedded boards no longer hardcode it.
76
+ const frameworkId = framework ?? frameworkPackage.replace(/^@typecad\/framework-/, '');
53
77
  const projectName = options.projectName || 'my-project';
54
78
  const result = scaffoldProject({
55
79
  projectName,
@@ -59,11 +83,14 @@ async function handleCreate(options) {
59
83
  architecture: target.architecture,
60
84
  boardPackage: target.boardPackage,
61
85
  frameworkPackage,
62
- framework,
86
+ framework: frameworkId,
63
87
  buildTarget: target.buildTarget,
64
88
  mcu: target.mcu,
65
89
  baudRate: target.isNative ? undefined : (options.baud ?? 9600),
66
90
  includeSketch: !options.noSketch,
91
+ ...(target.frameworkData
92
+ ? { frameworkData: target.frameworkData }
93
+ : {}),
67
94
  }, options.outDir);
68
95
  console.log(`\n${chalk.green("✓")} Created project files:`);
69
96
  for (const file of result.createdFiles) {
@@ -109,62 +136,6 @@ async function handleBoardAdd(options) {
109
136
  }
110
137
  console.log(generateFrameworkChecklist(spec));
111
138
  }
112
- /**
113
- * `cuttlefish doctor` — verify arduino-cli is installed and the board's core
114
- * (derived from the FQBN in cuttlefish.config.ts) is present. Exits 0 if the
115
- * environment is OK, non-zero otherwise. Reuses checkArduinoEnv so the
116
- * detection logic is shared with the build/test gates.
117
- */
118
- function runDoctor() {
119
- ui.printHeader();
120
- ui.printStep("Checking arduino-cli environment...");
121
- const config = loadCuttlefishConfig(process.cwd());
122
- const fqbn = config?.buildTarget;
123
- const result = checkArduinoEnv(fqbn);
124
- const check = result.check;
125
- // arduino-cli presence line
126
- if (check.arduinoCliInstalled) {
127
- ui.printInfo(`arduino-cli .... ${check.arduinoCliVersion ?? "unknown"} ✓`);
128
- }
129
- else if (!result.ok && result.reason === "arduino-cli-not-found") {
130
- ui.printError(`arduino-cli .... NOT FOUND on PATH`);
131
- }
132
- else {
133
- ui.printError(`arduino-cli .... found but unresponsive`);
134
- }
135
- // core presence line (only meaningful if we have an FQBN)
136
- if (fqbn) {
137
- if (check.requiredCore) {
138
- const status = check.requiredCoreInstalled ? "installed ✓" : "NOT installed ✗";
139
- const line = `${check.requiredCore} ....... ${status}`;
140
- if (check.requiredCoreInstalled) {
141
- ui.printInfo(line);
142
- }
143
- else {
144
- ui.printError(line);
145
- ui.printInfo(` → run: arduino-cli core install ${check.requiredCore}`);
146
- }
147
- }
148
- }
149
- else {
150
- ui.printInfo("(no buildTarget in cuttlefish.config.ts — skipping core check)");
151
- }
152
- // Exit code
153
- if (result.ok) {
154
- ui.printSuccess("Environment OK");
155
- return; // exitCode stays unset => 0
156
- }
157
- if (!result.ok) {
158
- for (const line of result.messages)
159
- ui.printInfo(line);
160
- process.exitCode = 1;
161
- }
162
- }
163
- /**
164
- * `cuttlefish licenses` dispatch — the presenter lives in licenses.ts so it is
165
- * unit-testable without importing this binary entry module (cli.ts has a
166
- * shebang and runs main() at import time). See runLicensesPresenter.
167
- */
168
139
  async function main() {
169
140
  try {
170
141
  const options = parseCommandLine(process.argv);
@@ -212,12 +183,36 @@ async function main() {
212
183
  }
213
184
  return;
214
185
  }
215
- if (options.command === "doctor") {
216
- runDoctor();
217
- return;
218
- }
219
- if (options.command === "licenses") {
220
- runLicensesPresenter(options.strict ?? false, options.all ?? false);
186
+ if (options.command === "doctor" || options.command === "licenses") {
187
+ // These commands run standalone (often before a build), so the framework
188
+ // isn't loaded yet. Load it from the config's framework field so the
189
+ // framework-supplied doctor/licenses handlers are available.
190
+ if (!hasLoadedFramework()) {
191
+ const config = loadCuttlefishConfig(process.cwd());
192
+ if (config?.framework) {
193
+ try {
194
+ loadFrameworkPackage(config.framework, process.cwd());
195
+ }
196
+ catch {
197
+ // Framework package not resolvable — fall through to the
198
+ // no-support message below rather than crashing the command.
199
+ }
200
+ }
201
+ }
202
+ const fw = hasLoadedFramework() ? getLoadedFramework() : undefined;
203
+ if (options.command === "doctor") {
204
+ if (!fw?.doctor) {
205
+ ui.printInfo("This framework provides no doctor support.");
206
+ return;
207
+ }
208
+ fw.doctor();
209
+ return;
210
+ }
211
+ if (!fw?.licenses) {
212
+ ui.printInfo("This framework provides no licenses support.");
213
+ return;
214
+ }
215
+ fw.licenses(options.strict ?? false, options.all ?? false);
221
216
  return;
222
217
  }
223
218
  // ── Handle build command — entry point comes from config ──────────
@@ -250,6 +245,37 @@ async function main() {
250
245
  // it intentionally has no inputFile (it scans scanDir instead), so the
251
246
  // generic "Missing input file path" check would otherwise block it.
252
247
  if (options.command === "gen-decls") {
248
+ // Check for --components flag (ESP-IDF components: managed + local).
249
+ // Runs before scanDir/single-file branches; components mode has no
250
+ // inputFile by design (it scans managed_components/ + components/).
251
+ const componentsDir = options.componentsDir;
252
+ if (componentsDir) {
253
+ ui.printHeader();
254
+ ui.printStep(`Generating component declarations for ${componentsDir}...`);
255
+ const config = loadCuttlefishConfig(componentsDir);
256
+ const frameworkConfig = (config?.frameworkConfig ?? {});
257
+ const componentsNode = frameworkConfig?.components ?? {};
258
+ const managedSpecs = Object.keys(componentsNode.managed ?? {});
259
+ // idf.py stores managed deps as <namespace>__<name> (slashes → __).
260
+ const managedNames = managedSpecs.map((spec) => spec.replace("/", "__"));
261
+ const localPaths = (componentsNode.local ?? []).map((p) => path.isAbsolute(p) ? p : path.resolve(componentsDir, p));
262
+ const builtinNames = componentsNode.builtin ?? [];
263
+ const created = generateComponentDeclsForProject(componentsDir, {
264
+ managed: managedNames,
265
+ local: localPaths,
266
+ builtin: [],
267
+ idfRoot: undefined,
268
+ });
269
+ if (created.length === 0) {
270
+ ui.printInfo("No component declaration files created.");
271
+ }
272
+ else {
273
+ ui.printSuccess(`Created ${created.length} declaration file(s):`);
274
+ for (const f of created)
275
+ ui.printFileCreated(f);
276
+ }
277
+ return;
278
+ }
253
279
  // Check for --all flag (scan directory)
254
280
  const scanDir = options.scanDir;
255
281
  if (scanDir) {
@@ -321,8 +347,56 @@ async function main() {
321
347
  // ── Load cuttlefish.config.ts (config wins over CLI flags) ──────────
322
348
  const inputDir = path.dirname(path.resolve(options.inputFile));
323
349
  const config = loadCuttlefishConfig(inputDir);
350
+ // ── Contract-based board narrowing ──────────────────────────────────
351
+ // If the config names a TypeCAD contract (*.contract.json from typecad.net),
352
+ // generate the narrowed `.cuttlefish/board.ts` BEFORE env.d.ts is written
353
+ // (it emits `export * from './board.js'` for this case) and before the
354
+ // board package is resolved downstream. The generated board re-exports
355
+ // only the pins/peripherals the PCB actually wires.
356
+ if (config?.contract) {
357
+ await generateContractBoard(config);
358
+ }
359
+ // ── Pre-transpile: generate component .d.ts stubs ───────────────────
360
+ // The transpile type-check needs the .d.ts imports in main.ts to resolve.
361
+ // For ESP-IDF builtin/managed/local components, those .d.ts files are
362
+ // generated by gen-decls — so we must run it BEFORE type-checking, not
363
+ // only as part of --compile. The cache is written under the transpile
364
+ // output dir (the same path --compile uses), so user imports resolve
365
+ // identically in both flows.
366
+ if (config?.frameworkConfig) {
367
+ const fc = config.frameworkConfig;
368
+ const componentsNode = fc?.components ?? {};
369
+ const managedSpecs = Object.keys(componentsNode.managed ?? {});
370
+ const localPaths = (componentsNode.local ?? []).map((p) => path.isAbsolute(p) ? p : path.resolve(inputDir, p));
371
+ const builtinNames = componentsNode.builtin ?? [];
372
+ if (managedSpecs.length > 0 || localPaths.length > 0 || builtinNames.length > 0) {
373
+ // Resolve the transpile output dir — the cache lives under it so the
374
+ // framework's compile path and this pre-transpile pass agree on location.
375
+ // output.outDir in config is relative to the project root (inputDir);
376
+ // if absent, the transpile output goes alongside the entry file.
377
+ const outBase = config.outputOutDir
378
+ ? path.resolve(inputDir, config.outputOutDir)
379
+ : inputDir;
380
+ try {
381
+ generateComponentDeclsForProject(outBase, {
382
+ managed: managedSpecs.map((s) => s.replace('/', '__')),
383
+ local: localPaths,
384
+ builtin: [],
385
+ idfRoot: undefined,
386
+ });
387
+ }
388
+ catch {
389
+ // Non-fatal: if gen-decls fails (e.g. component not yet fetched),
390
+ // the type-checker will surface the missing-import errors with
391
+ // clearer context than crashing here.
392
+ }
393
+ }
394
+ }
324
395
  // Load the UI engine (if @typecad/ui is installed) before any UI work.
325
396
  await loadUIEngine();
397
+ // Load the safety engine (if @typecad/safety is installed) before any
398
+ // safety work.
399
+ await loadSafetyEngine();
326
400
  let effectivePlatformContext = options.platformContext;
327
401
  let effectiveTarget = options.target;
328
402
  let effectiveOutDir = options.outDir;
@@ -343,9 +417,16 @@ async function main() {
343
417
  }
344
418
  let configBuildTarget = config.buildTarget;
345
419
  if (configBuildTarget) {
420
+ // Reconstruct frameworkData from buildTarget so the toolchain sees the
421
+ // full frameworkData, not just buildTarget.
346
422
  effectivePlatformContext = {
347
423
  architecture: configBuildTarget.split(":")?.[1]?.toLowerCase(),
348
- frameworkData: { buildTarget: configBuildTarget },
424
+ frameworkData: {
425
+ buildTarget: configBuildTarget,
426
+ // Thread the PSRAM type through to the framework so it can emit the
427
+ // PSRAM-enabling Kconfig (Zephyr) / define + FQBN option (Arduino).
428
+ ...(config.psram ? { psram: config.psram } : {}),
429
+ },
349
430
  };
350
431
  }
351
432
  if (config.outputOutDir && !options.outDir) {
@@ -424,6 +505,8 @@ async function main() {
424
505
  diagnostics: options.diagnostics,
425
506
  display: displayConfigForTranspile(config),
426
507
  projectRoot: options.projectRoot,
508
+ autosar: options.autosar,
509
+ autosarArxml: options.autosarArxml,
427
510
  });
428
511
  printDiagnostics(result.diagnostics);
429
512
  ui.printTasks(result.asyncTaskNames ?? [], result.usesTimers ?? false);
@@ -435,7 +518,8 @@ async function main() {
435
518
  initialBuildOk = true;
436
519
  // Initial compile + upload if flags are set
437
520
  if (options.compile) {
438
- const buildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
521
+ const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
522
+ const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
439
523
  const watchOpts = {
440
524
  outputDir: path.dirname(result.sourcePath),
441
525
  sourcePath: result.sourcePath,
@@ -444,8 +528,12 @@ async function main() {
444
528
  baud: options.baud ?? config?.console?.baudRate,
445
529
  optimize: config?.outputOptimize,
446
530
  extraFlags: config?.outputExtraFlags,
447
- defines: config?.outputDefines,
531
+ defines: psramDefines,
532
+ psram: config?.psram,
448
533
  frameworkConfig: config?.frameworkConfig,
534
+ zephyrConfig: config?.zephyrConfig,
535
+ display: displayConfigForTranspile(config),
536
+ debug: options.debug,
449
537
  };
450
538
  ui.printCompiling(buildTarget ?? "native");
451
539
  const compileResult = compileSource(watchOpts);
@@ -517,6 +605,8 @@ async function main() {
517
605
  diagnostics: options.diagnostics,
518
606
  display: displayConfigForTranspile(config),
519
607
  projectRoot: options.projectRoot,
608
+ autosar: options.autosar,
609
+ autosarArxml: options.autosarArxml,
520
610
  });
521
611
  printDiagnostics(rebuildResult.diagnostics);
522
612
  ui.printTasks(rebuildResult.asyncTaskNames ?? [], rebuildResult.usesTimers ?? false);
@@ -529,7 +619,8 @@ async function main() {
529
619
  else {
530
620
  ui.printSuccess();
531
621
  if (options.compile) {
532
- const buildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
622
+ const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
623
+ const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
533
624
  const rebuildOpts = {
534
625
  outputDir: path.dirname(rebuildResult.sourcePath),
535
626
  sourcePath: rebuildResult.sourcePath,
@@ -538,8 +629,12 @@ async function main() {
538
629
  baud: options.baud ?? config?.console?.baudRate,
539
630
  optimize: config?.outputOptimize,
540
631
  extraFlags: config?.outputExtraFlags,
541
- defines: config?.outputDefines,
632
+ defines: psramDefines,
633
+ psram: config?.psram,
542
634
  frameworkConfig: config?.frameworkConfig,
635
+ zephyrConfig: config?.zephyrConfig,
636
+ display: displayConfigForTranspile(config),
637
+ debug: options.debug,
543
638
  };
544
639
  ui.printCompiling(buildTarget ?? "native");
545
640
  const compileResult = compileSource(rebuildOpts);
@@ -604,6 +699,8 @@ async function main() {
604
699
  diagnostics: options.diagnostics,
605
700
  display: displayConfigForTranspile(config) ?? options.display,
606
701
  projectRoot: options.projectRoot,
702
+ autosar: options.autosar,
703
+ autosarArxml: options.autosarArxml,
607
704
  });
608
705
  printDiagnostics(result.diagnostics);
609
706
  ui.printTasks(result.asyncTaskNames ?? [], result.usesTimers ?? false);
@@ -630,7 +727,8 @@ async function main() {
630
727
  return;
631
728
  }
632
729
  // --compile (delegates to the active framework's toolchain)
633
- const buildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
730
+ const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
731
+ const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
634
732
  const toolchainOpts = {
635
733
  outputDir: path.dirname(result.sourcePath),
636
734
  sourcePath: result.sourcePath,
@@ -639,8 +737,12 @@ async function main() {
639
737
  baud: options.baud ?? config?.console?.baudRate,
640
738
  optimize: config?.outputOptimize,
641
739
  extraFlags: config?.outputExtraFlags,
642
- defines: config?.outputDefines,
740
+ defines: psramDefines,
741
+ psram: config?.psram,
643
742
  frameworkConfig: config?.frameworkConfig,
743
+ zephyrConfig: config?.zephyrConfig,
744
+ display: displayConfigForTranspile(config),
745
+ debug: options.debug,
644
746
  };
645
747
  ui.printCompiling(buildTarget ?? "native");
646
748
  let compileResult;
@@ -691,8 +793,10 @@ async function main() {
691
793
  return;
692
794
  }
693
795
  if (options.monitor) {
694
- // --monitor (blocks until Ctrl+C)
695
- ui.printMonitoring(port, options.baud);
796
+ // --monitor (blocks until Ctrl+C). Show the resolved baud (config ?? --baud)
797
+ // in the banner — toolchainOpts.baud is built the same way, so what the
798
+ // user sees is what the framework monitor opens the port at.
799
+ ui.printMonitoring(port, toolchainOpts.baud ?? 115200);
696
800
  monitorDevice(toolchainOpts);
697
801
  return;
698
802
  }
@@ -13,7 +13,7 @@ export interface ResolvedCuttlefishConfig {
13
13
  contract?: string;
14
14
  /** Build target identifier (e.g. FQBN for Arduino CLI). */
15
15
  buildTarget?: string;
16
- /** Output framework (e.g. 'arduino', 'platformio'). */
16
+ /** Output framework (e.g. 'arduino'). */
17
17
  outputFramework?: string;
18
18
  /** Optimization level. */
19
19
  outputOptimize?: string;
@@ -21,9 +21,12 @@ export interface ResolvedCuttlefishConfig {
21
21
  outputOutDir?: string;
22
22
  /**
23
23
  * Framework package for code generation strategy.
24
- * Can be '@typecad/framework-arduino', '@typecad/framework-avr', or a custom path.
24
+ * Can be '@typecad/framework-arduino', '@typecad/framework-zephyr',
25
+ * '@typecad/framework-native', or a custom path.
25
26
  */
26
27
  framework?: string;
28
+ /** ESP32 PSRAM type ('opi' | 'quad') when the target board has PSRAM. */
29
+ psram?: 'opi' | 'quad';
27
30
  /** Entry point TypeScript file (relative to config file directory). */
28
31
  entry?: string;
29
32
  /** Path to the config file that was loaded. */
@@ -39,6 +42,8 @@ export interface ResolvedCuttlefishConfig {
39
42
  outputDefines?: Record<string, string>;
40
43
  /** Framework-specific config (e.g. `native` section). */
41
44
  frameworkConfig?: Record<string, unknown>;
45
+ /** Zephyr-specific config (e.g. `zephyr` section). */
46
+ zephyrConfig?: Record<string, unknown>;
42
47
  /** Display profile config. */
43
48
  display?: import("./api/shared/display-profile.js").DisplayConfig;
44
49
  }