@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.10

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 (222) hide show
  1. package/README.md +10 -10
  2. package/dist/api/board-types.d.ts +1 -1
  3. package/dist/api/config.d.ts +32 -0
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/schema/types.d.ts +15 -0
  6. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  7. package/dist/api/shared/async-runtime-static.js +69 -24
  8. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  9. package/dist/api/shared/async-symbol-detector.js +140 -0
  10. package/dist/api/shared/async-types.d.ts +24 -0
  11. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  12. package/dist/api/shared/coop-scheduler.js +149 -0
  13. package/dist/api/shared/display-adapter.d.ts +4 -3
  14. package/dist/api/shared/display-adapter.js +15 -118
  15. package/dist/api/shared/display-adapters/sdl.js +38 -31
  16. package/dist/api/shared/display-profile.d.ts +47 -25
  17. package/dist/api/shared/display-profile.js +31 -164
  18. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  19. package/dist/api/shared/framework-manifest-registry.js +27 -0
  20. package/dist/api/shared/framework-manifest.d.ts +595 -0
  21. package/dist/api/shared/framework-manifest.js +193 -0
  22. package/dist/api/shared/glcdfont.d.ts +12 -0
  23. package/dist/api/shared/glcdfont.js +124 -0
  24. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  25. package/dist/api/shared/hal-op-ir.d.ts +764 -1
  26. package/dist/api/shared/hal-op-ir.js +129 -1
  27. package/dist/api/shared/index.d.ts +20 -2
  28. package/dist/api/shared/index.js +25 -1
  29. package/dist/api/shared/ir-core.d.ts +4 -0
  30. package/dist/api/shared/ir-declarations.d.ts +6 -0
  31. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  32. package/dist/api/shared/native-display-op-resolver.js +64 -0
  33. package/dist/api/shared/platform-strategy.d.ts +119 -2
  34. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  35. package/dist/api/shared/promise-runtime.d.ts +1 -1
  36. package/dist/api/shared/promise-runtime.js +95 -13
  37. package/dist/api/shared/toolchain-types.d.ts +17 -0
  38. package/dist/api/shared/types.d.ts +8 -0
  39. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  40. package/dist/api/shared/validate-framework-manifest.js +494 -0
  41. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  42. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  43. package/dist/api/shared/worker-runtime.d.ts +69 -0
  44. package/dist/api/shared/worker-runtime.js +163 -0
  45. package/dist/cli-utils.d.ts +1 -0
  46. package/dist/cli-utils.js +3 -1
  47. package/dist/cli.js +249 -32
  48. package/dist/config-loader.d.ts +7 -2
  49. package/dist/config-loader.js +23 -3
  50. package/dist/config-schema.d.ts +112 -70
  51. package/dist/config-schema.js +14 -0
  52. package/dist/contract/board-generator.d.ts +17 -0
  53. package/dist/contract/board-generator.js +57 -0
  54. package/dist/contract/contract-parser.d.ts +217 -0
  55. package/dist/contract/contract-parser.js +224 -0
  56. package/dist/contract/index.d.ts +22 -0
  57. package/dist/contract/index.js +68 -0
  58. package/dist/create/board-codegen.js +4 -4
  59. package/dist/create/board-generators.js +4 -5
  60. package/dist/create/board-spec.d.ts +72 -75
  61. package/dist/create/board-spec.js +0 -1
  62. package/dist/create/framework-catalog.d.ts +64 -0
  63. package/dist/create/framework-catalog.js +136 -0
  64. package/dist/create/index.d.ts +5 -1
  65. package/dist/create/index.js +3 -1
  66. package/dist/create/init-scaffold.d.ts +12 -3
  67. package/dist/create/init-scaffold.js +49 -14
  68. package/dist/create/init-templates.d.ts +4 -0
  69. package/dist/create/init-templates.js +263 -17
  70. package/dist/create/init-wizard.js +41 -27
  71. package/dist/create/install-deps.d.ts +32 -0
  72. package/dist/create/install-deps.js +46 -0
  73. package/dist/debug/preprocessor.js +178 -39
  74. package/dist/debug/types.d.ts +23 -0
  75. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  76. package/dist/diagnostics/mermaid-builder.js +34 -24
  77. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  78. package/dist/emit/compliance/arxml-writer.js +34 -0
  79. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  80. package/dist/emit/compliance/compliance-context.js +113 -0
  81. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  82. package/dist/emit/compliance/deviation-ledger.js +47 -0
  83. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  84. package/dist/emit/compliance/deviation-writer.js +37 -0
  85. package/dist/emit/compliance/index.d.ts +7 -0
  86. package/dist/emit/compliance/index.js +6 -0
  87. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  88. package/dist/emit/compliance/rule-engine.js +101 -0
  89. package/dist/emit/compliance/rules.d.ts +16 -0
  90. package/dist/emit/compliance/rules.js +191 -0
  91. package/dist/emit/compliance/types.d.ts +59 -0
  92. package/dist/emit/compliance/types.js +8 -0
  93. package/dist/emit/cpp-emitter.js +4 -3
  94. package/dist/emit/emitters/class-emitter.js +6 -1
  95. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  96. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  97. package/dist/emit/emitters/line-appender.js +19 -0
  98. package/dist/emit/emitters/line-marker.d.ts +38 -0
  99. package/dist/emit/emitters/line-marker.js +39 -0
  100. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  101. package/dist/emit/emitters/output-finalizer.js +101 -12
  102. package/dist/emit/emitters/setup.d.ts +17 -0
  103. package/dist/emit/emitters/setup.js +372 -30
  104. package/dist/emit/emitters/top-level-prep.js +8 -0
  105. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  106. package/dist/emit/emitters/ui-emitter.js +33 -9
  107. package/dist/emit/expression-renderer.d.ts +1 -1
  108. package/dist/emit/expression-renderer.js +91 -6
  109. package/dist/emit/route-hal-op.js +18 -5
  110. package/dist/emit/snprintf-helpers.js +15 -4
  111. package/dist/emit/statement-renderer.d.ts +10 -0
  112. package/dist/emit/statement-renderer.js +34 -5
  113. package/dist/emit/utils/async-state-machine.js +221 -125
  114. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  115. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  116. package/dist/framework-package.js +2 -0
  117. package/dist/framework-registry.d.ts +17 -0
  118. package/dist/install/framework-catalog.d.ts +53 -0
  119. package/dist/install/framework-catalog.js +107 -0
  120. package/dist/install/handle-install.d.ts +35 -0
  121. package/dist/install/handle-install.js +177 -0
  122. package/dist/install/index.d.ts +4 -0
  123. package/dist/install/index.js +3 -0
  124. package/dist/ir/adc-range-validation.js +40 -26
  125. package/dist/ir/build-ir-state.d.ts +1 -0
  126. package/dist/ir/build-ir-state.js +14 -0
  127. package/dist/ir/build-ir.js +9 -5
  128. package/dist/ir/call-graph.js +16 -0
  129. package/dist/ir/expression-to-ir.js +207 -6
  130. package/dist/ir/feature-registry.js +7 -25
  131. package/dist/ir/function-builder.js +22 -0
  132. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  133. package/dist/ir/hal/hal-emitter.js +50 -18
  134. package/dist/ir/hal/hal-parser.d.ts +6 -0
  135. package/dist/ir/hal/hal-parser.js +81 -0
  136. package/dist/ir/hal/hal-plugins.js +733 -1
  137. package/dist/ir/identifier-collector.js +35 -0
  138. package/dist/ir/interrupt-analysis.d.ts +5 -1
  139. package/dist/ir/interrupt-analysis.js +16 -19
  140. package/dist/ir/memory-budget-validation.js +1 -0
  141. package/dist/ir/network-validation.d.ts +4 -0
  142. package/dist/ir/network-validation.js +184 -0
  143. package/dist/ir/ownership-analysis.js +39 -1
  144. package/dist/ir/peripheral-ownership.js +5 -0
  145. package/dist/ir/peripheral-validation.d.ts +1 -1
  146. package/dist/ir/peripheral-validation.js +6 -3
  147. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  148. package/dist/ir/pin-alias-conflict.js +2 -1
  149. package/dist/ir/pin-capability-validation.js +71 -32
  150. package/dist/ir/pin-mode-validation.d.ts +2 -2
  151. package/dist/ir/pin-mode-validation.js +54 -18
  152. package/dist/ir/pin-safety.d.ts +1 -1
  153. package/dist/ir/pin-safety.js +2 -1
  154. package/dist/ir/program-analysis.d.ts +81 -0
  155. package/dist/ir/program-analysis.js +429 -0
  156. package/dist/ir/pulldown-validation.d.ts +1 -1
  157. package/dist/ir/pulldown-validation.js +2 -1
  158. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  159. package/dist/ir/pwm-timer-sharing.js +2 -1
  160. package/dist/ir/render-expr.js +11 -0
  161. package/dist/ir/resource-analysis.js +2 -0
  162. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  163. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  164. package/dist/ir/timing-validation.d.ts +6 -1
  165. package/dist/ir/timing-validation.js +61 -13
  166. package/dist/ir/transformers/call-statement.js +105 -0
  167. package/dist/ir/transformers/expressions.js +62 -0
  168. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  169. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  170. package/dist/ir/transformers/namespace-methods.js +17 -12
  171. package/dist/ir/transformers/ui-reactive.js +2 -2
  172. package/dist/ir/transformers/variables.js +137 -19
  173. package/dist/ir/try-catch-validation.js +2 -0
  174. package/dist/ir/type-resolution.js +20 -2
  175. package/dist/ir/ui-element-auto-wire.js +7 -5
  176. package/dist/ir/unit-suspicion-validation.js +9 -7
  177. package/dist/ir/utils/map-statements.d.ts +4 -0
  178. package/dist/ir/utils/map-statements.js +79 -0
  179. package/dist/ir/validation-orchestrator.js +15 -8
  180. package/dist/ir/worker-analysis.d.ts +10 -0
  181. package/dist/ir/worker-analysis.js +261 -0
  182. package/dist/libdef/c-to-decl.d.ts +27 -0
  183. package/dist/libdef/c-to-decl.js +397 -0
  184. package/dist/libdef/component-decls.d.ts +2 -0
  185. package/dist/libdef/component-decls.js +6 -0
  186. package/dist/libdef/component-discovery.d.ts +43 -0
  187. package/dist/libdef/component-discovery.js +83 -0
  188. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  189. package/dist/libdef/cpp-to-decl.js +72 -0
  190. package/dist/libdef/registry.js +5 -2
  191. package/dist/lint-cache.d.ts +59 -0
  192. package/dist/lint-cache.js +257 -0
  193. package/dist/orchestrator/graph-builder.js +14 -9
  194. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  195. package/dist/platform/coop-scheduler-runtime.js +41 -0
  196. package/dist/platform/generic-strategy.d.ts +15 -3
  197. package/dist/platform/generic-strategy.js +49 -4
  198. package/dist/safety/safety-bridge.d.ts +11 -0
  199. package/dist/safety/safety-bridge.js +48 -0
  200. package/dist/safety/sidecar-bridge.d.ts +5 -0
  201. package/dist/safety/sidecar-bridge.js +22 -0
  202. package/dist/safety-hook.d.ts +79 -0
  203. package/dist/safety-hook.js +35 -0
  204. package/dist/testing.d.ts +8 -2
  205. package/dist/testing.js +9 -2
  206. package/dist/transpile.d.ts +3 -0
  207. package/dist/transpile.js +191 -49
  208. package/dist/types.d.ts +22 -3
  209. package/dist/ui-hook.d.ts +17 -3
  210. package/dist/utils/cli.js +122 -2
  211. package/dist/utils/fs.d.ts +13 -0
  212. package/dist/utils/fs.js +50 -0
  213. package/dist/utils/ui.js +3 -1
  214. package/package.json +23 -3
  215. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  216. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  217. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  218. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  219. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  220. package/dist/api/shared/display-adapters/st7796.js +0 -132
  221. package/dist/ir/heap-array-validation.d.ts +0 -24
  222. package/dist/ir/heap-array-validation.js +0 -29
