@typecad/cuttlefish 1.0.0-alpha.12 → 1.0.0-alpha.14

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 (101) hide show
  1. package/assets/editor-extensions/typecad-debug/README.md +142 -0
  2. package/assets/editor-extensions/typecad-debug/out/extension.js +347 -0
  3. package/assets/editor-extensions/typecad-debug/out/extension.js.map +1 -0
  4. package/assets/editor-extensions/typecad-debug/package.json +76 -0
  5. package/assets/editor-extensions/typecad-ui/LICENSE +27 -0
  6. package/assets/editor-extensions/typecad-ui/README.md +52 -0
  7. package/assets/editor-extensions/typecad-ui/file-icon-dark.png +0 -0
  8. package/assets/editor-extensions/typecad-ui/file-icon-light.png +0 -0
  9. package/assets/editor-extensions/typecad-ui/icon.png +0 -0
  10. package/assets/editor-extensions/typecad-ui/language-configuration.json +43 -0
  11. package/assets/editor-extensions/typecad-ui/package.json +54 -0
  12. package/assets/editor-extensions/typecad-ui/snippets/typecad-ui.json +80 -0
  13. package/assets/editor-extensions/typecad-ui/syntaxes/markdown-ui.json +45 -0
  14. package/assets/editor-extensions/typecad-ui/syntaxes/typecad-ui.tmLanguage.json +1269 -0
  15. package/dist/add-preset.d.ts +4 -0
  16. package/dist/add-preset.js +74 -0
  17. package/dist/api/config.d.ts +8 -5
  18. package/dist/api/shared/display-adapters/sdl.js +1 -1
  19. package/dist/api/shared/display-profile.d.ts +11 -0
  20. package/dist/api/shared/display-profile.js +3 -0
  21. package/dist/api/shared/framework-manifest.d.ts +61 -504
  22. package/dist/api/shared/hal-op-ir.d.ts +19 -0
  23. package/dist/api/shared/toolchain-types.d.ts +0 -1
  24. package/dist/cli.js +31 -9
  25. package/dist/config-loader.d.ts +0 -2
  26. package/dist/config-loader.js +13 -6
  27. package/dist/config-schema.d.ts +17 -230
  28. package/dist/config-schema.js +1 -3
  29. package/dist/contract/contract-parser.d.ts +9 -127
  30. package/dist/create/board-checklist.js +1 -1
  31. package/dist/create/board-codegen.js +4 -4
  32. package/dist/create/board-spec.d.ts +74 -530
  33. package/dist/create/debug-artifacts.d.ts +20 -0
  34. package/dist/create/debug-artifacts.js +69 -0
  35. package/dist/create/editor-integration.d.ts +30 -0
  36. package/dist/create/editor-integration.js +218 -0
  37. package/dist/create/eslint-rules-template.d.ts +2 -2
  38. package/dist/create/framework-catalog.d.ts +8 -0
  39. package/dist/create/framework-catalog.js +28 -5
  40. package/dist/create/index.d.ts +9 -6
  41. package/dist/create/index.js +6 -4
  42. package/dist/create/{init-scaffold.d.ts → scaffold.d.ts} +5 -4
  43. package/dist/create/{init-scaffold.js → scaffold.js} +38 -2
  44. package/dist/create/templates.d.ts +29 -0
  45. package/dist/create/{init-templates.js → templates.js} +15 -2
  46. package/dist/create/wizard.d.ts +8 -0
  47. package/dist/create/{init-wizard.js → wizard.js} +11 -4
  48. package/dist/emit/compliance/rules.js +18 -4
  49. package/dist/emit/emitters/function-emitter-impl.js +7 -1
  50. package/dist/emit/emitters/line-appender.js +6 -0
  51. package/dist/emit/emitters/setup.js +22 -0
  52. package/dist/emit/emitters/ui-emitter.js +40 -15
  53. package/dist/emit/route-hal-op.js +55 -1
  54. package/dist/emit/statement-renderer.js +5 -2
  55. package/dist/ir/build-ir.js +46 -4
  56. package/dist/ir/expression-to-ir.js +17 -0
  57. package/dist/ir/hal/hal-emitter.js +23 -5
  58. package/dist/ir/hal/hal-plugins.js +11 -0
  59. package/dist/ir/pin-mode-validation.js +32 -9
  60. package/dist/ir/pin-state-tracking.d.ts +58 -0
  61. package/dist/ir/pin-state-tracking.js +182 -0
  62. package/dist/ir/program-analysis.d.ts +6 -0
  63. package/dist/ir/program-analysis.js +38 -0
  64. package/dist/ir/statement-to-ir.js +14 -0
  65. package/dist/ir/transformers/control-flow.js +29 -0
  66. package/dist/ir/transformers/ui-call-resolver.js +105 -1
  67. package/dist/ir/ui-element-auto-wire.js +7 -4
  68. package/dist/library/catalog.d.ts +35 -0
  69. package/dist/library/catalog.js +67 -0
  70. package/dist/library/cli.d.ts +2 -0
  71. package/dist/library/cli.js +168 -0
  72. package/dist/library/init.d.ts +25 -0
  73. package/dist/library/init.js +397 -0
  74. package/dist/library/install.d.ts +9 -0
  75. package/dist/library/install.js +80 -0
  76. package/dist/library/registry-search.d.ts +34 -0
  77. package/dist/library/registry-search.js +50 -0
  78. package/dist/library/validate.d.ts +17 -0
  79. package/dist/library/validate.js +175 -0
  80. package/dist/library-packages.d.ts +95 -0
  81. package/dist/library-packages.js +275 -0
  82. package/dist/orchestrator/graph-builder.d.ts +4 -1
  83. package/dist/orchestrator/graph-builder.js +23 -1
  84. package/dist/preview/api-shared-shim.d.ts +1 -0
  85. package/dist/preview/api-shared-shim.js +7 -0
  86. package/dist/preview/client.js +235 -2
  87. package/dist/preview/server.js +154 -62
  88. package/dist/testing.d.ts +2 -2
  89. package/dist/testing.js +1 -1
  90. package/dist/theme-tokens.d.ts +22 -0
  91. package/dist/theme-tokens.js +172 -0
  92. package/dist/transpile.js +90 -5
  93. package/dist/types.d.ts +24 -0
  94. package/dist/ui-hook.d.ts +7 -0
  95. package/dist/utils/cli.d.ts +2 -2
  96. package/dist/utils/cli.js +77 -3
  97. package/dist/utils/ui.d.ts +5 -0
  98. package/dist/utils/ui.js +7 -0
  99. package/package.json +18 -9
  100. package/dist/create/init-templates.d.ts +0 -28
  101. package/dist/create/init-wizard.d.ts +0 -8
