1+ import { readdir , readFile } from "node:fs/promises" ;
2+ import type { Dirent } from "node:fs" ;
3+ import path from "node:path" ;
4+
15/**
26 * Cached reference to the witValidator interface from the WASM component module.
37 * The jco-transpiled module self-initializes via top-level await,
48 * so the module is ready to use once the dynamic import resolves.
59 */
610let witValidatorApi : typeof import ( "wit-bindgen-wasm" ) . witValidator | null = null ;
711
12+ interface PreparedSourceContext {
13+ sourcePath ?: string ;
14+ sourceFilesJson ?: string ;
15+ }
16+
17+ function normalizeSourcePath ( sourcePath ?: string ) : string | undefined {
18+ const trimmedPath = sourcePath ?. trim ( ) ;
19+ if ( ! trimmedPath ) {
20+ return undefined ;
21+ }
22+
23+ return path . resolve ( trimmedPath ) ;
24+ }
25+
26+ function isErrnoException ( error : unknown ) : error is NodeJS . ErrnoException {
27+ return error instanceof Error && "code" in error ;
28+ }
29+
30+ async function readDirectoryEntries ( directoryPath : string ) : Promise < Array < Dirent > > {
31+ try {
32+ return await readdir ( directoryPath , { encoding : "utf8" , withFileTypes : true } ) ;
33+ } catch ( error : unknown ) {
34+ if ( isErrnoException ( error ) && error . code === "ENOENT" ) {
35+ return [ ] ;
36+ }
37+
38+ throw error ;
39+ }
40+ }
41+
42+ async function collectWitFilePathsRecursively ( directoryPath : string , filePaths : Array < string > ) : Promise < void > {
43+ const entries = await readDirectoryEntries ( directoryPath ) ;
44+ for ( const entry of entries ) {
45+ const entryPath = path . join ( directoryPath , entry . name ) ;
46+ if ( entry . isDirectory ( ) ) {
47+ await collectWitFilePathsRecursively ( entryPath , filePaths ) ;
48+ continue ;
49+ }
50+
51+ if ( entry . isFile ( ) && entry . name . toLowerCase ( ) . endsWith ( ".wit" ) ) {
52+ filePaths . push ( entryPath ) ;
53+ }
54+ }
55+ }
56+
57+ async function readWitFilesWithConcurrency ( filePaths : Array < string > , target : Record < string , string > ) : Promise < void > {
58+ if ( filePaths . length === 0 ) {
59+ return ;
60+ }
61+
62+ const maxConcurrency = 8 ;
63+ let currentIndex = 0 ;
64+
65+ const worker = async ( ) : Promise < void > => {
66+ while ( true ) {
67+ const index = currentIndex ;
68+ if ( index >= filePaths . length ) {
69+ return ;
70+ }
71+
72+ currentIndex += 1 ;
73+ const filePath = filePaths [ index ] ;
74+ try {
75+ const contents = await readFile ( filePath , "utf8" ) ;
76+ target [ filePath ] = contents ;
77+ } catch ( error : unknown ) {
78+ if ( isErrnoException ( error ) && ( error . code === "ENOENT" || error . code === "EACCES" ) ) {
79+ // Skip files that are missing or not accessible without failing the whole operation.
80+ continue ;
81+ }
82+
83+ throw error ;
84+ }
85+ }
86+ } ;
87+
88+ const workerCount = Math . min ( maxConcurrency , filePaths . length ) ;
89+ const workers : Array < Promise < void > > = [ ] ;
90+ for ( let i = 0 ; i < workerCount ; i += 1 ) {
91+ workers . push ( worker ( ) ) ;
92+ }
93+
94+ await Promise . all ( workers ) ;
95+ }
96+
97+ async function collectWitContext ( sourceDirectory : string ) : Promise < Record < string , string > > {
98+ const sourceFiles : Record < string , string > = { } ;
99+ const filePaths : Array < string > = [ ] ;
100+
101+ const entries = await readDirectoryEntries ( sourceDirectory ) ;
102+ for ( const entry of entries ) {
103+ const entryPath = path . join ( sourceDirectory , entry . name ) ;
104+ if ( entry . isDirectory ( ) ) {
105+ if ( entry . name === "deps" ) {
106+ await collectWitFilePathsRecursively ( entryPath , filePaths ) ;
107+ }
108+ continue ;
109+ }
110+
111+ if ( entry . isFile ( ) && entry . name . toLowerCase ( ) . endsWith ( ".wit" ) ) {
112+ filePaths . push ( entryPath ) ;
113+ }
114+ }
115+
116+ await readWitFilesWithConcurrency ( filePaths , sourceFiles ) ;
117+ return sourceFiles ;
118+ }
119+
120+ async function prepareSourceContext ( content : string , sourcePath ?: string ) : Promise < PreparedSourceContext > {
121+ const normalizedSourcePath = normalizeSourcePath ( sourcePath ) ;
122+ if ( ! normalizedSourcePath ) {
123+ return { } ;
124+ }
125+
126+ const sourceFiles = await collectWitContext ( path . dirname ( normalizedSourcePath ) ) ;
127+ sourceFiles [ normalizedSourcePath ] = content ;
128+
129+ return {
130+ sourcePath : normalizedSourcePath ,
131+ sourceFilesJson : JSON . stringify ( sourceFiles ) ,
132+ } ;
133+ }
134+
8135/**
9136 * Initialize the WASM module by dynamically importing it.
10137 * The jco-transpiled component handles WASM loading internally.
@@ -68,9 +195,10 @@ export async function isWitFileExtensionFromWasm(filename: string): Promise<bool
68195 * @param content - The WIT content to validate
69196 * @returns Promise that resolves to true if the syntax is valid
70197 */
71- export async function validateWitSyntaxFromWasm ( content : string ) : Promise < boolean > {
198+ export async function validateWitSyntaxFromWasm ( content : string , sourcePath ?: string ) : Promise < boolean > {
72199 const api = await getApi ( ) ;
73- return api . validateWitSyntax ( content ) ;
200+ const preparedSource = await prepareSourceContext ( content , sourcePath ) ;
201+ return api . validateWitSyntax ( content , preparedSource . sourcePath , preparedSource . sourceFilesJson ) ;
74202}
75203
76204/**
@@ -122,11 +250,19 @@ export async function extractInterfacesFromWasm(content: string): Promise<string
122250export async function generateBindingsFromWasm (
123251 content : string ,
124252 language : string ,
125- worldName ?: string
253+ worldName ?: string ,
254+ sourcePath ?: string
126255) : Promise < Record < string , string > > {
127256 const api = await getApi ( ) ;
128- const jsonResult = api . generateBindings ( content , language , worldName ) ;
129- return JSON . parse ( jsonResult ) ;
257+ const preparedSource = await prepareSourceContext ( content , sourcePath ) ;
258+ const jsonResult = api . generateBindings (
259+ content ,
260+ language ,
261+ worldName ,
262+ preparedSource . sourcePath ,
263+ preparedSource . sourceFilesJson
264+ ) ;
265+ return JSON . parse ( jsonResult ) as Record < string , string > ;
130266}
131267
132268/**
@@ -143,8 +279,16 @@ export interface WitValidationResult {
143279 * @param content - The WIT content to validate
144280 * @returns Promise that resolves to detailed validation results
145281 */
146- export async function validateWitSyntaxDetailedFromWasm ( content : string ) : Promise < WitValidationResult > {
282+ export async function validateWitSyntaxDetailedFromWasm (
283+ content : string ,
284+ sourcePath ?: string
285+ ) : Promise < WitValidationResult > {
147286 const api = await getApi ( ) ;
148- const resultJson = api . validateWitSyntaxDetailed ( content ) ;
149- return JSON . parse ( resultJson ) ;
287+ const preparedSource = await prepareSourceContext ( content , sourcePath ) ;
288+ const resultJson = api . validateWitSyntaxDetailed (
289+ content ,
290+ preparedSource . sourcePath ,
291+ preparedSource . sourceFilesJson
292+ ) ;
293+ return JSON . parse ( resultJson ) as WitValidationResult ;
150294}
0 commit comments