@@ -0,0 +1,397 @@
1
+ /**
2
+ * C-header to TypeScript declaration generator.
3
+ *
4
+ * Complements cpp-to-decl.ts (which is class-focused). ESP-IDF components
5
+ * are mostly C: free functions, opaque handles, typedef'd enums and structs.
6
+ *
7
+ * EMISSION POLICY — names match the C header 1-to-1.
8
+ *
9
+ * esp_err_t esp_wifi_init(const wifi_config_t *config);
10
+ *
11
+ * becomes
12
+ *
13
+ * export declare function esp_wifi_init(config: number): esp_err_t;
14
+ *
15
+ * not `esp_wifi.init(...)`. ESP-IDF examples call `esp_wifi_init`, never
16
+ * `esp_wifi.init`; the dotted form has no C++ representation (there is no
17
+ * `esp_wifi` object or namespace in the real header) and would not link.
18
+ * Mirroring the C names verbatim means the transpiler lowers TS calls
19
+ * directly to valid C with zero translation.
20
+ *
21
+ * Spec: docs/superpowers/specs/2026-07-19-demo-wifi-design.md
22
+ */
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+ import { stripPreprocessorBlocks } from './header-parser.js';
26
+ const BUILTIN_TS_TYPES = new Set(['number', 'string', 'boolean', 'void', 'any']);
27
+ /**
28
+ * Map a C type string to a TS type string. Pointer types become `number`
29
+ * (handles/opaque addresses) since the transpiler treats C pointers as
30
+ * numbers anyway. Unknown named types are returned as-is so they resolve
31
+ * to a typedef alias emitted by this same header (or fall back to `number`
32
+ * via the alias-emission pass in generateCDecl).
33
+ */
34
+ function mapCTypeToTs(cType) {
35
+ let t = cType.trim();
36
+ // Strip leading `const`/`volatile` qualifiers — they don't affect the TS shape.
37
+ t = t.replace(/^(?:const|volatile)\s+/, '');
38
+ const typeMap = {
39
+ int: 'number',
40
+ 'unsigned int': 'number',
41
+ 'unsigned': 'number',
42
+ 'short': 'number',
43
+ 'unsigned short': 'number',
44
+ 'long': 'number',
45
+ 'unsigned long': 'number',
46
+ 'long long': 'number',
47
+ 'unsigned long long': 'number',
48
+ uint8_t: 'number',
49
+ uint16_t: 'number',
50
+ uint32_t: 'number',
51
+ uint64_t: 'number',
52
+ int8_t: 'number',
53
+ int16_t: 'number',
54
+ int32_t: 'number',
55
+ int64_t: 'number',
56
+ size_t: 'number',
57
+ ssize_t: 'number',
58
+ intptr_t: 'number',
59
+ uintptr_t: 'number',
60
+ float: 'number',
61
+ double: 'number',
62
+ bool: 'boolean',
63
+ _Bool: 'boolean',
64
+ void: 'void',
65
+ char: 'number',
66
+ };
67
+ if (typeMap[t])
68
+ return typeMap[t];
69
+ // Strip a trailing pointer; pointers are number-typed handles/addresses.
70
+ if (t.endsWith('*'))
71
+ return 'number';
72
+ return t;
73
+ }
74
+ /** Parse function signatures like `esp_err_t foo(int x, const char *y);`. */
75
+ function parseFunctions(stripped) {
76
+ const fns = [];
77
+ const fnRegex = /^([\w\s\*]+?)\s+(\w+)\s*\(([^;]*)\)\s*;/gm;
78
+ let m;
79
+ while ((m = fnRegex.exec(stripped)) !== null) {
80
+ const returnType = m[1].trim();
81
+ const name = m[2];
82
+ const paramsRaw = m[3].trim();
83
+ // Skip non-function declarations these regexes might catch.
84
+ if (returnType === 'typedef' || returnType === 'struct' || returnType === 'enum')
85
+ continue;
86
+ if (returnType === 'static' || returnType === 'extern' || returnType === 'inline')
87
+ continue;
88
+ if (name === 'if' || name === 'for' || name === 'while' || name === 'return' || name === 'switch')
89
+ continue;
90
+ fns.push({ returnType, name, params: parseParams(paramsRaw) });
91
+ }
92
+ return fns;
93
+ }
94
+ function parseParams(raw) {
95
+ if (!raw || raw === 'void')
96
+ return [];
97
+ return raw.split(',').map((part, idx) => {
98
+ let trimmed = part.trim();
99
+ trimmed = trimmed.split('=')[0].trim();
100
+ if (!trimmed)
101
+ return { type: '', name: `_arg${idx}` };
102
+ // C array declarator on a parameter: `<type> <name>[<size>]` or `<type> <name>[]`.
103
+ // Normalize: the array decays to a pointer in C anyway, so we record the
104
+ // base type and the original name. e.g. `uint8_t mac[6]` → type="uint8_t",
105
+ // name="mac"; `uint8_t mac[]` → same.
106
+ const arrParam = trimmed.match(/^(.*?)\b(\w+)\s*\[[^\]]*\]\s*$/);
107
+ if (arrParam) {
108
+ const type = arrParam[1].trim();
109
+ // If the type is empty (e.g. bare `mac[6]` with no preceding type),
110
+ // fall through to the default parser rather than emitting a bogus type.
111
+ if (type)
112
+ return { type, name: arrParam[2] };
113
+ }
114
+ // A parameter is `<type> <name>` where name is the trailing identifier
115
+ // and type is everything before. For function-pointer params (rare in
116
+ // user-facing IDF APIs) we fall back to treating the whole thing as a
117
+ // type with a synthesized name.
118
+ if (trimmed.includes('(*)')) {
119
+ return { type: trimmed, name: `_arg${idx}` };
120
+ }
121
+ const m = trimmed.match(/^(.*?)(\b\w+)$/);
122
+ if (!m)
123
+ return { type: trimmed, name: `_arg${idx}` };
124
+ let type = m[1].trim();
125
+ const name = m[2];
126
+ // If the regex left the * glued to the name, pull it back to the type.
127
+ if (!type && trimmed.includes('*')) {
128
+ type = '*';
129
+ }
130
+ return { type: type || trimmed, name };
131
+ });
132
+ }
133
+ /** Parse typedef'd enums: `typedef enum { A, B=2, C } foo_t;`. */
134
+ function parseEnumTypedefs(stripped) {
135
+ const tds = [];
136
+ const re = /typedef\s+enum\s*(?:\w+\s*)?\{([^}]*)\}\s*(\w+)\s*;/g;
137
+ let m;
138
+ while ((m = re.exec(stripped)) !== null) {
139
+ const body = m[1];
140
+ const name = m[2];
141
+ const members = body.split(',').map((s) => s.trim()).filter(Boolean);
142
+ let next = 0;
143
+ const values = members.map((mem) => {
144
+ const eq = mem.indexOf('=');
145
+ if (eq >= 0) {
146
+ const val = parseInt(mem.slice(eq + 1).trim(), 10);
147
+ if (!Number.isNaN(val))
148
+ next = val + 1;
149
+ return { name: mem.slice(0, eq).trim(), value: Number.isNaN(val) ? undefined : val };
150
+ }
151
+ const v = next++;
152
+ return { name: mem, value: v };
153
+ });
154
+ tds.push({ kind: 'enum', name, values });
155
+ }
156
+ return tds;
157
+ }
158
+ /** Parse typedef'd structs: `typedef struct { int x; } foo_t;` or
159
+ * `typedef struct foo { int x; } foo_t;`. Handles array fields
160
+ * (`int arr[6]`) by recording the array-ness on the field. */
161
+ function parseStructTypedefs(stripped) {
162
+ const tds = [];
163
+ const re = /typedef\s+struct\s*(?:\w+\s*)?\{([^}]*)\}\s*(\w+)\s*;/g;
164
+ let m;
165
+ while ((m = re.exec(stripped)) !== null) {
166
+ const body = m[1];
167
+ const name = m[2];
168
+ const fields = body
169
+ .split(';')
170
+ .map((s) => s.trim())
171
+ .filter(Boolean)
172
+ .map((field, idx) => {
173
+ // Array field: `<type> <name>[<size>]` → name + isArray.
174
+ const arr = field.match(/^(.*?)\b(\w+)\s*\[[^\]]*\]\s*$/);
175
+ if (arr) {
176
+ return { type: arr[1].trim() || field, name: arr[2], isArray: true };
177
+ }
178
+ const pm = field.match(/^(.*?)(\b\w+)$/);
179
+ if (!pm)
180
+ return { type: field, name: `_f${idx}`, isArray: false };
181
+ return { type: pm[1].trim() || field, name: pm[2], isArray: false };
182
+ });
183
+ tds.push({ kind: 'struct', name, fields });
184
+ }
185
+ return tds;
186
+ }
187
+ /** Parse opaque handle typedefs: `typedef struct foo *foo_handle_t;`. */
188
+ function parseOpaqueTypedefs(stripped) {
189
+ const tds = [];
190
+ const re = /typedef\s+struct\s+\w+\s*\*\s*(\w+)\s*;/g;
191
+ let m;
192
+ while ((m = re.exec(stripped)) !== null) {
193
+ tds.push({ kind: 'opaque', name: m[1] });
194
+ }
195
+ return tds;
196
+ }
197
+ /** Parse function-pointer typedefs: `typedef void (*handler_t)(void *arg);`.
198
+ * Emits as `any` — TS has no faithful representation of a C function pointer,
199
+ * and IDF user code that registers one needs `rawCpp()` anyway. */
200
+ function parseFuncPtrTypedefs(stripped) {
201
+ const tds = [];
202
+ const re = /typedef\s+[\w\s\*]+?\(\s*\*\s*(\w+)\s*\)\s*\([^;]*\)\s*;/g;
203
+ let m;
204
+ while ((m = re.exec(stripped)) !== null) {
205
+ tds.push({ kind: 'funcptr', name: m[1] });
206
+ }
207
+ return tds;
208
+ }
209
+ /** Parse plain alias typedefs: `typedef int esp_err_t;`, `typedef uint32_t foo_t;`.
210
+ * These are extremely common in ESP-IDF (esp_err_t, TickType_t, etc.). */
211
+ function parseAliasTypedefs(stripped) {
212
+ const tds = [];
213
+ // `typedef <type> <name>;` where <type> is a single token (possibly with
214
+ // qualifiers) and <name> is the alias. Struct/enum/funcptr typedefs are
215
+ // handled by their own parsers, so exclude those keywords here.
216
+ const re = /typedef\s+(?!struct\b)(?!enum\b)(?!union\b)([\w\s\*]+?)\s+(\w+)\s*;/g;
217
+ let m;
218
+ while ((m = re.exec(stripped)) !== null) {
219
+ const aliasedType = m[1].trim();
220
+ const name = m[2];
221
+ // Skip if this is actually the tail of a struct/enum/funcptr typedef that
222
+ // a sibling parser already consumed (defensive — the negative lookahead
223
+ // should already exclude those, but the regex can still match fragments
224
+ // inside a `typedef struct { ... } foo_t;` body in edge cases).
225
+ if (aliasedType === '' || aliasedType.includes('{'))
226
+ continue;
227
+ // Skip array typedefs: `typedef uint8_t mac[6];` is NOT a plain alias.
228
+ // It would parse as aliasedType="uint8_t" name="mac" with the regex, but
229
+ // the trailing `[6]` between name and `;` makes it an array typedef — the
230
+ // regex's `\s*;` lookahead fails to match `[6];`, so this branch only
231
+ // fires for true plain aliases. Defensive: bail if the captured aliasedType
232
+ // somehow contains a `[` (would indicate a parse fragment).
233
+ if (aliasedType.includes('['))
234
+ continue;
235
+ tds.push({ kind: 'alias', name, aliasedType });
236
+ }
237
+ return tds;
238
+ }
239
+ /** Parse `#define FOO 42` and `#define BAR "str"` (integers and strings only). */
240
+ function parseSimpleDefines(content) {
241
+ const out = [];
242
+ const re = /^\s*#define\s+(\w+)\s+(.+?)\s*$/gm;
243
+ let m;
244
+ while ((m = re.exec(content)) !== null) {
245
+ const name = m[1];
246
+ const value = m[2].trim();
247
+ if (/^-?\d+$/.test(value) || /^"[^"]*"$/.test(value)) {
248
+ out.push({ name, value });
249
+ }
250
+ }
251
+ return out;
252
+ }
253
+ // Strip C-style comments so they don't interfere with parsing.
254
+ // Block comments and line comments are both removed; preprocessor
255
+ // directives are preserved (stripPreprocessorBlocks handles those next).
256
+ function stripComments(content) {
257
+ return content.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
258
+ }
259
+ function parseHeader(content) {
260
+ const noComments = stripComments(content);
261
+ const stripped = stripPreprocessorBlocks(noComments);
262
+ return {
263
+ functions: parseFunctions(stripped),
264
+ typedefs: [
265
+ ...parseEnumTypedefs(stripped),
266
+ ...parseStructTypedefs(stripped),
267
+ ...parseOpaqueTypedefs(stripped),
268
+ ...parseFuncPtrTypedefs(stripped),
269
+ ...parseAliasTypedefs(stripped),
270
+ ],
271
+ defines: parseSimpleDefines(noComments),
272
+ };
273
+ }
274
+ function emitEnumTypedef(td) {
275
+ const literalUnion = td.values.every((v) => typeof v.value === 'number')
276
+ ? td.values.map((v) => v.value).join(' | ')
277
+ : 'number';
278
+ const lines = [`export type ${td.name} = ${literalUnion};`];
279
+ // Export named constants so user code can reference WIFI_MODE_STA directly,
280
+ // exactly as ESP-IDF examples do.
281
+ for (const v of td.values) {
282
+ lines.push(`export const ${v.name}: ${td.name} = ${v.value ?? 0};`);
283
+ }
284
+ return lines;
285
+ }
286
+ function emitStructTypedef(td) {
287
+ const fields = td.fields.map((f) => {
288
+ const tsType = mapCTypeToTs(f.type);
289
+ return ` ${f.name}: ${f.isArray ? `${tsType}[]` : tsType};`;
290
+ });
291
+ return [`export interface ${td.name} {`, ...fields, `}`];
292
+ }
293
+ function emitTypedef(td) {
294
+ switch (td.kind) {
295
+ case 'enum':
296
+ return emitEnumTypedef(td);
297
+ case 'struct':
298
+ return emitStructTypedef(td);
299
+ case 'opaque':
300
+ return [`export type ${td.name} = number;`];
301
+ case 'alias':
302
+ return [`export type ${td.name} = ${mapCTypeToTs(td.aliasedType)};`];
303
+ case 'funcptr':
304
+ // Function-pointer typedefs have no faithful TS representation. Emit
305
+ // `any` with a comment so users know to use `rawCpp()` for callbacks.
306
+ return [
307
+ `// ${td.name} is a C function-pointer typedef; TS has no faithful representation.`,
308
+ `export type ${td.name} = any;`,
309
+ ];
310
+ }
311
+ }
312
+ /** Emit a free function declaration, name matching the C header 1-to-1. */
313
+ function emitFunction(fn) {
314
+ const params = fn.params.map((p, idx) => {
315
+ const name = p.name || `_arg${idx}`;
316
+ const tsType = p.type === '' ? 'any' : mapCTypeToTs(p.type);
317
+ return `${name}: ${tsType}`;
318
+ });
319
+ const returnType = mapCTypeToTs(fn.returnType);
320
+ return `export declare function ${fn.name}(${params.join(', ')}): ${returnType};`;
321
+ }
322
+ /**
323
+ * Top-level entry: read a header file, write `<header>.d.ts` alongside it.
324
+ * Returns the output path, or null if no declarations could be extracted.
325
+ */
326
+ export function generateCDecl(filePath, outputPath) {
327
+ if (!fs.existsSync(filePath))
328
+ return null;
329
+ const content = fs.readFileSync(filePath, 'utf8');
330
+ const header = parseHeader(content);
331
+ // Collect every type name the header defines so the alias-emission pass
332
+ // below doesn't emit spurious `export type X = number;` for types that
333
+ // are already declared.
334
+ const knownTypes = new Set([
335
+ ...header.typedefs.map((t) => t.name),
336
+ ...BUILTIN_TS_TYPES,
337
+ ]);
338
+ // Find referenced types that aren't declared anywhere in this header and
339
+ // aren't builtins — emit them as `export type X = number;` so the .d.ts
340
+ // compiles standalone. (Common case: a header references `esp_err_t` from
341
+ // another header without re-typedef'ing it.)
342
+ const referencedTypes = new Set();
343
+ for (const f of header.functions) {
344
+ referencedTypes.add(mapCTypeToTs(f.returnType));
345
+ for (const p of f.params)
346
+ referencedTypes.add(mapCTypeToTs(p.type));
347
+ }
348
+ for (const td of header.typedefs) {
349
+ if (td.kind === 'struct') {
350
+ for (const f of td.fields)
351
+ referencedTypes.add(mapCTypeToTs(f.type));
352
+ }
353
+ else if (td.kind === 'alias') {
354
+ referencedTypes.add(mapCTypeToTs(td.aliasedType));
355
+ }
356
+ }
357
+ const aliasesToEmit = [...referencedTypes].filter((t) => !knownTypes.has(t) && !t.includes('|') && !BUILTIN_TS_TYPES.has(t));
358
+ // Nothing to emit → tell the caller.
359
+ if (header.functions.length === 0 &&
360
+ header.typedefs.length === 0 &&
361
+ header.defines.length === 0 &&
362
+ aliasesToEmit.length === 0) {
363
+ return null;
364
+ }
365
+ const lines = [
366
+ '// Auto-generated by cuttlefish gen-decls. Do not edit.',
367
+ '// Source: ' + path.basename(filePath),
368
+ '// C names are preserved verbatim; calls lower 1-to-1 to the C header.',
369
+ '',
370
+ ];
371
+ // Standalone type aliases for cross-header references (e.g. esp_err_t).
372
+ for (const a of aliasesToEmit) {
373
+ lines.push(`export type ${a} = number;`);
374
+ }
375
+ if (aliasesToEmit.length > 0)
376
+ lines.push('');
377
+ // Typedefs (enums, structs, opaque handles, aliases, function pointers).
378
+ for (const td of header.typedefs) {
379
+ lines.push(...emitTypedef(td), '');
380
+ }
381
+ // #define constants (integer/string literals only).
382
+ for (const d of header.defines) {
383
+ lines.push(`export const ${d.name}: ${/^"/.test(d.value) ? 'string' : 'number'} = ${d.value};`);
384
+ }
385
+ if (header.defines.length > 0)
386
+ lines.push('');
387
+ // Free functions, named 1-to-1 with the C header.
388
+ for (const fn of header.functions) {
389
+ lines.push(emitFunction(fn));
390
+ }
391
+ const outPath = outputPath ?? filePath.replace(/\.h$/i, '.d.ts');
392
+ const outContent = lines.join('\n') + '\n';
393
+ if (!fs.existsSync(outPath) || fs.readFileSync(outPath, 'utf8') !== outContent) {
394
+ fs.writeFileSync(outPath, outContent, 'utf8');
395
+ }
396
+ return outPath;
397
+ }
@@ -0,0 +1,2 @@
1
+ export { generateComponentDeclsForProject } from "./cpp-to-decl.js";
2
+ export type { ComponentScanRoots } from "./component-discovery.js";
@@ -0,0 +1,6 @@
1
+ // Public barrel for component declaration generation.
2
+ //
3
+ // framework-esp32 imports `generateComponentDeclsForProject` from here so its
4
+ // dependency on cuttlefish is declared through the package exports map rather
5
+ // than a deep relative path. Spec: 2026-07-19-framework-esp32-components-design.md
6
+ export { generateComponentDeclsForProject } from "./cpp-to-decl.js";
@@ -0,0 +1,43 @@
1
+ export interface ComponentScanRoots {
2
+ /** Subdirectory names under managed_components/ (e.g. espressif__esp_wifi). */
3
+ managed: string[];
4
+ /** Absolute paths to local component directories. */
5
+ local: string[];
6
+ /**
7
+ * ESP-IDF built-in component names (e.g. esp_wifi). Resolved against
8
+ * `idfRoot/components/<name>/include/` when idfRoot is provided; ignored
9
+ * otherwise (the caller is responsible for passing idfRoot when builtins
10
+ * are present).
11
+ */
12
+ builtin: string[];
13
+ /** Optional ESP-IDF install root for resolving `builtin` names. */
14
+ idfRoot?: string;
15
+ }
16
+ /**
17
+ * A discovered header plus the directory its generated .d.ts should land in.
18
+ *
19
+ * `outputDir` is alongside the header for managed/local components (so the
20
+ * .d.ts lives in the same gitignored component dir, regenerated on each run).
21
+ * For built-in components it's a project-local cache — NEVER write into the
22
+ * IDF install itself.
23
+ */
24
+ export interface DiscoveredHeader {
25
+ /** Absolute path to the source .h file. */
26
+ path: string;
27
+ /** Absolute directory where the generated .d.ts should be written. */
28
+ outputDir: string;
29
+ }
30
+ /**
31
+ * Find all .h files for the declared components.
32
+ *
33
+ * - managed: scan `managed_components/<name>/include/` (falling back to the
34
+ * component dir if no include/ exists). Populated by `idf.py reconfigure`.
35
+ * - local: scan `<path>/include/` (or `<path>/`).
36
+ * - builtin: scan `<idfRoot>/components/<name>/include/` (IDF ships with these).
37
+ *
38
+ * For builtins the output .d.ts goes to `<projectDir>/.cuttlefish/component-decls/<component>/`
39
+ * — never into `$IDF_PATH/components/`, which would pollute the IDF install.
40
+ *
41
+ * Returned entries are unsorted — the caller decides ordering.
42
+ */
43
+ export declare function discoverComponentHeaders(projectDir: string, roots: ComponentScanRoots): DiscoveredHeader[];
@@ -0,0 +1,83 @@
1
+ import { existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ /** Walk a directory recursively, returning all .h files. */
4
+ function walkHeaders(dir) {
5
+ if (!existsSync(dir))
6
+ return [];
7
+ const out = [];
8
+ const stack = [dir];
9
+ while (stack.length > 0) {
10
+ const cur = stack.pop();
11
+ let entries;
12
+ try {
13
+ entries = readdirSync(cur);
14
+ }
15
+ catch {
16
+ continue;
17
+ }
18
+ for (const name of entries) {
19
+ const full = join(cur, name);
20
+ try {
21
+ const st = statSync(full);
22
+ if (st.isDirectory()) {
23
+ stack.push(full);
24
+ }
25
+ else if (name.toLowerCase().endsWith('.h')) {
26
+ out.push(full);
27
+ }
28
+ }
29
+ catch {
30
+ // Stat failed (race); skip.
31
+ }
32
+ }
33
+ }
34
+ return out;
35
+ }
36
+ /**
37
+ * Find all .h files for the declared components.
38
+ *
39
+ * - managed: scan `managed_components/<name>/include/` (falling back to the
40
+ * component dir if no include/ exists). Populated by `idf.py reconfigure`.
41
+ * - local: scan `<path>/include/` (or `<path>/`).
42
+ * - builtin: scan `<idfRoot>/components/<name>/include/` (IDF ships with these).
43
+ *
44
+ * For builtins the output .d.ts goes to `<projectDir>/.cuttlefish/component-decls/<component>/`
45
+ * — never into `$IDF_PATH/components/`, which would pollute the IDF install.
46
+ *
47
+ * Returned entries are unsorted — the caller decides ordering.
48
+ */
49
+ export function discoverComponentHeaders(projectDir, roots) {
50
+ const headers = [];
51
+ for (const name of roots.managed) {
52
+ const base = join(projectDir, 'managed_components', name);
53
+ const includeDir = join(base, 'include');
54
+ const dir = existsSync(includeDir) ? includeDir : base;
55
+ for (const h of walkHeaders(dir)) {
56
+ headers.push({ path: h, outputDir: dir });
57
+ }
58
+ }
59
+ for (const localPath of roots.local) {
60
+ const includeDir = join(localPath, 'include');
61
+ const dir = existsSync(includeDir) ? includeDir : localPath;
62
+ for (const h of walkHeaders(dir)) {
63
+ headers.push({ path: h, outputDir: dir });
64
+ }
65
+ }
66
+ if (roots.idfRoot) {
67
+ for (const name of roots.builtin) {
68
+ const base = join(roots.idfRoot, 'components', name);
69
+ const includeDir = join(base, 'include');
70
+ const dir = existsSync(includeDir) ? includeDir : base;
71
+ // Project-local cache for builtins. Mirrors the include/ layout so
72
+ // imports can substitute `managed_components/<name>/include/X.d.ts`
73
+ // patterns. Each header's .d.ts is named after the header basename.
74
+ const outDir = join(projectDir, '.cuttlefish', 'component-decls', name);
75
+ if (!existsSync(outDir))
76
+ mkdirSync(outDir, { recursive: true });
77
+ for (const h of walkHeaders(dir)) {
78
+ headers.push({ path: h, outputDir: outDir });
79
+ }
80
+ }
81
+ }
82
+ return headers;
83
+ }
@@ -32,3 +32,12 @@ export declare function generateDeclsForDirectory(dir: string, recursive?: boole
32
32
  * a later step for the directory-scan path.
33
33
  */
34
34
  export declare function generateDecl(filePath: string, outputPath?: string): string | null;
35
+ import { type ComponentScanRoots } from "./component-discovery.js";
36
+ /**
37
+ * Generate .d.ts declarations for every declared component's headers,
38
+ * picking the C or C++ emitter per header based on whether it declares classes.
39
+ *
40
+ * Returns the list of created/updated .d.ts paths (headers that produced no
41
+ * declarations are skipped).
42
+ */
43
+ export declare function generateComponentDeclsForProject(projectDir: string, roots: ComponentScanRoots): string[];
@@ -8,6 +8,7 @@ import fs from "node:fs";
8
8
  import path from "node:path";
9
9
  import { parseHeader } from "./header-parser.js";
10
10
  import { buildClassIndex, BaseClassResolver } from "./base-class-resolver.js";
11
+ import { writeText } from "../utils/fs.js";
11
12
  /**
12
13
  * Maps C++ types to TypeScript types
13
14
  */
@@ -582,3 +583,74 @@ function generateDeclWithResolver(filePath, resolver) {
582
583
  }
583
584
  return outPath;
584
585
  }
586
+ // ─── ESP-IDF component dispatch ───────────────────────────────────────────
587
+ //
588
+ // Picks the C or C++ emitter per header based on whether it declares classes.
589
+ // ESP-IDF components are mostly C (free functions + typedefs); Arduino-style
590
+ // libraries are C++ classes. Spec:
591
+ // docs/superpowers/specs/2026-07-19-framework-esp32-components-design.md
592
+ import { generateCDecl } from "./c-to-decl.js";
593
+ import { discoverComponentHeaders } from "./component-discovery.js";
594
+ /** Heuristic: does this header declare any C++ classes? */
595
+ function hasClasses(headerPath) {
596
+ try {
597
+ const content = fs.readFileSync(headerPath, "utf8");
598
+ return parseHeader(content).length > 0;
599
+ }
600
+ catch {
601
+ return false;
602
+ }
603
+ }
604
+ /**
605
+ * Generate .d.ts declarations for every declared component's headers,
606
+ * picking the C or C++ emitter per header based on whether it declares classes.
607
+ *
608
+ * Returns the list of created/updated .d.ts paths (headers that produced no
609
+ * declarations are skipped).
610
+ */
611
+ export function generateComponentDeclsForProject(projectDir, roots) {
612
+ const headers = discoverComponentHeaders(projectDir, roots);
613
+ const created = [];
614
+ for (const discovered of headers) {
615
+ // Output path: <outputDir>/<basename>.d.ts. For managed/local this is
616
+ // alongside the header; for builtins it's the project-local cache dir.
617
+ const baseName = path.basename(discovered.path).replace(/\.h$/i, '');
618
+ const outputPath = path.join(discovered.outputDir, baseName + '.d.ts');
619
+ const out = hasClasses(discovered.path)
620
+ ? generateDecl(discovered.path, outputPath)
621
+ : generateCDecl(discovered.path, outputPath);
622
+ if (out) {
623
+ created.push(out);
624
+ // Write a sibling .libdef.json so the import → #include resolver picks
625
+ // up the real (case-preserving) header name. Without this, the resolver
626
+ // falls back to toPascalCase(moduleKey) + ".h" — fine for Arduino libs
627
+ // (Adafruit_GFX → Adafruit_GFX.h, resolved separately) but wrong for
628
+ // ESP-IDF component headers (esp_wifi → <EspWifi.h>, not <esp_wifi.h>).
629
+ // The libdef declares the actual header basename ("esp_wifi.h"), which
630
+ // is the ground truth — the generator just read the file.
631
+ writeComponentLibdef(discovered.path, outputPath);
632
+ }
633
+ }
634
+ return created;
635
+ }
636
+ /**
637
+ * Write `<d.ts-basename>.libdef.json` next to a generated component `.d.ts`,
638
+ * pointing the import → #include resolver at the actual source header.
639
+ *
640
+ * The libdef `module` field is the lowercased basename (matches what
641
+ * `toModuleKey()` produces from an import specifier), and `include` is the
642
+ * case-preserving `<basename>.h` — quote-wrapped because component headers
643
+ * are project-local (resolved via the include path), not system angle-bracket
644
+ * includes. Matches the local-header convention in `resolveLocalModuleHeader`.
645
+ */
646
+ function writeComponentLibdef(sourceHeaderPath, dtsOutputPath) {
647
+ const headerBaseName = path.basename(sourceHeaderPath); // e.g. "esp_wifi.h"
648
+ const dtsBaseName = path.basename(dtsOutputPath, '.d.ts'); // e.g. "esp_wifi"
649
+ const libdefPath = path.join(path.dirname(dtsOutputPath), `${dtsBaseName}.libdef.json`);
650
+ const libdef = {
651
+ module: dtsBaseName.toLowerCase(),
652
+ include: `"${headerBaseName}"`,
653
+ source: sourceHeaderPath,
654
+ };
655
+ writeText(libdefPath, JSON.stringify(libdef, null, 2) + '\n');
656
+ }
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { listFiles, readText } from "../utils/fs.js";
3
+ import { listFilesRecursive, readText } from "../utils/fs.js";
4
4
  import { toModuleKey, toPascalCase } from "../utils/strings.js";
5
5
  import { getLoadedFramework, hasLoadedFramework } from "../framework-registry.js";
6
6
  function toArchitectureFromFqbn(fqbn) {
@@ -21,7 +21,10 @@ function getLibraryResolver() {
21
21
  }
22
22
  export function loadLibraryDefinitions(definitionsDir) {
23
23
  const registry = new Map();
24
- const files = listFiles(definitionsDir, ".libdef.json");
24
+ // Recursive scan: libdefs may live at the entry dir (single-level convention)
25
+ // or nested under cache trees like `.cuttlefish/component-decls/<component>/`
26
+ // (per-component overrides generated alongside .d.ts stubs).
27
+ const files = listFilesRecursive(definitionsDir, ".libdef.json");
25
28
  for (const filePath of files) {
26
29
  let def;
27
30
  try {