@typecad/cuttlefish 1.0.0-alpha.3 → 1.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +4 -4
  2. package/dist/api/shared/display-adapter.d.ts +2 -1
  3. package/dist/api/shared/display-adapter.js +8 -1
  4. package/dist/api/shared/display-adapters/sdl.js +9 -2
  5. package/dist/api/shared/display-profile.d.ts +20 -3
  6. package/dist/api/shared/display-profile.js +21 -6
  7. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  8. package/dist/api/shared/framework-manifest-registry.js +25 -0
  9. package/dist/api/shared/framework-manifest.d.ts +462 -0
  10. package/dist/api/shared/framework-manifest.js +149 -0
  11. package/dist/api/shared/glcdfont.d.ts +12 -0
  12. package/dist/api/shared/glcdfont.js +124 -0
  13. package/dist/api/shared/graphics-strategy.d.ts +28 -0
  14. package/dist/api/shared/hal-op-ir.d.ts +427 -1
  15. package/dist/api/shared/hal-op-ir.js +95 -1
  16. package/dist/api/shared/index.d.ts +11 -1
  17. package/dist/api/shared/index.js +14 -0
  18. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  19. package/dist/api/shared/native-display-op-resolver.js +64 -0
  20. package/dist/api/shared/platform-strategy.d.ts +9 -0
  21. package/dist/api/shared/promise-runtime.js +78 -0
  22. package/dist/api/shared/types.d.ts +8 -0
  23. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  24. package/dist/api/shared/validate-framework-manifest.js +417 -0
  25. package/dist/cli-utils.d.ts +1 -0
  26. package/dist/cli-utils.js +3 -1
  27. package/dist/cli.js +175 -4
  28. package/dist/config-loader.js +20 -1
  29. package/dist/config-schema.d.ts +36 -36
  30. package/dist/create/board-spec.d.ts +4 -4
  31. package/dist/create/index.d.ts +1 -1
  32. package/dist/create/index.js +1 -1
  33. package/dist/create/init-scaffold.d.ts +3 -0
  34. package/dist/create/init-scaffold.js +74 -2
  35. package/dist/create/init-templates.d.ts +4 -0
  36. package/dist/create/init-templates.js +217 -17
  37. package/dist/create/init-wizard.js +20 -1
  38. package/dist/emit/cpp-emitter.js +4 -3
  39. package/dist/emit/emitters/emitter-context.d.ts +5 -0
  40. package/dist/emit/emitters/function-emitter-impl.js +69 -59
  41. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  42. package/dist/emit/emitters/output-finalizer.js +21 -11
  43. package/dist/emit/emitters/setup.js +155 -0
  44. package/dist/emit/emitters/top-level-prep.js +2 -0
  45. package/dist/emit/emitters/ui-emitter.js +23 -8
  46. package/dist/emit/expression-renderer.js +10 -1
  47. package/dist/emit/snprintf-helpers.js +8 -0
  48. package/dist/emit/statement-renderer.js +13 -0
  49. package/dist/emit/utils/async-state-machine.js +185 -116
  50. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  51. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  52. package/dist/ir/adc-range-validation.js +26 -25
  53. package/dist/ir/build-ir.js +5 -1
  54. package/dist/ir/expression-to-ir.js +57 -6
  55. package/dist/ir/feature-registry.js +7 -25
  56. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  57. package/dist/ir/hal/hal-emitter.js +40 -12
  58. package/dist/ir/hal/hal-parser.d.ts +6 -0
  59. package/dist/ir/hal/hal-parser.js +74 -0
  60. package/dist/ir/hal/hal-plugins.js +571 -0
  61. package/dist/ir/identifier-collector.js +18 -0
  62. package/dist/ir/interrupt-analysis.js +8 -3
  63. package/dist/ir/memory-budget-validation.js +1 -0
  64. package/dist/ir/network-validation.d.ts +4 -0
  65. package/dist/ir/network-validation.js +184 -0
  66. package/dist/ir/ownership-analysis.js +33 -1
  67. package/dist/ir/peripheral-ownership.js +5 -0
  68. package/dist/ir/peripheral-validation.d.ts +1 -1
  69. package/dist/ir/peripheral-validation.js +6 -3
  70. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  71. package/dist/ir/pin-alias-conflict.js +2 -1
  72. package/dist/ir/pin-capability-validation.js +34 -32
  73. package/dist/ir/pin-mode-validation.js +5 -0
  74. package/dist/ir/pin-safety.d.ts +1 -1
  75. package/dist/ir/pin-safety.js +2 -1
  76. package/dist/ir/program-analysis.d.ts +39 -0
  77. package/dist/ir/program-analysis.js +192 -0
  78. package/dist/ir/pulldown-validation.d.ts +1 -1
  79. package/dist/ir/pulldown-validation.js +2 -1
  80. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  81. package/dist/ir/pwm-timer-sharing.js +2 -1
  82. package/dist/ir/resource-analysis.js +2 -0
  83. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  84. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  85. package/dist/ir/timing-validation.d.ts +6 -1
  86. package/dist/ir/timing-validation.js +51 -12
  87. package/dist/ir/transformers/expressions.js +58 -0
  88. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  89. package/dist/ir/transformers/variables.js +86 -19
  90. package/dist/ir/try-catch-validation.js +2 -0
  91. package/dist/ir/type-resolution.js +2 -2
  92. package/dist/ir/unit-suspicion-validation.js +9 -7
  93. package/dist/ir/validation-orchestrator.js +9 -7
  94. package/dist/libdef/c-to-decl.d.ts +27 -0
  95. package/dist/libdef/c-to-decl.js +397 -0
  96. package/dist/libdef/component-decls.d.ts +2 -0
  97. package/dist/libdef/component-decls.js +6 -0
  98. package/dist/libdef/component-discovery.d.ts +43 -0
  99. package/dist/libdef/component-discovery.js +83 -0
  100. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  101. package/dist/libdef/cpp-to-decl.js +72 -0
  102. package/dist/libdef/idf-discovery.d.ts +7 -0
  103. package/dist/libdef/idf-discovery.js +59 -0
  104. package/dist/libdef/registry.js +5 -2
  105. package/dist/licenses.d.ts +185 -0
  106. package/dist/licenses.js +963 -0
  107. package/dist/lint-cache.d.ts +59 -0
  108. package/dist/lint-cache.js +257 -0
  109. package/dist/orchestrator/graph-builder.js +6 -2
  110. package/dist/stores/display-profile-store.d.ts +1 -0
  111. package/dist/stores/display-profile-store.js +1 -0
  112. package/dist/testing.d.ts +4 -2
  113. package/dist/testing.js +4 -2
  114. package/dist/transpile.d.ts +3 -0
  115. package/dist/transpile.js +78 -32
  116. package/dist/types.d.ts +5 -1
  117. package/dist/ui-hook.d.ts +17 -1
  118. package/dist/utils/cli.js +71 -1
  119. package/dist/utils/fs.d.ts +13 -0
  120. package/dist/utils/fs.js +50 -0
  121. package/package.json +9 -4
  122. package/dist/ir/heap-array-validation.d.ts +0 -24
  123. package/dist/ir/heap-array-validation.js +0 -29
