@typecad/framework-arduino 0.1.0-alpha.1

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/dist/arduino/i2c-arduino.d.ts +47 -0
  4. package/dist/arduino/i2c-arduino.js +6 -0
  5. package/dist/arduino/index.d.ts +6 -0
  6. package/dist/arduino/index.js +8 -0
  7. package/dist/arduino/spi-arduino.d.ts +42 -0
  8. package/dist/arduino/spi-arduino.js +6 -0
  9. package/dist/arduino/stubs-uno.d.ts +9 -0
  10. package/dist/arduino/stubs-uno.js +65 -0
  11. package/dist/arduino/uart-arduino.d.ts +41 -0
  12. package/dist/arduino/uart-arduino.js +6 -0
  13. package/dist/arduino-class-map.d.ts +5 -0
  14. package/dist/arduino-class-map.js +21 -0
  15. package/dist/arduino-compile.d.ts +24 -0
  16. package/dist/arduino-compile.js +316 -0
  17. package/dist/arduino-libs.d.ts +6 -0
  18. package/dist/arduino-libs.js +11 -0
  19. package/dist/cli-metadata.d.ts +12 -0
  20. package/dist/cli-metadata.js +196 -0
  21. package/dist/cpp-parser.d.ts +47 -0
  22. package/dist/cpp-parser.js +292 -0
  23. package/dist/debug-codegen.d.ts +20 -0
  24. package/dist/debug-codegen.js +115 -0
  25. package/dist/displays/ili9341-spi.d.ts +6 -0
  26. package/dist/displays/ili9341-spi.js +28 -0
  27. package/dist/displays/ssd1309-i2c.d.ts +2 -0
  28. package/dist/displays/ssd1309-i2c.js +16 -0
  29. package/dist/displays/st7796-spi.d.ts +2 -0
  30. package/dist/displays/st7796-spi.js +20 -0
  31. package/dist/graphics/ili9341.d.ts +24 -0
  32. package/dist/graphics/ili9341.js +59 -0
  33. package/dist/index.d.ts +13 -0
  34. package/dist/index.js +27 -0
  35. package/dist/lib-declaration.d.ts +35 -0
  36. package/dist/lib-declaration.js +399 -0
  37. package/dist/lib-discovery.d.ts +38 -0
  38. package/dist/lib-discovery.js +189 -0
  39. package/dist/profile.d.ts +8 -0
  40. package/dist/profile.js +584 -0
  41. package/dist/strategy.d.ts +174 -0
  42. package/dist/strategy.js +2032 -0
  43. package/package.json +73 -0
  44. package/src/arduino/i2c-arduino.ts +72 -0
  45. package/src/arduino/index.ts +18 -0
  46. package/src/arduino/spi-arduino.ts +63 -0
  47. package/src/arduino/stubs-uno.ts +80 -0
  48. package/src/arduino/uart-arduino.ts +59 -0
  49. package/src/arduino-class-map.ts +32 -0
  50. package/src/arduino-compile.ts +359 -0
  51. package/src/arduino-libs.ts +33 -0
  52. package/src/cli-metadata.ts +250 -0
  53. package/src/cpp-parser.ts +361 -0
  54. package/src/debug-codegen.ts +155 -0
  55. package/src/displays/ili9341-spi.ts +33 -0
  56. package/src/displays/ssd1309-i2c.ts +19 -0
  57. package/src/displays/st7796-spi.ts +23 -0
  58. package/src/graphics/ili9341.ts +80 -0
  59. package/src/index.ts +37 -0
  60. package/src/lib-declaration.ts +493 -0
  61. package/src/lib-discovery.ts +267 -0
  62. package/src/profile.ts +676 -0
  63. package/src/strategy.ts +2126 -0
