@typecad/cuttlefish 1.0.0-alpha.6 → 1.0.0-alpha.8
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/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +581 -0
- package/dist/api/shared/framework-manifest.js +182 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +755 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +20 -2
- package/dist/api/shared/index.js +25 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +113 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli.js +186 -82
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +9 -2
- package/dist/create/init-scaffold.js +0 -14
- package/dist/create/init-templates.d.ts +2 -0
- package/dist/create/init-templates.js +74 -8
- package/dist/create/init-wizard.js +31 -7
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +257 -36
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/ir/adc-range-validation.js +14 -1
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +164 -0
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +723 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +20 -1
- package/dist/ir/pin-capability-validation.js +37 -0
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +49 -18
- package/dist/ir/program-analysis.d.ts +65 -0
- package/dist/ir/program-analysis.js +318 -2
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +60 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +92 -3
- package/dist/ir/type-resolution.js +18 -0
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +9 -2
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +7 -1
- package/dist/testing.js +8 -1
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +175 -41
- package/dist/types.d.ts +13 -2
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +78 -4
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +24 -5
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Public barrel for component declaration generation.
|
|
2
|
+
//
|
|
3
|
+
// framework-esp32 imports `generateComponentDeclsForProject` from here so its
|
|
4
|
+
// dependency on cuttlefish is declared through the package exports map rather
|
|
5
|
+
// than a deep relative path. Spec: 2026-07-19-framework-esp32-components-design.md
|
|
6
|
+
export { generateComponentDeclsForProject } from "./cpp-to-decl.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface ComponentScanRoots {
|
|
2
|
+
/** Subdirectory names under managed_components/ (e.g. espressif__esp_wifi). */
|
|
3
|
+
managed: string[];
|
|
4
|
+
/** Absolute paths to local component directories. */
|
|
5
|
+
local: string[];
|
|
6
|
+
/**
|
|
7
|
+
* ESP-IDF built-in component names (e.g. esp_wifi). Resolved against
|
|
8
|
+
* `idfRoot/components/<name>/include/` when idfRoot is provided; ignored
|
|
9
|
+
* otherwise (the caller is responsible for passing idfRoot when builtins
|
|
10
|
+
* are present).
|
|
11
|
+
*/
|
|
12
|
+
builtin: string[];
|
|
13
|
+
/** Optional ESP-IDF install root for resolving `builtin` names. */
|
|
14
|
+
idfRoot?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A discovered header plus the directory its generated .d.ts should land in.
|
|
18
|
+
*
|
|
19
|
+
* `outputDir` is alongside the header for managed/local components (so the
|
|
20
|
+
* .d.ts lives in the same gitignored component dir, regenerated on each run).
|
|
21
|
+
* For built-in components it's a project-local cache — NEVER write into the
|
|
22
|
+
* IDF install itself.
|
|
23
|
+
*/
|
|
24
|
+
export interface DiscoveredHeader {
|
|
25
|
+
/** Absolute path to the source .h file. */
|
|
26
|
+
path: string;
|
|
27
|
+
/** Absolute directory where the generated .d.ts should be written. */
|
|
28
|
+
outputDir: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Find all .h files for the declared components.
|
|
32
|
+
*
|
|
33
|
+
* - managed: scan `managed_components/<name>/include/` (falling back to the
|
|
34
|
+
* component dir if no include/ exists). Populated by `idf.py reconfigure`.
|
|
35
|
+
* - local: scan `<path>/include/` (or `<path>/`).
|
|
36
|
+
* - builtin: scan `<idfRoot>/components/<name>/include/` (IDF ships with these).
|
|
37
|
+
*
|
|
38
|
+
* For builtins the output .d.ts goes to `<projectDir>/.cuttlefish/component-decls/<component>/`
|
|
39
|
+
* — never into `$IDF_PATH/components/`, which would pollute the IDF install.
|
|
40
|
+
*
|
|
41
|
+
* Returned entries are unsorted — the caller decides ordering.
|
|
42
|
+
*/
|
|
43
|
+
export declare function discoverComponentHeaders(projectDir: string, roots: ComponentScanRoots): DiscoveredHeader[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/** Walk a directory recursively, returning all .h files. */
|
|
4
|
+
function walkHeaders(dir) {
|
|
5
|
+
if (!existsSync(dir))
|
|
6
|
+
return [];
|
|
7
|
+
const out = [];
|
|
8
|
+
const stack = [dir];
|
|
9
|
+
while (stack.length > 0) {
|
|
10
|
+
const cur = stack.pop();
|
|
11
|
+
let entries;
|
|
12
|
+
try {
|
|
13
|
+
entries = readdirSync(cur);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
for (const name of entries) {
|
|
19
|
+
const full = join(cur, name);
|
|
20
|
+
try {
|
|
21
|
+
const st = statSync(full);
|
|
22
|
+
if (st.isDirectory()) {
|
|
23
|
+
stack.push(full);
|
|
24
|
+
}
|
|
25
|
+
else if (name.toLowerCase().endsWith('.h')) {
|
|
26
|
+
out.push(full);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Stat failed (race); skip.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Find all .h files for the declared components.
|
|
38
|
+
*
|
|
39
|
+
* - managed: scan `managed_components/<name>/include/` (falling back to the
|
|
40
|
+
* component dir if no include/ exists). Populated by `idf.py reconfigure`.
|
|
41
|
+
* - local: scan `<path>/include/` (or `<path>/`).
|
|
42
|
+
* - builtin: scan `<idfRoot>/components/<name>/include/` (IDF ships with these).
|
|
43
|
+
*
|
|
44
|
+
* For builtins the output .d.ts goes to `<projectDir>/.cuttlefish/component-decls/<component>/`
|
|
45
|
+
* — never into `$IDF_PATH/components/`, which would pollute the IDF install.
|
|
46
|
+
*
|
|
47
|
+
* Returned entries are unsorted — the caller decides ordering.
|
|
48
|
+
*/
|
|
49
|
+
export function discoverComponentHeaders(projectDir, roots) {
|
|
50
|
+
const headers = [];
|
|
51
|
+
for (const name of roots.managed) {
|
|
52
|
+
const base = join(projectDir, 'managed_components', name);
|
|
53
|
+
const includeDir = join(base, 'include');
|
|
54
|
+
const dir = existsSync(includeDir) ? includeDir : base;
|
|
55
|
+
for (const h of walkHeaders(dir)) {
|
|
56
|
+
headers.push({ path: h, outputDir: dir });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const localPath of roots.local) {
|
|
60
|
+
const includeDir = join(localPath, 'include');
|
|
61
|
+
const dir = existsSync(includeDir) ? includeDir : localPath;
|
|
62
|
+
for (const h of walkHeaders(dir)) {
|
|
63
|
+
headers.push({ path: h, outputDir: dir });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (roots.idfRoot) {
|
|
67
|
+
for (const name of roots.builtin) {
|
|
68
|
+
const base = join(roots.idfRoot, 'components', name);
|
|
69
|
+
const includeDir = join(base, 'include');
|
|
70
|
+
const dir = existsSync(includeDir) ? includeDir : base;
|
|
71
|
+
// Project-local cache for builtins. Mirrors the include/ layout so
|
|
72
|
+
// imports can substitute `managed_components/<name>/include/X.d.ts`
|
|
73
|
+
// patterns. Each header's .d.ts is named after the header basename.
|
|
74
|
+
const outDir = join(projectDir, '.cuttlefish', 'component-decls', name);
|
|
75
|
+
if (!existsSync(outDir))
|
|
76
|
+
mkdirSync(outDir, { recursive: true });
|
|
77
|
+
for (const h of walkHeaders(dir)) {
|
|
78
|
+
headers.push({ path: h, outputDir: outDir });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return headers;
|
|
83
|
+
}
|
|
@@ -32,3 +32,12 @@ export declare function generateDeclsForDirectory(dir: string, recursive?: boole
|
|
|
32
32
|
* a later step for the directory-scan path.
|
|
33
33
|
*/
|
|
34
34
|
export declare function generateDecl(filePath: string, outputPath?: string): string | null;
|
|
35
|
+
import { type ComponentScanRoots } from "./component-discovery.js";
|
|
36
|
+
/**
|
|
37
|
+
* Generate .d.ts declarations for every declared component's headers,
|
|
38
|
+
* picking the C or C++ emitter per header based on whether it declares classes.
|
|
39
|
+
*
|
|
40
|
+
* Returns the list of created/updated .d.ts paths (headers that produced no
|
|
41
|
+
* declarations are skipped).
|
|
42
|
+
*/
|
|
43
|
+
export declare function generateComponentDeclsForProject(projectDir: string, roots: ComponentScanRoots): string[];
|
|
@@ -8,6 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { parseHeader } from "./header-parser.js";
|
|
10
10
|
import { buildClassIndex, BaseClassResolver } from "./base-class-resolver.js";
|
|
11
|
+
import { writeText } from "../utils/fs.js";
|
|
11
12
|
/**
|
|
12
13
|
* Maps C++ types to TypeScript types
|
|
13
14
|
*/
|
|
@@ -582,3 +583,74 @@ function generateDeclWithResolver(filePath, resolver) {
|
|
|
582
583
|
}
|
|
583
584
|
return outPath;
|
|
584
585
|
}
|
|
586
|
+
// ─── ESP-IDF component dispatch ───────────────────────────────────────────
|
|
587
|
+
//
|
|
588
|
+
// Picks the C or C++ emitter per header based on whether it declares classes.
|
|
589
|
+
// ESP-IDF components are mostly C (free functions + typedefs); Arduino-style
|
|
590
|
+
// libraries are C++ classes. Spec:
|
|
591
|
+
// docs/superpowers/specs/2026-07-19-framework-esp32-components-design.md
|
|
592
|
+
import { generateCDecl } from "./c-to-decl.js";
|
|
593
|
+
import { discoverComponentHeaders } from "./component-discovery.js";
|
|
594
|
+
/** Heuristic: does this header declare any C++ classes? */
|
|
595
|
+
function hasClasses(headerPath) {
|
|
596
|
+
try {
|
|
597
|
+
const content = fs.readFileSync(headerPath, "utf8");
|
|
598
|
+
return parseHeader(content).length > 0;
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Generate .d.ts declarations for every declared component's headers,
|
|
606
|
+
* picking the C or C++ emitter per header based on whether it declares classes.
|
|
607
|
+
*
|
|
608
|
+
* Returns the list of created/updated .d.ts paths (headers that produced no
|
|
609
|
+
* declarations are skipped).
|
|
610
|
+
*/
|
|
611
|
+
export function generateComponentDeclsForProject(projectDir, roots) {
|
|
612
|
+
const headers = discoverComponentHeaders(projectDir, roots);
|
|
613
|
+
const created = [];
|
|
614
|
+
for (const discovered of headers) {
|
|
615
|
+
// Output path: <outputDir>/<basename>.d.ts. For managed/local this is
|
|
616
|
+
// alongside the header; for builtins it's the project-local cache dir.
|
|
617
|
+
const baseName = path.basename(discovered.path).replace(/\.h$/i, '');
|
|
618
|
+
const outputPath = path.join(discovered.outputDir, baseName + '.d.ts');
|
|
619
|
+
const out = hasClasses(discovered.path)
|
|
620
|
+
? generateDecl(discovered.path, outputPath)
|
|
621
|
+
: generateCDecl(discovered.path, outputPath);
|
|
622
|
+
if (out) {
|
|
623
|
+
created.push(out);
|
|
624
|
+
// Write a sibling .libdef.json so the import → #include resolver picks
|
|
625
|
+
// up the real (case-preserving) header name. Without this, the resolver
|
|
626
|
+
// falls back to toPascalCase(moduleKey) + ".h" — fine for Arduino libs
|
|
627
|
+
// (Adafruit_GFX → Adafruit_GFX.h, resolved separately) but wrong for
|
|
628
|
+
// ESP-IDF component headers (esp_wifi → <EspWifi.h>, not <esp_wifi.h>).
|
|
629
|
+
// The libdef declares the actual header basename ("esp_wifi.h"), which
|
|
630
|
+
// is the ground truth — the generator just read the file.
|
|
631
|
+
writeComponentLibdef(discovered.path, outputPath);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return created;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Write `<d.ts-basename>.libdef.json` next to a generated component `.d.ts`,
|
|
638
|
+
* pointing the import → #include resolver at the actual source header.
|
|
639
|
+
*
|
|
640
|
+
* The libdef `module` field is the lowercased basename (matches what
|
|
641
|
+
* `toModuleKey()` produces from an import specifier), and `include` is the
|
|
642
|
+
* case-preserving `<basename>.h` — quote-wrapped because component headers
|
|
643
|
+
* are project-local (resolved via the include path), not system angle-bracket
|
|
644
|
+
* includes. Matches the local-header convention in `resolveLocalModuleHeader`.
|
|
645
|
+
*/
|
|
646
|
+
function writeComponentLibdef(sourceHeaderPath, dtsOutputPath) {
|
|
647
|
+
const headerBaseName = path.basename(sourceHeaderPath); // e.g. "esp_wifi.h"
|
|
648
|
+
const dtsBaseName = path.basename(dtsOutputPath, '.d.ts'); // e.g. "esp_wifi"
|
|
649
|
+
const libdefPath = path.join(path.dirname(dtsOutputPath), `${dtsBaseName}.libdef.json`);
|
|
650
|
+
const libdef = {
|
|
651
|
+
module: dtsBaseName.toLowerCase(),
|
|
652
|
+
include: `"${headerBaseName}"`,
|
|
653
|
+
source: sourceHeaderPath,
|
|
654
|
+
};
|
|
655
|
+
writeText(libdefPath, JSON.stringify(libdef, null, 2) + '\n');
|
|
656
|
+
}
|
package/dist/libdef/registry.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { listFilesRecursive, readText } from "../utils/fs.js";
|
|
4
4
|
import { toModuleKey, toPascalCase } from "../utils/strings.js";
|
|
5
5
|
import { getLoadedFramework, hasLoadedFramework } from "../framework-registry.js";
|
|
6
6
|
function toArchitectureFromFqbn(fqbn) {
|
|
@@ -21,7 +21,10 @@ function getLibraryResolver() {
|
|
|
21
21
|
}
|
|
22
22
|
export function loadLibraryDefinitions(definitionsDir) {
|
|
23
23
|
const registry = new Map();
|
|
24
|
-
|
|
24
|
+
// Recursive scan: libdefs may live at the entry dir (single-level convention)
|
|
25
|
+
// or nested under cache trees like `.cuttlefish/component-decls/<component>/`
|
|
26
|
+
// (per-component overrides generated alongside .d.ts stubs).
|
|
27
|
+
const files = listFilesRecursive(definitionsDir, ".libdef.json");
|
|
25
28
|
for (const filePath of files) {
|
|
26
29
|
let def;
|
|
27
30
|
try {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent cache for the ESLint build gate.
|
|
3
|
+
*
|
|
4
|
+
* ESLint is a mandatory correctness gate: it excludes non-AOT code patterns
|
|
5
|
+
* that the transpiler cannot accept. Running it on every `cuttlefish build`
|
|
6
|
+
* costs ~3s (38% of a small-project build) and almost always finds nothing on
|
|
7
|
+
* repeat runs. Its result (zero errors) is a whole-program boolean that
|
|
8
|
+
* depends only on a small set of inputs, so it is cleanly cacheable.
|
|
9
|
+
*
|
|
10
|
+
* Soundness contract:
|
|
11
|
+
* - The cache ONLY records a successful (zero-error) lint result.
|
|
12
|
+
* - A failing lint never persists (the build aborts anyway).
|
|
13
|
+
* - On ANY input change the entry is invalidated and ESLint runs for real.
|
|
14
|
+
* - `options.force` and the `CUTTLEFISH_NO_CACHE` env var bypass entirely.
|
|
15
|
+
*
|
|
16
|
+
* This mirrors the historical `.cuttlefish-cache.json` timestamp+hash cache
|
|
17
|
+
* that used to live in this package. It is scoped to the ESLint gate (and the
|
|
18
|
+
* type-check gate) rather than the transpile-IR pass, because — unlike IR
|
|
19
|
+
* lowering — these gates' outcomes are whole-program booleans with no
|
|
20
|
+
* cross-module rehydration requirement.
|
|
21
|
+
*/
|
|
22
|
+
/** Resolve the eslint config the same way runEslintCheck does, to avoid drift. */
|
|
23
|
+
export declare function resolveEslintConfigPath(projectRoot: string): string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Compute the inputs that the ESLint result depends on. Two builds with the
|
|
26
|
+
* same fingerprint are guaranteed to produce the same lint outcome.
|
|
27
|
+
*/
|
|
28
|
+
export interface LintFingerprint {
|
|
29
|
+
digest: string;
|
|
30
|
+
/** Absolute paths that were hashed into the digest (for debugging). */
|
|
31
|
+
inputs: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function computeLintFingerprint(projectRoot: string, srcDir: string): LintFingerprint | null;
|
|
34
|
+
export interface GateCacheResult {
|
|
35
|
+
/** True when the gate can be skipped because the recorded success still holds. */
|
|
36
|
+
hit: boolean;
|
|
37
|
+
/** The fingerprint to record after a successful gate run. */
|
|
38
|
+
fingerprint: LintFingerprint | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Decide whether the ESLint gate can be skipped for this project.
|
|
42
|
+
*
|
|
43
|
+
* Returns `hit: true` only when:
|
|
44
|
+
* - caching is not disabled, not force-bypassed,
|
|
45
|
+
* - a fingerprint can be computed (config + eslint resolvable), and
|
|
46
|
+
* - the on-disk cache records a success for that exact fingerprint.
|
|
47
|
+
*
|
|
48
|
+
* `force` mirrors TranspileOptions.force and bypasses the cache.
|
|
49
|
+
*/
|
|
50
|
+
export declare function checkLintCache(projectRoot: string, srcDir: string, opts?: {
|
|
51
|
+
force?: boolean;
|
|
52
|
+
}): GateCacheResult;
|
|
53
|
+
/**
|
|
54
|
+
* Record a successful ESLint run (zero errors). Never call this after a
|
|
55
|
+
* failing run — a failing build must not persist a "clean" marker.
|
|
56
|
+
*/
|
|
57
|
+
export declare function recordLintSuccess(projectRoot: string, fingerprint: LintFingerprint): void;
|
|
58
|
+
/** Drop the lint entry (used when the gate is skipped entirely / nothing to cache). */
|
|
59
|
+
export declare function invalidateLint(projectRoot: string): void;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent cache for the ESLint build gate.
|
|
3
|
+
*
|
|
4
|
+
* ESLint is a mandatory correctness gate: it excludes non-AOT code patterns
|
|
5
|
+
* that the transpiler cannot accept. Running it on every `cuttlefish build`
|
|
6
|
+
* costs ~3s (38% of a small-project build) and almost always finds nothing on
|
|
7
|
+
* repeat runs. Its result (zero errors) is a whole-program boolean that
|
|
8
|
+
* depends only on a small set of inputs, so it is cleanly cacheable.
|
|
9
|
+
*
|
|
10
|
+
* Soundness contract:
|
|
11
|
+
* - The cache ONLY records a successful (zero-error) lint result.
|
|
12
|
+
* - A failing lint never persists (the build aborts anyway).
|
|
13
|
+
* - On ANY input change the entry is invalidated and ESLint runs for real.
|
|
14
|
+
* - `options.force` and the `CUTTLEFISH_NO_CACHE` env var bypass entirely.
|
|
15
|
+
*
|
|
16
|
+
* This mirrors the historical `.cuttlefish-cache.json` timestamp+hash cache
|
|
17
|
+
* that used to live in this package. It is scoped to the ESLint gate (and the
|
|
18
|
+
* type-check gate) rather than the transpile-IR pass, because — unlike IR
|
|
19
|
+
* lowering — these gates' outcomes are whole-program booleans with no
|
|
20
|
+
* cross-module rehydration requirement.
|
|
21
|
+
*/
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import crypto from "node:crypto";
|
|
25
|
+
import { createRequire } from "node:module";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const CACHE_VERSION = 2;
|
|
30
|
+
const DEFAULT_CACHE_NAME = ".cuttlefish-cache.json";
|
|
31
|
+
/** Bypass the cache entirely when set (debugging / CI cold runs). */
|
|
32
|
+
function isCacheDisabled() {
|
|
33
|
+
return process.env.CUTTLEFISH_NO_CACHE === "1" || process.env.CUTTLEFISH_NO_CACHE === "true";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Hash a file's content. Returns null if the file cannot be read (treated as
|
|
37
|
+
* "changed" by callers because the input set is no longer what we recorded).
|
|
38
|
+
*/
|
|
39
|
+
function hashFile(absPath) {
|
|
40
|
+
try {
|
|
41
|
+
const content = fs.readFileSync(absPath, "utf8");
|
|
42
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Hash size + mtime for a file. Used for inputs (eslint config, eslint package)
|
|
50
|
+
* that we don't want to fully read on every build, and whose identity is
|
|
51
|
+
* sufficiently captured by size+mtime. Returns null if the file is missing.
|
|
52
|
+
*/
|
|
53
|
+
function fingerprintStat(absPath) {
|
|
54
|
+
try {
|
|
55
|
+
const stat = fs.statSync(absPath);
|
|
56
|
+
return `${stat.size}:${stat.mtimeMs}`;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Transpiler-self fingerprint: invalidate every cache entry when the
|
|
64
|
+
* transpiler's own compiled sources change. Mirrors the historical
|
|
65
|
+
* computeToolchainFingerprint() in incremental-cache.ts.
|
|
66
|
+
*/
|
|
67
|
+
let cachedToolchainFingerprint;
|
|
68
|
+
function computeToolchainFingerprint() {
|
|
69
|
+
if (cachedToolchainFingerprint !== undefined)
|
|
70
|
+
return cachedToolchainFingerprint;
|
|
71
|
+
const candidates = [
|
|
72
|
+
"transpile.js",
|
|
73
|
+
"lint-cache.js",
|
|
74
|
+
"eslint-check.js",
|
|
75
|
+
path.join("emit", "cpp-emitter.js"),
|
|
76
|
+
path.join("ir", "build-ir.js"),
|
|
77
|
+
path.join("ir", "ownership-analysis.js"),
|
|
78
|
+
path.join("ir", "validation-orchestrator.js"),
|
|
79
|
+
];
|
|
80
|
+
const signature = candidates
|
|
81
|
+
.map((rel) => path.join(__dirname, rel))
|
|
82
|
+
.filter((p) => fs.existsSync(p))
|
|
83
|
+
.map((p) => {
|
|
84
|
+
const stat = fs.statSync(p);
|
|
85
|
+
return `${path.basename(p)}:${stat.size}:${stat.mtimeMs}`;
|
|
86
|
+
})
|
|
87
|
+
.join("|");
|
|
88
|
+
cachedToolchainFingerprint = crypto.createHash("sha256").update(signature).digest("hex").slice(0, 16);
|
|
89
|
+
return cachedToolchainFingerprint;
|
|
90
|
+
}
|
|
91
|
+
/** Resolve the eslint config the same way runEslintCheck does, to avoid drift. */
|
|
92
|
+
export function resolveEslintConfigPath(projectRoot) {
|
|
93
|
+
const cuttlefishConfig = path.join(projectRoot, ".cuttlefish", "eslint.config.mjs");
|
|
94
|
+
if (fs.existsSync(cuttlefishConfig))
|
|
95
|
+
return cuttlefishConfig;
|
|
96
|
+
for (const name of ["eslint.config.mjs", "eslint.config.js", "eslint.config.cjs"]) {
|
|
97
|
+
const candidate = path.join(projectRoot, name);
|
|
98
|
+
if (fs.existsSync(candidate))
|
|
99
|
+
return candidate;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
/** Resolve the eslint package version that runEslintCheck would load. */
|
|
104
|
+
function resolveEslintIdentity(projectRoot) {
|
|
105
|
+
const resolvers = [
|
|
106
|
+
createRequire(path.join(projectRoot, "package.json")),
|
|
107
|
+
require,
|
|
108
|
+
];
|
|
109
|
+
for (const r of resolvers) {
|
|
110
|
+
try {
|
|
111
|
+
const eslintPath = r.resolve("eslint");
|
|
112
|
+
const pkgPath = r.resolve("eslint/package.json");
|
|
113
|
+
const version = JSON.parse(fs.readFileSync(pkgPath, "utf8")).version ?? "unknown";
|
|
114
|
+
return { path: eslintPath, version };
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// try next resolver
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
/** Recursively gather .ts/.tsx/.ui files under a directory (the lint input set). */
|
|
123
|
+
function gatherSourceFiles(srcDir) {
|
|
124
|
+
const out = [];
|
|
125
|
+
if (!fs.existsSync(srcDir) || !fs.statSync(srcDir).isDirectory())
|
|
126
|
+
return out;
|
|
127
|
+
const stack = [srcDir];
|
|
128
|
+
while (stack.length > 0) {
|
|
129
|
+
const dir = stack.pop();
|
|
130
|
+
let entries;
|
|
131
|
+
try {
|
|
132
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
const full = path.join(dir, entry.name);
|
|
139
|
+
if (entry.isDirectory()) {
|
|
140
|
+
if (entry.name === "node_modules" || entry.name === "out" || entry.name.startsWith("out-"))
|
|
141
|
+
continue;
|
|
142
|
+
stack.push(full);
|
|
143
|
+
}
|
|
144
|
+
else if (entry.isFile()) {
|
|
145
|
+
const ext = entry.name.toLowerCase();
|
|
146
|
+
if (ext.endsWith(".ts") || ext.endsWith(".tsx") || ext.endsWith(".ui")) {
|
|
147
|
+
out.push(full);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
out.sort();
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
export function computeLintFingerprint(projectRoot, srcDir) {
|
|
156
|
+
const configPath = resolveEslintConfigPath(projectRoot);
|
|
157
|
+
if (!configPath)
|
|
158
|
+
return null; // no config => runEslintCheck returns [] without loading eslint
|
|
159
|
+
const eslint = resolveEslintIdentity(projectRoot);
|
|
160
|
+
if (!eslint)
|
|
161
|
+
return null; // eslint unresolvable => runEslintCheck returns []
|
|
162
|
+
const parts = [];
|
|
163
|
+
parts.push(["toolchain", computeToolchainFingerprint()]);
|
|
164
|
+
parts.push(["eslint", `${eslint.version}@${eslint.path}`]);
|
|
165
|
+
parts.push(["config", `${configPath}:${fingerprintStat(configPath) ?? "missing"}`]);
|
|
166
|
+
const sources = gatherSourceFiles(srcDir);
|
|
167
|
+
for (const f of sources) {
|
|
168
|
+
const h = hashFile(f);
|
|
169
|
+
parts.push(["src", `${f}:${h ?? "missing"}`]);
|
|
170
|
+
}
|
|
171
|
+
const digest = crypto
|
|
172
|
+
.createHash("sha256")
|
|
173
|
+
.update(parts.map((p) => p.join("=")).join("\n"))
|
|
174
|
+
.digest("hex")
|
|
175
|
+
.slice(0, 32);
|
|
176
|
+
return { digest, inputs: parts.map((p) => p[1]) };
|
|
177
|
+
}
|
|
178
|
+
function cachePathFor(projectRoot) {
|
|
179
|
+
return path.join(projectRoot, DEFAULT_CACHE_NAME);
|
|
180
|
+
}
|
|
181
|
+
function loadCacheFile(projectRoot) {
|
|
182
|
+
const cachePath = cachePathFor(projectRoot);
|
|
183
|
+
try {
|
|
184
|
+
if (!fs.existsSync(cachePath))
|
|
185
|
+
return null;
|
|
186
|
+
const data = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
187
|
+
if (data.version === CACHE_VERSION &&
|
|
188
|
+
data.toolchainFingerprint === computeToolchainFingerprint() &&
|
|
189
|
+
path.resolve(data.rootDir) === path.resolve(projectRoot)) {
|
|
190
|
+
return data;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Corrupt or unreadable — treat as empty.
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
function saveCacheFile(projectRoot, data) {
|
|
199
|
+
data.updatedAt = Date.now();
|
|
200
|
+
try {
|
|
201
|
+
fs.writeFileSync(cachePathFor(projectRoot), JSON.stringify(data, null, 2), "utf8");
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Non-fatal: caching is best-effort. Next build just re-runs the gate.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function freshCacheFile(projectRoot) {
|
|
208
|
+
return {
|
|
209
|
+
version: CACHE_VERSION,
|
|
210
|
+
toolchainFingerprint: computeToolchainFingerprint(),
|
|
211
|
+
rootDir: path.resolve(projectRoot),
|
|
212
|
+
gates: {},
|
|
213
|
+
createdAt: Date.now(),
|
|
214
|
+
updatedAt: Date.now(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Decide whether the ESLint gate can be skipped for this project.
|
|
219
|
+
*
|
|
220
|
+
* Returns `hit: true` only when:
|
|
221
|
+
* - caching is not disabled, not force-bypassed,
|
|
222
|
+
* - a fingerprint can be computed (config + eslint resolvable), and
|
|
223
|
+
* - the on-disk cache records a success for that exact fingerprint.
|
|
224
|
+
*
|
|
225
|
+
* `force` mirrors TranspileOptions.force and bypasses the cache.
|
|
226
|
+
*/
|
|
227
|
+
export function checkLintCache(projectRoot, srcDir, opts = {}) {
|
|
228
|
+
const fingerprint = computeLintFingerprint(projectRoot, srcDir);
|
|
229
|
+
if (fingerprint === null)
|
|
230
|
+
return { hit: false, fingerprint: null };
|
|
231
|
+
if (opts.force || isCacheDisabled())
|
|
232
|
+
return { hit: false, fingerprint };
|
|
233
|
+
const data = loadCacheFile(projectRoot);
|
|
234
|
+
if (!data)
|
|
235
|
+
return { hit: false, fingerprint };
|
|
236
|
+
const entry = data.gates.lint;
|
|
237
|
+
return { hit: entry?.digest === fingerprint.digest, fingerprint };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Record a successful ESLint run (zero errors). Never call this after a
|
|
241
|
+
* failing run — a failing build must not persist a "clean" marker.
|
|
242
|
+
*/
|
|
243
|
+
export function recordLintSuccess(projectRoot, fingerprint) {
|
|
244
|
+
if (isCacheDisabled())
|
|
245
|
+
return;
|
|
246
|
+
const data = loadCacheFile(projectRoot) ?? freshCacheFile(projectRoot);
|
|
247
|
+
data.gates.lint = { digest: fingerprint.digest, recordedAt: Date.now() };
|
|
248
|
+
saveCacheFile(projectRoot, data);
|
|
249
|
+
}
|
|
250
|
+
/** Drop the lint entry (used when the gate is skipped entirely / nothing to cache). */
|
|
251
|
+
export function invalidateLint(projectRoot) {
|
|
252
|
+
const data = loadCacheFile(projectRoot);
|
|
253
|
+
if (data && data.gates.lint) {
|
|
254
|
+
delete data.gates.lint;
|
|
255
|
+
saveCacheFile(projectRoot, data);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -121,7 +121,7 @@ export function collectTranspileGraph(entryFile, boardPackage) {
|
|
|
121
121
|
}
|
|
122
122
|
if (!moduleSpecifier)
|
|
123
123
|
continue;
|
|
124
|
-
if (moduleSpecifier === "@typecad/expect" || moduleSpecifier === "@typecad/ui")
|
|
124
|
+
if (moduleSpecifier === "@typecad/expect" || moduleSpecifier === "@typecad/ui" || moduleSpecifier === "@typecad/safety")
|
|
125
125
|
continue;
|
|
126
126
|
if (moduleSpecifier.startsWith("@typecad/"))
|
|
127
127
|
continue;
|
|
@@ -159,22 +159,27 @@ export function collectTranspileGraph(entryFile, boardPackage) {
|
|
|
159
159
|
if (moduleSpecifier === "@typecad/expect") {
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
|
-
// Skip @typecad/ui
|
|
163
|
-
//
|
|
164
|
-
// intercepted
|
|
165
|
-
// must NOT be emitted as
|
|
166
|
-
// _ui_t
|
|
167
|
-
|
|
162
|
+
// Skip @typecad/ui and @typecad/safety — they provide compile-time
|
|
163
|
+
// authoring stubs only. Their calls (ui.mount/signal/bind, safe.read)
|
|
164
|
+
// are intercepted at IR-build time and lowered to IR; the packages
|
|
165
|
+
// themselves must NOT be emitted as C++ modules (they would synthesize
|
|
166
|
+
// bogus _ui_t / _safe_t structs). All runtime definitions the user code
|
|
167
|
+
// needs (enums, structs, shims) are provided by the safety polyfill.
|
|
168
|
+
if (moduleSpecifier === "@typecad/ui" || moduleSpecifier === "@typecad/safety") {
|
|
168
169
|
continue;
|
|
169
170
|
}
|
|
170
|
-
// Skip @typecad/board, @typecad/board-*, @typecad/mcu-*,
|
|
171
|
-
// @typecad/framework-* — these packages ship src/ for HAL metadata
|
|
171
|
+
// Skip @typecad/board, @typecad/board-*, @typecad/mcu-*, @typecad/hal,
|
|
172
|
+
// and @typecad/framework-* — these packages ship src/ for HAL metadata
|
|
172
173
|
// introspection (hal-parser.ts, board-resolver.ts) but their source
|
|
173
174
|
// must NOT be transpiled to C++. The HAL resolver loads class/method
|
|
174
175
|
// metadata from these files separately; emitting them as C++ produces
|
|
175
176
|
// thousands of lines of stub functions (board(), gpioWrite(), etc.)
|
|
176
177
|
// and pulls in unsupported types (Promise, variant, Object.freeze).
|
|
178
|
+
// Skipping @typecad/hal is especially important: its 28 source files
|
|
179
|
+
// (gpio.ts, i2c.ts, spi.ts, etc.) were all walked through full
|
|
180
|
+
// buildProgramIR, adding ~40 seconds to every transpile.
|
|
177
181
|
if (moduleSpecifier === "@typecad/board"
|
|
182
|
+
|| moduleSpecifier === "@typecad/hal"
|
|
178
183
|
|| moduleSpecifier.startsWith("@typecad/board-")
|
|
179
184
|
|| moduleSpecifier.startsWith("@typecad/mcu-")
|
|
180
185
|
|| moduleSpecifier.startsWith("@typecad/framework-")) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ProgramIR } from "../api/index.js";
|
|
2
|
+
import type { RuntimePolyfillIR } from "../api/shared/index.js";
|
|
3
|
+
import type { AsyncRuntimeConfig } from "../api/shared/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Build a RuntimePolyfillIR for the no-STL cooperative scheduler, when the
|
|
6
|
+
* program has async/timer work AND the strategy has opted into priority/time-
|
|
7
|
+
* budget scheduling via AsyncRuntimeConfig.
|
|
8
|
+
*
|
|
9
|
+
* The polyfill carries only the scheduler namespace + CoopSched_run() alias.
|
|
10
|
+
* Per-program work-unit registration + dispatch lines are emitted separately
|
|
11
|
+
* by each strategy's asyncLoopInjection() via buildCoopSchedInjection().
|
|
12
|
+
*
|
|
13
|
+
* Why a polyfill and not shimLines(): polyfills are dead-code-eliminated
|
|
14
|
+
* (filterPolyfillHelpers) and feature-gated centrally, matching how
|
|
15
|
+
* async_runtime / timer_methods are handled. The scheduler is STL-free, so it
|
|
16
|
+
* links on minimal-libc targets (Zephyr) where the std::function Promise
|
|
17
|
+
* runtime does not.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildCoopSchedulerPolyfill(_program: ProgramIR, config: AsyncRuntimeConfig, currentTimeExpr?: string): RuntimePolyfillIR | null;
|