package/dist/cli.js CHANGED
@@ -5,7 +5,8 @@ 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
+ import { discoverIdfRootForGenDecls } from "./libdef/idf-discovery.js";
9
10
  import { mapCppLocationToTs, readSourceMap, resolveMapPath } from "./mapping/source-map.js";
10
11
  import { compileSource, uploadFirmware, monitorDevice } from "./platform/toolchain.js";
11
12
  import { resolveStrategy } from "./platform/registry.js";
@@ -19,6 +20,8 @@ import { runExpectTests, assertTypeScriptInput, printDiagnostics, printMappedCom
19
20
  import { runPreviewServer } from "./preview/server.js";
20
21
  import * as ui from "./utils/ui.js";
21
22
  import chalk from "chalk";
23
+ import { checkArduinoEnv } from "@typecad/arduino-cli";
24
+ import { runLicensesPresenter } from "./licenses.js";
22
25
  function hasFatalDiagnostics(result) {
23
26
  return result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
24
27
  }
@@ -47,8 +50,14 @@ async function handleCreate(options) {
47
50
  ? '@typecad/framework-avr'
48
51
  : framework === 'arduino'
49
52
  ? '@typecad/framework-arduino'
50
- : `@typecad/framework-${framework}`;
53
+ : framework === 'esp32'
54
+ ? '@typecad/framework-esp32'
55
+ : `@typecad/framework-${framework}`;
51
56
  const projectName = options.projectName || 'my-project';
57
+ const isEspIdf = frameworkPackage === '@typecad/framework-esp32' || framework === 'esp32';
58
+ const idfTarget = target.frameworkData?.target
59
+ ?? target.architecture
60
+ ?? 'esp32';
52
61
  const result = scaffoldProject({
53
62
  projectName,
54
63
  targetId: target.id,
@@ -58,10 +67,18 @@ async function handleCreate(options) {
58
67
  boardPackage: target.boardPackage,
59
68
  frameworkPackage,
60
69
  framework,
61
- buildTarget: target.buildTarget,
70
+ buildTarget: isEspIdf ? idfTarget : target.buildTarget,
62
71
  mcu: target.mcu,
63
72
  baudRate: target.isNative ? undefined : (options.baud ?? 9600),
64
73
  includeSketch: !options.noSketch,
74
+ ...(isEspIdf
75
+ ? {
76
+ toolchainType: 'idf',
77
+ frameworkData: target.frameworkData ?? { target: idfTarget, buildTarget: idfTarget },
78
+ }
79
+ : target.frameworkData
80
+ ? { frameworkData: target.frameworkData }
81
+ : {}),
65
82
  }, options.outDir);
66
83
  console.log(`\n${chalk.green("✓")} Created project files:`);
67
84
  for (const file of result.createdFiles) {
@@ -107,6 +124,62 @@ async function handleBoardAdd(options) {
107
124
  }
108
125
  console.log(generateFrameworkChecklist(spec));
109
126
  }
127
+ /**
128
+ * `cuttlefish doctor` — verify arduino-cli is installed and the board's core
129
+ * (derived from the FQBN in cuttlefish.config.ts) is present. Exits 0 if the
130
+ * environment is OK, non-zero otherwise. Reuses checkArduinoEnv so the
131
+ * detection logic is shared with the build/test gates.
132
+ */
133
+ function runDoctor() {
134
+ ui.printHeader();
135
+ ui.printStep("Checking arduino-cli environment...");
136
+ const config = loadCuttlefishConfig(process.cwd());
137
+ const fqbn = config?.buildTarget;
138
+ const result = checkArduinoEnv(fqbn);
139
+ const check = result.check;
140
+ // arduino-cli presence line
141
+ if (check.arduinoCliInstalled) {
142
+ ui.printInfo(`arduino-cli .... ${check.arduinoCliVersion ?? "unknown"} ✓`);
143
+ }
144
+ else if (!result.ok && result.reason === "arduino-cli-not-found") {
145
+ ui.printError(`arduino-cli .... NOT FOUND on PATH`);
146
+ }
147
+ else {
148
+ ui.printError(`arduino-cli .... found but unresponsive`);
149
+ }
150
+ // core presence line (only meaningful if we have an FQBN)
151
+ if (fqbn) {
152
+ if (check.requiredCore) {
153
+ const status = check.requiredCoreInstalled ? "installed ✓" : "NOT installed ✗";
154
+ const line = `${check.requiredCore} ....... ${status}`;
155
+ if (check.requiredCoreInstalled) {
156
+ ui.printInfo(line);
157
+ }
158
+ else {
159
+ ui.printError(line);
160
+ ui.printInfo(` → run: arduino-cli core install ${check.requiredCore}`);
161
+ }
162
+ }
163
+ }
164
+ else {
165
+ ui.printInfo("(no buildTarget in cuttlefish.config.ts — skipping core check)");
166
+ }
167
+ // Exit code
168
+ if (result.ok) {
169
+ ui.printSuccess("Environment OK");
170
+ return; // exitCode stays unset => 0
171
+ }
172
+ if (!result.ok) {
173
+ for (const line of result.messages)
174
+ ui.printInfo(line);
175
+ process.exitCode = 1;
176
+ }
177
+ }
178
+ /**
179
+ * `cuttlefish licenses` dispatch — the presenter lives in licenses.ts so it is
180
+ * unit-testable without importing this binary entry module (cli.ts has a
181
+ * shebang and runs main() at import time). See runLicensesPresenter.
182
+ */
110
183
  async function main() {
111
184
  try {
112
185
  const options = parseCommandLine(process.argv);
@@ -154,6 +227,14 @@ async function main() {
154
227
  }
155
228
  return;
156
229
  }
230
+ if (options.command === "doctor") {
231
+ runDoctor();
232
+ return;
233
+ }
234
+ if (options.command === "licenses") {
235
+ runLicensesPresenter(options.strict ?? false, options.all ?? false);
236
+ return;
237
+ }
157
238
  // ── Handle build command — entry point comes from config ──────────
158
239
  if (options.command === "build") {
159
240
  const buildConfig = loadCuttlefishConfig(process.cwd());
@@ -184,6 +265,47 @@ async function main() {
184
265
  // it intentionally has no inputFile (it scans scanDir instead), so the
185
266
  // generic "Missing input file path" check would otherwise block it.
186
267
  if (options.command === "gen-decls") {
268
+ // Check for --components flag (ESP-IDF components: managed + local).
269
+ // Runs before scanDir/single-file branches; components mode has no
270
+ // inputFile by design (it scans managed_components/ + components/).
271
+ const componentsDir = options.componentsDir;
272
+ if (componentsDir) {
273
+ ui.printHeader();
274
+ ui.printStep(`Generating component declarations for ${componentsDir}...`);
275
+ const config = loadCuttlefishConfig(componentsDir);
276
+ const frameworkConfig = (config?.frameworkConfig ?? {});
277
+ const componentsNode = frameworkConfig?.components ?? {};
278
+ const managedSpecs = Object.keys(componentsNode.managed ?? {});
279
+ // idf.py stores managed deps as <namespace>__<name> (slashes → __).
280
+ const managedNames = managedSpecs.map((spec) => spec.replace("/", "__"));
281
+ const localPaths = (componentsNode.local ?? []).map((p) => path.isAbsolute(p) ? p : path.resolve(componentsDir, p));
282
+ const builtinNames = componentsNode.builtin ?? [];
283
+ // Built-in components resolve against an ESP-IDF install. The CLI
284
+ // uses minimal discovery (env var + well-known paths). If no install
285
+ // is found, builtins are skipped with a notice — the framework-esp32
286
+ // compile path performs richer discovery and is the primary entry
287
+ // point for builtin gen-decls.
288
+ const idfRoot = discoverIdfRootForGenDecls();
289
+ if (builtinNames.length > 0 && !idfRoot) {
290
+ ui.printInfo('components.builtin declared but no ESP-IDF install was found; skipping builtin headers. ' +
291
+ 'Run via `cuttlefish build --compile` (which performs full IDF discovery) or set $IDF_PATH.');
292
+ }
293
+ const created = generateComponentDeclsForProject(componentsDir, {
294
+ managed: managedNames,
295
+ local: localPaths,
296
+ builtin: idfRoot ? builtinNames : [],
297
+ idfRoot,
298
+ });
299
+ if (created.length === 0) {
300
+ ui.printInfo("No component declaration files created.");
301
+ }
302
+ else {
303
+ ui.printSuccess(`Created ${created.length} declaration file(s):`);
304
+ for (const f of created)
305
+ ui.printFileCreated(f);
306
+ }
307
+ return;
308
+ }
187
309
  // Check for --all flag (scan directory)
188
310
  const scanDir = options.scanDir;
189
311
  if (scanDir) {
@@ -255,6 +377,47 @@ async function main() {
255
377
  // ── Load cuttlefish.config.ts (config wins over CLI flags) ──────────
256
378
  const inputDir = path.dirname(path.resolve(options.inputFile));
257
379
  const config = loadCuttlefishConfig(inputDir);
380
+ // ── Pre-transpile: generate component .d.ts stubs ───────────────────
381
+ // The transpile type-check needs the .d.ts imports in main.ts to resolve.
382
+ // For ESP-IDF builtin/managed/local components, those .d.ts files are
383
+ // generated by gen-decls — so we must run it BEFORE type-checking, not
384
+ // only as part of --compile. The cache is written under the transpile
385
+ // output dir (the same path --compile uses), so user imports resolve
386
+ // identically in both flows.
387
+ if (config?.frameworkConfig) {
388
+ const fc = config.frameworkConfig;
389
+ const componentsNode = fc?.components ?? {};
390
+ const managedSpecs = Object.keys(componentsNode.managed ?? {});
391
+ const localPaths = (componentsNode.local ?? []).map((p) => path.isAbsolute(p) ? p : path.resolve(inputDir, p));
392
+ const builtinNames = componentsNode.builtin ?? [];
393
+ if (managedSpecs.length > 0 || localPaths.length > 0 || builtinNames.length > 0) {
394
+ // Resolve the transpile output dir — the cache lives under it so the
395
+ // framework's compile path and this pre-transpile pass agree on location.
396
+ // output.outDir in config is relative to the project root (inputDir);
397
+ // if absent, the transpile output goes alongside the entry file.
398
+ const outBase = config.outputOutDir
399
+ ? path.resolve(inputDir, config.outputOutDir)
400
+ : inputDir;
401
+ // For builtins we need IDF_PATH. Use cuttlefish's minimal discovery
402
+ // (env var + well-known paths) — full discovery is done by
403
+ // framework-esp32 at --compile time. If no install is found,
404
+ // builtins are skipped; --compile will regenerate them.
405
+ const idfRoot = discoverIdfRootForGenDecls();
406
+ try {
407
+ generateComponentDeclsForProject(outBase, {
408
+ managed: managedSpecs.map((s) => s.replace('/', '__')),
409
+ local: localPaths,
410
+ builtin: idfRoot ? builtinNames : [],
411
+ idfRoot,
412
+ });
413
+ }
414
+ catch {
415
+ // Non-fatal: if gen-decls fails (e.g. component not yet fetched),
416
+ // the type-checker will surface the missing-import errors with
417
+ // clearer context than crashing here.
418
+ }
419
+ }
420
+ }
258
421
  // Load the UI engine (if @typecad/ui is installed) before any UI work.
259
422
  await loadUIEngine();
260
423
  let effectivePlatformContext = options.platformContext;
@@ -277,9 +440,17 @@ async function main() {
277
440
  }
278
441
  let configBuildTarget = config.buildTarget;
279
442
  if (configBuildTarget) {
443
+ // Reconstruct frameworkData from buildTarget, preserving other fields
444
+ // (psram, components, etc.) from the resolved frameworkConfig so the
445
+ // transpile-time diagnostics (e.g. scroll-canvas-memory PSRAM budget)
446
+ // and the toolchain both see the full frameworkData, not just buildTarget.
447
+ const fcPsram = config.frameworkConfig?.psram;
280
448
  effectivePlatformContext = {
281
449
  architecture: configBuildTarget.split(":")?.[1]?.toLowerCase(),
282
- frameworkData: { buildTarget: configBuildTarget },
450
+ frameworkData: {
451
+ buildTarget: configBuildTarget,
452
+ ...(fcPsram ? { psram: fcPsram } : {}),
453
+ },
283
454
  };
284
455
  }
285
456
  if (config.outputOutDir && !options.outDir) {
@@ -9,6 +9,12 @@
9
9
  import path from "node:path";
10
10
  import fs from "node:fs";
11
11
  import ts from "typescript";
12
+ /** True for the framework-esp32 package or any path that resolves to it.
13
+ * Used to decide whether to route frameworkData → frameworkConfig so the
14
+ * ESP-IDF toolchain can read components, target, etc. */
15
+ function isEspIdfFramework(framework) {
16
+ return framework === "@typecad/framework-esp32" || framework.endsWith("framework-esp32");
17
+ }
12
18
  import { safeValidateConfig } from "./config-schema.js";
13
19
  /** The filename we search for when walking up directories. */
14
20
  const CONFIG_FILENAME = "cuttlefish.config.ts";
@@ -323,8 +329,21 @@ export function parseConfigFile(configPath) {
323
329
  if (outputDefines)
324
330
  resolved.outputDefines = outputDefines;
325
331
  const nativeSection = extractFrameworkSection(configObject, "native");
326
- if (nativeSection)
332
+ if (nativeSection) {
327
333
  resolved.frameworkConfig = nativeSection;
334
+ }
335
+ else if (resolved.framework && isEspIdfFramework(resolved.framework)) {
336
+ // ESP-IDF: route frameworkData through frameworkConfig so the toolchain
337
+ // can read components, target, and other framework-specific fields.
338
+ // extractFrameworkSection walks the AST and preserves nested shapes
339
+ // (components.managed, components.local, etc.).
340
+ const frameworkData = configObject
341
+ ? extractFrameworkSection(configObject, "frameworkData")
342
+ : undefined;
343
+ if (frameworkData) {
344
+ resolved.frameworkConfig = frameworkData;
345
+ }
346
+ }
328
347
  // Parse display profile config (nested object with profile name, wiring, touch)
329
348
  const displaySection = extractFrameworkSection(configObject, "display");
330
349
  if (displaySection)
@@ -41,16 +41,16 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
41
41
  buildTarget: z.ZodOptional<z.ZodString>;
42
42
  board: z.ZodOptional<z.ZodString>;
43
43
  }, "strict", z.ZodTypeAny, {
44
- buildTarget?: string | undefined;
45
- port?: string | undefined;
46
44
  board?: string | undefined;
45
+ port?: string | undefined;
46
+ buildTarget?: string | undefined;
47
47
  baudRate?: number | undefined;
48
48
  include?: string[] | undefined;
49
49
  timeout?: number | undefined;
50
50
  }, {
51
- buildTarget?: string | undefined;
52
- port?: string | undefined;
53
51
  board?: string | undefined;
52
+ port?: string | undefined;
53
+ buildTarget?: string | undefined;
54
54
  baudRate?: number | undefined;
55
55
  include?: string[] | undefined;
56
56
  timeout?: number | undefined;
@@ -83,6 +83,12 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
83
83
  } | undefined;
84
84
  frameworkData?: Record<string, unknown> | undefined;
85
85
  target?: string | undefined;
86
+ board?: string | undefined;
87
+ exclude?: string[] | undefined;
88
+ toolchain?: {
89
+ type?: string | undefined;
90
+ frameworkOptions?: Record<string, unknown> | undefined;
91
+ } | undefined;
86
92
  framework?: string | undefined;
87
93
  native?: Record<string, unknown> | undefined;
88
94
  output?: {
@@ -92,20 +98,14 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
92
98
  defines?: Record<string, string> | undefined;
93
99
  extraFlags?: string[] | undefined;
94
100
  } | undefined;
95
- board?: string | undefined;
96
101
  mcu?: string | undefined;
97
102
  include?: string[] | undefined;
98
103
  entry?: string | undefined;
99
- toolchain?: {
100
- type?: string | undefined;
101
- frameworkOptions?: Record<string, unknown> | undefined;
102
- } | undefined;
103
104
  contract?: string | undefined;
104
- exclude?: string[] | undefined;
105
105
  test?: {
106
- buildTarget?: string | undefined;
107
- port?: string | undefined;
108
106
  board?: string | undefined;
107
+ port?: string | undefined;
108
+ buildTarget?: string | undefined;
109
109
  baudRate?: number | undefined;
110
110
  include?: string[] | undefined;
111
111
  timeout?: number | undefined;
@@ -117,6 +117,12 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
117
117
  } | undefined;
118
118
  frameworkData?: Record<string, unknown> | undefined;
119
119
  target?: string | undefined;
120
+ board?: string | undefined;
121
+ exclude?: string[] | undefined;
122
+ toolchain?: {
123
+ type?: string | undefined;
124
+ frameworkOptions?: Record<string, unknown> | undefined;
125
+ } | undefined;
120
126
  framework?: string | undefined;
121
127
  native?: Record<string, unknown> | undefined;
122
128
  output?: {
@@ -126,20 +132,14 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
126
132
  defines?: Record<string, string> | undefined;
127
133
  extraFlags?: string[] | undefined;
128
134
  } | undefined;
129
- board?: string | undefined;
130
135
  mcu?: string | undefined;
131
136
  include?: string[] | undefined;
132
137
  entry?: string | undefined;
133
- toolchain?: {
134
- type?: string | undefined;
135
- frameworkOptions?: Record<string, unknown> | undefined;
136
- } | undefined;
137
138
  contract?: string | undefined;
138
- exclude?: string[] | undefined;
139
139
  test?: {
140
- buildTarget?: string | undefined;
141
- port?: string | undefined;
142
140
  board?: string | undefined;
141
+ port?: string | undefined;
142
+ buildTarget?: string | undefined;
143
143
  baudRate?: number | undefined;
144
144
  include?: string[] | undefined;
145
145
  timeout?: number | undefined;
@@ -151,6 +151,12 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
151
151
  } | undefined;
152
152
  frameworkData?: Record<string, unknown> | undefined;
153
153
  target?: string | undefined;
154
+ board?: string | undefined;
155
+ exclude?: string[] | undefined;
156
+ toolchain?: {
157
+ type?: string | undefined;
158
+ frameworkOptions?: Record<string, unknown> | undefined;
159
+ } | undefined;
154
160
  framework?: string | undefined;
155
161
  native?: Record<string, unknown> | undefined;
156
162
  output?: {
@@ -160,20 +166,14 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
160
166
  defines?: Record<string, string> | undefined;
161
167
  extraFlags?: string[] | undefined;
162
168
  } | undefined;
163
- board?: string | undefined;
164
169
  mcu?: string | undefined;
165
170
  include?: string[] | undefined;
166
171
  entry?: string | undefined;
167
- toolchain?: {
168
- type?: string | undefined;
169
- frameworkOptions?: Record<string, unknown> | undefined;
170
- } | undefined;
171
172
  contract?: string | undefined;
172
- exclude?: string[] | undefined;
173
173
  test?: {
174
- buildTarget?: string | undefined;
175
- port?: string | undefined;
176
174
  board?: string | undefined;
175
+ port?: string | undefined;
176
+ buildTarget?: string | undefined;
177
177
  baudRate?: number | undefined;
178
178
  include?: string[] | undefined;
179
179
  timeout?: number | undefined;
@@ -185,6 +185,12 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
185
185
  } | undefined;
186
186
  frameworkData?: Record<string, unknown> | undefined;
187
187
  target?: string | undefined;
188
+ board?: string | undefined;
189
+ exclude?: string[] | undefined;
190
+ toolchain?: {
191
+ type?: string | undefined;
192
+ frameworkOptions?: Record<string, unknown> | undefined;
193
+ } | undefined;
188
194
  framework?: string | undefined;
189
195
  native?: Record<string, unknown> | undefined;
190
196
  output?: {
@@ -194,20 +200,14 @@ export declare const CuttlefishConfigSchema: z.ZodEffects<z.ZodObject<{
194
200
  defines?: Record<string, string> | undefined;
195
201
  extraFlags?: string[] | undefined;
196
202
  } | undefined;
197
- board?: string | undefined;
198
203
  mcu?: string | undefined;
199
204
  include?: string[] | undefined;
200
205
  entry?: string | undefined;
201
- toolchain?: {
202
- type?: string | undefined;
203
- frameworkOptions?: Record<string, unknown> | undefined;
204
- } | undefined;
205
206
  contract?: string | undefined;
206
- exclude?: string[] | undefined;
207
207
  test?: {
208
- buildTarget?: string | undefined;
209
- port?: string | undefined;
210
208
  board?: string | undefined;
209
+ port?: string | undefined;
210
+ buildTarget?: string | undefined;
211
211
  baudRate?: number | undefined;
212
212
  include?: string[] | undefined;
213
213
  timeout?: number | undefined;
@@ -40,6 +40,7 @@ export declare const BoardSpecSchema: z.ZodObject<{
40
40
  }, "strict", z.ZodTypeAny, {
41
41
  gpio: number;
42
42
  capabilities: "FULL_GPIO" | "FULL_GPIO_ANALOG" | "FULL_GPIO_TOUCH" | "FULL_GPIO_ANALOG_TOUCH" | "FULL_GPIO_DAC" | "INPUT_ONLY";
43
+ notes?: string | undefined;
43
44
  functions?: {
44
45
  type: "i2c" | "spi" | "uart" | "adc" | "dac" | "pwm" | "touch" | "usb";
45
46
  instance: number;
@@ -48,11 +49,11 @@ export declare const BoardSpecSchema: z.ZodObject<{
48
49
  alt?: string[] | undefined;
49
50
  warnings?: string[] | undefined;
50
51
  unsafe?: boolean | undefined;
51
- notes?: string | undefined;
52
52
  onboardLed?: boolean | undefined;
53
53
  }, {
54
54
  gpio: number;
55
55
  capabilities: "FULL_GPIO" | "FULL_GPIO_ANALOG" | "FULL_GPIO_TOUCH" | "FULL_GPIO_ANALOG_TOUCH" | "FULL_GPIO_DAC" | "INPUT_ONLY";
56
+ notes?: string | undefined;
56
57
  functions?: {
57
58
  type: "i2c" | "spi" | "uart" | "adc" | "dac" | "pwm" | "touch" | "usb";
58
59
  instance: number;
@@ -61,7 +62,6 @@ export declare const BoardSpecSchema: z.ZodObject<{
61
62
  alt?: string[] | undefined;
62
63
  warnings?: string[] | undefined;
63
64
  unsafe?: boolean | undefined;
64
- notes?: string | undefined;
65
65
  onboardLed?: boolean | undefined;
66
66
  }>, "many">;
67
67
  unsafe: z.ZodArray<z.ZodString, "many">;
@@ -436,6 +436,7 @@ export declare const BoardSpecSchema: z.ZodObject<{
436
436
  pins: {
437
437
  gpio: number;
438
438
  capabilities: "FULL_GPIO" | "FULL_GPIO_ANALOG" | "FULL_GPIO_TOUCH" | "FULL_GPIO_ANALOG_TOUCH" | "FULL_GPIO_DAC" | "INPUT_ONLY";
439
+ notes?: string | undefined;
439
440
  functions?: {
440
441
  type: "i2c" | "spi" | "uart" | "adc" | "dac" | "pwm" | "touch" | "usb";
441
442
  instance: number;
@@ -444,7 +445,6 @@ export declare const BoardSpecSchema: z.ZodObject<{
444
445
  alt?: string[] | undefined;
445
446
  warnings?: string[] | undefined;
446
447
  unsafe?: boolean | undefined;
447
- notes?: string | undefined;
448
448
  onboardLed?: boolean | undefined;
449
449
  }[];
450
450
  analog: string[];
@@ -568,6 +568,7 @@ export declare const BoardSpecSchema: z.ZodObject<{
568
568
  pins: {
569
569
  gpio: number;
570
570
  capabilities: "FULL_GPIO" | "FULL_GPIO_ANALOG" | "FULL_GPIO_TOUCH" | "FULL_GPIO_ANALOG_TOUCH" | "FULL_GPIO_DAC" | "INPUT_ONLY";
571
+ notes?: string | undefined;
571
572
  functions?: {
572
573
  type: "i2c" | "spi" | "uart" | "adc" | "dac" | "pwm" | "touch" | "usb";
573
574
  instance: number;
@@ -576,7 +577,6 @@ export declare const BoardSpecSchema: z.ZodObject<{
576
577
  alt?: string[] | undefined;
577
578
  warnings?: string[] | undefined;
578
579
  unsafe?: boolean | undefined;
579
- notes?: string | undefined;
580
580
  onboardLed?: boolean | undefined;
581
581
  }[];
582
582
  analog: string[];
@@ -1,6 +1,6 @@
1
1
  export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget, printInitNextSteps } from './init-scaffold.js';
2
2
  export type { KnownTarget, KnownBoard, ScaffoldProjectResult } from './init-scaffold.js';
3
- export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateGitignore, generateEslintConfig, } from './init-templates.js';
3
+ export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig, } from './init-templates.js';
4
4
  export type { InitProjectOptions } from './init-templates.js';
5
5
  export { runInitWizard } from './init-wizard.js';
6
6
  export { scaffoldBoardPackages } from './board-codegen.js';
@@ -1,5 +1,5 @@
1
1
  export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget, printInitNextSteps } from './init-scaffold.js';
2
- export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateGitignore, generateEslintConfig, } from './init-templates.js';
2
+ export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig, } from './init-templates.js';
3
3
  export { runInitWizard } from './init-wizard.js';
4
4
  // Board codegen tool (`cuttlefish board add`)
5
5
  export { scaffoldBoardPackages } from './board-codegen.js';
@@ -10,6 +10,9 @@ export interface KnownTarget {
10
10
  framework: string;
11
11
  buildTarget?: string;
12
12
  mcu?: string;
13
+ /** Framework-specific config (becomes frameworkData in cuttlefish.config.ts).
14
+ * Used by framework-esp32 to carry `target: 'esp32'|'esp32s3'|'esp32c3'|'esp32c6'`. */
15
+ frameworkData?: Record<string, unknown>;
13
16
  }
14
17
  export declare function registerKnownTarget(target: KnownTarget): void;
15
18
  export declare const KNOWN_TARGETS: ReadonlyArray<KnownTarget>;
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs";
3
3
  import chalk from "chalk";
4
- import { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateGitignore, generateBoardForwardingFile, generateEslintConfig, } from "./init-templates.js";
4
+ import { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateBoardForwardingFile, generateEslintConfig, } from "./init-templates.js";
5
5
  import { generateEslintRules } from "./eslint-rules-template.js";
6
6
  const _knownTargets = [
7
7
  {
@@ -33,6 +33,18 @@ const _knownTargets = [
33
33
  buildTarget: 'esp32:esp32:esp32',
34
34
  mcu: 'esp32',
35
35
  },
36
+ {
37
+ id: 'esp32-devkit-idf',
38
+ displayName: 'ESP32 DevKit (native ESP-IDF)',
39
+ isNative: false,
40
+ architecture: 'esp32',
41
+ boardPackage: '@typecad/board-esp32-devkit',
42
+ frameworkPackage: '@typecad/framework-esp32',
43
+ framework: 'esp32',
44
+ buildTarget: 'esp32:esp32:esp32',
45
+ mcu: 'esp32',
46
+ frameworkData: { target: 'esp32' },
47
+ },
36
48
  {
37
49
  id: 'esp32s3',
38
50
  displayName: 'ESP32-S3',
@@ -44,6 +56,18 @@ const _knownTargets = [
44
56
  buildTarget: 'esp32:esp32:esp32s3',
45
57
  mcu: 'esp32s3',
46
58
  },
59
+ {
60
+ id: 'esp32s3-idf',
61
+ displayName: 'ESP32-S3 (native ESP-IDF)',
62
+ isNative: false,
63
+ architecture: 'esp32s3',
64
+ boardPackage: '@typecad/board-esp32s3',
65
+ frameworkPackage: '@typecad/framework-esp32',
66
+ framework: 'esp32',
67
+ buildTarget: 'esp32:esp32:esp32s3',
68
+ mcu: 'esp32s3',
69
+ frameworkData: { target: 'esp32s3' },
70
+ },
47
71
  {
48
72
  id: 'esp32c3',
49
73
  displayName: 'ESP32-C3',
@@ -55,6 +79,18 @@ const _knownTargets = [
55
79
  buildTarget: 'esp32:esp32:esp32c3',
56
80
  mcu: 'esp32c3',
57
81
  },
82
+ {
83
+ id: 'esp32c3-idf',
84
+ displayName: 'ESP32-C3 (native ESP-IDF)',
85
+ isNative: false,
86
+ architecture: 'esp32c3',
87
+ boardPackage: '@typecad/board-esp32c3',
88
+ frameworkPackage: '@typecad/framework-esp32',
89
+ framework: 'esp32',
90
+ buildTarget: 'esp32:esp32:esp32c3',
91
+ mcu: 'esp32c3',
92
+ frameworkData: { target: 'esp32c3' },
93
+ },
58
94
  {
59
95
  id: 'esp32c6',
60
96
  displayName: 'ESP32-C6',
@@ -66,6 +102,18 @@ const _knownTargets = [
66
102
  buildTarget: 'esp32:esp32:esp32c6',
67
103
  mcu: 'esp32c6',
68
104
  },
105
+ {
106
+ id: 'esp32c6-idf',
107
+ displayName: 'ESP32-C6 (native ESP-IDF)',
108
+ isNative: false,
109
+ architecture: 'esp32c6',
110
+ boardPackage: '@typecad/board-esp32c6',
111
+ frameworkPackage: '@typecad/framework-esp32',
112
+ framework: 'esp32',
113
+ buildTarget: 'esp32:esp32:esp32c6',
114
+ mcu: 'esp32c6',
115
+ frameworkData: { target: 'esp32c6' },
116
+ },
69
117
  {
70
118
  id: 'rp2040',
71
119
  displayName: 'RP2040 (Pico)',
@@ -150,6 +198,24 @@ export function scaffoldProject(options, outDir) {
150
198
  fs.writeFileSync(sketchPath, generateStarterSketch(options), 'utf-8');
151
199
  createdFiles.push(sketchPath);
152
200
  }
201
+ // Embedded projects get a starter hardware test (@typecad/expect / cuttlefish-test).
202
+ // Native projects have no serial/board path, so they get no test setup.
203
+ if (!options.isNative) {
204
+ const testsDir = path.join(resolvedOutDir, 'tests');
205
+ fs.mkdirSync(testsDir, { recursive: true });
206
+ const testPath = path.join(testsDir, '01-basics.test.ts');
207
+ fs.writeFileSync(testPath, generateStarterTest(options), 'utf-8');
208
+ createdFiles.push(testPath);
209
+ // Host-side simulation (@typecad/simulator + vitest). sim/ is kept separate
210
+ // from tests/ so `vitest run sim/` never loads the @typecad/expect no-op
211
+ // stubs, and `cuttlefish-test` (which globs tests/) never tries to flash a
212
+ // simulator file as firmware.
213
+ const simDir = path.join(resolvedOutDir, 'sim');
214
+ fs.mkdirSync(simDir, { recursive: true });
215
+ const simPath = path.join(simDir, 'main.test.ts');
216
+ fs.writeFileSync(simPath, generateStarterSim(options), 'utf-8');
217
+ createdFiles.push(simPath);
218
+ }
153
219
  return { createdFiles, outDir: resolvedOutDir, options };
154
220
  }
155
221
  export function printInitNextSteps(options, outDir) {
@@ -164,9 +230,15 @@ export function printInitNextSteps(options, outDir) {
164
230
  if (!options.isNative) {
165
231
  const portHint = process.platform === 'win32' ? 'COM4' : '/dev/ttyACM0';
166
232
  console.log();
233
+ console.log(chalk.bold.white("To simulate without hardware:"));
234
+ console.log(` ${chalk.cyan("npm run simulate")}`);
235
+ console.log();
167
236
  console.log(chalk.bold.white("To upload to your board:"));
168
237
  console.log(` ${chalk.cyan("npm run upload")}`);
169
238
  console.log();
170
- console.log(chalk.dim(`Edit ${chalk.white("package.json")} to change the serial port from ${chalk.white(portHint)} to your port.`));
239
+ console.log(chalk.bold.white("To run hardware tests:"));
240
+ console.log(` ${chalk.cyan("npm run test:hw")}`);
241
+ console.log();
242
+ console.log(chalk.dim(`Edit ${chalk.white("cuttlefish.config.ts")} to change the serial port from ${chalk.white(portHint)} to your port.`));
171
243
  }
172
244
  }