@typecad/cuttlefish 1.0.0-alpha.13 → 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 (64) 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/api/config.d.ts +8 -1
  16. package/dist/api/shared/framework-manifest.d.ts +61 -504
  17. package/dist/cli.js +17 -6
  18. package/dist/config-loader.js +3 -1
  19. package/dist/config-schema.d.ts +17 -223
  20. package/dist/config-schema.js +1 -0
  21. package/dist/contract/contract-parser.d.ts +9 -127
  22. package/dist/create/board-checklist.js +1 -1
  23. package/dist/create/board-codegen.js +4 -4
  24. package/dist/create/board-spec.d.ts +74 -530
  25. package/dist/create/editor-integration.d.ts +30 -0
  26. package/dist/create/editor-integration.js +218 -0
  27. package/dist/create/eslint-rules-template.d.ts +2 -2
  28. package/dist/create/framework-catalog.d.ts +8 -0
  29. package/dist/create/framework-catalog.js +28 -5
  30. package/dist/create/index.d.ts +7 -6
  31. package/dist/create/index.js +5 -4
  32. package/dist/create/{init-scaffold.d.ts → scaffold.d.ts} +4 -4
  33. package/dist/create/{init-scaffold.js → scaffold.js} +33 -2
  34. package/dist/create/templates.d.ts +29 -0
  35. package/dist/create/{init-templates.js → templates.js} +15 -0
  36. package/dist/create/wizard.d.ts +8 -0
  37. package/dist/create/{init-wizard.js → wizard.js} +11 -4
  38. package/dist/emit/emitters/setup.js +22 -0
  39. package/dist/ir/build-ir.js +24 -3
  40. package/dist/library/catalog.d.ts +35 -0
  41. package/dist/library/catalog.js +67 -0
  42. package/dist/library/cli.d.ts +2 -0
  43. package/dist/library/cli.js +168 -0
  44. package/dist/library/init.d.ts +25 -0
  45. package/dist/library/init.js +397 -0
  46. package/dist/library/install.d.ts +9 -0
  47. package/dist/library/install.js +80 -0
  48. package/dist/library/registry-search.d.ts +34 -0
  49. package/dist/library/registry-search.js +50 -0
  50. package/dist/library/validate.d.ts +17 -0
  51. package/dist/library/validate.js +175 -0
  52. package/dist/library-packages.d.ts +95 -0
  53. package/dist/library-packages.js +275 -0
  54. package/dist/orchestrator/graph-builder.js +16 -0
  55. package/dist/preview/client.js +17 -3
  56. package/dist/testing.d.ts +2 -2
  57. package/dist/testing.js +1 -1
  58. package/dist/transpile.js +55 -0
  59. package/dist/types.d.ts +19 -0
  60. package/dist/utils/cli.d.ts +2 -2
  61. package/dist/utils/cli.js +68 -3
  62. package/package.json +16 -8
  63. package/dist/create/init-templates.d.ts +0 -28
  64. package/dist/create/init-wizard.d.ts +0 -8
@@ -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,14 +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
10
  export { generateFrameworkDebugArtifacts } from './debug-artifacts.js';
11
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';
12
13
  export { scaffoldBoardPackages } from './board-codegen.js';
13
14
  export type { ScaffoldBoardResult } from './board-codegen.js';
14
15
  export { generateFrameworkChecklist } from './board-checklist.js';
@@ -1,9 +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
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';
7
8
  // Board codegen tool (`cuttlefish board add`)
8
9
  export { scaffoldBoardPackages } from './board-codegen.js';
9
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,10 +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
36
  debugProfile?: boolean;
