@typecad/cuttlefish 1.0.0-alpha.12 → 1.0.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/editor-extensions/typecad-debug/README.md +142 -0
- package/assets/editor-extensions/typecad-debug/out/extension.js +347 -0
- package/assets/editor-extensions/typecad-debug/out/extension.js.map +1 -0
- package/assets/editor-extensions/typecad-debug/package.json +76 -0
- package/assets/editor-extensions/typecad-ui/LICENSE +27 -0
- package/assets/editor-extensions/typecad-ui/README.md +52 -0
- package/assets/editor-extensions/typecad-ui/file-icon-dark.png +0 -0
- package/assets/editor-extensions/typecad-ui/file-icon-light.png +0 -0
- package/assets/editor-extensions/typecad-ui/icon.png +0 -0
- package/assets/editor-extensions/typecad-ui/language-configuration.json +43 -0
- package/assets/editor-extensions/typecad-ui/package.json +54 -0
- package/assets/editor-extensions/typecad-ui/snippets/typecad-ui.json +80 -0
- package/assets/editor-extensions/typecad-ui/syntaxes/markdown-ui.json +45 -0
- package/assets/editor-extensions/typecad-ui/syntaxes/typecad-ui.tmLanguage.json +1269 -0
- package/dist/add-preset.d.ts +4 -0
- package/dist/add-preset.js +74 -0
- package/dist/api/config.d.ts +8 -5
- package/dist/api/shared/display-adapters/sdl.js +1 -1
- package/dist/api/shared/display-profile.d.ts +11 -0
- package/dist/api/shared/display-profile.js +3 -0
- package/dist/api/shared/framework-manifest.d.ts +61 -504
- package/dist/api/shared/hal-op-ir.d.ts +19 -0
- package/dist/api/shared/toolchain-types.d.ts +0 -1
- package/dist/cli.js +31 -9
- package/dist/config-loader.d.ts +0 -2
- package/dist/config-loader.js +13 -6
- package/dist/config-schema.d.ts +17 -230
- package/dist/config-schema.js +1 -3
- package/dist/contract/contract-parser.d.ts +9 -127
- package/dist/create/board-checklist.js +1 -1
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-spec.d.ts +74 -530
- package/dist/create/debug-artifacts.d.ts +20 -0
- package/dist/create/debug-artifacts.js +69 -0
- package/dist/create/editor-integration.d.ts +30 -0
- package/dist/create/editor-integration.js +218 -0
- package/dist/create/eslint-rules-template.d.ts +2 -2
- package/dist/create/framework-catalog.d.ts +8 -0
- package/dist/create/framework-catalog.js +28 -5
- package/dist/create/index.d.ts +9 -6
- package/dist/create/index.js +6 -4
- package/dist/create/{init-scaffold.d.ts → scaffold.d.ts} +5 -4
- package/dist/create/{init-scaffold.js → scaffold.js} +38 -2
- package/dist/create/templates.d.ts +29 -0
- package/dist/create/{init-templates.js → templates.js} +15 -2
- package/dist/create/wizard.d.ts +8 -0
- package/dist/create/{init-wizard.js → wizard.js} +11 -4
- package/dist/emit/compliance/rules.js +18 -4
- package/dist/emit/emitters/function-emitter-impl.js +7 -1
- package/dist/emit/emitters/line-appender.js +6 -0
- package/dist/emit/emitters/setup.js +22 -0
- package/dist/emit/emitters/ui-emitter.js +40 -15
- package/dist/emit/route-hal-op.js +55 -1
- package/dist/emit/statement-renderer.js +5 -2
- package/dist/ir/build-ir.js +46 -4
- package/dist/ir/expression-to-ir.js +17 -0
- package/dist/ir/hal/hal-emitter.js +23 -5
- package/dist/ir/hal/hal-plugins.js +11 -0
- package/dist/ir/pin-mode-validation.js +32 -9
- package/dist/ir/pin-state-tracking.d.ts +58 -0
- package/dist/ir/pin-state-tracking.js +182 -0
- package/dist/ir/program-analysis.d.ts +6 -0
- package/dist/ir/program-analysis.js +38 -0
- package/dist/ir/statement-to-ir.js +14 -0
- package/dist/ir/transformers/control-flow.js +29 -0
- package/dist/ir/transformers/ui-call-resolver.js +105 -1
- package/dist/ir/ui-element-auto-wire.js +7 -4
- package/dist/library/catalog.d.ts +35 -0
- package/dist/library/catalog.js +67 -0
- package/dist/library/cli.d.ts +2 -0
- package/dist/library/cli.js +168 -0
- package/dist/library/init.d.ts +25 -0
- package/dist/library/init.js +397 -0
- package/dist/library/install.d.ts +9 -0
- package/dist/library/install.js +80 -0
- package/dist/library/registry-search.d.ts +34 -0
- package/dist/library/registry-search.js +50 -0
- package/dist/library/validate.d.ts +17 -0
- package/dist/library/validate.js +175 -0
- package/dist/library-packages.d.ts +95 -0
- package/dist/library-packages.js +275 -0
- package/dist/orchestrator/graph-builder.d.ts +4 -1
- package/dist/orchestrator/graph-builder.js +23 -1
- package/dist/preview/api-shared-shim.d.ts +1 -0
- package/dist/preview/api-shared-shim.js +7 -0
- package/dist/preview/client.js +235 -2
- package/dist/preview/server.js +154 -62
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +1 -1
- package/dist/theme-tokens.d.ts +22 -0
- package/dist/theme-tokens.js +172 -0
- package/dist/transpile.js +90 -5
- package/dist/types.d.ts +24 -0
- package/dist/ui-hook.d.ts +7 -0
- package/dist/utils/cli.d.ts +2 -2
- package/dist/utils/cli.js +77 -3
- package/dist/utils/ui.d.ts +5 -0
- package/dist/utils/ui.js +7 -0
- package/package.json +18 -9
- package/dist/create/init-templates.d.ts +0 -28
- package/dist/create/init-wizard.d.ts +0 -8
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Library installation — `cuttlefish library install <pkg...>`.
|
|
3
|
+
//
|
|
4
|
+
// Finds the nearest project package.json (walking up from the cwd) and runs
|
|
5
|
+
// npm install against it. The Windows spawn handling mirrors the typecad
|
|
6
|
+
// binary's package command: npm.cmd must go through cmd.exe, and a resolved
|
|
7
|
+
// .cmd/.bat cannot be exec'd directly.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
const IS_WIN = process.platform === "win32";
|
|
13
|
+
const IS_CMD_EXT = /\.(?:cmd|bat)$/i;
|
|
14
|
+
/** Walk up from `fromDir` to the nearest package.json. */
|
|
15
|
+
export function findProjectPackageJson(fromDir = process.cwd()) {
|
|
16
|
+
let dir = path.resolve(fromDir);
|
|
17
|
+
const root = path.parse(dir).root;
|
|
18
|
+
for (;;) {
|
|
19
|
+
const candidate = path.join(dir, "package.json");
|
|
20
|
+
if (fs.existsSync(candidate))
|
|
21
|
+
return candidate;
|
|
22
|
+
if (dir === root)
|
|
23
|
+
return null;
|
|
24
|
+
dir = path.dirname(dir);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Installed version spec for a dependency name, or undefined. */
|
|
28
|
+
export function installedDependencyVersion(packageJsonPath, name) {
|
|
29
|
+
try {
|
|
30
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
31
|
+
for (const section of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
32
|
+
const deps = pkg[section];
|
|
33
|
+
if (deps && typeof deps === "object" && typeof deps[name] === "string") {
|
|
34
|
+
return deps[name];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// unreadable manifest — treat as not installed
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function resolveNpmExecutable() {
|
|
44
|
+
const isWin = IS_WIN;
|
|
45
|
+
const cmd = isWin ? "npm.cmd" : "npm";
|
|
46
|
+
for (const dir of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
47
|
+
if (!dir)
|
|
48
|
+
continue;
|
|
49
|
+
const candidate = path.join(dir, cmd);
|
|
50
|
+
try {
|
|
51
|
+
if (fs.statSync(candidate).isFile())
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// keep scanning
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Install npm packages into the project owning `cwd`. Returns the directory
|
|
62
|
+
* the install ran in. Throws with npm's stderr on failure.
|
|
63
|
+
*/
|
|
64
|
+
export function npmInstall(packageNames, cwd) {
|
|
65
|
+
const resolved = resolveNpmExecutable();
|
|
66
|
+
if (IS_WIN && (!resolved || IS_CMD_EXT.test(resolved))) {
|
|
67
|
+
execFileSync("cmd.exe", ["/d", "/s", "/c", "npm", "install", ...packageNames], {
|
|
68
|
+
cwd,
|
|
69
|
+
stdio: "pipe",
|
|
70
|
+
timeout: 300_000,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
execFileSync(resolved ?? "npm", ["install", ...packageNames], {
|
|
75
|
+
cwd,
|
|
76
|
+
stdio: "pipe",
|
|
77
|
+
timeout: 300_000,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Fields of an npm search result this module consumes. */
|
|
2
|
+
export interface NpmSearchPackage {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
keywords?: string[];
|
|
7
|
+
links?: {
|
|
8
|
+
npm?: string;
|
|
9
|
+
};
|
|
10
|
+
publisher?: {
|
|
11
|
+
username?: string;
|
|
12
|
+
};
|
|
13
|
+
date?: string;
|
|
14
|
+
}
|
|
15
|
+
/** One shaped search result. */
|
|
16
|
+
export interface LibrarySearchResult {
|
|
17
|
+
name: string;
|
|
18
|
+
version: string;
|
|
19
|
+
description: string | null;
|
|
20
|
+
/** Category id derived from the package's keywords, or null. */
|
|
21
|
+
category: string | null;
|
|
22
|
+
npmUrl: string;
|
|
23
|
+
published: string | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build the `text=` query for the registry search endpoint. Exported for tests.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildLibrarySearchQuery(text?: string, categoryId?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Search npm for cuttlefish library packages. `size` caps results (registry
|
|
31
|
+
* max is 250). Throws on HTTP failure — the CLI surfaces the message.
|
|
32
|
+
*/
|
|
33
|
+
export declare function searchLibraryPackages(text?: string, categoryId?: string, size?: number): Promise<LibrarySearchResult[]>;
|
|
34
|
+
export declare function shapeResult(pkg: NpmSearchPackage): LibrarySearchResult;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// npm registry search for cuttlefish library packages — the discovery client
|
|
3
|
+
// behind `cuttlefish library search`.
|
|
4
|
+
//
|
|
5
|
+
// Ports the concept from the typecad binary's `typecad package` command: the
|
|
6
|
+
// npm registry's search endpoint is the catalog, keywords are the taxonomy.
|
|
7
|
+
// The marker keyword (cuttlefish-library) is always part of the query; a
|
|
8
|
+
// category narrows it; free text ANDs on top. The endpoint ANDs `keywords:`
|
|
9
|
+
// terms, so the composition needs no client-side filtering.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
import { LIBRARY_MARKER_KEYWORD, categoryKeyword, categoryFromKeywords } from "./catalog.js";
|
|
12
|
+
const REGISTRY_SEARCH_ENDPOINT = "https://registry.npmjs.org/-/v1/search";
|
|
13
|
+
/**
|
|
14
|
+
* Build the `text=` query for the registry search endpoint. Exported for tests.
|
|
15
|
+
*/
|
|
16
|
+
export function buildLibrarySearchQuery(text, categoryId) {
|
|
17
|
+
const terms = [`keywords:${LIBRARY_MARKER_KEYWORD}`];
|
|
18
|
+
if (categoryId) {
|
|
19
|
+
terms.push(`keywords:${categoryKeyword(categoryId)}`);
|
|
20
|
+
}
|
|
21
|
+
const freeText = text?.trim();
|
|
22
|
+
if (freeText) {
|
|
23
|
+
// Free text terms are space-separated; the registry ANDs them.
|
|
24
|
+
terms.push(freeText);
|
|
25
|
+
}
|
|
26
|
+
return terms.join(" ");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Search npm for cuttlefish library packages. `size` caps results (registry
|
|
30
|
+
* max is 250). Throws on HTTP failure — the CLI surfaces the message.
|
|
31
|
+
*/
|
|
32
|
+
export async function searchLibraryPackages(text, categoryId, size = 250) {
|
|
33
|
+
const url = `${REGISTRY_SEARCH_ENDPOINT}?text=${encodeURIComponent(buildLibrarySearchQuery(text, categoryId))}&size=${size}`;
|
|
34
|
+
const res = await fetch(url);
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(`npm registry returned ${res.status}: ${res.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
const data = (await res.json());
|
|
39
|
+
return data.objects.map((o) => shapeResult(o.package));
|
|
40
|
+
}
|
|
41
|
+
export function shapeResult(pkg) {
|
|
42
|
+
return {
|
|
43
|
+
name: pkg.name,
|
|
44
|
+
version: pkg.version,
|
|
45
|
+
description: pkg.description ?? null,
|
|
46
|
+
category: categoryFromKeywords(pkg.keywords),
|
|
47
|
+
npmUrl: pkg.links?.npm ?? `https://www.npmjs.com/package/${pkg.name}`,
|
|
48
|
+
published: pkg.date ?? null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SelfCheckFinding } from "../emit/compliance/index.js";
|
|
2
|
+
export interface LibraryValidationFinding {
|
|
3
|
+
severity: "error" | "warning";
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface LibraryValidationReport {
|
|
7
|
+
valid: boolean;
|
|
8
|
+
/** The package directory that was validated. */
|
|
9
|
+
dir: string;
|
|
10
|
+
errors: string[];
|
|
11
|
+
warnings: string[];
|
|
12
|
+
/** Unrecorded AUTOSAR violations in the shim sources (strict mode). */
|
|
13
|
+
autosarFindings: SelfCheckFinding[];
|
|
14
|
+
}
|
|
15
|
+
export declare function validateLibraryPackage(dir: string): LibraryValidationReport;
|
|
16
|
+
/** Render the human-readable report. Returns the process exit code (0/1). */
|
|
17
|
+
export declare function printValidationReport(report: LibraryValidationReport): number;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// `cuttlefish library validate` — the standalone library-package validator.
|
|
3
|
+
//
|
|
4
|
+
// Before this, manifest errors only surfaced at import time inside a
|
|
5
|
+
// transpile. This runs the same checks (and more) on a package directory:
|
|
6
|
+
// manifest schema, file existence, include/gateToken consistency, npm
|
|
7
|
+
// keyword consistency, and an AUTOSAR --strict pass over the shim bytes —
|
|
8
|
+
// the same self-check the generated application runs over them.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { ComplianceContext, runSelfCheck } from "../emit/compliance/index.js";
|
|
13
|
+
import { LIBRARY_MARKER_KEYWORD, libraryCategory } from "./catalog.js";
|
|
14
|
+
function readJson(filePath) {
|
|
15
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
16
|
+
}
|
|
17
|
+
export function validateLibraryPackage(dir) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
const warnings = [];
|
|
20
|
+
const root = path.resolve(dir);
|
|
21
|
+
const fail = (message) => {
|
|
22
|
+
errors.push(message);
|
|
23
|
+
};
|
|
24
|
+
// ── Manifest ───────────────────────────────────────────────────────────
|
|
25
|
+
const manifestPath = path.join(root, "cuttlefish.library.json");
|
|
26
|
+
let manifest;
|
|
27
|
+
if (!fs.existsSync(manifestPath)) {
|
|
28
|
+
fail("cuttlefish.library.json not found — is this a library package directory?");
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
try {
|
|
32
|
+
manifest = readJson(manifestPath);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
fail(`cuttlefish.library.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (manifest) {
|
|
39
|
+
for (const field of ["id", "module", "framework", "include", "gateToken"]) {
|
|
40
|
+
if (typeof manifest[field] !== "string" || manifest[field].length === 0) {
|
|
41
|
+
fail(`Manifest field '${field}' must be a non-empty string.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!Array.isArray(manifest.shims) || manifest.shims.length === 0) {
|
|
45
|
+
fail("Manifest 'shims' must be a non-empty array.");
|
|
46
|
+
}
|
|
47
|
+
const include = typeof manifest.include === "string" ? manifest.include : "";
|
|
48
|
+
const gateToken = typeof manifest.gateToken === "string" ? manifest.gateToken : "";
|
|
49
|
+
if (include.length > 0 && gateToken.length > 0 && !include.includes(gateToken)) {
|
|
50
|
+
fail(`The gate token '${gateToken}' does not appear in the include '${include}' — ` +
|
|
51
|
+
`the include line is the only guaranteed emission, so contributions would never gate on.`);
|
|
52
|
+
}
|
|
53
|
+
// ── Shim files ───────────────────────────────────────────────────────
|
|
54
|
+
if (Array.isArray(manifest.shims)) {
|
|
55
|
+
for (const entry of manifest.shims) {
|
|
56
|
+
if (typeof entry !== "object" ||
|
|
57
|
+
entry === null ||
|
|
58
|
+
typeof entry.path !== "string" ||
|
|
59
|
+
typeof entry.outName !== "string") {
|
|
60
|
+
fail("Every 'shims' entry needs string 'path' and 'outName' fields.");
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const shimPath = path.join(root, entry.path);
|
|
64
|
+
if (!fs.existsSync(shimPath)) {
|
|
65
|
+
fail(`Shim file listed in the manifest is missing: ${entry.path}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ── Overlay fragment ─────────────────────────────────────────────────
|
|
70
|
+
if (typeof manifest.overlay === "string" && manifest.overlay.length > 0) {
|
|
71
|
+
const overlayPath = path.join(root, manifest.overlay);
|
|
72
|
+
if (!fs.existsSync(overlayPath)) {
|
|
73
|
+
fail(`Overlay fragment listed in the manifest is missing: ${manifest.overlay}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ── package.json consistency ───────────────────────────────────────────
|
|
78
|
+
const pkgJsonPath = path.join(root, "package.json");
|
|
79
|
+
let pkgName;
|
|
80
|
+
let pkgKeywords = [];
|
|
81
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
82
|
+
warnings.push("No package.json — nothing published, so keyword checks are skipped.");
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
try {
|
|
86
|
+
const pkg = readJson(pkgJsonPath);
|
|
87
|
+
if (typeof pkg.name === "string") {
|
|
88
|
+
pkgName = pkg.name;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(pkg.keywords)) {
|
|
91
|
+
pkgKeywords = pkg.keywords.filter((k) => typeof k === "string");
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(pkg.files) && !pkg.files.includes("cuttlefish.library.json")) {
|
|
94
|
+
fail("package.json 'files' must include 'cuttlefish.library.json' or the manifest will not ship.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
fail(`package.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (pkgKeywords.length > 0 && !pkgKeywords.map((k) => k.toLowerCase()).includes(LIBRARY_MARKER_KEYWORD)) {
|
|
102
|
+
fail(`package.json keywords must include the marker '${LIBRARY_MARKER_KEYWORD}' — it is what library search scans for.`);
|
|
103
|
+
}
|
|
104
|
+
const categoryKeywords = pkgKeywords.filter((k) => k.startsWith("cuttlefish-") && k !== LIBRARY_MARKER_KEYWORD);
|
|
105
|
+
if (pkgKeywords.length > 0 && categoryKeywords.length === 0) {
|
|
106
|
+
warnings.push(`No category keyword in package.json — add one of ${LIBRARY_MARKER_KEYWORD}'s siblings (e.g. cuttlefish-led) so the library is browsable by category.`);
|
|
107
|
+
}
|
|
108
|
+
for (const kw of categoryKeywords) {
|
|
109
|
+
const id = kw.slice("cuttlefish-".length);
|
|
110
|
+
if (!libraryCategory(id)) {
|
|
111
|
+
warnings.push(`Keyword '${kw}' is not a known category keyword (cuttlefish-<id>).`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (categoryKeywords.length > 1) {
|
|
115
|
+
warnings.push(`Multiple category keywords (${categoryKeywords.join(", ")}) — search results show the first taxonomy match only.`);
|
|
116
|
+
}
|
|
117
|
+
if (manifest && pkgName !== undefined && typeof manifest.module === "string" && manifest.module !== pkgName) {
|
|
118
|
+
fail(`Manifest 'module' (${manifest.module}) must equal the package.json name (${pkgName}) — ` +
|
|
119
|
+
`the import specifier users write resolves against the package name.`);
|
|
120
|
+
}
|
|
121
|
+
// ── AUTOSAR strict over the shim bytes ─────────────────────────────────
|
|
122
|
+
const autosarFindings = [];
|
|
123
|
+
if (manifest && Array.isArray(manifest.shims)) {
|
|
124
|
+
const headerLines = [];
|
|
125
|
+
const sourceLines = [];
|
|
126
|
+
let anyReadable = false;
|
|
127
|
+
for (const entry of manifest.shims) {
|
|
128
|
+
if (typeof entry !== "object" || entry === null)
|
|
129
|
+
continue;
|
|
130
|
+
const rel = entry.path;
|
|
131
|
+
if (typeof rel !== "string")
|
|
132
|
+
continue;
|
|
133
|
+
const abs = path.join(root, rel);
|
|
134
|
+
if (!fs.existsSync(abs))
|
|
135
|
+
continue;
|
|
136
|
+
anyReadable = true;
|
|
137
|
+
const content = fs.readFileSync(abs, "utf8").split("\n");
|
|
138
|
+
if (abs.endsWith(".h") || abs.endsWith(".hpp")) {
|
|
139
|
+
headerLines.push(...content, "");
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
sourceLines.push(...content, "");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (anyReadable) {
|
|
146
|
+
const ctx = new ComplianceContext("strict");
|
|
147
|
+
autosarFindings.push(...runSelfCheck(ctx, sourceLines, headerLines));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const finding of autosarFindings) {
|
|
151
|
+
fail(`AUTOSAR ${finding.ruleId} (${finding.file} line ${finding.line}): ${finding.snippet}`);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
valid: errors.length === 0,
|
|
155
|
+
dir: root,
|
|
156
|
+
errors,
|
|
157
|
+
warnings,
|
|
158
|
+
autosarFindings,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Render the human-readable report. Returns the process exit code (0/1). */
|
|
162
|
+
export function printValidationReport(report) {
|
|
163
|
+
for (const warning of report.warnings) {
|
|
164
|
+
console.log(` ! ${warning}`);
|
|
165
|
+
}
|
|
166
|
+
for (const error of report.errors) {
|
|
167
|
+
console.log(` x ${error}`);
|
|
168
|
+
}
|
|
169
|
+
if (report.valid) {
|
|
170
|
+
console.log(` Valid library package${report.warnings.length > 0 ? ` (${report.warnings.length} warning${report.warnings.length === 1 ? "" : "s"})` : ""}.`);
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
console.log(` ${report.errors.length} error${report.errors.length === 1 ? "" : "s"}.`);
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { LibraryDefinition } from "./types.js";
|
|
2
|
+
/** A native source file a library contributes to the emitted sources. */
|
|
3
|
+
export interface CuttlefishLibraryShim {
|
|
4
|
+
/** Path of the shim file, relative to the library package root. */
|
|
5
|
+
path: string;
|
|
6
|
+
/** File name the shim is written as, in the emitted source directory. */
|
|
7
|
+
outName: string;
|
|
8
|
+
}
|
|
9
|
+
/** The parsed `cuttlefish.library.json` manifest. */
|
|
10
|
+
export interface CuttlefishLibraryManifest {
|
|
11
|
+
/** Stable library id (diagnostics / sidecar records). */
|
|
12
|
+
id: string;
|
|
13
|
+
/** The import specifier this library resolves (e.g. '@typecad/zephyr-esp32s3-rgb'). */
|
|
14
|
+
module: string;
|
|
15
|
+
/** The framework (strategy id) this library requires, e.g. 'zephyr'. */
|
|
16
|
+
framework: string;
|
|
17
|
+
/** Build-target prefixes this library supports (matched against buildTarget). */
|
|
18
|
+
targets?: string[];
|
|
19
|
+
/** C++ include directive content for the import, e.g. '"__tc_rgbled.h"'. */
|
|
20
|
+
include: string;
|
|
21
|
+
/** Token whose presence in emitted sources gates the library's artifacts. */
|
|
22
|
+
gateToken: string;
|
|
23
|
+
/** Native shim files written into the emitted source directory. */
|
|
24
|
+
shims: CuttlefishLibraryShim[];
|
|
25
|
+
/** CONFIG_ lines contributed to the framework's prj.conf. */
|
|
26
|
+
kconfig?: string[];
|
|
27
|
+
/** Path of a devicetree overlay fragment, relative to the package root. */
|
|
28
|
+
overlay?: string;
|
|
29
|
+
}
|
|
30
|
+
/** A registered library: manifest + resolved package root. */
|
|
31
|
+
export interface RegisteredCuttlefishLibrary {
|
|
32
|
+
manifest: CuttlefishLibraryManifest;
|
|
33
|
+
packageRoot: string;
|
|
34
|
+
}
|
|
35
|
+
/** One record of the `libraries.json` sidecar (build-time contribution set). */
|
|
36
|
+
export interface LibrarySidecarEntry {
|
|
37
|
+
id: string;
|
|
38
|
+
module: string;
|
|
39
|
+
framework: string;
|
|
40
|
+
gateToken: string;
|
|
41
|
+
kconfig: string[];
|
|
42
|
+
/** Absolute path of the overlay fragment, or null. */
|
|
43
|
+
overlay: string | null;
|
|
44
|
+
/** Shim file names written into the emitted source directory. */
|
|
45
|
+
shims: string[];
|
|
46
|
+
}
|
|
47
|
+
/** Sidecar file name — written next to the emitted sources. */
|
|
48
|
+
export declare const LIBRARY_SIDECAR_NAME = "libraries.json";
|
|
49
|
+
/** Clear all registrations. Called once per transpilation, before graph build. */
|
|
50
|
+
export declare function resetCuttlefishLibraries(): void;
|
|
51
|
+
/** All libraries registered during this transpilation (import-driven). */
|
|
52
|
+
export declare function getRegisteredCuttlefishLibraries(): RegisteredCuttlefishLibrary[];
|
|
53
|
+
/**
|
|
54
|
+
* Whether the specifier resolves to a library registered in this
|
|
55
|
+
* transpilation. Library packages are exempt from the @typecad/* "SDK
|
|
56
|
+
* imports are type-level only" include skip — their import IS the include
|
|
57
|
+
* of the shim header.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isRegisteredCuttlefishLibrary(moduleSpecifier: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the package root for a bare npm specifier from the importing file,
|
|
62
|
+
* then register the package as a cuttlefish library if it ships a
|
|
63
|
+
* `cuttlefish.library.json`. Returns true when the specifier resolved to a
|
|
64
|
+
* registered library (the import must then be skipped by the graph builder).
|
|
65
|
+
*/
|
|
66
|
+
export declare function registerCuttlefishLibraryFromSpecifier(fromFile: string, moduleSpecifier: string): boolean;
|
|
67
|
+
/** Libdef entries for the registry: import → shim include. */
|
|
68
|
+
export declare function cuttlefishLibraryLibdefs(): LibraryDefinition[];
|
|
69
|
+
/**
|
|
70
|
+
* Validate registered libraries against the loaded framework (and, when
|
|
71
|
+
* known, the build target). Throws with an actionable message when a library
|
|
72
|
+
* cannot work in this project — far earlier and clearer than the native
|
|
73
|
+
* compiler would.
|
|
74
|
+
*/
|
|
75
|
+
export declare function validateCuttlefishLibraries(frameworkId: string, buildTarget?: string): void;
|
|
76
|
+
/**
|
|
77
|
+
* Emit the library artifacts for the libraries actually used in this
|
|
78
|
+
* transpilation, and write the `libraries.json` sidecar:
|
|
79
|
+
*
|
|
80
|
+
* 1. scan the emitted sources for each library's gate token;
|
|
81
|
+
* 2. write (write-if-changed) the shims of every gated library into the
|
|
82
|
+
* emitted source directory — the framework scaffold's CMake/sketch
|
|
83
|
+
* regen picks the sources up automatically;
|
|
84
|
+
* 3. delete shim files recorded in the previous sidecar that are no longer
|
|
85
|
+
* used, so removing an import removes its native code;
|
|
86
|
+
* 4. persist the sidecar (even when empty — that clears stale state).
|
|
87
|
+
*/
|
|
88
|
+
export declare function writeCuttlefishLibraryArtifacts(outDir: string, srcDir: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Read the `libraries.json` sidecar for build-time consumers (framework
|
|
91
|
+
* toolchains). Tolerant of a missing or corrupt file (returns []).
|
|
92
|
+
*/
|
|
93
|
+
export declare function readCuttlefishLibrarySidecar(outDir: string): LibrarySidecarEntry[];
|
|
94
|
+
/** Re-exported for transpile.ts libdef injection convenience. */
|
|
95
|
+
export declare function libraryDefinitionKey(module: string): string;
|