@@ -0,0 +1,35 @@
1
+ import { type ArduinoLibrary } from "./lib-discovery.js";
2
+ import { type CppParseResult } from "./cpp-parser.js";
3
+ /**
4
+ * Result of generating a .d.ts file for an Arduino library.
5
+ */
6
+ export interface GeneratedArduinoLib {
7
+ /** Path to the generated .d.ts file */
8
+ declPath: string;
9
+ /** The actual header file name (e.g., "Microfire_SHT3x.h") */
10
+ headerName: string;
11
+ }
12
+ /**
13
+ * Generate a .d.ts file for an Arduino library.
14
+ * Returns the path to the generated file and header info, or undefined if generation failed.
15
+ */
16
+ export declare function generateArduinoLibDecl(library: ArduinoLibrary, fromFile: string): GeneratedArduinoLib | undefined;
17
+ /**
18
+ * Try to generate .d.ts for a missing Arduino library import.
19
+ * Returns the path to the generated declaration file, or undefined if not found.
20
+ */
21
+ export declare function tryGenerateArduinoLibDecl(moduleSpecifier: string, fromFile: string): string | undefined;
22
+ /**
23
+ * Try to get the actual header file name for an Arduino library import.
24
+ * Returns the header file name (e.g., "Microfire_SHT3x.h") or undefined if not found.
25
+ */
26
+ export declare function getArduinoLibraryHeaderName(moduleSpecifier: string): string | undefined;
27
+ /**
28
+ * Get fully qualified class names for an Arduino library.
29
+ * Returns a map of simple class name to fully qualified name (e.g., "SHT3x" -> "Microfire::SHT3x").
30
+ */
31
+ export declare function getArduinoLibraryClassNames(moduleSpecifier: string): Map<string, string>;
32
+ /**
33
+ * Generate usage documentation for an Arduino library.
34
+ */
35
+ export declare function generateUsageDocumentation(parsed: CppParseResult, libraryName: string, library?: ArduinoLibrary): string;
@@ -0,0 +1,399 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/framework-arduino — Arduino Library Declaration Generator
3
+ //
4
+ // Generates TypeScript .d.ts declarations and usage documentation from
5
+ // parsed Arduino library C++ headers.
6
+ // ---------------------------------------------------------------------------
7
+ import path from "node:path";
8
+ import fs from "node:fs";
9
+ import { isArduinoLibraryImport, findArduinoLibrary, findLibraryHeader } from "./lib-discovery.js";
10
+ import { parseCppClass } from "./cpp-parser.js";
11
+ // ---------------------------------------------------------------------------
12
+ // Project root detection
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Find the project root by looking for package.json or tsconfig.json.
16
+ */
17
+ function findProjectRoot(fromFile) {
18
+ let currentDir = path.dirname(path.resolve(fromFile));
19
+ while (currentDir !== path.dirname(currentDir)) {
20
+ if (fs.existsSync(path.join(currentDir, "package.json")) ||
21
+ fs.existsSync(path.join(currentDir, "tsconfig.json"))) {
22
+ return currentDir;
23
+ }
24
+ currentDir = path.dirname(currentDir);
25
+ }
26
+ return undefined;
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Declaration generation
30
+ // ---------------------------------------------------------------------------
31
+ /**
32
+ * Generate export-based declaration content for @types packages.
33
+ */
34
+ function generateExportDeclaration(parsed, moduleDocstring) {
35
+ const lines = [];
36
+ // Add header comment explaining auto-generation and manual editing
37
+ lines.push("/**");
38
+ if (moduleDocstring) {
39
+ lines.push(` * ${moduleDocstring}`);
40
+ }
41
+ lines.push(" *");
42
+ lines.push(" * Auto-generated by TypeCAD from Arduino library C++ headers.");
43
+ lines.push(" * Arduino peripheral types (TwoWire, HardwareSerial, etc.) are mapped to TypeCAD equivalents.");
44
+ lines.push(" *");
45
+ lines.push(" * You can manually edit this file to:");
46
+ lines.push(" * - Add missing methods or properties");
47
+ lines.push(" * - Fix incorrect type mappings");
48
+ lines.push(" * - Add JSDoc documentation");
49
+ lines.push(" *");
50
+ lines.push(" * To regenerate: delete this file and re-run TypeCAD transpile.");
51
+ lines.push(" */");
52
+ lines.push("");
53
+ // Export constants
54
+ for (const constant of parsed.constants) {
55
+ lines.push(`export declare const ${constant.name}: ${constant.type};`);
56
+ }
57
+ if (parsed.constants.length > 0 && parsed.classes.length > 0) {
58
+ lines.push("");
59
+ }
60
+ // Export classes
61
+ for (const cppClass of parsed.classes) {
62
+ lines.push(`export declare class ${cppClass.name} {`);
63
+ // Add constructors
64
+ for (const ctor of cppClass.constructors) {
65
+ const params = ctor.parameters
66
+ .filter(p => p.name)
67
+ .map(p => `${p.name}: ${p.type}`)
68
+ .join(", ");
69
+ lines.push(` constructor(${params});`);
70
+ }
71
+ // Add public methods
72
+ const publicMethods = cppClass.methods.filter(m => m.isPublic);
73
+ for (const method of publicMethods) {
74
+ const params = method.parameters
75
+ .filter(p => p.name)
76
+ .map(p => `${p.name}: ${p.type}`)
77
+ .join(", ");
78
+ lines.push(` ${method.name}(${params}): ${method.returnType};`);
79
+ }
80
+ lines.push("}");
81
+ }
82
+ lines.push("");
83
+ return lines.join("\n");
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Public API
87
+ // ---------------------------------------------------------------------------
88
+ /**
89
+ * Generate a .d.ts file for an Arduino library.
90
+ * Returns the path to the generated file and header info, or undefined if generation failed.
91
+ */
92
+ export function generateArduinoLibDecl(library, fromFile) {
93
+ // Find the main header file
94
+ const headerPath = findLibraryHeader(library);
95
+ if (!headerPath) {
96
+ console.log(` No header file found for Arduino library '${library.name}'`);
97
+ return undefined;
98
+ }
99
+ if (!fs.existsSync(headerPath)) {
100
+ console.log(` Header file does not exist: ${headerPath}`);
101
+ return undefined;
102
+ }
103
+ const content = fs.readFileSync(headerPath, "utf8");
104
+ const parsed = parseCppClass(content);
105
+ if (parsed.classes.length === 0 && parsed.constants.length === 0) {
106
+ console.log(` No class definitions found in '${library.name}' header`);
107
+ return undefined;
108
+ }
109
+ // Generate export-based declaration for @types
110
+ const declaration = generateExportDeclaration(parsed, `Arduino library: ${library.name}`);
111
+ // Find project root
112
+ const projectRoot = findProjectRoot(fromFile);
113
+ if (!projectRoot) {
114
+ console.log(` Could not find project root for '${fromFile}'`);
115
+ return undefined;
116
+ }
117
+ // Place declarations in node_modules/@types/LibraryName/
118
+ // TypeScript automatically looks here for type declarations
119
+ const typeDir = path.join(projectRoot, "node_modules", "@types", library.name);
120
+ // Ensure output directory exists
121
+ if (!fs.existsSync(typeDir)) {
122
+ fs.mkdirSync(typeDir, { recursive: true });
123
+ }
124
+ const declPath = path.join(typeDir, "index.d.ts");
125
+ // Write the declaration file
126
+ fs.writeFileSync(declPath, declaration, "utf8");
127
+ // Also write a minimal package.json for module resolution
128
+ const packageJson = {
129
+ name: `@types/${library.name}`,
130
+ version: "0.0.0",
131
+ main: "index.d.ts",
132
+ types: "index.d.ts"
133
+ };
134
+ fs.writeFileSync(path.join(typeDir, "package.json"), JSON.stringify(packageJson, null, 2), "utf8");
135
+ // Extract the actual header file name from the path
136
+ const headerName = path.basename(headerPath);
137
+ // Generate usage documentation
138
+ const usageDoc = generateUsageDocumentation(parsed, library.name, library);
139
+ fs.writeFileSync(path.join(typeDir, "USAGE.md"), usageDoc, "utf8");
140
+ return { declPath, headerName };
141
+ }
142
+ /**
143
+ * Try to generate .d.ts for a missing Arduino library import.
144
+ * Returns the path to the generated declaration file, or undefined if not found.
145
+ */
146
+ export function tryGenerateArduinoLibDecl(moduleSpecifier, fromFile) {
147
+ if (!isArduinoLibraryImport(moduleSpecifier)) {
148
+ return undefined;
149
+ }
150
+ const library = findArduinoLibrary(moduleSpecifier);
151
+ if (!library) {
152
+ return undefined;
153
+ }
154
+ console.log(`\n Found Arduino library '${library.name}' at: ${library.path}`);
155
+ const result = generateArduinoLibDecl(library, fromFile);
156
+ if (result) {
157
+ console.log(` Generated declaration: ${result.declPath}`);
158
+ console.log(` Review the generated types and adjust if needed.\n`);
159
+ return result.declPath;
160
+ }
161
+ return undefined;
162
+ }
163
+ /**
164
+ * Try to get the actual header file name for an Arduino library import.
165
+ * Returns the header file name (e.g., "Microfire_SHT3x.h") or undefined if not found.
166
+ */
167
+ export function getArduinoLibraryHeaderName(moduleSpecifier) {
168
+ if (!isArduinoLibraryImport(moduleSpecifier)) {
169
+ return undefined;
170
+ }
171
+ const library = findArduinoLibrary(moduleSpecifier);
172
+ if (!library) {
173
+ return undefined;
174
+ }
175
+ const headerPath = findLibraryHeader(library);
176
+ if (!headerPath) {
177
+ return undefined;
178
+ }
179
+ return path.basename(headerPath);
180
+ }
181
+ /**
182
+ * Get fully qualified class names for an Arduino library.
183
+ * Returns a map of simple class name to fully qualified name (e.g., "SHT3x" -> "Microfire::SHT3x").
184
+ */
185
+ export function getArduinoLibraryClassNames(moduleSpecifier) {
186
+ const result = new Map();
187
+ if (!isArduinoLibraryImport(moduleSpecifier)) {
188
+ return result;
189
+ }
190
+ const library = findArduinoLibrary(moduleSpecifier);
191
+ if (!library) {
192
+ return result;
193
+ }
194
+ const headerPath = findLibraryHeader(library);
195
+ if (!headerPath || !fs.existsSync(headerPath)) {
196
+ return result;
197
+ }
198
+ const content = fs.readFileSync(headerPath, "utf8");
199
+ const parsed = parseCppClass(content);
200
+ for (const cppClass of parsed.classes) {
201
+ result.set(cppClass.name, cppClass.fullName);
202
+ }
203
+ return result;
204
+ }
205
+ // ---------------------------------------------------------------------------
206
+ // Documentation generation
207
+ // ---------------------------------------------------------------------------
208
+ /**
209
+ * Generate usage documentation for an Arduino library.
210
+ */
211
+ export function generateUsageDocumentation(parsed, libraryName, library) {
212
+ const lines = [];
213
+ lines.push(`# ${libraryName} - TypeCAD Usage Guide`);
214
+ lines.push("");
215
+ if (library?.sentence) {
216
+ lines.push(`> ${library.sentence}`);
217
+ lines.push("");
218
+ }
219
+ if (library?.author) {
220
+ lines.push(`**Author:** ${library.author}`);
221
+ lines.push("");
222
+ }
223
+ lines.push("## Overview");
224
+ lines.push("");
225
+ lines.push(`This library was auto-discovered from your Arduino installation and TypeCAD`);
226
+ lines.push(`has generated TypeScript type definitions for it.`);
227
+ lines.push("");
228
+ if (parsed.classes.length > 0) {
229
+ lines.push("## Classes");
230
+ lines.push("");
231
+ for (const cppClass of parsed.classes) {
232
+ lines.push(`### ${cppClass.name}`);
233
+ lines.push("");
234
+ // Show C++ fully qualified name if different
235
+ if (cppClass.fullName !== cppClass.name) {
236
+ lines.push(`**C++:** \`${cppClass.fullName}\``);
237
+ lines.push("");
238
+ }
239
+ // Constructor
240
+ if (cppClass.constructors.length > 0) {
241
+ lines.push("#### Constructor");
242
+ lines.push("");
243
+ lines.push("```typescript");
244
+ lines.push(`import { ${cppClass.name} } from '${libraryName}';`);
245
+ lines.push("");
246
+ for (const ctor of cppClass.constructors) {
247
+ const params = ctor.parameters
248
+ .filter(p => p.name)
249
+ .map(p => `${p.name}: ${p.type}`)
250
+ .join(", ");
251
+ lines.push(`const sensor = new ${cppClass.name}(${params});`);
252
+ }
253
+ lines.push("```");
254
+ lines.push("");
255
+ }
256
+ // Methods
257
+ if (cppClass.methods.length > 0) {
258
+ lines.push("#### Methods");
259
+ lines.push("");
260
+ lines.push("| Method | Parameters | Returns |");
261
+ lines.push("|--------|------------|---------|");
262
+ for (const method of cppClass.methods) {
263
+ const params = method.parameters
264
+ .filter(p => p.name)
265
+ .map(p => `${p.name}: ${p.type}`)
266
+ .join(", ");
267
+ lines.push(`| \`${method.name}()\` | ${params || "none"} | ${method.returnType} |`);
268
+ }
269
+ lines.push("");
270
+ // Usage examples
271
+ lines.push("#### Example Usage");
272
+ lines.push("");
273
+ lines.push("```typescript");
274
+ lines.push(`import { ${cppClass.name} } from '${libraryName}';`);
275
+ // Add peripheral imports if needed
276
+ const peripheralTypes = new Set();
277
+ for (const method of cppClass.methods) {
278
+ for (const param of method.parameters) {
279
+ if (param.type === "I2C0" || param.type === "I2C1") {
280
+ peripheralTypes.add("I2C0");
281
+ }
282
+ else if (param.type === "SPI0" || param.type === "SPI1") {
283
+ peripheralTypes.add("SPI0");
284
+ }
285
+ else if (param.type === "UART0" || param.type === "UART1") {
286
+ peripheralTypes.add("UART0");
287
+ }
288
+ }
289
+ for (const ctor of cppClass.constructors) {
290
+ for (const param of ctor.parameters) {
291
+ if (param.type === "I2C0" || param.type === "I2C1") {
292
+ peripheralTypes.add("I2C0");
293
+ }
294
+ else if (param.type === "SPI0" || param.type === "SPI1") {
295
+ peripheralTypes.add("SPI0");
296
+ }
297
+ else if (param.type === "UART0" || param.type === "UART1") {
298
+ peripheralTypes.add("UART0");
299
+ }
300
+ }
301
+ }
302
+ }
303
+ if (peripheralTypes.size > 0) {
304
+ lines.push(`import { ${[...peripheralTypes].join(", ")} } from '@typecad/board';`);
305
+ }
306
+ lines.push("");
307
+ // Constructor example
308
+ if (cppClass.constructors.length > 0) {
309
+ const ctor = cppClass.constructors[0];
310
+ const args = ctor.parameters
311
+ .filter(p => p.name)
312
+ .map(p => {
313
+ // Provide example values for common types
314
+ if (p.type === "I2C0")
315
+ return "I2C0";
316
+ if (p.type === "SPI0")
317
+ return "SPI0";
318
+ if (p.type === "UART0")
319
+ return "UART0";
320
+ if (p.type === "number")
321
+ return "0x44";
322
+ if (p.type === "boolean")
323
+ return "true";
324
+ if (p.type === "string")
325
+ return '"example"';
326
+ return p.name;
327
+ })
328
+ .join(", ");
329
+ lines.push(`const device = new ${cppClass.name}(${args});`);
330
+ }
331
+ else {
332
+ lines.push(`const device = new ${cppClass.name}();`);
333
+ }
334
+ lines.push("");
335
+ // Show common method calls
336
+ const beginMethod = cppClass.methods.find(m => m.name === "begin");
337
+ if (beginMethod) {
338
+ const args = beginMethod.parameters
339
+ .filter(p => p.name)
340
+ .map(p => {
341
+ if (p.type === "I2C0")
342
+ return "I2C0";
343
+ if (p.type === "SPI0")
344
+ return "SPI0";
345
+ if (p.type === "UART0")
346
+ return "UART0";
347
+ if (p.type === "number")
348
+ return "0x44";
349
+ if (p.type === "boolean")
350
+ return "true";
351
+ return p.name;
352
+ })
353
+ .join(", ");
354
+ lines.push(`device.begin(${args});`);
355
+ }
356
+ const measureMethod = cppClass.methods.find(m => m.name === "measure" || m.name === "read");
357
+ if (measureMethod) {
358
+ lines.push(`device.${measureMethod.name}();`);
359
+ }
360
+ const connectedMethod = cppClass.methods.find(m => m.name === "connected" || m.name === "begin");
361
+ if (connectedMethod && connectedMethod.returnType === "boolean") {
362
+ lines.push("");
363
+ lines.push(`if (device.${connectedMethod.name}()) {`);
364
+ lines.push(` // Device is ready`);
365
+ lines.push(`}`);
366
+ }
367
+ lines.push("```");
368
+ lines.push("");
369
+ }
370
+ }
371
+ }
372
+ // Type mappings section
373
+ lines.push("## Type Mappings");
374
+ lines.push("");
375
+ lines.push("TypeCAD automatically maps Arduino C++ types to TypeScript equivalents:");
376
+ lines.push("");
377
+ lines.push("| Arduino C++ | TypeCAD TypeScript |");
378
+ lines.push("|-------------|---------------------|");
379
+ lines.push("| `TwoWire` / `TwoWire*` | `I2C0` |");
380
+ lines.push("| `SPIClass` / `SPIClass*` | `SPI0` |");
381
+ lines.push("| `HardwareSerial` / `HardwareSerial*` | `UART0` |");
382
+ lines.push("| `int`, `uint8_t`, `uint16_t`, etc. | `number` |");
383
+ lines.push("| `float`, `double` | `number` |");
384
+ lines.push("| `bool` | `boolean` |");
385
+ lines.push("| `String`, `const char*` | `string` |");
386
+ lines.push("");
387
+ lines.push("## Editing Type Definitions");
388
+ lines.push("");
389
+ lines.push("The generated type definitions are best-effort. You can manually edit the");
390
+ lines.push(`\`node_modules/@types/${libraryName}/index.d.ts\` file to:`);
391
+ lines.push("");
392
+ lines.push("- Add missing methods or properties");
393
+ lines.push("- Fix incorrect type mappings");
394
+ lines.push("- Add JSDoc documentation");
395
+ lines.push("");
396
+ lines.push("To regenerate: delete the file and re-run TypeCAD transpile.");
397
+ lines.push("");
398
+ return lines.join("\n");
399
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Information about an installed Arduino library.
3
+ */
4
+ export interface ArduinoLibrary {
5
+ /** Library name (e.g., "BH1750") */
6
+ name: string;
7
+ /** Version string */
8
+ version?: string;
9
+ /** Path to the library directory */
10
+ path: string;
11
+ /** Author information */
12
+ author?: string;
13
+ /** Brief description */
14
+ sentence?: string;
15
+ }
16
+ /**
17
+ * Clear the library cache (useful for testing or after installing new libraries).
18
+ */
19
+ export declare function clearLibraryCache(): void;
20
+ /**
21
+ * Get all installed Arduino libraries using arduino-cli.
22
+ */
23
+ export declare function getInstalledLibraries(): Map<string, ArduinoLibrary>;
24
+ /**
25
+ * Find an Arduino library by name.
26
+ */
27
+ export declare function findArduinoLibrary(name: string): ArduinoLibrary | undefined;
28
+ /**
29
+ * Find the main header file for an Arduino library.
30
+ * Arduino libraries typically have a .h file matching the library name.
31
+ */
32
+ export declare function findLibraryHeader(library: ArduinoLibrary): string | undefined;
33
+ /**
34
+ * Check if a module specifier looks like an Arduino library import.
35
+ * Arduino library imports are bare names like "BH1750", "Servo", "Wire"
36
+ * (not relative paths, not npm packages).
37
+ */
38
+ export declare function isArduinoLibraryImport(moduleSpecifier: string): boolean;
@@ -0,0 +1,189 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/framework-arduino — Arduino Library Discovery
3
+ //
4
+ // Handles finding installed Arduino libraries via arduino-cli, locating
5
+ // their headers and sources, and detecting Arduino library imports.
6
+ // ---------------------------------------------------------------------------
7
+ import { spawnSync } from "node:child_process";
8
+ import path from "node:path";
9
+ import fs from "node:fs";
10
+ // ---------------------------------------------------------------------------
11
+ // Library cache
12
+ // ---------------------------------------------------------------------------
13
+ let libraryCache = null;
14
+ let cacheTimestamp = 0;
15
+ const CACHE_TTL = 60000; // 1 minute cache
16
+ /**
17
+ * Clear the library cache (useful for testing or after installing new libraries).
18
+ */
19
+ export function clearLibraryCache() {
20
+ libraryCache = null;
21
+ cacheTimestamp = 0;
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // arduino-cli runner
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Execute arduino-cli and return parsed JSON output.
28
+ */
29
+ function runArduinoCli(args) {
30
+ try {
31
+ const result = spawnSync("arduino-cli", args, {
32
+ encoding: "utf8",
33
+ timeout: 30000,
34
+ });
35
+ if (result.status !== 0) {
36
+ return null;
37
+ }
38
+ const output = result.stdout?.trim();
39
+ if (!output) {
40
+ return null;
41
+ }
42
+ return JSON.parse(output);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Library discovery
50
+ // ---------------------------------------------------------------------------
51
+ /**
52
+ * Get all installed Arduino libraries using arduino-cli.
53
+ */
54
+ export function getInstalledLibraries() {
55
+ // Check cache
56
+ const now = Date.now();
57
+ if (libraryCache && (now - cacheTimestamp) < CACHE_TTL) {
58
+ return libraryCache;
59
+ }
60
+ const libraries = new Map();
61
+ // Run arduino-cli lib list --format json
62
+ const result = runArduinoCli(["lib", "list", "--format", "json"]);
63
+ if (!result) {
64
+ libraryCache = libraries;
65
+ cacheTimestamp = now;
66
+ return libraries;
67
+ }
68
+ // Handle both formats:
69
+ // 1. { installed_libraries: [{ library: {...} }] } (newer arduino-cli)
70
+ // 2. [{ ... }] (older format or different command)
71
+ let libList = [];
72
+ if (Array.isArray(result)) {
73
+ libList = result;
74
+ }
75
+ else if (typeof result === "object" && result !== null) {
76
+ const wrapped = result;
77
+ if (wrapped.installed_libraries && Array.isArray(wrapped.installed_libraries)) {
78
+ libList = wrapped.installed_libraries.map(item => item.library);
79
+ }
80
+ }
81
+ for (const lib of libList) {
82
+ if (!lib.name || !lib.install_dir) {
83
+ continue;
84
+ }
85
+ const library = {
86
+ name: lib.name,
87
+ version: lib.version,
88
+ path: lib.install_dir,
89
+ author: lib.author,
90
+ sentence: lib.sentence,
91
+ };
92
+ // Store by name (case-insensitive key)
93
+ libraries.set(lib.name.toLowerCase(), library);
94
+ // Also store with original casing
95
+ if (lib.name !== lib.name.toLowerCase()) {
96
+ libraries.set(lib.name, library);
97
+ }
98
+ }
99
+ libraryCache = libraries;
100
+ cacheTimestamp = now;
101
+ return libraries;
102
+ }
103
+ /**
104
+ * Find an Arduino library by name.
105
+ */
106
+ export function findArduinoLibrary(name) {
107
+ const libraries = getInstalledLibraries();
108
+ // Try exact match first, then case-insensitive
109
+ return libraries.get(name) || libraries.get(name.toLowerCase());
110
+ }
111
+ /**
112
+ * Find the main header file for an Arduino library.
113
+ * Arduino libraries typically have a .h file matching the library name.
114
+ */
115
+ export function findLibraryHeader(library) {
116
+ const libName = library.name;
117
+ const libDir = library.path;
118
+ if (!fs.existsSync(libDir)) {
119
+ return undefined;
120
+ }
121
+ // Common header locations:
122
+ // 1. LibraryName.h in root
123
+ // 2. src/LibraryName.h
124
+ // 3. Any .h file in root if only one exists
125
+ const candidates = [
126
+ path.join(libDir, `${libName}.h`),
127
+ path.join(libDir, "src", `${libName}.h`),
128
+ ];
129
+ for (const candidate of candidates) {
130
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
131
+ return candidate;
132
+ }
133
+ }
134
+ // Fallback: look for any .h file in the root or src directory
135
+ const dirs = [libDir, path.join(libDir, "src")];
136
+ for (const dir of dirs) {
137
+ if (!fs.existsSync(dir))
138
+ continue;
139
+ const headers = [];
140
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
141
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(".h")) {
142
+ headers.push(path.join(dir, entry.name));
143
+ }
144
+ }
145
+ // If there's only one header, use it
146
+ if (headers.length === 1) {
147
+ return headers[0];
148
+ }
149
+ // If there are multiple, prefer one matching the library name
150
+ for (const h of headers) {
151
+ if (path.basename(h, ".h").toLowerCase() === libName.toLowerCase()) {
152
+ return h;
153
+ }
154
+ }
155
+ }
156
+ return undefined;
157
+ }
158
+ /**
159
+ * Check if a module specifier looks like an Arduino library import.
160
+ * Arduino library imports are bare names like "BH1750", "Servo", "Wire"
161
+ * (not relative paths, not npm packages).
162
+ */
163
+ export function isArduinoLibraryImport(moduleSpecifier) {
164
+ // Skip relative imports
165
+ if (moduleSpecifier.startsWith(".")) {
166
+ return false;
167
+ }
168
+ // Skip npm-style imports (scoped or with path separators)
169
+ if (moduleSpecifier.startsWith("@") || moduleSpecifier.includes("/")) {
170
+ return false;
171
+ }
172
+ // Skip known non-Arduino imports
173
+ const skipList = new Set([
174
+ "TypeCAD",
175
+ "typescript",
176
+ "node",
177
+ "fs",
178
+ "path",
179
+ "http",
180
+ "https",
181
+ "crypto",
182
+ "os",
183
+ "util",
184
+ ]);
185
+ if (skipList.has(moduleSpecifier.toLowerCase())) {
186
+ return false;
187
+ }
188
+ return true;
189
+ }
@@ -0,0 +1,8 @@
1
+ import type { ProgramIR, Diagnostic, PlatformContext } from "@typecad/cuttlefish/api/shared";
2
+ export interface ResolvedArduinoProfile {
3
+ forcedIncludes: string[];
4
+ symbolAliases: Record<string, string>;
5
+ shimLines: string[];
6
+ diagnostics: Diagnostic[];
7
+ }
8
+ export declare function resolveArduinoProfile(program: ProgramIR, platformContext?: PlatformContext): ResolvedArduinoProfile;