37
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`));
@@ -173,6 +201,9 @@ export function printInitNextSteps(options, outDir, opts = {}) {
173
201
  console.log(chalk.bold.white("To debug (VS Code):"));
174
202
  console.log(` ${chalk.cyan("open the folder and press F5")} ${chalk.dim("(builds + flashes, then attaches GDB)")}`);
175
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)")}`);
176
207
  if (!options.isNative) {
177
208
  const portHint = process.platform === 'win32' ? 'COM4' : '/dev/ttyACM0';
178
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;
@@ -557,6 +557,21 @@ dist/
557
557
  .cuttlefish/eslint-transpiler-rules.mjs
558
558
  `;
559
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
+ }
560
575
  export function generateEslintConfig(_options) {
561
576
  // `no-restricted-syntax` selectors are generated from LINT_RULES
562
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>;
@@ -1,8 +1,8 @@
1
1
  import * as readline from "node:readline/promises";
2
2
  import { stdin as input, stdout as output } from "node:process";
3
3
  import chalk from "chalk";
4
- import { KNOWN_TARGETS } from "./init-scaffold.js";
5
- import { frameworksForTarget, frameworkCatalogEntry, FRAMEWORK_CATALOG, frameworkTargetProfile } from "./framework-catalog.js";
4
+ import { KNOWN_TARGETS } from "./scaffold.js";
5
+ import { frameworksForTarget, frameworkCatalogEntry, frameworkCompatibleWithTarget, FRAMEWORK_CATALOG, frameworkTargetProfile } from "./framework-catalog.js";
6
6
  function validateProjectName(name) {
7
7
  if (!name || name.trim().length === 0) {
8
8
  return "Project name cannot be empty.";
@@ -53,7 +53,7 @@ async function promptConfirm(rl, prompt, defaultValue) {
53
53
  return defaultValue;
54
54
  return trimmed === "y" || trimmed === "yes";
55
55
  }
56
- export async function runInitWizard(partialOptions) {
56
+ export async function runCreateWizard(partialOptions) {
57
57
  const rl = readline.createInterface({ input, output });
58
58
  try {
59
59
  console.log();
@@ -101,9 +101,16 @@ export async function runInitWizard(partialOptions) {
101
101
  if (partialOptions?.framework) {
102
102
  const requested = frameworkCatalogEntry(partialOptions.framework);
103
103
  if (!requested) {
104
- const available = FRAMEWORK_CATALOG.filter((f) => f.installable).map((f) => f.id).join(", ");
104
+ const available = FRAMEWORK_CATALOG.filter(f => f.installable).map(f => f.id).join(", ");
105
105
  throw new Error(`Unknown framework '${partialOptions.framework}'. Available: ${available}`);
106
106
  }
107
+ // Same compatibility guard as the non-interactive CLI path: a preseeded
108
+ // --framework bypasses the narrowing below, so validate it explicitly.
109
+ if (!frameworkCompatibleWithTarget(target, requested.id)) {
110
+ const list = compatible.map(f => f.id).join(", ");
111
+ throw new Error(`Framework '${requested.id}' is not compatible with target '${target.id}' (${target.displayName}). ` +
112
+ `Compatible frameworks: ${list}`);
113
+ }
107
114
  framework = requested.id;
108
115
  frameworkPackage = requested.packageName;
109
116
  console.log(`${chalk.cyan("?")} Framework: ${chalk.white(requested.label)}`);
@@ -5,6 +5,7 @@ import { analyzeProgram } from "../../ir/program-analysis.js";
5
5
  import { collectStatementIdentifiers } from "../../ir/identifier-collector.js";
6
6
  import { ensureDir } from "../../utils/fs.js";
7
7
  import { resolveImport } from "../../libdef/registry.js";
8
+ import { isRegisteredCuttlefishLibrary } from "../../library-packages.js";
8
9
  import { emitPolyfillBoilerplate } from "../native-helpers-emitter.js";
9
10
  import { resolveStrategy } from "../../platform/registry.js";
10
11
  import { buildCoopSchedulerPolyfill } from "../../platform/coop-scheduler-runtime.js";
@@ -547,6 +548,14 @@ export function buildEmitterContext(program, options) {
547
548
  if (!programAnalysis.usesPreferences) {
548
549
  shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PREFERENCES_BEGIN', '// CUTTLEFISH_PREFERENCES_END');
549
550
  }
551
+ // Entry-TU marker for framework shims: the UI runtime header (ui_tick) is
552
+ // emitted ONLY into the entry file (emitUIRuntime gates on isEntryFile), so
553
+ // shim helpers that keep the UI alive during bounded blocking waits (e.g.
554
+ // Zephyr's wifi connect) must compile those calls down everywhere else.
555
+ // Defined before the shim lines land in the preamble, after all filters.
556
+ if (isEntryFile && entryHasUI() && shimLines.length > 0) {
557
+ shimLines.unshift("#define CUTTLEFISH_ENTRY_UI_TU 1");
558
+ }
550
559
  profileDiagnostics = [...strategy.profileDiagnostics(program, options.platformContext)];
551
560
  }
552
561
  for (const imported of program.imports) {
@@ -554,6 +563,19 @@ export function buildEmitterContext(program, options) {
554
563
  // named imports — add it to the symbolMap so cross-module references
555
564
  // resolve. Demo #12 Finding C — was skipped, so `encode` wasn't declared.
556
565
  const defaultName = imported.defaultImportName;
566
+ // Cuttlefish library packages: the import emits the shim-header include
567
+ // (resolved through the libdef registry from the library manifest) and
568
+ // contributes native shims + build fragments. This must run BEFORE the
569
+ // SDK skip below — @typecad-scoped libraries would otherwise be treated
570
+ // as type-level-only like the rest of the SDK.
571
+ if (isRegisteredCuttlefishLibrary(imported.moduleSpecifier)) {
572
+ const resolved = resolveImport(imported, options.libdefs, options.target, options.platformContext, program.fileName);
573
+ includes.push(normalizeInclude(resolved.include));
574
+ Object.assign(symbolMap, resolved.symbolMap);
575
+ if (defaultName)
576
+ symbolMap[defaultName] = defaultName;
577
+ continue;
578
+ }
557
579
  if (isCuttlefishSDKImport(imported.moduleSpecifier, program.fileName)) {
558
580
  for (const symbol of imported.namedImports) {
559
581
  symbolMap[symbol] = symbol;