@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.10
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/README.md +10 -10
- package/dist/api/board-types.d.ts +1 -1
- 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 +595 -0
- package/dist/api/shared/framework-manifest.js +193 -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 +764 -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 +119 -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/types.d.ts +8 -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-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +249 -32
- 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/framework-catalog.d.ts +64 -0
- package/dist/create/framework-catalog.js +136 -0
- package/dist/create/index.d.ts +5 -1
- package/dist/create/index.js +3 -1
- package/dist/create/init-scaffold.d.ts +12 -3
- package/dist/create/init-scaffold.js +49 -14
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +263 -17
- package/dist/create/init-wizard.js +41 -27
- package/dist/create/install-deps.d.ts +32 -0
- package/dist/create/install-deps.js +46 -0
- 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 +372 -30
- 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/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +40 -26
- 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 +207 -6
- 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 +733 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +16 -19
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +39 -1
- package/dist/ir/peripheral-ownership.js +5 -0
- package/dist/ir/peripheral-validation.d.ts +1 -1
- package/dist/ir/peripheral-validation.js +6 -3
- package/dist/ir/pin-alias-conflict.d.ts +1 -1
- package/dist/ir/pin-alias-conflict.js +2 -1
- package/dist/ir/pin-capability-validation.js +71 -32
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +54 -18
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +81 -0
- package/dist/ir/program-analysis.js +429 -0
- package/dist/ir/pulldown-validation.d.ts +1 -1
- package/dist/ir/pulldown-validation.js +2 -1
- package/dist/ir/pwm-timer-sharing.d.ts +1 -1
- package/dist/ir/pwm-timer-sharing.js +2 -1
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/resource-analysis.js +2 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
- package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +61 -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 +137 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +20 -2
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/unit-suspicion-validation.js +9 -7
- 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 +15 -8
- 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 +8 -2
- package/dist/testing.js +9 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +191 -49
- package/dist/types.d.ts +22 -3
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +122 -2
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/dist/utils/ui.js +3 -1
- package/package.json +23 -3
- 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/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -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;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { generateCoopScheduler } from "../api/shared/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a RuntimePolyfillIR for the no-STL cooperative scheduler, when the
|
|
4
|
+
* program has async/timer work AND the strategy has opted into priority/time-
|
|
5
|
+
* budget scheduling via AsyncRuntimeConfig.
|
|
6
|
+
*
|
|
7
|
+
* The polyfill carries only the scheduler namespace + CoopSched_run() alias.
|
|
8
|
+
* Per-program work-unit registration + dispatch lines are emitted separately
|
|
9
|
+
* by each strategy's asyncLoopInjection() via buildCoopSchedInjection().
|
|
10
|
+
*
|
|
11
|
+
* Why a polyfill and not shimLines(): polyfills are dead-code-eliminated
|
|
12
|
+
* (filterPolyfillHelpers) and feature-gated centrally, matching how
|
|
13
|
+
* async_runtime / timer_methods are handled. The scheduler is STL-free, so it
|
|
14
|
+
* links on minimal-libc targets (Zephyr) where the std::function Promise
|
|
15
|
+
* runtime does not.
|
|
16
|
+
*/
|
|
17
|
+
export function buildCoopSchedulerPolyfill(_program, config, currentTimeExpr = "millis()") {
|
|
18
|
+
if (!config.enablePriority && !config.enableTimeBudget)
|
|
19
|
+
return null;
|
|
20
|
+
return {
|
|
21
|
+
kind: "polyfill",
|
|
22
|
+
id: "coop_scheduler",
|
|
23
|
+
domain: "embedded",
|
|
24
|
+
// STL-free: only needs a 32-bit unsigned time source (millis() /
|
|
25
|
+
// k_uptime_get_32()) and fixed-width types, both available everywhere.
|
|
26
|
+
requiredIncludes: [],
|
|
27
|
+
forwardDeclarations: [],
|
|
28
|
+
helperStructs: [
|
|
29
|
+
generateCoopScheduler({
|
|
30
|
+
capacity: 8,
|
|
31
|
+
priorities: config.schedulerPriorities ?? 2,
|
|
32
|
+
enableTimeBudget: config.enableTimeBudget ?? false,
|
|
33
|
+
timeBudgetMs: config.timeBudgetMs ?? 5,
|
|
34
|
+
currentTimeExpr,
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
helperFunctions: [],
|
|
38
|
+
shimMacros: [],
|
|
39
|
+
dependencies: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -17,7 +17,9 @@ export declare class GenericStrategy implements PlatformStrategy {
|
|
|
17
17
|
overrideBaseName(originalBaseName: string, _outDirBaseName: string, _isEntryFile: boolean, _isNpmPackage: boolean): string;
|
|
18
18
|
effectiveEmitMode(requestedMode: string, _isNpmPackage: boolean): string;
|
|
19
19
|
normalizeCppType(typeName: string): string;
|
|
20
|
-
defaultNumericType(
|
|
20
|
+
defaultNumericType(compliance?: {
|
|
21
|
+
isBanned(ruleId: string): boolean;
|
|
22
|
+
}): string;
|
|
21
23
|
mapReturnType(_functionName: string, returnType: string): string;
|
|
22
24
|
isStringLikeType(cppType: string): boolean;
|
|
23
25
|
isPointerType(cppType: string): boolean;
|
|
@@ -54,8 +56,8 @@ export declare class GenericStrategy implements PlatformStrategy {
|
|
|
54
56
|
structFieldInitializer(_fieldValue: ExpressionIR, _compiletimeVarNames: Set<string>, _renderExpr: (e: ExpressionIR) => string): string | undefined;
|
|
55
57
|
getAsyncRuntimeConfig(): AsyncRuntimeConfig;
|
|
56
58
|
generateNativePolyfills(program: ProgramIR, ctx?: PlatformContext): RuntimePolyfillIR[];
|
|
57
|
-
asyncLoopInjection(
|
|
58
|
-
asyncLoopInjection(
|
|
59
|
+
asyncLoopInjection(taskVarNames: string[], config: AsyncRuntimeConfig): string[];
|
|
60
|
+
asyncLoopInjection(taskVarNames: string[], hasPromiseRuntime: boolean): string[];
|
|
59
61
|
asyncDriverFunctionName(): string;
|
|
60
62
|
shouldSkipTypeAlias(_cppType: string): boolean;
|
|
61
63
|
emitDiagnostics(_emitMode: string): Diagnostic[];
|
|
@@ -68,6 +70,7 @@ export declare class GenericStrategy implements PlatformStrategy {
|
|
|
68
70
|
close: string;
|
|
69
71
|
} | undefined;
|
|
70
72
|
getStdLibSupport(_architecture?: string): StdLibSupport;
|
|
73
|
+
debugMode(_target?: string): 'gdb' | 'printf';
|
|
71
74
|
generateDebugInitCode(): string[];
|
|
72
75
|
generateDebugBreakpointCode(params: {
|
|
73
76
|
fileName: string;
|
|
@@ -98,4 +101,13 @@ export declare class GenericStrategy implements PlatformStrategy {
|
|
|
98
101
|
supportedDisplayDrivers(): ReadonlySet<string>;
|
|
99
102
|
colorFormat(): "rgb565" | "rgb666" | "rgb888" | "mono";
|
|
100
103
|
graphicsCapacity(): GraphicsCapacity;
|
|
104
|
+
readDigitalPin(_pin: string): string;
|
|
105
|
+
readAnalogPin(_pin: string): string;
|
|
106
|
+
writeDigitalPin(_pin: string, _val: string): string;
|
|
107
|
+
setPinMode(_pin: string, _mode: string): string;
|
|
108
|
+
delayMs(_ms: string): string;
|
|
109
|
+
delayMicroseconds(_us: string): string;
|
|
110
|
+
halCallNames(): ReadonlySet<string>;
|
|
111
|
+
isHalCall(_name: string): boolean;
|
|
112
|
+
analogReadCallNames(): ReadonlySet<string>;
|
|
101
113
|
}
|
|
@@ -42,10 +42,14 @@ export class GenericStrategy {
|
|
|
42
42
|
// ── Type normalisation ──────────────────────────────────────────────────
|
|
43
43
|
normalizeCppType(typeName) {
|
|
44
44
|
if (typeName === "auto")
|
|
45
|
-
return
|
|
45
|
+
return this.defaultNumericType();
|
|
46
46
|
return typeName;
|
|
47
47
|
}
|
|
48
|
-
defaultNumericType() {
|
|
48
|
+
defaultNumericType(compliance) {
|
|
49
|
+
// A3-9-1: fixed-width integers under AUTOSAR. Legacy 'int' spelling
|
|
50
|
+
// preserved when autosar is off so default output is byte-identical.
|
|
51
|
+
return compliance?.isBanned("A3-9-1") ? "int32_t" : "int";
|
|
52
|
+
}
|
|
49
53
|
mapReturnType(_functionName, returnType) {
|
|
50
54
|
return this.normalizeCppType(returnType);
|
|
51
55
|
}
|
|
@@ -185,17 +189,23 @@ export class GenericStrategy {
|
|
|
185
189
|
helpers.push(asyncRuntime);
|
|
186
190
|
return helpers;
|
|
187
191
|
}
|
|
188
|
-
asyncLoopInjection(
|
|
192
|
+
asyncLoopInjection(taskVarNames, configOrBool) {
|
|
189
193
|
let hasPromiseRuntime;
|
|
190
|
-
if (typeof configOrBool ===
|
|
194
|
+
if (typeof configOrBool === "boolean") {
|
|
191
195
|
hasPromiseRuntime = configOrBool;
|
|
192
196
|
}
|
|
193
197
|
else {
|
|
194
198
|
hasPromiseRuntime = configOrBool.hasPromiseRuntime;
|
|
195
199
|
}
|
|
200
|
+
// Drive every async state-machine task once per driver-function iteration.
|
|
201
|
+
// Task globals auto-start on their first .run() (STATE_0 runs
|
|
202
|
+
// unconditionally); the state machine no-ops in its terminal/cyclic state.
|
|
196
203
|
const lines = [];
|
|
197
204
|
if (hasPromiseRuntime)
|
|
198
205
|
lines.push(" cuttlefish_pump_microtasks();");
|
|
206
|
+
for (const n of taskVarNames) {
|
|
207
|
+
lines.push(` ${n}.run();`);
|
|
208
|
+
}
|
|
199
209
|
return lines;
|
|
200
210
|
}
|
|
201
211
|
asyncDriverFunctionName() { return "main"; }
|
|
@@ -213,6 +223,9 @@ export class GenericStrategy {
|
|
|
213
223
|
enumApiGuard(_enumName) { return undefined; }
|
|
214
224
|
getStdLibSupport(_architecture) { return DEFAULT_STDLIB_SUPPORT; }
|
|
215
225
|
// ── Debug code generation ─────────────────────────────────────────────────
|
|
226
|
+
debugMode(_target) {
|
|
227
|
+
return 'printf';
|
|
228
|
+
}
|
|
216
229
|
generateDebugInitCode() {
|
|
217
230
|
return [
|
|
218
231
|
'// === DEBUG: Initialize ===',
|
|
@@ -286,4 +299,36 @@ export class GenericStrategy {
|
|
|
286
299
|
nodeStorage: "flash",
|
|
287
300
|
};
|
|
288
301
|
}
|
|
302
|
+
// ── HAL (generic fallback) ──────────────────────────────────────────────
|
|
303
|
+
// The generic/host target has no GPIO or timing hardware. These return
|
|
304
|
+
// documented no-op stubs so emit stays deterministic rather than throwing
|
|
305
|
+
// mid-codegen. Host tests that exercise pin-polling paths use a mock
|
|
306
|
+
// PlatformStrategy (see tests/packages/cuttlefish/async-runtime-static.test.ts).
|
|
307
|
+
readDigitalPin(_pin) {
|
|
308
|
+
return "/* gpio unavailable on generic target */ 0";
|
|
309
|
+
}
|
|
310
|
+
readAnalogPin(_pin) {
|
|
311
|
+
return "/* adc unavailable on generic target */ 0";
|
|
312
|
+
}
|
|
313
|
+
writeDigitalPin(_pin, _val) {
|
|
314
|
+
return "/* gpio unavailable on generic target */";
|
|
315
|
+
}
|
|
316
|
+
setPinMode(_pin, _mode) {
|
|
317
|
+
return "/* gpio unavailable on generic target */";
|
|
318
|
+
}
|
|
319
|
+
delayMs(_ms) {
|
|
320
|
+
return "/* delay unavailable on generic target */";
|
|
321
|
+
}
|
|
322
|
+
delayMicroseconds(_us) {
|
|
323
|
+
return "/* delay unavailable on generic target */";
|
|
324
|
+
}
|
|
325
|
+
halCallNames() {
|
|
326
|
+
return new Set();
|
|
327
|
+
}
|
|
328
|
+
isHalCall(_name) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
analogReadCallNames() {
|
|
332
|
+
return new Set();
|
|
333
|
+
}
|
|
289
334
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Dynamically load @typecad/safety/engine and register the hook.
|
|
2
|
+
* Called by transpile.ts at the start of each transpile run.
|
|
3
|
+
* Safe to call when @typecad/safety is absent (hook stays null). */
|
|
4
|
+
export declare function loadSafetyEngine(): Promise<void>;
|
|
5
|
+
/** Reset the safety bridge: forget a load was attempted and clear the hook.
|
|
6
|
+
* Mirrors resetUIEngine() so each transpile run / test starts clean. */
|
|
7
|
+
export declare function resetSafetyEngine(): void;
|
|
8
|
+
/** Test-only: force the bridge into the "no @typecad/safety" state — mark the
|
|
9
|
+
* load as already attempted and leave the hook null, exactly as if the
|
|
10
|
+
* dynamic import had failed because the package is not installed. */
|
|
11
|
+
export declare function __simulateSafetyAbsentForTest(): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Safety bridge — loads the safety engine from @typecad/safety and registers it.
|
|
3
|
+
//
|
|
4
|
+
// transpile.ts calls loadSafetyEngine() at the start of transpileFile(). If
|
|
5
|
+
// @typecad/safety is not installed, the import fails gracefully and the hook
|
|
6
|
+
// stays null — cuttlefish works as a pure TS→C++ transpiler with no safety
|
|
7
|
+
// support.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
import { setSafetyHook } from "../safety-hook.js";
|
|
10
|
+
let loaded = false;
|
|
11
|
+
/** Dynamically load @typecad/safety/engine and register the hook.
|
|
12
|
+
* Called by transpile.ts at the start of each transpile run.
|
|
13
|
+
* Safe to call when @typecad/safety is absent (hook stays null). */
|
|
14
|
+
export async function loadSafetyEngine() {
|
|
15
|
+
if (loaded)
|
|
16
|
+
return;
|
|
17
|
+
loaded = true;
|
|
18
|
+
try {
|
|
19
|
+
// Dynamic import — @typecad/safety is optional. The module specifier is
|
|
20
|
+
// routed through a variable (rather than a string literal) so tsc types
|
|
21
|
+
// the result as `any` and never resolves @typecad/safety's declaration
|
|
22
|
+
// files while type-checking this file. A literal specifier would make tsc
|
|
23
|
+
// load safety's dist/engine-index.d.ts, which imports back
|
|
24
|
+
// @typecad/cuttlefish/safety-hook-types — a self-referencing package path
|
|
25
|
+
// that resolves into this package's own dist/ output and causes TS5055
|
|
26
|
+
// ("would overwrite input file") on every rebuild where dist/ exists.
|
|
27
|
+
const safetyEnginePath = "@typecad/safety/engine";
|
|
28
|
+
const engine = await import(safetyEnginePath);
|
|
29
|
+
const hook = engine.registerSafetyEngine();
|
|
30
|
+
setSafetyHook(hook);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// @typecad/safety is not installed — cuttlefish works without safety.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Reset the safety bridge: forget a load was attempted and clear the hook.
|
|
37
|
+
* Mirrors resetUIEngine() so each transpile run / test starts clean. */
|
|
38
|
+
export function resetSafetyEngine() {
|
|
39
|
+
loaded = false;
|
|
40
|
+
setSafetyHook(null);
|
|
41
|
+
}
|
|
42
|
+
/** Test-only: force the bridge into the "no @typecad/safety" state — mark the
|
|
43
|
+
* load as already attempted and leave the hook null, exactly as if the
|
|
44
|
+
* dynamic import had failed because the package is not installed. */
|
|
45
|
+
export function __simulateSafetyAbsentForTest() {
|
|
46
|
+
loaded = true;
|
|
47
|
+
setSafetyHook(null);
|
|
48
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SafetyMetadata } from "../safety-hook.js";
|
|
2
|
+
/** Dynamically import the safety package's sidecar renderer.
|
|
3
|
+
* Uses a variable-held specifier so tsc doesn't try to resolve the
|
|
4
|
+
* @typecad/safety types at build time (avoids circular dependency). */
|
|
5
|
+
export declare function renderSafetySidecar(metadata: SafetyMetadata, toolVersion: string): Promise<string>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Dynamically import the safety package's sidecar renderer.
|
|
2
|
+
* Uses a variable-held specifier so tsc doesn't try to resolve the
|
|
3
|
+
* @typecad/safety types at build time (avoids circular dependency). */
|
|
4
|
+
export async function renderSafetySidecar(metadata, toolVersion) {
|
|
5
|
+
const spec = "@typecad/safety/iso26262/sidecar-writer";
|
|
6
|
+
try {
|
|
7
|
+
const mod = await import(spec);
|
|
8
|
+
return mod.renderSafetySidecar(metadata, toolVersion);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Fallback: inline the renderer so the sidecar is produced even if
|
|
12
|
+
// the subpath export isn't configured.
|
|
13
|
+
return JSON.stringify({
|
|
14
|
+
schemaVersion: "1.0.0",
|
|
15
|
+
standard: "ISO 26262 Part 6",
|
|
16
|
+
tool: "cuttlefish (@typecad/safety)",
|
|
17
|
+
toolVersion,
|
|
18
|
+
generatedAt: new Date().toISOString(),
|
|
19
|
+
safetyFunctions: metadata.functions,
|
|
20
|
+
}, null, 2);
|
|
21
|
+
}
|
|
22
|
+
}
|