@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
package/dist/transpile.js
CHANGED
|
@@ -3,15 +3,33 @@ import path from "node:path";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import ts from "typescript";
|
|
6
|
+
// Read once at module load. Used to populate the AUTOSAR deviation sidecar's
|
|
7
|
+
// `toolVersion` field. Falls back to "unknown" if the read fails (e.g. tests
|
|
8
|
+
// that import the module from an unexpected location).
|
|
9
|
+
const CUTTLEFISH_VERSION = (() => {
|
|
10
|
+
try {
|
|
11
|
+
const require_ = createRequire(import.meta.url);
|
|
12
|
+
// transpile.ts lives in src/, compiled to dist/transpile.js. The package
|
|
13
|
+
// root is one level up from dist/, so from dist/ the path is ../package.json.
|
|
14
|
+
// From src/ (TypeScript source used by tests) it's also ../package.json.
|
|
15
|
+
const pkg = require_("../package.json");
|
|
16
|
+
return pkg.version ?? "unknown";
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return "unknown";
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
6
22
|
import { buildProgramIR } from "./ir/build-ir.js";
|
|
7
23
|
import { classDeclarationToIR } from "./ir/declaration-builders.js";
|
|
8
24
|
import { clickHandlers } from "./ir/transformers/ui-call-resolver.js";
|
|
9
25
|
import { requireUIHook, hasUIHook } from "./ui-hook.js";
|
|
10
26
|
import { loadUIEngine } from "./ui/ui-bridge.js";
|
|
27
|
+
import { hasSafetyHook, requireSafetyHook } from "./safety-hook.js";
|
|
28
|
+
import { loadSafetyEngine } from "./safety/safety-bridge.js";
|
|
11
29
|
import { setDisplayProfile, resetDisplayProfile } from "./stores/display-profile-store.js";
|
|
12
30
|
import { setThemeCss, resetThemeCss, setThemeClass } from "./stores/theme-store.js";
|
|
13
31
|
import { emitCpp, registerAllEnumNames } from "./emit/cpp-emitter.js";
|
|
14
|
-
import { readText } from "./utils/fs.js";
|
|
32
|
+
import { readText, writeText } from "./utils/fs.js";
|
|
15
33
|
import { debug as logDebug, info } from "./utils/logger.js";
|
|
16
34
|
import { loadLibraryDefinitions, generateLibdefStubs } from "./libdef/registry.js";
|
|
17
35
|
import { buildCallGraph } from "./ir/call-graph.js";
|
|
@@ -19,7 +37,7 @@ import { clearCaches, } from "./cache.js";
|
|
|
19
37
|
import { detectEntryPoints, detectExportedEntryPoints } from "./ir/entry-points.js";
|
|
20
38
|
import { analyzeReachability } from "./ir/reachability.js";
|
|
21
39
|
import { filterProgramIR } from "./ir/filter.js";
|
|
22
|
-
import { setActiveStrategy } from "./ir/hal-resolver.js";
|
|
40
|
+
import { setActiveStrategy, loadHALModules } from "./ir/hal-resolver.js";
|
|
23
41
|
import { CompilationContext, contextStorage } from "./ir/build-ir-state.js";
|
|
24
42
|
import { buildSymbolTable, mergeSymbolTable, resolveInheritance, createSymbolTable } from "./ir/symbol-table.js";
|
|
25
43
|
import { loadBreakpoints, preprocess as debugPreprocess } from "./debug/index.js";
|
|
@@ -28,6 +46,7 @@ import { typeCheckFiles } from "./orchestrator/type-checker.js";
|
|
|
28
46
|
import { runSemanticGates } from "./orchestrator/type-checker.js";
|
|
29
47
|
import { autoGenerateMissingDecls } from "./orchestrator/dts-generator.js";
|
|
30
48
|
import { runEslintCheck, printEslintErrors } from "./eslint-check.js";
|
|
49
|
+
import { checkLintCache, recordLintSuccess } from "./lint-cache.js";
|
|
31
50
|
import { initProfiler, getProfiler } from "./profiler/index.js";
|
|
32
51
|
import { buildDiagnosticsReport, writeDiagnosticsReport } from "./diagnostics/diagnostics-report.js";
|
|
33
52
|
import { resolveImport, } from "./transpile/resolution.js";
|
|
@@ -47,23 +66,30 @@ function loadExpectPreprocessor() {
|
|
|
47
66
|
}
|
|
48
67
|
}
|
|
49
68
|
function cleanOutput(_entryDir, outDir) {
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
catch (e) {
|
|
58
|
-
if (process.env.CUTTLEFISH_DEBUG)
|
|
59
|
-
console.error("[transpile] Failed to clean output dir:", e);
|
|
60
|
-
}
|
|
69
|
+
// Preserved for incremental-build support: writeText now skips writing when
|
|
70
|
+
// content is identical, so keeping the existing output dir intact lets
|
|
71
|
+
// downstream build tools (idf.py/ninja, arduino-cli) reuse their build
|
|
72
|
+
// caches. Stale files from removed source modules are harmless — they're
|
|
73
|
+
// not referenced by the current entry file and won't be compiled.
|
|
74
|
+
// The output dir is still created (via writeText → ensureDir) on first run.
|
|
61
75
|
}
|
|
62
76
|
/**
|
|
63
77
|
* Auto-generates .d.ts files for C++ modules that are missing declarations.
|
|
64
78
|
* Also tries to generate declarations for framework libraries.
|
|
65
79
|
* Returns list of generated files.
|
|
66
80
|
*/
|
|
81
|
+
/**
|
|
82
|
+
* True iff the program imports `@typecad/safety` anywhere. The safety
|
|
83
|
+
* transform pass uses this as its fast no-op guard — when no file imports
|
|
84
|
+
* safety, the pass returns the program unchanged.
|
|
85
|
+
*/
|
|
86
|
+
function entryImportsSafety(program) {
|
|
87
|
+
for (const imp of program.imports) {
|
|
88
|
+
if (imp.moduleSpecifier === "@typecad/safety")
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
67
93
|
/**
|
|
68
94
|
* Apply tree-shaking to program IR if enabled
|
|
69
95
|
*/
|
|
@@ -131,6 +157,8 @@ function applyTreeShaking(programIR, target, treeShakingOptions) {
|
|
|
131
157
|
import { isStringEnum } from "./api/shared/index.js";
|
|
132
158
|
import { resolveStrategy } from "./platform/registry.js";
|
|
133
159
|
import { loadFrameworkPackage } from "./framework-package.js";
|
|
160
|
+
export { loadFrameworkPackage };
|
|
161
|
+
export { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
|
|
134
162
|
import { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
|
|
135
163
|
function formatFatalDiagnostics(entries) {
|
|
136
164
|
const errors = entries.filter(({ diagnostic }) => diagnostic.severity === "error");
|
|
@@ -138,7 +166,11 @@ function formatFatalDiagnostics(entries) {
|
|
|
138
166
|
`Transpilation aborted because ${errors.length} unsupported pattern${errors.length === 1 ? "" : "s"} were found.`,
|
|
139
167
|
];
|
|
140
168
|
for (const { filePath, diagnostic } of errors) {
|
|
141
|
-
|
|
169
|
+
// Prefer diagnostic.filePath (set explicitly by the emitter, e.g. AUTOSAR
|
|
170
|
+
// diagnostics that map a C++ line back to TS) over the wrapper's filePath
|
|
171
|
+
// (the TS source file being processed, which may not be the right pointer
|
|
172
|
+
// for emit-stage diagnostics).
|
|
173
|
+
const resolvedFile = diagnostic.filePath ?? filePath;
|
|
142
174
|
const locationBase = resolvedFile
|
|
143
175
|
? path.relative(process.cwd(), resolvedFile) || resolvedFile
|
|
144
176
|
: diagnostic.source ?? "user code";
|
|
@@ -217,6 +249,10 @@ export async function transpileFile(options) {
|
|
|
217
249
|
// requireUIHook() works throughout this transpile run. Gracefully no-ops
|
|
218
250
|
// when @typecad/ui is absent (pure TS→C++ transpile).
|
|
219
251
|
await loadUIEngine();
|
|
252
|
+
// Load the safety engine from @typecad/safety (if installed). Sets up the
|
|
253
|
+
// hook so requireSafetyHook() works throughout this transpile run.
|
|
254
|
+
// Gracefully no-ops when @typecad/safety is absent.
|
|
255
|
+
await loadSafetyEngine();
|
|
220
256
|
const entryFile = path.resolve(options.inputFile);
|
|
221
257
|
const entryDir = path.dirname(entryFile);
|
|
222
258
|
const sourceDir = entryDir;
|
|
@@ -238,24 +274,29 @@ export async function transpileFile(options) {
|
|
|
238
274
|
const configDisplay = options.display;
|
|
239
275
|
if (configDisplay) {
|
|
240
276
|
const { resolveDisplayProfile } = await import("./api/shared/display-profile.js");
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
277
|
+
// Build the named-profile registry. Prefer the strategy's hook (per-framework
|
|
278
|
+
// canonical profiles — Arduino returns its BUILT_IN_PROFILES, Zephyr returns
|
|
279
|
+
// its DT-binding profiles). Fall back to the legacy dynamic import of the
|
|
280
|
+
// framework's displays/ili9341-spi module when the strategy provides none.
|
|
281
|
+
// The registry maps a config `profile` name (e.g. "st7796-zephyr") to its
|
|
282
|
+
// DisplayProfile; an empty registry makes resolveDisplayProfile fall back to
|
|
283
|
+
// the bare default driver (config.driver ?? "ili9341") when no profile name
|
|
284
|
+
// is set, or throw "Unknown display profile" when one is.
|
|
285
|
+
let registry = new Map();
|
|
286
|
+
if (strategy.getProfileRegistry) {
|
|
287
|
+
registry = strategy.getProfileRegistry();
|
|
288
|
+
}
|
|
289
|
+
else if (options.frameworkPackage) {
|
|
290
|
+
const profileMod = await import(options.frameworkPackage + "/displays/ili9341-spi").catch(() => null);
|
|
291
|
+
if (profileMod?.BUILT_IN_PROFILES) {
|
|
292
|
+
for (const [k, v] of Object.entries(profileMod.BUILT_IN_PROFILES)) {
|
|
293
|
+
registry.set(k, v);
|
|
250
294
|
}
|
|
251
295
|
}
|
|
252
|
-
const resolved = resolveDisplayProfile(configDisplay, registry);
|
|
253
|
-
const buildTarget = options.platformContext?.frameworkData?.buildTarget;
|
|
254
|
-
setDisplayProfile(resolved.profile, { cs: resolved.cs, dc: resolved.dc, rst: resolved.rst, bus: resolved.bus, address: resolved.address, reset: resolved.reset, buildTarget });
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
// Fall back to default profile — not fatal
|
|
258
296
|
}
|
|
297
|
+
const resolved = resolveDisplayProfile(configDisplay, registry);
|
|
298
|
+
const buildTarget = options.platformContext?.frameworkData?.buildTarget;
|
|
299
|
+
setDisplayProfile(resolved.profile, { cs: resolved.cs, dc: resolved.dc, rst: resolved.rst, bus: resolved.bus, address: resolved.address, reset: resolved.reset, buildTarget });
|
|
259
300
|
// Apply theme CSS override if specified.
|
|
260
301
|
if (configDisplay.themeCss) {
|
|
261
302
|
setThemeCss(configDisplay.themeCss);
|
|
@@ -302,22 +343,43 @@ export async function transpileFile(options) {
|
|
|
302
343
|
}
|
|
303
344
|
// ── ESLint gate ──────────────────────────────────────────────────────────
|
|
304
345
|
// ESLint catches what the type-checker cannot (idiom violations, banned
|
|
305
|
-
// globals, explicit `any`, etc.).
|
|
306
|
-
//
|
|
346
|
+
// globals, explicit `any`, etc.). It is a mandatory correctness gate: it
|
|
347
|
+
// excludes non-AOT code patterns the transpiler cannot accept, so it cannot
|
|
348
|
+
// be dropped. Errors abort the build, mirroring the type-check behavior
|
|
349
|
+
// above. Skipped alongside type-checking when disabled.
|
|
350
|
+
//
|
|
351
|
+
// Because the lint result is a whole-program boolean that depends only on
|
|
352
|
+
// the source files, the eslint config, and the eslint/transpiler versions,
|
|
353
|
+
// it is cacheable. On a cache hit we skip the ~3s ESLint run entirely; on a
|
|
354
|
+
// miss we run ESLint and, only if clean, persist the result. See lint-cache.ts
|
|
355
|
+
// for the soundness contract.
|
|
307
356
|
if (options.skipLint !== true && options.skipTypeCheck !== true && transpileFiles.length > 0) {
|
|
308
|
-
profiler.startTimer("lint:eslint");
|
|
309
357
|
// The eslint config lives at the project root (next to cuttlefish.config.ts),
|
|
310
358
|
// not under src/. Fall back to entryDir for ad-hoc API/test callers that pass
|
|
311
359
|
// a bare input file without a configured project.
|
|
312
360
|
const eslintRoot = options.projectRoot ?? entryDir;
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
361
|
+
const lintCache = checkLintCache(eslintRoot, sourceDir, { force: options.force });
|
|
362
|
+
if (lintCache.hit) {
|
|
363
|
+
// Cache hit: previous clean run still applies, skip ESLint entirely.
|
|
364
|
+
profiler.startTimer("lint:eslint:cached");
|
|
365
|
+
profiler.endTimer("lint:eslint:cached");
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
profiler.startTimer("lint:eslint");
|
|
369
|
+
const eslintErrors = await runEslintCheck(eslintRoot);
|
|
370
|
+
profiler.endTimer("lint:eslint");
|
|
371
|
+
if (eslintErrors.length > 0) {
|
|
372
|
+
// Abort with a formatted message. The structured-diagnostic channel is
|
|
373
|
+
// not populated here because a thrown error discards the output anyway;
|
|
374
|
+
// printEslintErrors gives the user file/line/column/caret directly.
|
|
375
|
+
// Do NOT persist a cache entry for a failing run.
|
|
376
|
+
printEslintErrors(eslintErrors);
|
|
377
|
+
throw new Error(`ESLint reported ${eslintErrors.length} error${eslintErrors.length === 1 ? "" : "s"} — transpilation aborted.`);
|
|
378
|
+
}
|
|
379
|
+
// Clean run: record the fingerprint so subsequent unchanged builds skip.
|
|
380
|
+
if (lintCache.fingerprint) {
|
|
381
|
+
recordLintSuccess(eslintRoot, lintCache.fingerprint);
|
|
382
|
+
}
|
|
321
383
|
}
|
|
322
384
|
}
|
|
323
385
|
const npmPackages = graphResult.npmPackages;
|
|
@@ -442,7 +504,13 @@ export async function transpileFile(options) {
|
|
|
442
504
|
}
|
|
443
505
|
sourceText = preprocess(sourceText, filePath);
|
|
444
506
|
}
|
|
445
|
-
|
|
507
|
+
// Printf instrumentation runs only in printf debug mode. In gdb mode
|
|
508
|
+
// (e.g. ESP32-S3), --debug emits #line markers at the emit stage and
|
|
509
|
+
// uses VS Code native breakpoints; the printf preprocessor is skipped
|
|
510
|
+
// so the source reaches IR/emit unmodified.
|
|
511
|
+
const buildTarget = options.platformContext?.frameworkData?.buildTarget;
|
|
512
|
+
const debugMode = strategy.debugMode?.(buildTarget) ?? 'printf';
|
|
513
|
+
if (options.debug && breakpoints && debugMode === 'printf') {
|
|
446
514
|
const instrumented = debugPreprocess({
|
|
447
515
|
fileName: filePath,
|
|
448
516
|
breakpoints,
|
|
@@ -459,6 +527,15 @@ export async function transpileFile(options) {
|
|
|
459
527
|
};
|
|
460
528
|
profiler.startTimer("ir:build-all");
|
|
461
529
|
profiler.captureMemorySnapshot("ir:pre-build");
|
|
530
|
+
// Load + parse the @typecad/hal source files ONCE for this transpile run.
|
|
531
|
+
// buildProgramIR used to force-reload HAL per graph file (O(files) re-reads
|
|
532
|
+
// and re-parses of all 28 HAL modules); warming it here makes the per-file
|
|
533
|
+
// loadHALModules() call inside buildProgramIR a cheap no-op. A fresh run of
|
|
534
|
+
// transpileFile always reaches this point, so edits to @typecad/hal source
|
|
535
|
+
// are picked up on the next build.
|
|
536
|
+
profiler.startTimer("ir:load-hal");
|
|
537
|
+
loadHALModules(true);
|
|
538
|
+
profiler.endTimer("ir:load-hal");
|
|
462
539
|
const rawIRArray = await Promise.all(filesToProcess.map(buildRawIR));
|
|
463
540
|
profiler.captureMemorySnapshot("ir:post-build");
|
|
464
541
|
profiler.endTimer("ir:build-all");
|
|
@@ -620,6 +697,60 @@ export async function transpileFile(options) {
|
|
|
620
697
|
profiler.startTimer("emit:register-enums");
|
|
621
698
|
registerAllEnumNames(allEnumIRs);
|
|
622
699
|
profiler.endTimer("emit:register-enums");
|
|
700
|
+
// ── Phase D: safety transform pass ──────────────────────────────────────
|
|
701
|
+
// When @typecad/safety is in use, run its post-build IR transform to inject
|
|
702
|
+
// safety ops (e.g. companion safety.record_pin_mode after every
|
|
703
|
+
// gpio.pin_mode). Produces new IR per file; no-op when safety is off or
|
|
704
|
+
// absent.
|
|
705
|
+
if (hasSafetyHook()) {
|
|
706
|
+
profiler.startTimer("safety:transform");
|
|
707
|
+
const hook = requireSafetyHook();
|
|
708
|
+
for (const [filePath, item] of preBuilt) {
|
|
709
|
+
const newIR = hook.transformIR(item.programIR, {
|
|
710
|
+
safetyInUse: entryImportsSafety(item.programIR),
|
|
711
|
+
target: options.target,
|
|
712
|
+
});
|
|
713
|
+
preBuilt.set(filePath, { ...item, programIR: newIR });
|
|
714
|
+
}
|
|
715
|
+
profiler.endTimer("safety:transform");
|
|
716
|
+
// Part B: ISO 26262 Part 6 read-only IR analysis. Runs after transformIR
|
|
717
|
+
// so it sees the final IR (including injected safety ops). Returned
|
|
718
|
+
// diagnostics flow into the build's diagnostic list; error-severity
|
|
719
|
+
// diagnostics abort via throwIfFatalDiagnostics.
|
|
720
|
+
if (hook.analyzeIR) {
|
|
721
|
+
profiler.startTimer("safety:analyze");
|
|
722
|
+
for (const [, item] of preBuilt) {
|
|
723
|
+
const irDiags = hook.analyzeIR(item.programIR, {
|
|
724
|
+
safetyInUse: entryImportsSafety(item.programIR),
|
|
725
|
+
target: options.target,
|
|
726
|
+
});
|
|
727
|
+
if (irDiags.length > 0) {
|
|
728
|
+
diagnostics.push(...irDiags);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
profiler.endTimer("safety:analyze");
|
|
732
|
+
}
|
|
733
|
+
// Part C: collect safety metadata and write sidecar artifact.
|
|
734
|
+
if (hook.collectSafetyMetadata) {
|
|
735
|
+
profiler.startTimer("safety:collect");
|
|
736
|
+
for (const [filePath, item] of preBuilt) {
|
|
737
|
+
if (!entryImportsSafety(item.programIR))
|
|
738
|
+
continue;
|
|
739
|
+
const metadata = hook.collectSafetyMetadata(item.programIR, {
|
|
740
|
+
safetyInUse: true,
|
|
741
|
+
target: options.target,
|
|
742
|
+
});
|
|
743
|
+
if (metadata.length > 0 && metadata[0].functions.length > 0) {
|
|
744
|
+
const { renderSafetySidecar } = await import("./safety/sidecar-bridge.js");
|
|
745
|
+
const toolVersion = CUTTLEFISH_VERSION;
|
|
746
|
+
const sidecarPath = path.join(outDir, path.basename(filePath).replace(/\.\w+$/, ".safety-sidecar.json"));
|
|
747
|
+
const sidecarJson = await renderSafetySidecar(metadata[0], toolVersion);
|
|
748
|
+
writeText(sidecarPath, sidecarJson);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
profiler.endTimer("safety:collect");
|
|
752
|
+
}
|
|
753
|
+
}
|
|
623
754
|
// ── Pass 2: emit (only for files that needed retranspilation) ─────────────
|
|
624
755
|
profiler.startTimer("emit:all");
|
|
625
756
|
profiler.captureMemorySnapshot("emit:pre");
|
|
@@ -644,6 +775,9 @@ export async function transpileFile(options) {
|
|
|
644
775
|
crossModuleEnumNames: allEnumNames,
|
|
645
776
|
crossModuleStringEnumNames: allStringEnumNames,
|
|
646
777
|
crossModuleVariableTypes: allVariableTypes,
|
|
778
|
+
autosar: options.autosar,
|
|
779
|
+
toolVersion: CUTTLEFISH_VERSION,
|
|
780
|
+
autosarArxml: options.autosarArxml,
|
|
647
781
|
};
|
|
648
782
|
// Pass the already-resolved strategy (framework-loaded or target-based)
|
|
649
783
|
emitOptions.strategy = strategy;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TargetProfile as CoreTargetProfile, PlatformContext as CorePlatformContext, SourceSpan as CoreSourceSpan, Diagnostic as CoreDiagnostic } from "./api/shared/index.js";
|
|
2
2
|
export type EmitMode = "cpp" | "split";
|
|
3
|
+
export type ComplianceMode = "off" | "warn" | "strict";
|
|
3
4
|
export type TargetProfile = CoreTargetProfile;
|
|
4
5
|
export type PlatformContext = CorePlatformContext;
|
|
5
6
|
export type SourceSpan = CoreSourceSpan;
|
|
@@ -95,6 +96,10 @@ export interface TranspileOptions {
|
|
|
95
96
|
diagnostics?: boolean;
|
|
96
97
|
/** Display profile config from cuttlefish.config.ts */
|
|
97
98
|
display?: import("./api/shared/display-profile.js").DisplayConfig;
|
|
99
|
+
/** AUTOSAR C++14 compliance mode for emitted code (default: "off"). */
|
|
100
|
+
autosar?: ComplianceMode;
|
|
101
|
+
/** When true (and autosar is warn/strict), also emit the .autosar-deviations.arxml sidecar. */
|
|
102
|
+
autosarArxml?: boolean;
|
|
98
103
|
}
|
|
99
104
|
export interface LibraryDefinitionCondition {
|
|
100
105
|
target?: TargetProfile;
|
|
@@ -134,8 +139,10 @@ export interface CommandLineOptions {
|
|
|
134
139
|
monitor: boolean;
|
|
135
140
|
/** Serial port for upload and monitor (e.g. COM4 or /dev/ttyACM0) */
|
|
136
141
|
port?: string;
|
|
137
|
-
/** Baud rate for monitor
|
|
138
|
-
|
|
142
|
+
/** Baud rate for monitor. Undefined when --baud is absent so the consumer
|
|
143
|
+
* can fall back to config.console.baudRate (the framework monitor default
|
|
144
|
+
* applies only when neither is set). */
|
|
145
|
+
baud?: number;
|
|
139
146
|
platformContext?: PlatformContext;
|
|
140
147
|
mapFile?: string;
|
|
141
148
|
cppFile?: string;
|
|
@@ -168,6 +175,10 @@ export interface CommandLineOptions {
|
|
|
168
175
|
expectFile?: string;
|
|
169
176
|
/** Generate diagnostics.md and diagnostics.json reports */
|
|
170
177
|
diagnostics?: boolean;
|
|
178
|
+
/** AUTOSAR C++14 compliance mode for emitted code (default: "off"). */
|
|
179
|
+
autosar?: ComplianceMode;
|
|
180
|
+
/** When true (and autosar is warn/strict), also emit the .autosar-deviations.arxml sidecar. */
|
|
181
|
+
autosarArxml?: boolean;
|
|
171
182
|
/** Config file for preview command */
|
|
172
183
|
configPath?: string;
|
|
173
184
|
/** Project root (dir of cuttlefish.config.ts); passed to transpileFile for the ESLint gate. */
|
package/dist/ui-hook.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ export interface LoweredUI {
|
|
|
9
9
|
screenCount: number;
|
|
10
10
|
imageTables: string;
|
|
11
11
|
keyframeTables: string;
|
|
12
|
-
scrollMemoryDiagnostics: Diagnostic[];
|
|
13
12
|
diagnostics: Diagnostic[];
|
|
14
13
|
}
|
|
15
14
|
export interface UIModule {
|
|
@@ -58,7 +57,23 @@ export interface TranspilerUIHook {
|
|
|
58
57
|
lowerOnMount(htmlPath: string, opts: LowerOptions): LoweredUI;
|
|
59
58
|
resolveColor(input: string, format: "rgb565" | "rgb666" | "rgb888" | "mono"): number;
|
|
60
59
|
resolveColorInternal(input: string, format: "rgb565" | "rgb666" | "rgb888" | "mono"): number;
|
|
61
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Emits ONLY the CuttlefishGFX class definition (the shared GFX base for
|
|
62
|
+
* native display paths). Called separately from emitRuntimeHeader so the
|
|
63
|
+
* class can be emitted BEFORE display adapter declarations that
|
|
64
|
+
* instantiate CuttlefishGFX by value. Returns "" when active is false
|
|
65
|
+
* (Arduino path).
|
|
66
|
+
*/
|
|
67
|
+
emitCuttlefishGfx(active: boolean): string;
|
|
68
|
+
/**
|
|
69
|
+
* Options controlling runtime-header emission.
|
|
70
|
+
* - nativeDisplayActive: when true, emit the shared CuttlefishGFX class
|
|
71
|
+
* (used by native display adapters on AVR/ESP32). Arduino paths pass
|
|
72
|
+
* false (default) and use Adafruit_GFX directly.
|
|
73
|
+
*/
|
|
74
|
+
emitRuntimeHeader(opts?: {
|
|
75
|
+
nativeDisplayActive?: boolean;
|
|
76
|
+
}): string;
|
|
62
77
|
splitUiFile(src: string): UiFileParts;
|
|
63
78
|
generateProjectUITypeDeclarations(projectRoot: string): {
|
|
64
79
|
written: string[];
|
|
@@ -67,7 +82,6 @@ export interface TranspilerUIHook {
|
|
|
67
82
|
error: Error;
|
|
68
83
|
}>;
|
|
69
84
|
};
|
|
70
|
-
analyzeScrollMemory(styled: unknown, budget: number): Diagnostic[];
|
|
71
85
|
}
|
|
72
86
|
/** Set the UI hook. Called by transpile.ts after dynamically loading
|
|
73
87
|
* @typecad/ui/engine. Passing null clears it (end of transpile run). */
|
package/dist/utils/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ export function printHelp() {
|
|
|
17
17
|
console.log(` cuttlefish board add <spec.jsonc> [--force] Generate board + MCU packages from a chip spec`);
|
|
18
18
|
console.log(` cuttlefish gen-decls <input.cpp|--all <directory>>`);
|
|
19
19
|
console.log(` cuttlefish map-error <mapFile> [options]`);
|
|
20
|
-
console.log(` cuttlefish doctor Check
|
|
20
|
+
console.log(` cuttlefish doctor Check the active framework's environment (e.g. toolchain + board core)`);
|
|
21
21
|
console.log(` cuttlefish licenses [--all] [--strict] Scan this project's Arduino libraries for SPDX licenses (--all: every installed library)`);
|
|
22
22
|
console.log();
|
|
23
23
|
console.log(chalk.gray(`Transpilation is always performed first. Use --compile, --upload, and`));
|
|
@@ -39,6 +39,16 @@ export function printHelp() {
|
|
|
39
39
|
console.log(` --emit-maps <bool> Emit source maps: "true" or "false" (default: true)`);
|
|
40
40
|
console.log(` Source maps enable mapping C++ errors back to TypeScript`);
|
|
41
41
|
console.log();
|
|
42
|
+
console.log(` --autosar[=<mode>] AUTOSAR C++14 compliance mode for emitted code:`);
|
|
43
|
+
console.log(` off no enforcement (default)`);
|
|
44
|
+
console.log(` warn emit AUTOSAR_* diagnostics + sidecar, build still succeeds`);
|
|
45
|
+
console.log(` strict abort emit on unrecorded required violations`);
|
|
46
|
+
console.log(` A sidecar <name>.autosar-deviations.json is written next to the`);
|
|
47
|
+
console.log(` emitted artifact in warn/strict modes. See COMPLIANCE.md.`);
|
|
48
|
+
console.log();
|
|
49
|
+
console.log(` --autosar-arxml Also write <name>.autosar-deviations.arxml (Artop/DaVinci).`);
|
|
50
|
+
console.log(` No-op unless --autosar is warn or strict.`);
|
|
51
|
+
console.log();
|
|
42
52
|
console.log(chalk.cyan(`BUILD COMMANDS`) + chalk.gray(` (chain in order: --compile → --upload → --monitor)`));
|
|
43
53
|
console.log();
|
|
44
54
|
console.log(` --compile Compile the generated output using the framework toolchain.`);
|
|
@@ -104,6 +114,12 @@ export function printHelp() {
|
|
|
104
114
|
console.log(` --diagnostics Generate diagnostics.md and diagnostics.json reports`);
|
|
105
115
|
console.log(` Includes IR graph, heap analysis, and task analysis`);
|
|
106
116
|
console.log();
|
|
117
|
+
console.log(` --debug Inject Serial.println instrumentation at breakpoints.`);
|
|
118
|
+
console.log(` Reads .cuttlefish/breakpoints.json (written by the`);
|
|
119
|
+
console.log(` TypeCAD Debug VS Code extension). At each breakpoint the`);
|
|
120
|
+
console.log(` firmware prints the location, original line, and in-scope`);
|
|
121
|
+
console.log(` variables, then halts — press ENTER over serial to continue.`);
|
|
122
|
+
console.log();
|
|
107
123
|
console.log(` --help, -h Show this help message`);
|
|
108
124
|
console.log();
|
|
109
125
|
console.log(chalk.cyan(`PROJECT CREATION`));
|
|
@@ -211,7 +227,11 @@ function parsePipelineCommand(argv, command, inputFile) {
|
|
|
211
227
|
const emitMapsFlag = readFirstFlagValue(argv, ["--emit-maps"]);
|
|
212
228
|
const buildTarget = readFirstFlagValue(argv, ["--build-target"]);
|
|
213
229
|
const port = readFirstFlagValue(argv, ["--port"]);
|
|
214
|
-
|
|
230
|
+
// Default to undefined (NOT 9600) so the build/upload/monitor flow's
|
|
231
|
+
// `options.baud ?? config.console.baudRate` falls through to the configured
|
|
232
|
+
// baud. Hardcoding 9600 here shadowed config.console.baudRate whenever --baud
|
|
233
|
+
// was absent (the monitor then opened the port at 9600, ignoring the config).
|
|
234
|
+
const baud = readNumberFlag(argv, ["--baud"]);
|
|
215
235
|
const frameworkFlag = readFirstFlagValue(argv, ["--framework"]);
|
|
216
236
|
const compile = readBooleanFlag(argv, ["--compile"]);
|
|
217
237
|
const upload = readBooleanFlag(argv, ["--upload"]);
|
|
@@ -231,6 +251,32 @@ function parsePipelineCommand(argv, command, inputFile) {
|
|
|
231
251
|
const keepUnusedTypes = readBooleanFlag(argv, ["--keep-unused-types"]);
|
|
232
252
|
const keepUnusedVariables = readBooleanFlag(argv, ["--keep-unused-variables"]);
|
|
233
253
|
const entryPoints = readRepeatedFlag(argv, "--entry-point");
|
|
254
|
+
// AUTOSAR C++14 compliance mode for emitted code. Accepts both
|
|
255
|
+
// `--autosar=strict` (= syntax) and `--autosar strict` (space syntax),
|
|
256
|
+
// plus the bare `--autosar` (treated as "strict"). Defaults to undefined,
|
|
257
|
+
// which the emitter treats as "off".
|
|
258
|
+
let autosar;
|
|
259
|
+
const autosarFlagIdx = argv.findIndex((a) => a === "--autosar" || a.startsWith("--autosar="));
|
|
260
|
+
if (autosarFlagIdx !== -1) {
|
|
261
|
+
const raw = argv[autosarFlagIdx];
|
|
262
|
+
if (raw === "--autosar") {
|
|
263
|
+
// Bare flag → strict. (No following value consumed.)
|
|
264
|
+
autosar = "strict";
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
// --autosar=<mode>
|
|
268
|
+
const value = raw.slice("--autosar=".length);
|
|
269
|
+
if (value === "strict" || value === "warn" || value === "off") {
|
|
270
|
+
autosar = value;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
throw new Error(`--autosar must be one of: strict, warn, off (got: ${value})`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// --autosar-arxml: also write the .autosar-deviations.arxml sidecar
|
|
278
|
+
// (Artop/DaVinci tooling). No-op unless --autosar is warn or strict.
|
|
279
|
+
const autosarArxml = readBooleanFlag(argv, ["--autosar-arxml"]);
|
|
234
280
|
const emitMode = emitFlag === "cpp" || emitFlag === "split" ? emitFlag : "split";
|
|
235
281
|
const emitMaps = emitMapsFlag === undefined ? true : emitMapsFlag !== "false";
|
|
236
282
|
// Accept any target string — the framework package registers its own strategy id.
|
|
@@ -281,6 +327,8 @@ function parsePipelineCommand(argv, command, inputFile) {
|
|
|
281
327
|
expect,
|
|
282
328
|
expectFile,
|
|
283
329
|
frameworkPackage: frameworkFlag,
|
|
330
|
+
autosar,
|
|
331
|
+
autosarArxml,
|
|
284
332
|
};
|
|
285
333
|
}
|
|
286
334
|
export function parseCommandLine(argv) {
|
|
@@ -339,7 +387,7 @@ export function parseCommandLine(argv) {
|
|
|
339
387
|
port: port ? String(port) : undefined,
|
|
340
388
|
};
|
|
341
389
|
}
|
|
342
|
-
// doctor subcommand — verify
|
|
390
|
+
// doctor subcommand — verify the active framework's environment
|
|
343
391
|
if (firstArg === "doctor") {
|
|
344
392
|
return {
|
|
345
393
|
command: "doctor",
|
|
@@ -431,12 +479,16 @@ export function parseCommandLine(argv) {
|
|
|
431
479
|
// gen-decls - generate .d.ts from C++ files
|
|
432
480
|
if (command === "gen-decls") {
|
|
433
481
|
const allFlag = argv.includes("--all");
|
|
482
|
+
const componentsFlag = argv.includes("--components");
|
|
483
|
+
if (allFlag && componentsFlag) {
|
|
484
|
+
throw new Error("gen-decls: --components and --all are mutually exclusive. Use one or the other.");
|
|
485
|
+
}
|
|
434
486
|
// The positional path may sit at argv[3] or argv[4] depending on whether
|
|
435
487
|
// --all precedes or follows it. Scan argv starting after the subcommand
|
|
436
488
|
// name (argv[2]) for the first token that is not a flag and not a known
|
|
437
489
|
// flag's value. This mirrors the default-pipeline approach of "first
|
|
438
490
|
// non-flag token wins".
|
|
439
|
-
const booleanFlags = new Set(["--all"]);
|
|
491
|
+
const booleanFlags = new Set(["--all", "--components"]);
|
|
440
492
|
const valueFlags = new Set([
|
|
441
493
|
"--emit", "--target", "--outDir", "--out-dir", "--emit-maps", "--build-target",
|
|
442
494
|
]);
|
|
@@ -457,6 +509,28 @@ export function parseCommandLine(argv) {
|
|
|
457
509
|
inputPath = tok;
|
|
458
510
|
break;
|
|
459
511
|
}
|
|
512
|
+
// --components mode: scan managed_components/ + components/ declared in
|
|
513
|
+
// cuttlefish.config.ts. Falls back to cwd when no path is given.
|
|
514
|
+
if (componentsFlag) {
|
|
515
|
+
const componentsDir = inputPath || process.cwd();
|
|
516
|
+
return {
|
|
517
|
+
command: "gen-decls",
|
|
518
|
+
inputFile: undefined,
|
|
519
|
+
emitMode,
|
|
520
|
+
target,
|
|
521
|
+
outDir: outDir ? path.resolve(process.cwd(), outDir) : undefined,
|
|
522
|
+
emitMaps,
|
|
523
|
+
noTranspile: false,
|
|
524
|
+
compile: false,
|
|
525
|
+
upload: false,
|
|
526
|
+
monitor: false,
|
|
527
|
+
watch: false,
|
|
528
|
+
baud: 9600,
|
|
529
|
+
platformContext,
|
|
530
|
+
scanDir: undefined,
|
|
531
|
+
componentsDir: path.resolve(process.cwd(), componentsDir),
|
|
532
|
+
};
|
|
533
|
+
}
|
|
460
534
|
if (!inputPath && !allFlag) {
|
|
461
535
|
throw new Error("Missing input C++ file path. Use: gen-decls <file.cpp> or gen-decls --all <directory>");
|
|
462
536
|
}
|
package/dist/utils/fs.d.ts
CHANGED
|
@@ -2,3 +2,16 @@ export declare function ensureDir(dirPath: string): void;
|
|
|
2
2
|
export declare function readText(filePath: string): string;
|
|
3
3
|
export declare function writeText(filePath: string, content: string): void;
|
|
4
4
|
export declare function listFiles(dirPath: string, extension: string): string[];
|
|
5
|
+
/**
|
|
6
|
+
* Recursive variant of {@link listFiles}. Walks `dirPath` depth-first and
|
|
7
|
+
* returns every file (in every subdirectory) whose name ends with `extension`.
|
|
8
|
+
*
|
|
9
|
+
* Used by {@link loadLibraryDefinitions} so libdefs under nested cache dirs
|
|
10
|
+
* like `.cuttlefish/component-decls/<component>/` are discovered. Purely
|
|
11
|
+
* additive vs. the non-recursive `listFiles` — single-level layouts keep
|
|
12
|
+
* working, nested layouts now also work.
|
|
13
|
+
*
|
|
14
|
+
* Returns absolute paths. Unreadable directories are silently skipped
|
|
15
|
+
* (matches `listFiles`'s not-exist behavior for non-existent roots).
|
|
16
|
+
*/
|
|
17
|
+
export declare function listFilesRecursive(dirPath: string, extension: string): string[];
|
package/dist/utils/fs.js
CHANGED
|
@@ -10,6 +10,15 @@ export function readText(filePath) {
|
|
|
10
10
|
}
|
|
11
11
|
export function writeText(filePath, content) {
|
|
12
12
|
ensureDir(path.dirname(filePath));
|
|
13
|
+
// Skip writing when content is identical — preserves mtime so downstream
|
|
14
|
+
// build tools (idf.py/ninja, arduino-cli, make) can skip recompilation.
|
|
15
|
+
try {
|
|
16
|
+
if (fs.readFileSync(filePath, "utf8") === content)
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// File doesn't exist yet — fall through to write.
|
|
21
|
+
}
|
|
13
22
|
fs.writeFileSync(filePath, content, "utf8");
|
|
14
23
|
}
|
|
15
24
|
export function listFiles(dirPath, extension) {
|
|
@@ -21,3 +30,44 @@ export function listFiles(dirPath, extension) {
|
|
|
21
30
|
.filter((name) => name.toLowerCase().endsWith(extension.toLowerCase()))
|
|
22
31
|
.map((name) => path.join(dirPath, name));
|
|
23
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Recursive variant of {@link listFiles}. Walks `dirPath` depth-first and
|
|
35
|
+
* returns every file (in every subdirectory) whose name ends with `extension`.
|
|
36
|
+
*
|
|
37
|
+
* Used by {@link loadLibraryDefinitions} so libdefs under nested cache dirs
|
|
38
|
+
* like `.cuttlefish/component-decls/<component>/` are discovered. Purely
|
|
39
|
+
* additive vs. the non-recursive `listFiles` — single-level layouts keep
|
|
40
|
+
* working, nested layouts now also work.
|
|
41
|
+
*
|
|
42
|
+
* Returns absolute paths. Unreadable directories are silently skipped
|
|
43
|
+
* (matches `listFiles`'s not-exist behavior for non-existent roots).
|
|
44
|
+
*/
|
|
45
|
+
export function listFilesRecursive(dirPath, extension) {
|
|
46
|
+
if (!fs.existsSync(dirPath)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const ext = extension.toLowerCase();
|
|
50
|
+
const out = [];
|
|
51
|
+
const stack = [dirPath];
|
|
52
|
+
while (stack.length > 0) {
|
|
53
|
+
const cur = stack.pop();
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = fs.readdirSync(cur, { withFileTypes: true });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Permission error / race / etc. Skip this subtree.
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const full = path.join(cur, entry.name);
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
stack.push(full);
|
|
66
|
+
}
|
|
67
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(ext)) {
|
|
68
|
+
out.push(full);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|