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 < string > > > {
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+ const contents = await readFile ( filePath , "utf8" ) ;
75+ target [ filePath ] = contents ;
76+ }
77+ } ;
78+
79+ const workerCount = Math . min ( maxConcurrency , filePaths . length ) ;
80+ const workers : Array < Promise < void > > = [ ] ;
81+ for ( let i = 0 ; i < workerCount ; i += 1 ) {
82+ workers . push ( worker ( ) ) ;
83+ }
84+
85+ await Promise . all ( workers ) ;
86+ }
87+
88+ async function collectWitFilesRecursively ( directoryPath : string , sourceFiles : Record < string , string > ) : Promise < void > {
89+ const filePaths : Array < string > = [ ] ;
90+ await collectWitFilePathsRecursively ( directoryPath , filePaths ) ;
91+ await readWitFilesWithConcurrency ( filePaths , sourceFiles ) ;
92+ }
93+
94+ async function collectWitContext ( sourceDirectory : string ) : Promise < Record < string , string > > {
95+ const sourceFiles : Record < string , string > = { } ;
96+ const filePaths : Array < string > = [ ] ;
97+
98+ const entries = await readDirectoryEntries ( sourceDirectory ) ;
99+ for ( const entry of entries ) {
100+ const entryPath = path . join ( sourceDirectory , entry . name ) ;
101+ if ( entry . isDirectory ( ) ) {
102+ if ( entry . name === "deps" ) {
103+ await collectWitFilePathsRecursively ( entryPath , filePaths ) ;
104+ }
105+ continue ;
106+ }
107+
108+ if ( entry . isFile ( ) && entry . name . toLowerCase ( ) . endsWith ( ".wit" ) ) {
109+ filePaths . push ( entryPath ) ;
110+ }
111+ }
112+
113+ await readWitFilesWithConcurrency ( filePaths , sourceFiles ) ;
114+ return sourceFiles ;
115+ }
116+
117+ async function prepareSourceContext ( content : string , sourcePath ?: string ) : Promise < PreparedSourceContext > {
118+ const normalizedSourcePath = normalizeSourcePath ( sourcePath ) ;
119+ if ( ! normalizedSourcePath ) {
120+ return { } ;
121+ }
122+
123+ const sourceFiles = await collectWitContext ( path . dirname ( normalizedSourcePath ) ) ;
124+ sourceFiles [ normalizedSourcePath ] = content ;
125+
126+ return {
127+ sourcePath : normalizedSourcePath ,
128+ sourceFilesJson : JSON . stringify ( sourceFiles ) ,
129+ } ;
130+ }
131+
8132/**
9133 * Initialize the WASM module by dynamically importing it.
10134 * The jco-transpiled component handles WASM loading internally.
@@ -68,9 +192,10 @@ export async function isWitFileExtensionFromWasm(filename: string): Promise<bool
68192 * @param content - The WIT content to validate
69193 * @returns Promise that resolves to true if the syntax is valid
70194 */
71- export async function validateWitSyntaxFromWasm ( content : string ) : Promise < boolean > {
195+ export async function validateWitSyntaxFromWasm ( content : string , sourcePath ?: string ) : Promise < boolean > {
72196 const api = await getApi ( ) ;
73- return api . validateWitSyntax ( content ) ;
197+ const preparedSource = await prepareSourceContext ( content , sourcePath ) ;
198+ return api . validateWitSyntax ( content , preparedSource . sourcePath , preparedSource . sourceFilesJson ) ;
74199}
75200
76201/**
@@ -122,11 +247,19 @@ export async function extractInterfacesFromWasm(content: string): Promise<string
122247export async function generateBindingsFromWasm (
123248 content : string ,
124249 language : string ,
125- worldName ?: string
250+ worldName ?: string ,
251+ sourcePath ?: string
126252) : Promise < Record < string , string > > {
127253 const api = await getApi ( ) ;
128- const jsonResult = api . generateBindings ( content , language , worldName ) ;
129- return JSON . parse ( jsonResult ) ;
254+ const preparedSource = await prepareSourceContext ( content , sourcePath ) ;
255+ const jsonResult = api . generateBindings (
256+ content ,
257+ language ,
258+ worldName ,
259+ preparedSource . sourcePath ,
260+ preparedSource . sourceFilesJson
261+ ) ;
262+ return JSON . parse ( jsonResult ) as Record < string , string > ;
130263}
131264
132265/**
@@ -143,8 +276,16 @@ export interface WitValidationResult {
143276 * @param content - The WIT content to validate
144277 * @returns Promise that resolves to detailed validation results
145278 */
146- export async function validateWitSyntaxDetailedFromWasm ( content : string ) : Promise < WitValidationResult > {
279+ export async function validateWitSyntaxDetailedFromWasm (
280+ content : string ,
281+ sourcePath ?: string
282+ ) : Promise < WitValidationResult > {
147283 const api = await getApi ( ) ;
148- const resultJson = api . validateWitSyntaxDetailed ( content ) ;
149- return JSON . parse ( resultJson ) ;
284+ const preparedSource = await prepareSourceContext ( content , sourcePath ) ;
285+ const resultJson = api . validateWitSyntaxDetailed (
286+ content ,
287+ preparedSource . sourcePath ,
288+ preparedSource . sourceFilesJson
289+ ) ;
290+ return JSON . parse ( resultJson ) as WitValidationResult ;
150291}
0 commit comments