@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.
- package/assets/editor-extensions/typecad-debug/README.md +142 -0
- package/assets/editor-extensions/typecad-debug/out/extension.js +347 -0
- package/assets/editor-extensions/typecad-debug/out/extension.js.map +1 -0
- package/assets/editor-extensions/typecad-debug/package.json +76 -0
- package/assets/editor-extensions/typecad-ui/LICENSE +27 -0
- package/assets/editor-extensions/typecad-ui/README.md +52 -0
- package/assets/editor-extensions/typecad-ui/file-icon-dark.png +0 -0
- package/assets/editor-extensions/typecad-ui/file-icon-light.png +0 -0
- package/assets/editor-extensions/typecad-ui/icon.png +0 -0
- package/assets/editor-extensions/typecad-ui/language-configuration.json +43 -0
- package/assets/editor-extensions/typecad-ui/package.json +54 -0
- package/assets/editor-extensions/typecad-ui/snippets/typecad-ui.json +80 -0
- package/assets/editor-extensions/typecad-ui/syntaxes/markdown-ui.json +45 -0
- package/assets/editor-extensions/typecad-ui/syntaxes/typecad-ui.tmLanguage.json +1269 -0
- package/dist/api/config.d.ts +8 -1
- package/dist/api/shared/framework-manifest.d.ts +61 -504
- package/dist/cli.js +17 -6
- package/dist/config-loader.js +3 -1
- package/dist/config-schema.d.ts +17 -223
- package/dist/config-schema.js +1 -0
- package/dist/contract/contract-parser.d.ts +9 -127
- package/dist/create/board-checklist.js +1 -1
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-spec.d.ts +74 -530
- package/dist/create/editor-integration.d.ts +30 -0
- package/dist/create/editor-integration.js +218 -0
- package/dist/create/eslint-rules-template.d.ts +2 -2
- package/dist/create/framework-catalog.d.ts +8 -0
- package/dist/create/framework-catalog.js +28 -5
- package/dist/create/index.d.ts +7 -6
- package/dist/create/index.js +5 -4
- package/dist/create/{init-scaffold.d.ts → scaffold.d.ts} +4 -4
- package/dist/create/{init-scaffold.js → scaffold.js} +33 -2
- package/dist/create/templates.d.ts +29 -0
- package/dist/create/{init-templates.js → templates.js} +15 -0
- package/dist/create/wizard.d.ts +8 -0
- package/dist/create/{init-wizard.js → wizard.js} +11 -4
- package/dist/emit/emitters/setup.js +22 -0
- package/dist/ir/build-ir.js +24 -3
- package/dist/library/catalog.d.ts +35 -0
- package/dist/library/catalog.js +67 -0
- package/dist/library/cli.d.ts +2 -0
- package/dist/library/cli.js +168 -0
- package/dist/library/init.d.ts +25 -0
- package/dist/library/init.js +397 -0
- package/dist/library/install.d.ts +9 -0
- package/dist/library/install.js +80 -0
- package/dist/library/registry-search.d.ts +34 -0
- package/dist/library/registry-search.js +50 -0
- package/dist/library/validate.d.ts +17 -0
- package/dist/library/validate.js +175 -0
- package/dist/library-packages.d.ts +95 -0
- package/dist/library-packages.js +275 -0
- package/dist/orchestrator/graph-builder.js +16 -0
- package/dist/preview/client.js +17 -3
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +1 -1
- package/dist/transpile.js +55 -0
- package/dist/types.d.ts +19 -0
- package/dist/utils/cli.d.ts +2 -2
- package/dist/utils/cli.js +68 -3
- package/package.json +16 -8
- package/dist/create/init-templates.d.ts +0 -28
- 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 {
|
|
2
|
-
export declare function generateEslintRules(_options:
|
|
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
|
-
//
|
|
70
|
-
//
|
|
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)
|
package/dist/create/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget,
|
|
2
|
-
export type { KnownTarget, KnownBoard, ScaffoldProjectResult } from './
|
|
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 './
|
|
8
|
-
export type {
|
|
9
|
-
export {
|
|
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';
|
package/dist/create/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget,
|
|
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 './
|
|
5
|
-
export {
|
|
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 {
|
|
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:
|
|
31
|
+
options: CreateProjectOptions;
|
|
32
32
|
}
|
|
33
|
-
export declare function scaffoldProject(options:
|
|
34
|
-
export declare function
|
|
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 "./
|
|
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
|
|
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 "./
|
|
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
|
|
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(
|
|
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;
|