@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.
- 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/add-preset.d.ts +4 -0
- package/dist/add-preset.js +74 -0
- package/dist/api/config.d.ts +8 -5
- package/dist/api/shared/display-adapters/sdl.js +1 -1
- package/dist/api/shared/display-profile.d.ts +11 -0
- package/dist/api/shared/display-profile.js +3 -0
- package/dist/api/shared/framework-manifest.d.ts +61 -504
- package/dist/api/shared/hal-op-ir.d.ts +19 -0
- package/dist/api/shared/toolchain-types.d.ts +0 -1
- package/dist/cli.js +31 -9
- package/dist/config-loader.d.ts +0 -2
- package/dist/config-loader.js +13 -6
- package/dist/config-schema.d.ts +17 -230
- package/dist/config-schema.js +1 -3
- 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/debug-artifacts.d.ts +20 -0
- package/dist/create/debug-artifacts.js +69 -0
- 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 +9 -6
- package/dist/create/index.js +6 -4
- package/dist/create/{init-scaffold.d.ts → scaffold.d.ts} +5 -4
- package/dist/create/{init-scaffold.js → scaffold.js} +38 -2
- package/dist/create/templates.d.ts +29 -0
- package/dist/create/{init-templates.js → templates.js} +15 -2
- package/dist/create/wizard.d.ts +8 -0
- package/dist/create/{init-wizard.js → wizard.js} +11 -4
- package/dist/emit/compliance/rules.js +18 -4
- package/dist/emit/emitters/function-emitter-impl.js +7 -1
- package/dist/emit/emitters/line-appender.js +6 -0
- package/dist/emit/emitters/setup.js +22 -0
- package/dist/emit/emitters/ui-emitter.js +40 -15
- package/dist/emit/route-hal-op.js +55 -1
- package/dist/emit/statement-renderer.js +5 -2
- package/dist/ir/build-ir.js +46 -4
- package/dist/ir/expression-to-ir.js +17 -0
- package/dist/ir/hal/hal-emitter.js +23 -5
- package/dist/ir/hal/hal-plugins.js +11 -0
- package/dist/ir/pin-mode-validation.js +32 -9
- package/dist/ir/pin-state-tracking.d.ts +58 -0
- package/dist/ir/pin-state-tracking.js +182 -0
- package/dist/ir/program-analysis.d.ts +6 -0
- package/dist/ir/program-analysis.js +38 -0
- package/dist/ir/statement-to-ir.js +14 -0
- package/dist/ir/transformers/control-flow.js +29 -0
- package/dist/ir/transformers/ui-call-resolver.js +105 -1
- package/dist/ir/ui-element-auto-wire.js +7 -4
- 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.d.ts +4 -1
- package/dist/orchestrator/graph-builder.js +23 -1
- package/dist/preview/api-shared-shim.d.ts +1 -0
- package/dist/preview/api-shared-shim.js +7 -0
- package/dist/preview/client.js +235 -2
- package/dist/preview/server.js +154 -62
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +1 -1
- package/dist/theme-tokens.d.ts +22 -0
- package/dist/theme-tokens.js +172 -0
- package/dist/transpile.js +90 -5
- package/dist/types.d.ts +24 -0
- package/dist/ui-hook.d.ts +7 -0
- package/dist/utils/cli.d.ts +2 -2
- package/dist/utils/cli.js +77 -3
- package/dist/utils/ui.d.ts +5 -0
- package/dist/utils/ui.js +7 -0
- package/package.json +18 -9
- package/dist/create/init-templates.d.ts +0 -28
- package/dist/create/init-wizard.d.ts +0 -8
package/dist/preview/server.js
CHANGED
|
@@ -1,55 +1,83 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
5
6
|
import chalk from "chalk";
|
|
6
7
|
import { findConfigFile, parseConfigFile } from "../config-loader.js";
|
|
7
|
-
import { requireUIHook } from "../ui-hook.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
<meta
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
8
|
+
import { requireUIHook, hasUIHook } from "../ui-hook.js";
|
|
9
|
+
import { loadUIEngine } from "../ui/ui-bridge.js";
|
|
10
|
+
const HTML = `<!doctype html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="utf-8">
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
15
|
+
<title>Cuttlefish Preview</title>
|
|
16
|
+
<style>
|
|
17
|
+
:root { color-scheme: dark; font-family: ui-sans-serif, system-ui, sans-serif; background: #111315; color: #e8ecef; }
|
|
18
|
+
body { margin: 0; height: 100vh; overflow: hidden; display: grid; grid-template-columns: minmax(360px, 1fr) 280px; }
|
|
19
|
+
main { display: grid; place-items: center; padding: 24px; background: #191d20; }
|
|
20
|
+
canvas { image-rendering: pixelated; width: min(92vw, 960px); max-height: calc(100vh - 48px); aspect-ratio: var(--display-aspect, 4 / 3); background: #000; box-shadow: 0 12px 36px rgba(0,0,0,.35); }
|
|
21
|
+
#debugOverlay { position: absolute; pointer-events: none; image-rendering: pixelated; background: transparent; }
|
|
22
|
+
main { position: relative; }
|
|
23
|
+
#debugModes { display: grid; gap: 2px; }
|
|
24
|
+
#debugModes label { display: flex; align-items: center; gap: 8px; color: #aab3ba; font-size: 12px; cursor: pointer; padding: 5px 6px; border-radius: 5px; }
|
|
25
|
+
#debugModes label:hover { background: #232a30; color: #dfe6eb; }
|
|
26
|
+
#debugModes input[type="checkbox"] { width: 14px; height: 14px; margin: 0; }
|
|
27
|
+
#debugState { color: #7d8790; font-size: 11px; margin-top: 6px; }
|
|
28
|
+
aside { border-left: 1px solid #2d3338; padding: 18px; display: flex; flex-direction: column; gap: 18px; min-height: 0; }
|
|
29
|
+
aside > section:first-child, aside > section:nth-child(2) { flex-shrink: 0; } /* Display + GPIO stay visible */
|
|
30
|
+
aside > section:last-child { min-height: 0; display: flex; flex-direction: column; }
|
|
31
|
+
#diagnostics { overflow-y: auto; min-height: 0; }
|
|
32
|
+
h1 { font-size: 15px; margin: 0 0 8px; font-weight: 650; }
|
|
33
|
+
#status, #diagnostics, .empty { color: #aab3ba; font-size: 12px; line-height: 1.4; }
|
|
34
|
+
#pins { display: grid; gap: 8px; }
|
|
35
|
+
button { appearance: none; border: 1px solid #44505a; background: #252b30; color: #f3f6f8; border-radius: 6px; padding: 9px 10px; text-align: left; font: inherit; cursor: pointer; }
|
|
36
|
+
button:hover { background: #303841; }
|
|
37
|
+
@media (max-width: 760px) {
|
|
38
|
+
body { height: auto; min-height: 100vh; overflow: auto; grid-template-rows: auto 1fr; grid-template-columns: 1fr; }
|
|
39
|
+
aside { border-left: 0; border-top: 1px solid #2d3338; }
|
|
40
|
+
canvas { width: min(94vw, 640px); }
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
<script type="importmap">
|
|
44
|
+
{
|
|
45
|
+
"imports": {
|
|
46
|
+
"@typecad/ui/": "/__cuttlefish-ui/",
|
|
47
|
+
"@typecad/cuttlefish/api/shared": "/__cuttlefish/preview/api-shared-shim.js"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
<main><canvas id="display" width="320" height="240"></canvas></main>
|
|
54
|
+
<aside>
|
|
55
|
+
<section>
|
|
56
|
+
<h1>Display</h1>
|
|
57
|
+
<div id="status"></div>
|
|
58
|
+
</section>
|
|
59
|
+
<section>
|
|
60
|
+
<h1>GPIO</h1>
|
|
61
|
+
<div id="pins"></div>
|
|
62
|
+
</section>
|
|
63
|
+
<section>
|
|
64
|
+
<h1>Debug overlay</h1>
|
|
65
|
+
<div id="debugModes">
|
|
66
|
+
<label><input type="checkbox" data-mode="boxes"> boxes (colors by kind)</label>
|
|
67
|
+
<label><input type="checkbox" data-mode="clips"> scroll clips (dashed)</label>
|
|
68
|
+
<label><input type="checkbox" data-mode="dirty"> dirty flash (repaints)</label>
|
|
69
|
+
<label><input type="checkbox" data-mode="inspect"> inspect taps (blocks input)</label>
|
|
70
|
+
</div>
|
|
71
|
+
<div id="debugState">overlay: off — check a box or press D</div>
|
|
72
|
+
<div style="color:#7d8790;font-size:11px;margin-top:4px">D cycles boxes/clips/dirty · Ctrl-click always inspects</div>
|
|
73
|
+
</section>
|
|
74
|
+
<section>
|
|
75
|
+
<h1>Diagnostics</h1>
|
|
76
|
+
<div id="diagnostics"></div>
|
|
77
|
+
</section>
|
|
78
|
+
</aside>
|
|
79
|
+
<script type="module" src="/__cuttlefish/preview/client.js"></script>
|
|
80
|
+
</body>
|
|
53
81
|
</html>`;
|
|
54
82
|
function contentType(filePath) {
|
|
55
83
|
if (filePath.endsWith(".js"))
|
|
@@ -60,6 +88,42 @@ function contentType(filePath) {
|
|
|
60
88
|
return "application/json; charset=utf-8";
|
|
61
89
|
return "text/plain; charset=utf-8";
|
|
62
90
|
}
|
|
91
|
+
/** Serve a file from a dist root, with ESM-specifier fallbacks: extensionless
|
|
92
|
+
* paths try +".js" (package-exports style "./preview/host-ui-runtime" →
|
|
93
|
+
* host-ui-runtime.js) and bare directories try +"/index.js". */
|
|
94
|
+
function serveFromRoot(res, root, relRaw) {
|
|
95
|
+
const rel = decodeURIComponent(relRaw);
|
|
96
|
+
const candidates = [rel, `${rel}.js`, `${rel}/index.js`];
|
|
97
|
+
for (const candidate of candidates) {
|
|
98
|
+
const filePath = path.resolve(root, candidate);
|
|
99
|
+
if (!filePath.startsWith(root))
|
|
100
|
+
break; // path traversal — 404 below
|
|
101
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
102
|
+
// no-store: these are compiled dist files that change between builds;
|
|
103
|
+
// heuristic browser caching serves stale modules after a rebuild and
|
|
104
|
+
// breaks the preview dev loop (and any debugging of it).
|
|
105
|
+
res.writeHead(200, { "content-type": contentType(filePath), "cache-control": "no-store" });
|
|
106
|
+
fs.createReadStream(filePath).pipe(res);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
writeText(res, 404, "Not found");
|
|
111
|
+
}
|
|
112
|
+
/** Resolve a dependency package's dist directory (served to the browser).
|
|
113
|
+
* Resolves through the package's exports map (subpath → dist file) and walks
|
|
114
|
+
* up to the enclosing "dist" — packages don't export "./package.json". */
|
|
115
|
+
function packageDistDir(specifier) {
|
|
116
|
+
const require = createRequire(import.meta.url);
|
|
117
|
+
const entry = require.resolve(specifier); // e.g. .../dist/engine.js
|
|
118
|
+
let dir = path.dirname(entry);
|
|
119
|
+
for (let i = 0; i < 4 && path.basename(dir) !== "dist"; i++) {
|
|
120
|
+
dir = path.dirname(dir);
|
|
121
|
+
}
|
|
122
|
+
if (path.basename(dir) !== "dist") {
|
|
123
|
+
throw new Error(`Could not locate the dist directory for ${specifier} (resolved ${entry}).`);
|
|
124
|
+
}
|
|
125
|
+
return dir;
|
|
126
|
+
}
|
|
63
127
|
function writeText(res, status, text, type = "text/plain; charset=utf-8") {
|
|
64
128
|
res.writeHead(status, { "content-type": type });
|
|
65
129
|
res.end(text);
|
|
@@ -127,13 +191,28 @@ async function listen(server, preferredPort) {
|
|
|
127
191
|
throw new Error(`No available preview port found starting at ${preferredPort}.`);
|
|
128
192
|
}
|
|
129
193
|
export async function runPreviewServer(options = {}) {
|
|
194
|
+
// The preview pipeline drives the UI engine directly (snapshot builds,
|
|
195
|
+
// type-decl generation) without going through transpileFile(), which is the
|
|
196
|
+
// only path that lazily registers the hook — so load it here first. Preview
|
|
197
|
+
// is a UI feature: when the engine is absent, fail with a clear message
|
|
198
|
+
// instead of the generic "hook is not registered" error.
|
|
199
|
+
await loadUIEngine();
|
|
200
|
+
if (!hasUIHook()) {
|
|
201
|
+
throw new Error(`cuttlefish preview requires the @typecad/ui package — install it in this project (npm install @typecad/ui).`);
|
|
202
|
+
}
|
|
130
203
|
const configPath = resolveConfigPath(options.configPath);
|
|
131
204
|
const config = parseConfigFile(configPath);
|
|
132
205
|
if (!config)
|
|
133
206
|
throw new Error(`Could not parse ${configPath}`);
|
|
207
|
+
// Snapshots re-read the config on every build (see /snapshot.json below);
|
|
208
|
+
// this holds the most recent config that parsed, for fallback mid-edit.
|
|
209
|
+
let lastGoodConfig = config;
|
|
134
210
|
const projectRoot = path.dirname(configPath);
|
|
135
211
|
requireUIHook().generateProjectUITypeDeclarations(projectRoot);
|
|
136
212
|
const distRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
213
|
+
// The browser client imports the host runtime from @typecad/ui via the
|
|
214
|
+
// import map — resolve where that package's dist lives on this machine.
|
|
215
|
+
const uiDistRoot = packageDistDir("@typecad/ui/engine");
|
|
137
216
|
const clients = new Set();
|
|
138
217
|
const server = http.createServer(async (req, res) => {
|
|
139
218
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -144,13 +223,26 @@ export async function runPreviewServer(options = {}) {
|
|
|
144
223
|
}
|
|
145
224
|
if (url.pathname === "/snapshot.json") {
|
|
146
225
|
requireUIHook().generateProjectUITypeDeclarations(projectRoot);
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
226
|
+
// Re-read the config for every snapshot build so edits (themeClass,
|
|
227
|
+
// entry, display) take effect on page refresh without restarting the
|
|
228
|
+
// preview server. A config that momentarily fails to parse (mid-edit)
|
|
229
|
+
// falls back to the last good one — the page keeps working; refresh
|
|
230
|
+
// again once the edit settles.
|
|
231
|
+
const latest = parseConfigFile(configPath) ?? lastGoodConfig;
|
|
232
|
+
lastGoodConfig = latest;
|
|
233
|
+
// Imported via a computed file:// URL (never a string-literal bare
|
|
234
|
+
// specifier) so tsc types this as `any` and never resolves
|
|
235
|
+
// @typecad/ui's declaration files — see the comment in ui/ui-bridge.ts
|
|
236
|
+
// for why a literal specifier causes TS5055 on rebuilds where dist/
|
|
237
|
+
// already exists. The ?t= cache-bust forces a fresh ESM load per
|
|
238
|
+
// snapshot build: without it the server caches the module graph from
|
|
239
|
+
// startup, and engine rebuilds (font planning, layout fixes, ...)
|
|
240
|
+
// never take effect until the server restarts — a recurring source of
|
|
241
|
+
// "fixed but the preview still shows it" confusion. Dev-only cost:
|
|
242
|
+
// re-evaluating the module graph per snapshot request.
|
|
243
|
+
const buildProgramUrl = pathToFileURL(path.join(uiDistRoot, "preview", "build-program.js")).href;
|
|
244
|
+
const { buildPreviewSnapshot } = await import(buildProgramUrl + "?t=" + Date.now());
|
|
245
|
+
const snapshot = await buildPreviewSnapshot({ config: latest, projectRoot });
|
|
154
246
|
writeText(res, 200, JSON.stringify(snapshot), "application/json; charset=utf-8");
|
|
155
247
|
return;
|
|
156
248
|
}
|
|
@@ -165,15 +257,15 @@ export async function runPreviewServer(options = {}) {
|
|
|
165
257
|
req.on("close", () => clients.delete(res));
|
|
166
258
|
return;
|
|
167
259
|
}
|
|
260
|
+
if (url.pathname.startsWith("/__cuttlefish-ui/")) {
|
|
261
|
+
// The @typecad/ui package's dist — the browser client resolves
|
|
262
|
+
// "@typecad/ui/..." bare specifiers onto this prefix via the import
|
|
263
|
+
// map (host runtime, gfx, and their relative engine imports).
|
|
264
|
+
serveFromRoot(res, uiDistRoot, url.pathname.slice("/__cuttlefish-ui/".length));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
168
267
|
if (url.pathname.startsWith("/__cuttlefish/")) {
|
|
169
|
-
|
|
170
|
-
const filePath = path.resolve(distRoot, rel);
|
|
171
|
-
if (!filePath.startsWith(distRoot) || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
172
|
-
writeText(res, 404, "Not found");
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
res.writeHead(200, { "content-type": contentType(filePath) });
|
|
176
|
-
fs.createReadStream(filePath).pipe(res);
|
|
268
|
+
serveFromRoot(res, distRoot, url.pathname.slice("/__cuttlefish/".length));
|
|
177
269
|
return;
|
|
178
270
|
}
|
|
179
271
|
writeText(res, 404, "Not found");
|
package/dist/testing.d.ts
CHANGED
|
@@ -19,8 +19,8 @@ export { transpileFile } from "./transpile.js";
|
|
|
19
19
|
export { resetUIEngine, __simulateUIAbsentForTest } from "./ui/ui-bridge.js";
|
|
20
20
|
export { resetSafetyEngine, __simulateSafetyAbsentForTest } from "./safety/safety-bridge.js";
|
|
21
21
|
export { setSafetyHook, hasSafetyHook } from "./safety-hook.js";
|
|
22
|
-
export { scaffoldProject, normalizeProjectName, KNOWN_BOARDS, generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig,
|
|
23
|
-
export type {
|
|
22
|
+
export { scaffoldProject, normalizeProjectName, KNOWN_BOARDS, generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig, runCreateWizard, } from "./create/index.js";
|
|
23
|
+
export type { CreateProjectOptions } from "./create/index.js";
|
|
24
24
|
export { scaffoldBoardPackages, parseBoardSpec, safeParseBoardSpec, stripJsonc, } from "./create/index.js";
|
|
25
25
|
export type { BoardSpec, ScaffoldBoardResult } from "./create/index.js";
|
|
26
26
|
export { BoardGenerators } from "./create/index.js";
|
package/dist/testing.js
CHANGED
|
@@ -23,7 +23,7 @@ export { resetUIEngine, __simulateUIAbsentForTest } from "./ui/ui-bridge.js";
|
|
|
23
23
|
export { resetSafetyEngine, __simulateSafetyAbsentForTest } from "./safety/safety-bridge.js";
|
|
24
24
|
export { setSafetyHook, hasSafetyHook } from "./safety-hook.js";
|
|
25
25
|
// ── Project scaffolding ──────────────────────────────────────────────────────
|
|
26
|
-
export { scaffoldProject, normalizeProjectName, KNOWN_BOARDS, generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig,
|
|
26
|
+
export { scaffoldProject, normalizeProjectName, KNOWN_BOARDS, generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig, runCreateWizard, } from "./create/index.js";
|
|
27
27
|
// ── Board codegen (`cuttlefish board add`) ───────────────────────────────────
|
|
28
28
|
export { scaffoldBoardPackages, parseBoardSpec, safeParseBoardSpec, stripJsonc, } from "./create/index.js";
|
|
29
29
|
export { BoardGenerators } from "./create/index.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** One declaration (`--token: value`), order-preserving. */
|
|
2
|
+
type Decl = [name: string, value: string];
|
|
3
|
+
export interface ShadcnTheme {
|
|
4
|
+
name: string;
|
|
5
|
+
light: Decl[];
|
|
6
|
+
dark: Decl[];
|
|
7
|
+
}
|
|
8
|
+
/** Where a project keeps its own themes: src/styles/themes/*.css next to
|
|
9
|
+
* the kit stylesheet. Project themes win over same-named package ones —
|
|
10
|
+
* pasting a theme is dropping a file into YOUR project, never node_modules. */
|
|
11
|
+
export declare function projectThemesDir(projectRoot: string): string;
|
|
12
|
+
/** Theme names, project-local (src/styles/themes/) first, then the ones
|
|
13
|
+
* shipped in the package (assets/shadcn/themes/). */
|
|
14
|
+
export declare function listShadcnThemes(projectRoot?: string): string[];
|
|
15
|
+
/** Load a theme by name — project-local directory first, then the package
|
|
16
|
+
* included set. Throws with the available names when missing. */
|
|
17
|
+
export declare function loadShadcnTheme(name: string, projectRoot?: string): ShadcnTheme;
|
|
18
|
+
/** Merge a theme's tokens into a kit stylesheet's :root/.dark blocks and
|
|
19
|
+
* return the rewritten text. Throws when a merged block is missing any
|
|
20
|
+
* token the kit's recipes reference. */
|
|
21
|
+
export declare function applyShadcnTheme(cssText: string, theme: ShadcnTheme): string;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// shadcn kit themes — the included token sets under assets/shadcn/themes/.
|
|
3
|
+
//
|
|
4
|
+
// A theme file is two CSS blocks (:root = light tokens, .dark = dark tokens)
|
|
5
|
+
// plus an optional header comment. Selecting a theme MERGES each block into
|
|
6
|
+
// the kit's stylesheet: declarations the theme defines override the kit's,
|
|
7
|
+
// and kit-specific extras the theme lacks (notably --destructive-background,
|
|
8
|
+
// which stock shadcn themes don't carry) keep their current values — so any
|
|
9
|
+
// stock theme from ui.shadcn.com or tweakcn pastes cleanly.
|
|
10
|
+
//
|
|
11
|
+
// Used by `cuttlefish add shadcn --theme <name>` (scaffold time) and
|
|
12
|
+
// `cuttlefish theme <name>` (swap an existing project's kit stylesheet).
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
const THEMES_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../assets/shadcn/themes");
|
|
18
|
+
/** The tokens the kit's recipes reference; every merged theme must cover them. */
|
|
19
|
+
const REQUIRED_TOKENS = [
|
|
20
|
+
"--background", "--foreground",
|
|
21
|
+
"--card", "--card-foreground",
|
|
22
|
+
"--primary", "--primary-foreground",
|
|
23
|
+
"--secondary", "--secondary-foreground",
|
|
24
|
+
"--muted", "--muted-foreground",
|
|
25
|
+
"--accent", "--accent-foreground",
|
|
26
|
+
"--destructive", "--destructive-foreground", "--destructive-background",
|
|
27
|
+
"--border", "--input", "--radius",
|
|
28
|
+
];
|
|
29
|
+
/** Blank out /* … */ comment interiors (same length) so selector searches
|
|
30
|
+
* can't match inside a comment while byte offsets stay valid against the
|
|
31
|
+
* original text. The kit's header comment literally contains `.dark { ... }`
|
|
32
|
+
* as documentation — an unmasked search spliced tokens into the comment and
|
|
33
|
+
* left the real block untouched. */
|
|
34
|
+
function maskComments(css) {
|
|
35
|
+
return css.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length));
|
|
36
|
+
}
|
|
37
|
+
/** Locate `selector {` in css, skipping comment spans. Custom-property values
|
|
38
|
+
* contain no braces, so the block ends at the first `}` after the opening
|
|
39
|
+
* one. */
|
|
40
|
+
function blockRange(css, selector) {
|
|
41
|
+
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42
|
+
const m = new RegExp(escaped + "\\s*\\{").exec(maskComments(css));
|
|
43
|
+
if (!m)
|
|
44
|
+
return null;
|
|
45
|
+
const open = m.index + m[0].length;
|
|
46
|
+
const close = css.indexOf("}", open);
|
|
47
|
+
if (close < 0)
|
|
48
|
+
return null;
|
|
49
|
+
return { selStart: m.index, open, close };
|
|
50
|
+
}
|
|
51
|
+
/** Parse a token block's declarations (line-oriented; one decl per line, the
|
|
52
|
+
* format both the kit and stock shadcn themes use). */
|
|
53
|
+
function parseDecls(block) {
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const line of block.split("\n")) {
|
|
56
|
+
const m = /^\s*(--[\w-]+)\s*:\s*([^;]+);/.exec(line);
|
|
57
|
+
if (m)
|
|
58
|
+
out.push([m[1], m[2].trim()]);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function emitBlock(selector, decls) {
|
|
63
|
+
const body = decls.map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
64
|
+
return `${selector} {\n${body}\n}`;
|
|
65
|
+
}
|
|
66
|
+
/** Order-preserving override merge: base order, overrides in place, theme-only
|
|
67
|
+
* declarations appended. */
|
|
68
|
+
function mergeDecls(base, override) {
|
|
69
|
+
const out = base.map(([k, v]) => [k, v]);
|
|
70
|
+
const indexByName = new Map(out.map(([k], i) => [k, i]));
|
|
71
|
+
for (const [k, v] of override) {
|
|
72
|
+
const existing = indexByName.get(k);
|
|
73
|
+
if (existing !== undefined)
|
|
74
|
+
out[existing][1] = v;
|
|
75
|
+
else {
|
|
76
|
+
indexByName.set(k, out.length);
|
|
77
|
+
out.push([k, v]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/** Where a project keeps its own themes: src/styles/themes/*.css next to
|
|
83
|
+
* the kit stylesheet. Project themes win over same-named package ones —
|
|
84
|
+
* pasting a theme is dropping a file into YOUR project, never node_modules. */
|
|
85
|
+
export function projectThemesDir(projectRoot) {
|
|
86
|
+
return path.resolve(projectRoot, "src/styles/themes");
|
|
87
|
+
}
|
|
88
|
+
function themeNamesIn(dir) {
|
|
89
|
+
if (!fs.existsSync(dir))
|
|
90
|
+
return [];
|
|
91
|
+
return fs.readdirSync(dir)
|
|
92
|
+
.filter((f) => f.endsWith(".css") && !f.startsWith("_"))
|
|
93
|
+
.map((f) => f.replace(/\.css$/, ""))
|
|
94
|
+
.sort();
|
|
95
|
+
}
|
|
96
|
+
/** Theme names, project-local (src/styles/themes/) first, then the ones
|
|
97
|
+
* shipped in the package (assets/shadcn/themes/). */
|
|
98
|
+
export function listShadcnThemes(projectRoot) {
|
|
99
|
+
const local = projectRoot ? themeNamesIn(projectThemesDir(projectRoot)) : [];
|
|
100
|
+
const included = themeNamesIn(THEMES_DIR);
|
|
101
|
+
return [...new Set([...local, ...included])].sort();
|
|
102
|
+
}
|
|
103
|
+
function formatThemeList(projectRoot) {
|
|
104
|
+
const local = projectRoot ? themeNamesIn(projectThemesDir(projectRoot)) : [];
|
|
105
|
+
const included = themeNamesIn(THEMES_DIR);
|
|
106
|
+
const lines = [];
|
|
107
|
+
if (local.length > 0) {
|
|
108
|
+
lines.push("project (src/styles/themes/):");
|
|
109
|
+
for (const n of local)
|
|
110
|
+
lines.push(` ${n}`);
|
|
111
|
+
}
|
|
112
|
+
lines.push("included with cuttlefish:");
|
|
113
|
+
for (const n of included)
|
|
114
|
+
lines.push(` ${n}`);
|
|
115
|
+
lines.push("(paste your own: src/styles/themes/<name>.css in this project)");
|
|
116
|
+
return lines.join("\n");
|
|
117
|
+
}
|
|
118
|
+
/** Load a theme by name — project-local directory first, then the package
|
|
119
|
+
* included set. Throws with the available names when missing. */
|
|
120
|
+
export function loadShadcnTheme(name, projectRoot) {
|
|
121
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
122
|
+
throw new Error(`Invalid theme name "${name}" — use letters, digits, and dashes.`);
|
|
123
|
+
}
|
|
124
|
+
const candidates = projectRoot
|
|
125
|
+
? [path.join(projectThemesDir(projectRoot), `${name}.css`), path.join(THEMES_DIR, `${name}.css`)]
|
|
126
|
+
: [path.join(THEMES_DIR, `${name}.css`)];
|
|
127
|
+
const file = candidates.find((c) => fs.existsSync(c));
|
|
128
|
+
if (!file) {
|
|
129
|
+
throw new Error(`Unknown theme "${name}". Available themes:\n${formatThemeList(projectRoot)}`);
|
|
130
|
+
}
|
|
131
|
+
const text = fs.readFileSync(file, "utf-8");
|
|
132
|
+
const rootRange = blockRange(text, ":root");
|
|
133
|
+
const darkRange = blockRange(text, ".dark");
|
|
134
|
+
if (!rootRange || !darkRange) {
|
|
135
|
+
throw new Error(`Theme "${name}" must define both a :root and a .dark token block.`);
|
|
136
|
+
}
|
|
137
|
+
const light = parseDecls(text.slice(rootRange.open, rootRange.close));
|
|
138
|
+
const dark = parseDecls(text.slice(darkRange.open, darkRange.close));
|
|
139
|
+
if (light.length === 0 || dark.length === 0) {
|
|
140
|
+
throw new Error(`Theme "${name}" has empty token blocks — paste the theme's :root and .dark declarations.`);
|
|
141
|
+
}
|
|
142
|
+
return { name, light, dark };
|
|
143
|
+
}
|
|
144
|
+
/** Merge a theme's tokens into a kit stylesheet's :root/.dark blocks and
|
|
145
|
+
* return the rewritten text. Throws when a merged block is missing any
|
|
146
|
+
* token the kit's recipes reference. */
|
|
147
|
+
export function applyShadcnTheme(cssText, theme) {
|
|
148
|
+
let out = cssText;
|
|
149
|
+
for (const [selector, themeDecls] of [[".dark", theme.dark], [":root", theme.light]]) {
|
|
150
|
+
const range = blockRange(out, selector);
|
|
151
|
+
if (!range) {
|
|
152
|
+
throw new Error(`The kit stylesheet has no ${selector} token block to replace.`);
|
|
153
|
+
}
|
|
154
|
+
const merged = mergeDecls(parseDecls(out.slice(range.open, range.close)), themeDecls);
|
|
155
|
+
out = out.slice(0, range.selStart) + emitBlock(selector, merged) + out.slice(range.close + 1);
|
|
156
|
+
}
|
|
157
|
+
// Validate AFTER both merges (a token can live in either block).
|
|
158
|
+
const rootRange = blockRange(out, ":root");
|
|
159
|
+
const darkRange = blockRange(out, ".dark");
|
|
160
|
+
const names = new Set();
|
|
161
|
+
if (rootRange)
|
|
162
|
+
for (const [k] of parseDecls(out.slice(rootRange.open, rootRange.close)))
|
|
163
|
+
names.add(k);
|
|
164
|
+
if (darkRange)
|
|
165
|
+
for (const [k] of parseDecls(out.slice(darkRange.open, darkRange.close)))
|
|
166
|
+
names.add(k);
|
|
167
|
+
const missing = REQUIRED_TOKENS.filter((t) => !names.has(t));
|
|
168
|
+
if (missing.length > 0) {
|
|
169
|
+
throw new Error(`Theme "${theme.name}" leaves required tokens unset: ${missing.join(", ")}`);
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
package/dist/transpile.js
CHANGED
|
@@ -26,12 +26,14 @@ import { requireUIHook, hasUIHook } from "./ui-hook.js";
|
|
|
26
26
|
import { loadUIEngine } from "./ui/ui-bridge.js";
|
|
27
27
|
import { hasSafetyHook, requireSafetyHook } from "./safety-hook.js";
|
|
28
28
|
import { loadSafetyEngine } from "./safety/safety-bridge.js";
|
|
29
|
-
import { setDisplayProfile, resetDisplayProfile } from "./stores/display-profile-store.js";
|
|
29
|
+
import { setDisplayProfile, resetDisplayProfile, getDisplayProfile } from "./stores/display-profile-store.js";
|
|
30
30
|
import { setThemeCss, resetThemeCss, setThemeClass } from "./stores/theme-store.js";
|
|
31
31
|
import { emitCpp, registerAllEnumNames } from "./emit/cpp-emitter.js";
|
|
32
32
|
import { readText, writeText, resetWrittenFiles, wasWrittenThisRun } from "./utils/fs.js";
|
|
33
33
|
import { debug as logDebug, info } from "./utils/logger.js";
|
|
34
|
+
import { printDebugStrategy } from "./utils/ui.js";
|
|
34
35
|
import { loadLibraryDefinitions, generateLibdefStubs } from "./libdef/registry.js";
|
|
36
|
+
import { resetCuttlefishLibraries, validateCuttlefishLibraries, cuttlefishLibraryLibdefs, libraryDefinitionKey, writeCuttlefishLibraryArtifacts, } from "./library-packages.js";
|
|
35
37
|
import { buildCallGraph } from "./ir/call-graph.js";
|
|
36
38
|
import { clearCaches, } from "./cache.js";
|
|
37
39
|
import { detectEntryPoints, detectExportedEntryPoints } from "./ir/entry-points.js";
|
|
@@ -201,6 +203,15 @@ import { loadFrameworkPackage } from "./framework-package.js";
|
|
|
201
203
|
export { loadFrameworkPackage };
|
|
202
204
|
export { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
|
|
203
205
|
import { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
|
|
206
|
+
/** Under --strict-css, UI CSS-compatibility warnings (code css-*: ignored
|
|
207
|
+
* alpha, quantized font sizes, unsupported display/position values, ...) are
|
|
208
|
+
* upgraded to errors so the build fails instead of approximating silently. */
|
|
209
|
+
function upgradeStrictCss(d, strict) {
|
|
210
|
+
if (strict && d.severity === "warning" && typeof d.code === "string" && d.code.startsWith("css-")) {
|
|
211
|
+
return { ...d, severity: "error" };
|
|
212
|
+
}
|
|
213
|
+
return d;
|
|
214
|
+
}
|
|
204
215
|
function formatFatalDiagnostics(entries) {
|
|
205
216
|
const errors = entries.filter(({ diagnostic }) => diagnostic.severity === "error");
|
|
206
217
|
const lines = [
|
|
@@ -260,7 +271,11 @@ function loadPlatformStrategy(frameworkPackage, _boardPackage, fromDir, debug) {
|
|
|
260
271
|
if (hasLoadedFramework()) {
|
|
261
272
|
const { strategy } = getLoadedFramework();
|
|
262
273
|
if (debug) {
|
|
263
|
-
|
|
274
|
+
// Styled like the other step lines (cyan ⇉) — says what the debug
|
|
275
|
+
// build is actually doing, in user terms. Falls back to the package
|
|
276
|
+
// name when the strategy carries no id.
|
|
277
|
+
const frameworkName = strategy.id || frameworkPackage.replace(/^@typecad\/framework-/, "");
|
|
278
|
+
printDebugStrategy(frameworkName);
|
|
264
279
|
}
|
|
265
280
|
return strategy;
|
|
266
281
|
}
|
|
@@ -351,8 +366,26 @@ export async function transpileFile(options) {
|
|
|
351
366
|
// see incremental-cache.ts — so we always transpile the full graph.)
|
|
352
367
|
cleanOutput(entryDir, outDir);
|
|
353
368
|
profiler.startTimer("graph:collect");
|
|
354
|
-
|
|
369
|
+
// Image-conversion cap: never decode larger than the physical panel —
|
|
370
|
+
// converted <img> assets downscale to fit (no 24MB C arrays from photos).
|
|
371
|
+
const imageDecodeMax = await (async () => {
|
|
372
|
+
try {
|
|
373
|
+
const { effectiveDisplaySize } = await import("./api/shared/display-profile.js");
|
|
374
|
+
const size = effectiveDisplaySize(getDisplayProfile());
|
|
375
|
+
return { maxW: size.width, maxH: size.height };
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return {};
|
|
379
|
+
}
|
|
380
|
+
})();
|
|
381
|
+
resetCuttlefishLibraries();
|
|
382
|
+
const graphResult = await collectTranspileGraph(entryFile, options.boardPackage, imageDecodeMax);
|
|
355
383
|
profiler.endTimer("graph:collect");
|
|
384
|
+
// Cuttlefish library packages registered during the graph walk — validate
|
|
385
|
+
// them against the loaded framework (and build target, when known) before
|
|
386
|
+
// any codegen. A framework/target mismatch is a hard error here, far
|
|
387
|
+
// clearer than the native compiler's take on a missing header or node.
|
|
388
|
+
validateCuttlefishLibraries(strategy.id, options.platformContext?.frameworkData?.buildTarget);
|
|
356
389
|
const transpileFiles = graphResult.files;
|
|
357
390
|
// ── Type-check all files before transpiling ────────────────────────────────
|
|
358
391
|
// Skip type-checking if explicitly disabled
|
|
@@ -425,6 +458,15 @@ export async function transpileFile(options) {
|
|
|
425
458
|
}
|
|
426
459
|
const npmPackages = graphResult.npmPackages;
|
|
427
460
|
const definitions = loadLibraryDefinitions(sourceDir);
|
|
461
|
+
// Cuttlefish library packages: the import resolves to the library's shim
|
|
462
|
+
// include (e.g. '"__tc_rgbled.h"') instead of a transpiled module header.
|
|
463
|
+
// Project-local .libdef.json files keep precedence.
|
|
464
|
+
for (const libdef of cuttlefishLibraryLibdefs()) {
|
|
465
|
+
const key = libraryDefinitionKey(libdef.module);
|
|
466
|
+
if (!definitions.has(key)) {
|
|
467
|
+
definitions.set(key, libdef);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
428
470
|
let entryOutputs;
|
|
429
471
|
const diagnostics = [];
|
|
430
472
|
const allRemovedSymbols = [];
|
|
@@ -449,10 +491,12 @@ export async function transpileFile(options) {
|
|
|
449
491
|
// parser warnings (unknown CSS properties, unknown HTML tags) here so the
|
|
450
492
|
// author sees typos and unsupported features instead of silent drops.
|
|
451
493
|
// Guarded: @typecad/ui is optional, so there may be no UI engine loaded.
|
|
494
|
+
// --strict-css upgrades the css-* compatibility warnings to errors.
|
|
452
495
|
if (hasUIHook()) {
|
|
453
496
|
for (const mod of requireUIHook().allUIModules()) {
|
|
454
497
|
for (const d of mod.diagnostics) {
|
|
455
|
-
|
|
498
|
+
const upgraded = upgradeStrictCss(d, options.strictCss);
|
|
499
|
+
diagnostics.push({ ...upgraded, filePath: d.filePath ?? path.basename(mod.htmlPath) });
|
|
456
500
|
}
|
|
457
501
|
}
|
|
458
502
|
}
|
|
@@ -582,10 +626,12 @@ export async function transpileFile(options) {
|
|
|
582
626
|
profiler.endTimer("ir:build-all");
|
|
583
627
|
// ── UI mount-time warnings (scroll memory budget, etc.) ─────────────────
|
|
584
628
|
// Guarded: @typecad/ui is optional; no engine means no UI modules.
|
|
629
|
+
// --strict-css upgrades the css-* compatibility warnings to errors.
|
|
585
630
|
if (hasUIHook()) {
|
|
586
631
|
for (const mod of requireUIHook().allUIModules()) {
|
|
587
632
|
for (const d of mod.mountDiagnostics) {
|
|
588
|
-
|
|
633
|
+
const upgraded = upgradeStrictCss(d, options.strictCss);
|
|
634
|
+
diagnostics.push({ ...upgraded, filePath: d.filePath ?? path.basename(mod.htmlPath) });
|
|
589
635
|
}
|
|
590
636
|
}
|
|
591
637
|
}
|
|
@@ -905,6 +951,45 @@ export async function transpileFile(options) {
|
|
|
905
951
|
});
|
|
906
952
|
}
|
|
907
953
|
}
|
|
954
|
+
// ── Persist the resolved board constants next to the emitted source ─────
|
|
955
|
+
// Framework toolchains re-read this at compile time to rebuild their chip
|
|
956
|
+
// descriptors (framework-zephyr's resolveChipFromBoard). Board packages
|
|
957
|
+
// carry chip data — controller splits, ADC channel maps, PWM specs — that
|
|
958
|
+
// lives only in the board package, so without this file the toolchain's
|
|
959
|
+
// registry fallback silently resolves board-derived targets (rpi_pico,
|
|
960
|
+
// esp32c3/c6, blackpill) to the XIAO default descriptor.
|
|
961
|
+
try {
|
|
962
|
+
// Read from the built program IRs — the IR build context (and its
|
|
963
|
+
// current-board-constants slot) is already closed at this point. Any
|
|
964
|
+
// file's IR may carry the constants (the board import is traversed while
|
|
965
|
+
// building whichever file imports it first), so scan for a populated one.
|
|
966
|
+
let boardConstants;
|
|
967
|
+
for (const pb of preBuilt.values()) {
|
|
968
|
+
const bc = pb.programIR.boardConstants;
|
|
969
|
+
if (bc && bc.size > 0) {
|
|
970
|
+
boardConstants = bc;
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (boardConstants && boardConstants.size > 0) {
|
|
975
|
+
fs.writeFileSync(path.join(outDir, "board-constants.json"), JSON.stringify(Object.fromEntries(boardConstants), null, 2));
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
catch {
|
|
979
|
+
// Best-effort persistence; the toolchain falls back to its registry.
|
|
980
|
+
}
|
|
981
|
+
// ── Cuttlefish library packages: shims + libraries.json sidecar ─────────
|
|
982
|
+
// Shims are written next to the emitted sources (the framework scaffold's
|
|
983
|
+
// CMake/sketch regen compiles them); the sidecar records the used
|
|
984
|
+
// libraries' build contributions (Kconfig lines, overlay fragments) for
|
|
985
|
+
// the framework toolchain — the board-constants.json convention.
|
|
986
|
+
try {
|
|
987
|
+
const emittedSrcDir = entryOutputs ? path.dirname(entryOutputs.sourcePath) : outDir;
|
|
988
|
+
writeCuttlefishLibraryArtifacts(outDir, emittedSrcDir);
|
|
989
|
+
}
|
|
990
|
+
catch {
|
|
991
|
+
// Best-effort; a missing shim surfaces at native compile time.
|
|
992
|
+
}
|
|
908
993
|
return {
|
|
909
994
|
...entryOutputs,
|
|
910
995
|
diagnostics,
|