@@ -0,0 +1,20 @@
1
+ export interface FrameworkDebugArtifactsOptions {
2
+ /** Framework package name (e.g. '@typecad/framework-zephyr'), if known. */
3
+ frameworkPackage?: string;
4
+ /** Absolute path to the new cuttlefish project root. */
5
+ workspaceRoot: string;
6
+ /** The framework build target (e.g. Zephyr board id), if known. */
7
+ buildTarget?: string;
8
+ }
9
+ /**
10
+ * Module loader for the framework package. Returns the loaded module, or
11
+ * undefined when it cannot be resolved. Injectable for tests.
12
+ */
13
+ export type FrameworkModuleLoader = (packageName: string) => any | undefined;
14
+ /**
15
+ * Generate the framework's starter debug artifacts for a new project.
16
+ * Returns the workspace-relative paths written (e.g. ['.vscode/launch.json']),
17
+ * or [] when the framework has no debug support, isn't resolvable yet, or the
18
+ * generator failed (warns, never throws).
19
+ */
20
+ export declare function generateFrameworkDebugArtifacts(o: FrameworkDebugArtifactsOptions, loadModule?: FrameworkModuleLoader): string[];
@@ -0,0 +1,69 @@
1
+ // ---------------------------------------------------------------------------
2
+ // debug-artifacts.ts — create-time framework debug profile generation
3
+ //
4
+ // `cuttlefish create` scaffolds a project that has never been built, so the
5
+ // framework toolchain's post-build debug-artifact writer (e.g. Zephyr's
6
+ // writeDebugConfig, which runs after a successful `west build --debug`) has
7
+ // never had a chance to run. Without a .vscode/launch.json, pressing F5 in VS
8
+ // Code just opens the "select a debugger" menu.
9
+ //
10
+ // Frameworks that support native debugging may export
11
+ // `writeProjectDebugArtifacts({ workspaceRoot, buildTarget })` from their
12
+ // package root (see @typecad/framework-zephyr). This helper loads the freshly
13
+ // installed framework package and invokes that export — the framework decides
14
+ // whether the target is debug-capable and what to write. Frameworks without
15
+ // the export (or targets without native debug support) are a silent no-op.
16
+ //
17
+ // Everything here is best-effort: a failed or skipped generation never fails
18
+ // `cuttlefish create`, because the first `--debug` build still writes the
19
+ // artifacts the hard way.
20
+ // ---------------------------------------------------------------------------
21
+ import path from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { loadFrameworkPackage } from "../framework-package.js";
24
+ const cliModuleDir = path.dirname(fileURLToPath(import.meta.url));
25
+ /**
26
+ * Generate the framework's starter debug artifacts for a new project.
27
+ * Returns the workspace-relative paths written (e.g. ['.vscode/launch.json']),
28
+ * or [] when the framework has no debug support, isn't resolvable yet, or the
29
+ * generator failed (warns, never throws).
30
+ */
31
+ export function generateFrameworkDebugArtifacts(o, loadModule) {
32
+ if (!o.frameworkPackage || !o.workspaceRoot)
33
+ return [];
34
+ // Default loader: prefer the new project's node_modules (populated by the
35
+ // create install step), then the CLI's own module location (monorepo /
36
+ // --no-install flows where the framework is a workspace sibling of the
37
+ // running cuttlefish).
38
+ const loader = loadModule ?? ((packageName) => {
39
+ for (const dir of [o.workspaceRoot, cliModuleDir]) {
40
+ try {
41
+ return loadFrameworkPackage(packageName, dir);
42
+ }
43
+ catch {
44
+ // not resolvable from this location — try the next
45
+ }
46
+ }
47
+ return undefined;
48
+ });
49
+ let mod;
50
+ try {
51
+ mod = loader(o.frameworkPackage);
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ if (!mod || typeof mod.writeProjectDebugArtifacts !== "function")
57
+ return [];
58
+ try {
59
+ const written = mod.writeProjectDebugArtifacts({
60
+ workspaceRoot: o.workspaceRoot,
61
+ buildTarget: o.buildTarget,
62
+ });
63
+ return Array.isArray(written) ? written.map(String) : [];
64
+ }
65
+ catch (e) {
66
+ console.warn(`! Debug profile generation failed: ${e.message}`);
67
+ return [];
68
+ }
69
+ }
@@ -0,0 +1,30 @@
1
+ /** VS Code extension identifiers (publisher.name) of the bundled extensions. */
2
+ export declare const TYPECAD_UI_EXTENSION_ID = "typecad.typecad-ui";
3
+ export declare const TYPECAD_DEBUG_EXTENSION_ID = "typecad.vscode-typecad-debug";
4
+ /**
5
+ * Source location of an unpacked bundled extension inside the cuttlefish
6
+ * package. Resolved from this module's location so it works from both src/
7
+ * (dev and tests importing source) and dist/ (the published CLI) — assets/
8
+ * sits at the package root either way.
9
+ */
10
+ export declare function bundledExtensionSourceDir(dir: string): string;
11
+ /** Kept for compatibility with the original single-extension helper. */
12
+ export declare function typecadUiExtensionSourceDir(): string;
13
+ /** Content of .vscode/extensions.json — auto-installs the workspace-bundled extensions. */
14
+ export declare function generateExtensionsJson(): string;
15
+ export declare const WATCH_TASK_LABEL = "cuttlefish: watch build";
16
+ /** The watch task entry written to .vscode/tasks.json. */
17
+ export declare function watchBuildTask(): Record<string, unknown>;
18
+ /**
19
+ * Content of .vscode/tasks.json containing the watch task. Kept separate from
20
+ * the merge logic so tests can assert the shape without a filesystem.
21
+ */
22
+ export declare function generateTasksJson(existingTasks?: ReadonlyArray<Record<string, unknown>>): string;
23
+ /**
24
+ * Copy the bundled extensions into <outDir>/.vscode/extensions/, then write the
25
+ * companion .vscode/extensions.json and — when the project has a `dev` watch
26
+ * script — the .vscode/tasks.json watch task. Returns the absolute paths
27
+ * written, or [] when the extension assets are missing (warns, never throws).
28
+ * `assetsRoot` overrides the bundled-asset location (tests).
29
+ */
30
+ export declare function writeEditorIntegration(outDir: string, assetsRoot?: string): string[];
@@ -0,0 +1,218 @@
1
+ // ---------------------------------------------------------------------------
2
+ // editor-integration.ts — create-time editor scaffolding for .ui highlighting
3
+ //
4
+ // Cuttlefish projects keep their UI in .ui single-file components (TS script +
5
+ // CSS style + Svelte-style markup), which no editor knows out of the box. The
6
+ // package ships grammar-only VS Code extensions under
7
+ // assets/editor-extensions/ and `cuttlefish create` copies them into the new
8
+ // project's .vscode/extensions/ folder:
9
+ //
10
+ // typecad-ui — .ui syntax highlighting, snippets, file icons, markdown
11
+ // ```ui fence highlighting (grammar-only, no code).
12
+ // typecad-debug — the built TypeCAD Debug extension (breakpoint syncing +
13
+ // F5 commands), so debugging works with zero install.
14
+ //
15
+ // VS Code (1.89+, trusted workspaces) detects workspace-bundled extensions and
16
+ // installs them scoped to that workspace. The companion .vscode/extensions.json
17
+ // carries forceInstall entries (microsoft/vscode#299830) so VS Code builds with
18
+ // that feature skip the approval prompt; older builds show a one-time install
19
+ // prompt instead.
20
+ //
21
+ // A .vscode/tasks.json watch task is also written: it runs the project's
22
+ // `cuttlefish build --watch` dev script with NO_COLOR=1 and parses the
23
+ // transpiler/ESLint diagnostic lines into Problems-panel squiggles. The task
24
+ // merges by label (never clobbers user edits or the framework debug writer's
25
+ // tasks, which merge the same way).
26
+ //
27
+ // Everything here is best-effort like the debug-artifact generation: a missing
28
+ // or unreadable asset tree warns and skips rather than failing `cuttlefish
29
+ // create`, because a project without editor integration still builds fine.
30
+ // ---------------------------------------------------------------------------
31
+ import fs from 'node:fs';
32
+ import path from 'node:path';
33
+ import { fileURLToPath } from 'node:url';
34
+ /** VS Code extension identifiers (publisher.name) of the bundled extensions. */
35
+ export const TYPECAD_UI_EXTENSION_ID = 'typecad.typecad-ui';
36
+ export const TYPECAD_DEBUG_EXTENSION_ID = 'typecad.vscode-typecad-debug';
37
+ /** Extensions to bundle, as { asset folder name → extension id } pairs. */
38
+ const BUNDLED_EXTENSIONS = [
39
+ { dir: 'typecad-ui', id: TYPECAD_UI_EXTENSION_ID },
40
+ { dir: 'typecad-debug', id: TYPECAD_DEBUG_EXTENSION_ID },
41
+ ];
42
+ const ASSETS_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'assets', 'editor-extensions');
43
+ /**
44
+ * Source location of an unpacked bundled extension inside the cuttlefish
45
+ * package. Resolved from this module's location so it works from both src/
46
+ * (dev and tests importing source) and dist/ (the published CLI) — assets/
47
+ * sits at the package root either way.
48
+ */
49
+ export function bundledExtensionSourceDir(dir) {
50
+ return path.join(ASSETS_ROOT, dir);
51
+ }
52
+ /** Kept for compatibility with the original single-extension helper. */
53
+ export function typecadUiExtensionSourceDir() {
54
+ return bundledExtensionSourceDir('typecad-ui');
55
+ }
56
+ /** Content of .vscode/extensions.json — auto-installs the workspace-bundled extensions. */
57
+ export function generateExtensionsJson() {
58
+ return `${JSON.stringify({ forceInstall: BUNDLED_EXTENSIONS.map(e => e.id) }, null, 2)}\n`;
59
+ }
60
+ // -- watch task + problem matchers ------------------------------------------------
61
+ /**
62
+ * Problem matchers for cuttlefish's two diagnostic line formats:
63
+ * transpiler: "<file> error|warning [code] (line,column): message"
64
+ * eslint gate: "error <file>(line,column) [rule]: message"
65
+ * NO_COLOR=1 is set on the task so chalk stays off and the lines stay greppable.
66
+ */
67
+ const PROBLEM_MATCHERS = [
68
+ {
69
+ owner: 'cuttlefish-transpiler',
70
+ severity: 'error',
71
+ fileLocation: ['relative', '${workspaceFolder}'],
72
+ pattern: {
73
+ regexp: '^\\s*(.*?)\\s+(error|warning)(?:\\s+\\[[^\\]]*\\])?\\s*\\((\\d+),(\\d+)\\):\\s+(.*)$',
74
+ file: 1,
75
+ severity: 2,
76
+ line: 3,
77
+ column: 4,
78
+ message: 5,
79
+ },
80
+ background: {
81
+ beginsPattern: 'Transpiling',
82
+ endsPattern: 'Watching for changes',
83
+ },
84
+ },
85
+ {
86
+ owner: 'cuttlefish-eslint',
87
+ severity: 'error',
88
+ fileLocation: ['relative', '${workspaceFolder}'],
89
+ pattern: {
90
+ regexp: '^\\s*error\\s+(.*?)\\((\\d+),(\\d+)\\)\\s+\\[[^\\]]*\\]:\\s+(.*)$',
91
+ file: 1,
92
+ line: 2,
93
+ column: 3,
94
+ message: 4,
95
+ },
96
+ background: {
97
+ beginsPattern: 'Transpiling',
98
+ endsPattern: 'Watching for changes',
99
+ },
100
+ },
101
+ ];
102
+ export const WATCH_TASK_LABEL = 'cuttlefish: watch build';
103
+ /** The watch task entry written to .vscode/tasks.json. */
104
+ export function watchBuildTask() {
105
+ return {
106
+ label: WATCH_TASK_LABEL,
107
+ command: 'npm run dev',
108
+ type: 'shell',
109
+ isBackground: true,
110
+ group: 'build',
111
+ options: { env: { NO_COLOR: '1' } },
112
+ problemMatcher: PROBLEM_MATCHERS,
113
+ };
114
+ }
115
+ /**
116
+ * Content of .vscode/tasks.json containing the watch task. Kept separate from
117
+ * the merge logic so tests can assert the shape without a filesystem.
118
+ */
119
+ export function generateTasksJson(existingTasks = []) {
120
+ const tasks = [...existingTasks];
121
+ const idx = tasks.findIndex(t => t.label === WATCH_TASK_LABEL);
122
+ const task = watchBuildTask();
123
+ if (idx >= 0)
124
+ tasks[idx] = task;
125
+ else
126
+ tasks.push(task);
127
+ return `${JSON.stringify({ version: '2.0.0', tasks }, null, 2)}\n`;
128
+ }
129
+ /**
130
+ * Read-merge-write .vscode/tasks.json with the watch task (by label), so user
131
+ * edits and the framework debug writer's tasks survive. Mirrors the Zephyr
132
+ * debug-config merge semantics.
133
+ */
134
+ function writeTasksJson(vscodeDir) {
135
+ const tasksPath = path.join(vscodeDir, 'tasks.json');
136
+ let existing = [];
137
+ if (fs.existsSync(tasksPath)) {
138
+ try {
139
+ const doc = JSON.parse(fs.readFileSync(tasksPath, 'utf-8'));
140
+ const tasks = doc.tasks;
141
+ if (Array.isArray(tasks)) {
142
+ existing = tasks.filter((t) => !!t && typeof t === 'object');
143
+ }
144
+ }
145
+ catch {
146
+ // malformed — start fresh
147
+ }
148
+ }
149
+ fs.writeFileSync(tasksPath, generateTasksJson(existing), 'utf-8');
150
+ return tasksPath;
151
+ }
152
+ // -- install -----------------------------------------------------------------
153
+ /** All files under `dir`, as paths relative to `dir`. */
154
+ function listFilesRecursive(dir) {
155
+ const out = [];
156
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
157
+ if (entry.isDirectory()) {
158
+ for (const nested of listFilesRecursive(path.join(dir, entry.name))) {
159
+ out.push(path.join(entry.name, nested));
160
+ }
161
+ }
162
+ else {
163
+ out.push(entry.name);
164
+ }
165
+ }
166
+ return out;
167
+ }
168
+ /**
169
+ * Whether <outDir> has an `npm run dev` script for the watch task to drive.
170
+ * The task is skipped when it doesn't (e.g. the monorepo root or hand-made
171
+ * projects), because a task referencing a missing script just errors on run.
172
+ */
173
+ function hasDevScript(outDir) {
174
+ try {
175
+ const pkg = JSON.parse(fs.readFileSync(path.join(outDir, 'package.json'), 'utf-8'));
176
+ const scripts = pkg.scripts;
177
+ return typeof scripts?.dev === 'string';
178
+ }
179
+ catch {
180
+ return false;
181
+ }
182
+ }
183
+ /**
184
+ * Copy the bundled extensions into <outDir>/.vscode/extensions/, then write the
185
+ * companion .vscode/extensions.json and — when the project has a `dev` watch
186
+ * script — the .vscode/tasks.json watch task. Returns the absolute paths
187
+ * written, or [] when the extension assets are missing (warns, never throws).
188
+ * `assetsRoot` overrides the bundled-asset location (tests).
189
+ */
190
+ export function writeEditorIntegration(outDir, assetsRoot) {
191
+ const vscodeDir = path.join(outDir, '.vscode');
192
+ const extensionRoot = path.join(vscodeDir, 'extensions');
193
+ const root = assetsRoot ?? ASSETS_ROOT;
194
+ const written = [];
195
+ let copiedAny = false;
196
+ for (const ext of BUNDLED_EXTENSIONS) {
197
+ const src = path.join(root, ext.dir);
198
+ if (!fs.existsSync(path.join(src, 'package.json'))) {
199
+ console.warn(`! Editor integration: ${ext.dir} extension assets not found at ${src} — skipped`);
200
+ continue;
201
+ }
202
+ const dest = path.join(extensionRoot, ext.dir);
203
+ fs.mkdirSync(dest, { recursive: true });
204
+ fs.cpSync(src, dest, { recursive: true });
205
+ for (const rel of listFilesRecursive(dest))
206
+ written.push(path.join(dest, rel));
207
+ copiedAny = true;
208
+ }
209
+ if (!copiedAny)
210
+ return [];
211
+ const extensionsJsonPath = path.join(vscodeDir, 'extensions.json');
212
+ fs.writeFileSync(extensionsJsonPath, generateExtensionsJson(), 'utf-8');
213
+ written.push(extensionsJsonPath);
214
+ if (hasDevScript(outDir)) {
215
+ written.push(writeTasksJson(vscodeDir));
216
+ }
217
+ return written;
218
+ }
@@ -1,2 +1,2 @@
1
- import type { InitProjectOptions } from "./init-templates.js";
2
- export declare function generateEslintRules(_options: InitProjectOptions): string;
1
+ import type { CreateProjectOptions } from "./templates.js";
2
+ export declare function generateEslintRules(_options: CreateProjectOptions): string;
@@ -53,6 +53,14 @@ export declare function frameworkTargetProfile(target: TargetProfileInput, frame
53
53
  * common framework is offered first in the prompt.
54
54
  */
55
55
  export declare function frameworksForTarget(target: BoardLike): FrameworkCatalogEntry[];
56
+ /**
57
+ * Whether `frameworkId` is actually offered for `target`: compatible with the
58
+ * board's architecture AND installable. Auto-picking narrows via
59
+ * frameworksForTarget, but an explicit `--framework` bypasses that narrowing —
60
+ * callers accepting one must reject anything this returns false for (e.g.
61
+ * `--framework arduino` on the Zephyr-only xiao-nrf52840).
62
+ */
63
+ export declare function frameworkCompatibleWithTarget(target: BoardLike, frameworkId: string): boolean;
56
64
  export type PackageManager = "npm" | "pnpm" | "yarn";
57
65
  /**
58
66
  * Detect the package manager for a directory. Priority:
@@ -21,7 +21,7 @@ export const FRAMEWORK_CATALOG = [
21
21
  * Architecture → compatible framework ids. Derived from each framework
22
22
  * package's framework.manifest.ts `profile.targets`:
23
23
  * - arduino: avr, esp32 family, rp2040/rp2350, samd, stm32
24
- * - zephyr: nrf52 (xiao_ble), esp32, esp32s3
24
+ * - zephyr: nrf52 (xiao_ble), esp32 family (esp32, esp32s3, esp32c3, esp32c6)
25
25
  * - native: desktop only
26
26
  * Unknown embedded architectures fall back to [arduino] (the broadest core).
27
27
  */
@@ -30,11 +30,15 @@ const ARCHITECTURE_FRAMEWORKS = {
30
30
  esp32: ["arduino", "zephyr"],
31
31
  esp32s2: ["arduino"],
32
32
  esp32s3: ["arduino", "zephyr"],
33
- esp32c3: ["arduino"],
34
- esp32c6: ["arduino"],
33
+ esp32c3: ["arduino", "zephyr"],
34
+ esp32c6: ["arduino", "zephyr"],
35
35
  rp2040: ["arduino"],
36
36
  rp2350: ["arduino"],
37
37
  samd: ["arduino"],
38
+ // Per-chip key (esp32c3/c6 style). The generic 'stm32' entry stays
39
+ // ["arduino"] for future STM32duino support; the F411 Black Pill is
40
+ // Zephyr-only today.
41
+ stm32f411: ["zephyr"],
38
42
  stm32: ["arduino"],
39
43
  nrf52: ["zephyr"],
40
44
  };
@@ -66,8 +70,17 @@ const FRAMEWORK_TOOLCHAIN = {
66
70
  const ZEPHYR_BOARD_IDS = {
67
71
  "esp32-devkit": "esp32_devkitc/esp32/procpu",
68
72
  esp32s3: "esp32s3_devkitc/esp32s3/procpu",
69
- // xiao_ble isn't currently a KNOWN_TARGETS entry, but keep the descriptor
70
- // correct for completeness: nRF52840 (single core), base (non-sense) variant.
73
+ // Single-variant RISC-V board the bare id is also accepted, but the
74
+ // qualified form is kept for consistency (verified against Zephyr 4.3
75
+ // boards/espressif/esp32c3_devkitm).
76
+ esp32c3: "esp32c3_devkitm/esp32c3",
77
+ // hpcore/lpcore cpucluster variants — the qualified form is required (the
78
+ // bare id is rejected); hpcore is the application core.
79
+ esp32c6: "esp32c6_devkitc/esp32c6/hpcore",
80
+ // First STM32 target — WeAct Black Pill V2.0 (STM32F411CEU6). Single
81
+ // variant; qualified for consistency with every other catalog target.
82
+ "blackpill-f411ce": "blackpill_f411ce/stm32f411xe",
83
+ // nRF52840 (single core), base (non-sense) variant.
71
84
  "xiao-nrf52840": "xiao_ble/nrf52840",
72
85
  };
73
86
  /**
@@ -105,6 +118,16 @@ export function frameworksForTarget(target) {
105
118
  // any id that has no catalog entry (defensive — keeps the prompt clean).
106
119
  return FRAMEWORK_CATALOG.filter((entry) => ids.includes(entry.id));
107
120
  }
121
+ /**
122
+ * Whether `frameworkId` is actually offered for `target`: compatible with the
123
+ * board's architecture AND installable. Auto-picking narrows via
124
+ * frameworksForTarget, but an explicit `--framework` bypasses that narrowing —
125
+ * callers accepting one must reject anything this returns false for (e.g.
126
+ * `--framework arduino` on the Zephyr-only xiao-nrf52840).
127
+ */
128
+ export function frameworkCompatibleWithTarget(target, frameworkId) {
129
+ return frameworksForTarget(target).some((f) => f.id === frameworkId && f.installable);
130
+ }
108
131
  /**
109
132
  * Detect the package manager for a directory. Priority:
110
133
  * 1. package.json#packageManager field (the strongest signal, Corepack-style)
@@ -1,12 +1,15 @@
1
- export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget, printInitNextSteps } from './init-scaffold.js';
2
- export type { KnownTarget, KnownBoard, ScaffoldProjectResult } from './init-scaffold.js';
3
- export { FRAMEWORK_CATALOG, frameworksForTarget, frameworkCatalogEntry, detectPackageManager, frameworkTargetProfile } from './framework-catalog.js';
1
+ export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget, printCreateNextSteps } from './scaffold.js';
2
+ export type { KnownTarget, KnownBoard, ScaffoldProjectResult } from './scaffold.js';
3
+ export { FRAMEWORK_CATALOG, frameworksForTarget, frameworkCompatibleWithTarget, frameworkCatalogEntry, detectPackageManager, frameworkTargetProfile } from './framework-catalog.js';
4
4
  export type { FrameworkCatalogEntry, BoardLike, PackageManager, FrameworkTargetProfile, TargetProfileInput } from './framework-catalog.js';
5
5
  export { installProjectDependencies, __setProjectInstallRunnerForTest } from './install-deps.js';
6
6
  export type { ProjectInstallResult } from './install-deps.js';
7
- export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig, } from './init-templates.js';
8
- export type { InitProjectOptions } from './init-templates.js';
9
- export { runInitWizard } from './init-wizard.js';
7
+ export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEditorconfig, generateEslintConfig, } from './templates.js';
8
+ export type { CreateProjectOptions } from './templates.js';
9
+ export { runCreateWizard } from './wizard.js';
10
+ export { generateFrameworkDebugArtifacts } from './debug-artifacts.js';
11
+ export type { FrameworkDebugArtifactsOptions, FrameworkModuleLoader } from './debug-artifacts.js';
12
+ export { generateExtensionsJson, generateTasksJson, watchBuildTask, writeEditorIntegration, bundledExtensionSourceDir, typecadUiExtensionSourceDir, TYPECAD_UI_EXTENSION_ID, TYPECAD_DEBUG_EXTENSION_ID, WATCH_TASK_LABEL, } from './editor-integration.js';
10
13
  export { scaffoldBoardPackages } from './board-codegen.js';
11
14
  export type { ScaffoldBoardResult } from './board-codegen.js';
12
15
  export { generateFrameworkChecklist } from './board-checklist.js';
@@ -1,8 +1,10 @@
1
- export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget, printInitNextSteps } from './init-scaffold.js';
2
- export { FRAMEWORK_CATALOG, frameworksForTarget, frameworkCatalogEntry, detectPackageManager, frameworkTargetProfile } from './framework-catalog.js';
1
+ export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget, printCreateNextSteps } from './scaffold.js';
2
+ export { FRAMEWORK_CATALOG, frameworksForTarget, frameworkCompatibleWithTarget, frameworkCatalogEntry, detectPackageManager, frameworkTargetProfile } from './framework-catalog.js';
3
3
  export { installProjectDependencies, __setProjectInstallRunnerForTest } from './install-deps.js';
4
- export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig, } from './init-templates.js';
5
- export { runInitWizard } from './init-wizard.js';
4
+ export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEditorconfig, generateEslintConfig, } from './templates.js';
5
+ export { runCreateWizard } from './wizard.js';
6
+ export { generateFrameworkDebugArtifacts } from './debug-artifacts.js';
7
+ export { generateExtensionsJson, generateTasksJson, watchBuildTask, writeEditorIntegration, bundledExtensionSourceDir, typecadUiExtensionSourceDir, TYPECAD_UI_EXTENSION_ID, TYPECAD_DEBUG_EXTENSION_ID, WATCH_TASK_LABEL, } from './editor-integration.js';
6
8
  // Board codegen tool (`cuttlefish board add`)
7
9
  export { scaffoldBoardPackages } from './board-codegen.js';
8
10
  export { generateFrameworkChecklist } from './board-checklist.js';
@@ -1,5 +1,5 @@
1
1
  import type { ArchitectureIdentifier } from "../api/index.js";
2
- import type { InitProjectOptions } from "./init-templates.js";
2
+ import type { CreateProjectOptions } from "./templates.js";
3
3
  export interface KnownTarget {
4
4
  id: string;
5
5
  displayName: string;
@@ -28,9 +28,10 @@ export declare function normalizeProjectName(name: string): string;
28
28
  export interface ScaffoldProjectResult {
29
29
  createdFiles: string[];
30
30
  outDir: string;
31
- options: InitProjectOptions;
31
+ options: CreateProjectOptions;
32
32
  }
33
- export declare function scaffoldProject(options: InitProjectOptions, outDir?: string): ScaffoldProjectResult;
34
- export declare function printInitNextSteps(options: InitProjectOptions, outDir: string, opts?: {
33
+ export declare function scaffoldProject(options: CreateProjectOptions, outDir?: string): ScaffoldProjectResult;
34
+ export declare function printCreateNextSteps(options: CreateProjectOptions, outDir: string, opts?: {
35
35
  installed?: boolean;
36
+ debugProfile?: boolean;
36
37
  }): void;
@@ -1,8 +1,9 @@
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, generateStarterTest, generateStarterSim, generateGitignore, generateBoardForwardingFile, generateEslintConfig, } from "./init-templates.js";
4
+ import { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEditorconfig, generateBoardForwardingFile, generateEslintConfig, } from "./templates.js";
5
5
  import { generateEslintRules } from "./eslint-rules-template.js";
6
+ import { writeEditorIntegration } from "./editor-integration.js";
6
7
  const _knownTargets = [
7
8
  {
8
9
  id: 'native',
@@ -74,6 +75,28 @@ const _knownTargets = [
74
75
  buildTarget: 'rp2040:rp2040:rpipico2',
75
76
  mcu: 'rp2350',
76
77
  },
78
+ {
79
+ id: 'xiao-nrf52840',
80
+ displayName: 'XIAO nRF52840',
81
+ isNative: false,
82
+ architecture: 'nrf52',
83
+ boardPackage: '@typecad/board-xiao-nrf52840',
84
+ // Zephyr-only target: the build target is the `west build -b` board id,
85
+ // not an Arduino FQBN (frameworkTargetProfile resolves it for Zephyr).
86
+ buildTarget: 'xiao_ble/nrf52840',
87
+ mcu: 'nrf52840',
88
+ },
89
+ {
90
+ id: 'blackpill-f411ce',
91
+ displayName: 'Black Pill (STM32F411)',
92
+ isNative: false,
93
+ architecture: 'stm32f411',
94
+ boardPackage: '@typecad/board-blackpill-f411ce',
95
+ // Zephyr-only target: the build target is the `west build -b` board id,
96
+ // not an Arduino FQBN (frameworkTargetProfile resolves it for Zephyr).
97
+ buildTarget: 'blackpill_f411ce/stm32f411xe',
98
+ mcu: 'stm32f411',
99
+ },
77
100
  ];
78
101
  export function registerKnownTarget(target) {
79
102
  const existing = _knownTargets.findIndex(t => t.id === target.id);
@@ -125,6 +148,11 @@ export function scaffoldProject(options, outDir) {
125
148
  writeFile('.cuttlefish/eslint.config.mjs', generateEslintConfig(options));
126
149
  writeFile('.cuttlefish/eslint-transpiler-rules.mjs', generateEslintRules(options));
127
150
  writeFile('.gitignore', generateGitignore(options));
151
+ writeFile('.editorconfig', generateEditorconfig(options));
152
+ // Editor integration: workspace-bundled VS Code extension for .ui syntax
153
+ // highlighting (best-effort — warns and skips if the assets are missing).
154
+ for (const file of writeEditorIntegration(resolvedOutDir))
155
+ createdFiles.push(file);
128
156
  if (options.boardPackage) {
129
157
  const boardFilePath = path.join(cuttlefishDir, 'board.ts');
130
158
  fs.writeFileSync(boardFilePath, generateBoardForwardingFile(options.boardPackage), 'utf-8');
@@ -156,7 +184,7 @@ export function scaffoldProject(options, outDir) {
156
184
  }
157
185
  return { createdFiles, outDir: resolvedOutDir, options };
158
186
  }
159
- export function printInitNextSteps(options, outDir, opts = {}) {
187
+ export function printCreateNextSteps(options, outDir, opts = {}) {
160
188
  const relativeDir = path.relative(process.cwd(), outDir) || '.';
161
189
  console.log();
162
190
  console.log(chalk.cyan(`⤳ Cuttlefish`));
@@ -168,6 +196,14 @@ export function printInitNextSteps(options, outDir, opts = {}) {
168
196
  console.log(chalk.dim(` npm install`));
169
197
  }
170
198
  console.log(` ${chalk.cyan("npm run compile")}`);
199
+ if (opts.debugProfile) {
200
+ console.log();
201
+ console.log(chalk.bold.white("To debug (VS Code):"));
202
+ console.log(` ${chalk.cyan("open the folder and press F5")} ${chalk.dim("(builds + flashes, then attaches GDB)")}`);
203
+ }
204
+ console.log();
205
+ console.log(chalk.bold.white("To edit .ui files (VS Code):"));
206
+ console.log(` ${chalk.cyan("open the folder and approve the workspace extension")} ${chalk.dim("(adds .ui syntax highlighting)")}`);
171
207
  if (!options.isNative) {
172
208
  const portHint = process.platform === 'win32' ? 'COM4' : '/dev/ttyACM0';
173
209
  console.log();
@@ -0,0 +1,29 @@
1
+ import type { ArchitectureIdentifier } from '../api/index.js';
2
+ export interface CreateProjectOptions {
3
+ projectName: string;
4
+ targetId: string;
5
+ targetDisplayName: string;
6
+ isNative: boolean;
7
+ architecture?: ArchitectureIdentifier;
8
+ boardPackage?: string;
9
+ frameworkPackage: string;
10
+ framework: string;
11
+ buildTarget?: string;
12
+ mcu?: string;
13
+ baudRate?: number;
14
+ includeSketch: boolean;
15
+ toolchainType?: string;
16
+ /** Extra frameworkData fields (e.g. `{ target: 'esp32s3' }` for framework-esp32). */
17
+ frameworkData?: Record<string, unknown>;
18
+ }
19
+ export declare function generateProjectPackageJson(options: CreateProjectOptions): string;
20
+ export declare function generateProjectTsconfig(options: CreateProjectOptions): string;
21
+ export declare function generateProjectConfig(options: CreateProjectOptions): string;
22
+ export declare function generateProjectEnvDts(options: CreateProjectOptions): string;
23
+ export declare function generateStarterSketch(options: CreateProjectOptions): string;
24
+ export declare function generateStarterTest(_options: CreateProjectOptions): string;
25
+ export declare function generateStarterSim(options: CreateProjectOptions): string;
26
+ export declare function generateBoardForwardingFile(boardPackage: string): string;
27
+ export declare function generateGitignore(_options: CreateProjectOptions): string;
28
+ export declare function generateEditorconfig(_options: CreateProjectOptions): string;
29
+ export declare function generateEslintConfig(_options: CreateProjectOptions): string;
@@ -149,7 +149,6 @@ const config = {
149
149
 
150
150
  // Output options
151
151
  output: {
152
- optimize: 'speed',
153
152
  outDir: './out',
154
153
  },
155
154
  };
@@ -198,7 +197,6 @@ const config: CuttlefishConfig = {
198
197
  // Output / build options
199
198
  output: {
200
199
  framework: '${options.framework}',
201
- optimize: 'size',
202
200
  outDir: './out',
203
201
  },
204
202
 
@@ -559,6 +557,21 @@ dist/
559
557
  .cuttlefish/eslint-transpiler-rules.mjs
560
558
  `;
561
559
  }
560
+ export function generateEditorconfig(_options) {
561
+ return `# Auto-generated by 'cuttlefish create'. Edit to customise your build.
562
+ root = true
563
+
564
+ [*]
565
+ charset = utf-8
566
+ end_of_line = lf
567
+ insert_final_newline = true
568
+ trim_trailing_whitespace = true
569
+
570
+ [*.{ts,js,mjs,cjs,json,css,ui,md,yml}]
571
+ indent_style = space
572
+ indent_size = 2
573
+ `;
574
+ }
562
575
  export function generateEslintConfig(_options) {
563
576
  // `no-restricted-syntax` selectors are generated from LINT_RULES
564
577
  // (packages/cuttlefish/src/ir/feature-registry.ts), the single source of
@@ -0,0 +1,8 @@
1
+ import type { CreateProjectOptions } from "./templates.js";
2
+ export declare function runCreateWizard(partialOptions?: {
3
+ projectName?: string;
4
+ board?: string;
5
+ framework?: string;
6
+ baud?: number;
7
+ noSketch?: boolean;
8
+ }): Promise<CreateProjectOptions | null>;