diff --git a/dist/dicom/test/app.js b/dist/dicom/dist/demo-app.js similarity index 98% rename from dist/dicom/test/app.js rename to dist/dicom/dist/demo-app.js index 4387ccae4..495d323ea 100644 --- a/dist/dicom/test/app.js +++ b/dist/dicom/dist/demo-app.js @@ -59,6 +59,7 @@ const demoAppMachine = XState.createMachine({ onError: { actions: [ (c, event) => { + console.log(event) const message = `Could not process file: ${event.data.toString()}` console.error(message) alert(message) diff --git a/dist/dicom/index.html b/dist/dicom/index.html index 6784b2979..ab3c5afe8 100644 --- a/dist/dicom/index.html +++ b/dist/dicom/index.html @@ -38,6 +38,6 @@ - + diff --git a/dist/dicom/package.json b/dist/dicom/package.json index f1c93065e..05a36ae9a 100644 --- a/dist/dicom/package.json +++ b/dist/dicom/package.json @@ -3,25 +3,29 @@ "version": "0.0.1", "description": "DICOM IO from itk-wasm", "type": "module", - "main": "./dist/itk-dicom.umd.cjs", "module": "./dist/itk-dicom.js", "types": "./dist/index.d.ts", "exports": { ".": { - "import": "./dist/itk-dicom.js", - "require": "./dist/itk-dicom.umd.cjs" + "browser": "./dist/itk-dicom.js", + "node": "./dist/itk-dicom.node.js", + "umd": "./dist/itk-dicom.umd.js", + "default": "./dist/itk-dicom.js" } }, "scripts": { "dev": "vite --port 8173", - "build": "tsc && vite build --sourcemap", - "build:watch": "tsc && vite build --minify false --mode development --watch", + "build": "npm run build:browser && npm run build:node", + "build:node": "rollup -c ./rollup.node.config.js", + "build:browser": "tsc && vite build --sourcemap", + "build:browser:watch": "tsc && vite build --minify false --mode development --watch", "cypress:open": "npx cypress open", "cypress:run": "npx cypress run", "cypress:runChrome": "npx cypress run --browser chrome", "cypress:runFirefox": "npx cypress run --browser firefox", "test:debug": "start-server-and-test dev http-get://localhost:8173 cypress:open", - "test": "start-server-and-test dev http-get://localhost:8173 cypress:run", + "test": "npm run test:node && start-server-and-test dev http-get://localhost:8173 cypress:run", + "test:node": "ava", "test:chrome": "start-server-and-test dev http-get://localhost:8173 cypress:runChrome", "test:firefox": "start-server-and-test dev http-get://localhost:8173 cypress:runFirefox" }, @@ -40,10 +44,16 @@ }, "homepage": "https://wasm.itk.org", "dependencies": { - "itk-wasm": "^1.0.0-b.21" + "itk-wasm": "^1.0.0-b.27" }, "devDependencies": { + "@rollup/plugin-commonjs": "^22.0.2", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^14.0.1", + "@rollup/plugin-typescript": "^8.5.0", + "ava": "^4.3.3", "cypress": "^10.7.0", + "rollup": "^2.79.0", "rollup-plugin-copy": "^3.4.0", "start-server-and-test": "^1.14.0", "typescript": "^4.7.4", diff --git a/dist/dicom/rollup.node.config.js b/dist/dicom/rollup.node.config.js new file mode 100644 index 000000000..0a60a2841 --- /dev/null +++ b/dist/dicom/rollup.node.config.js @@ -0,0 +1,28 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve' +import typescript from '@rollup/plugin-typescript' +import commonjs from '@rollup/plugin-commonjs' +import json from '@rollup/plugin-json' +import { terser } from 'rollup-plugin-terser' + +export default { + input: './src/indexNode.ts', + output: [ + { + file: './dist/itk-dicom.node.js', + format: 'es', + sourcemap: true, + plugins: [terser(),], + }, + ], + plugins: [ + commonjs({ + transformMixedEsModules: true + }), + nodeResolve({ + preferBuiltins: true, + browser: false, + }), + typescript(), + json(), + ], +} diff --git a/dist/dicom/src/StructuredReportToTextNodeResult.ts b/dist/dicom/src/StructuredReportToTextNodeResult.ts new file mode 100644 index 000000000..29c3bb283 --- /dev/null +++ b/dist/dicom/src/StructuredReportToTextNodeResult.ts @@ -0,0 +1,7 @@ +interface StructuredReportToTextNodeResult { + /** Output text file */ + outputText: string + +} + +export default StructuredReportToTextNodeResult diff --git a/dist/dicom/src/indexNode.ts b/dist/dicom/src/indexNode.ts new file mode 100644 index 000000000..748c19882 --- /dev/null +++ b/dist/dicom/src/indexNode.ts @@ -0,0 +1,10 @@ + + +import StructuredReportToTextNodeResult from './StructuredReportToTextNodeResult.js' +export type { StructuredReportToTextNodeResult } + +import StructuredReportToTextOptions from './StructuredReportToTextOptions.js' +export type { StructuredReportToTextOptions } + +import structuredReportToTextNode from './structuredReportToTextNode.js' +export { structuredReportToTextNode } diff --git a/dist/dicom/src/structuredReportToText.ts b/dist/dicom/src/structuredReportToText.ts index 7db807b94..1b7281023 100644 --- a/dist/dicom/src/structuredReportToText.ts +++ b/dist/dicom/src/structuredReportToText.ts @@ -9,7 +9,9 @@ import StructuredReportToTextResult from './StructuredReportToTextResult.js' /** * Read a DICOM structured report file and generate a plain text representation + * * @param {Uint8Array} dicomFile - Input DICOM file + * * @returns {Promise} - result object */ async function structuredReportToText( diff --git a/dist/dicom/src/structuredReportToTextNode.ts b/dist/dicom/src/structuredReportToTextNode.ts new file mode 100644 index 000000000..bf9d35a6e --- /dev/null +++ b/dist/dicom/src/structuredReportToTextNode.ts @@ -0,0 +1,107 @@ +import { + TextStream, + InterfaceTypes, + runPipelineNode +} from 'itk-wasm' + +import StructuredReportToTextOptions from './StructuredReportToTextOptions.js' +import StructuredReportToTextNodeResult from './StructuredReportToTextNodeResult.js' + + +import path from 'path' + +/** + * Read a DICOM structured report file and generate a plain text representation + * + * @param {Uint8Array} dicomFile - Input DICOM file + * + * @returns {Promise} - result object + */ +async function structuredReportToTextNode( dicomFile: Uint8Array, + options: StructuredReportToTextOptions = {}) + : Promise { + + const desiredOutputs = [ + { type: InterfaceTypes.TextStream }, + ] + const inputs = [ + { type: InterfaceTypes.BinaryFile, data: { data: dicomFile, path: "file0" } }, + ] + + const args = [] + // Inputs + args.push('file0') + // Outputs + args.push('0') + // Options + args.push('--memory-io') + if (options.unknownRelationship) { + args.push('--unknown-relationship') + } + if (options.invalidItemValue) { + args.push('--invalid-item-value') + } + if (options.ignoreConstraints) { + args.push('--ignore-constraints') + } + if (options.ignoreItemErrors) { + args.push('--ignore-item-errors') + } + if (options.skipInvalidItems) { + args.push('--skip-invalid-items') + } + if (options.noDocumentHeader) { + args.push('--no-document-header') + } + if (options.numberNestedItems) { + args.push('--number-nested-items') + } + if (options.shortenLongValues) { + args.push('--shorten-long-values') + } + if (options.printInstanceUid) { + args.push('--print-instance-uid') + } + if (options.printSopclassShort) { + args.push('--print-sopclass-short') + } + if (options.printSopclassLong) { + args.push('--print-sopclass-long') + } + if (options.printSopclassUid) { + args.push('--print-sopclass-uid') + } + if (options.printAllCodes) { + args.push('--print-all-codes') + } + if (options.printInvalidCodes) { + args.push('--print-invalid-codes') + } + if (options.printTemplateId) { + args.push('--print-template-id') + } + if (options.indicateEnhanced) { + args.push('--indicate-enhanced') + } + if (options.printColor) { + args.push('--print-color') + } + + const pipelinePath = path.join(path.dirname(import.meta.url.substring(7)), 'pipelines', 'structured-report-to-text') + + const { + returnValue, + stderr, + outputs + } = await runPipelineNode(pipelinePath, args, desiredOutputs, inputs) + if (returnValue !== 0) { + throw new Error(stderr) + } + + const result = { + outputText: (outputs[0].data as TextStream).data, + } + return result +} + +export default structuredReportToTextNode diff --git a/dist/dicom/test/node.js b/dist/dicom/test/node.js new file mode 100644 index 000000000..06a87965b --- /dev/null +++ b/dist/dicom/test/node.js @@ -0,0 +1,20 @@ +import fs from 'fs' +import test from 'ava' +import { structuredReportToTextNode } from '../dist/itk-dicom.node.js' + +test('structuredReportToText', async t => { + + const fileName = '88.33-comprehensive-SR.dcm' + const testFilePath = `../../build-emscripten/ExternalData/test/Input/${fileName}` + + const dicomFileBuffer = fs.readFileSync(testFilePath) + const dicomFile = new Uint8Array(dicomFileBuffer) + + const { outputText } = await structuredReportToTextNode(dicomFile) + + t.assert(outputText.includes('Comprehensive SR Document')) + + const { outputText: outputTextNoHeader } = await structuredReportToTextNode(dicomFile, { noDocumentHeader: true }) + t.assert(!outputTextNoHeader.includes('Comprehensive SR Document')) + t.assert(outputTextNoHeader.includes('Breast Imaging Report')) +}) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b05eb4643..0181cc291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "axios": "^0.23.0", "commander": "^9.4.0", "fs-extra": "^10.0.0", - "mime-types": "^2.1.33", + "mime-types": "^2.1.35", "promise-file-reader": "^1.0.3", "webworker-promise": "^0.4.2" }, @@ -59,7 +59,7 @@ "puppeteer": "^10.4.0", "readable-stream": "^3.6.0", "resolve-typescript-plugin": "^1.2.0", - "rollup": "^2.58.0", + "rollup": "^2.79.0", "rollup-plugin-terser": "^7.0.2", "semantic-release": "^19.0.2", "shx": "^0.3.4", diff --git a/package.json b/package.json index f27f5fe15..70fe9426e 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "puppeteer": "^10.4.0", "readable-stream": "^3.6.0", "resolve-typescript-plugin": "^1.2.0", - "rollup": "^2.58.0", + "rollup": "^2.79.0", "rollup-plugin-terser": "^7.0.2", "semantic-release": "^19.0.2", "shx": "^0.3.4", @@ -144,7 +144,7 @@ "axios": "^0.23.0", "commander": "^9.4.0", "fs-extra": "^10.0.0", - "mime-types": "^2.1.33", + "mime-types": "^2.1.35", "promise-file-reader": "^1.0.3", "webworker-promise": "^0.4.2" }, diff --git a/src/itk-wasm-cli.js b/src/itk-wasm-cli.js index 417b28d28..0d8520949 100755 --- a/src/itk-wasm-cli.js +++ b/src/itk-wasm-cli.js @@ -250,244 +250,275 @@ const interfaceJsonTypeToInterfaceType = new Map([ ['OUTPUT_POLYDATA', 'PolyData'], ]) +function typescriptBindings(srcOutputDir, buildDir, wasmBinaries, forNode=false) { + // index module + let indexContent = '' + const nodeText = forNode ? 'Node' : '' + + wasmBinaries.forEach((wasmBinaryName) => { + let wasmBinaryRelativePath = `${buildDir}/${wasmBinaryName}` + if (!fs.existsSync(wasmBinaryRelativePath)) { + wasmBinaryRelativePath = wasmBinaryName + } -function bindings(outputDir, wasmBinaries, options) { - const { buildDir, dockcrossScript } = processCommonOptions() + const parsedPath = path.parse(path.resolve(wasmBinaryRelativePath)) + const runPath = path.join(parsedPath.dir, parsedPath.name) + const runPipelineScriptPath = path.join(path.dirname(import.meta.url.substring(7)), 'interfaceJSONNode.js') + const runPipelineRun = spawnSync('node', [runPipelineScriptPath, runPath], { + env: process.env, + stdio: ['ignore', 'pipe', 'inherit'] + }) + if (runPipelineRun.status !== 0) { + console.error(runPipelineRun.error); + process.exit(runPipelineRun.status) + } + const interfaceJson = JSON.parse(runPipelineRun.stdout.toString()) - try { - fs.mkdirSync(outputDir, { recursive: true }) - } catch (err) { - if (err.code !== 'EEXIST') throw err - } + const moduleKebabCase = parsedPath.name + const moduleCamelCase = camelCase(parsedPath.name) + const modulePascalCase = `${moduleCamelCase[0].toUpperCase()}${moduleCamelCase.substring(1)}` - let srcOutputDir = outputDir - if (options.package) { - srcOutputDir = path.join(outputDir, 'src') - try { - fs.mkdirSync(srcOutputDir, { recursive: true }) - } catch (err) { - if (err.code !== 'EEXIST') throw err + // Result module + let resultContent = `interface ${modulePascalCase}${nodeText}Result {\n` + if (!forNode) { + resultContent += ` /** WebWorker used for computation */\n webWorker: Worker | null\n\n` } - } + interfaceJson.outputs.forEach((output) => { + if (!interfaceJsonTypeToTypeScriptType.has(output.type)) { - const language = options.language === undefined ? 'typescript' : options.language - switch (language) { - case 'typescript': { - // index module - let indexContent = '' - - wasmBinaries.forEach((wasmBinaryName) => { - let wasmBinaryRelativePath = `${buildDir}/${wasmBinaryName}` - if (!fs.existsSync(wasmBinaryRelativePath)) { - wasmBinaryRelativePath = wasmBinaryName + console.error(`Unexpected output type: ${output.type}`) + process.exit(1) + } + resultContent += ` /** ${output.description} */\n` + const outputType = interfaceJsonTypeToTypeScriptType.get(output.type) + resultContent += ` ${camelCase(output.name)}: ${outputType}\n\n` + }) + resultContent += `}\n\nexport default ${modulePascalCase}${nodeText}Result\n` + fs.writeFileSync(path.join(srcOutputDir, `${modulePascalCase}${nodeText}Result.ts`), resultContent) + indexContent += `\n\nimport ${modulePascalCase}${nodeText}Result from './${modulePascalCase}${nodeText}Result.js'\n` + indexContent += `export type { ${modulePascalCase}${nodeText}Result }\n\n` + + // Options module + const haveParameters = !!interfaceJson.parameters.length + if (haveParameters) { + let optionsContent = `interface ${modulePascalCase}Options {\n` + interfaceJson.parameters.forEach((parameter) => { + if (parameter.name === 'memory-io') { + // Internal + return } + if (!interfaceJsonTypeToTypeScriptType.has(parameter.type)) { - const parsedPath = path.parse(path.resolve(wasmBinaryRelativePath)) - const runPath = path.join(parsedPath.dir, parsedPath.name) - const runPipelineScriptPath = path.join(path.dirname(import.meta.url.substring(7)), 'interfaceJSONNode.js') - const runPipelineRun = spawnSync('node', [runPipelineScriptPath, runPath], { - env: process.env, - stdio: ['ignore', 'pipe', 'inherit'] - }) - if (runPipelineRun.status !== 0) { - console.error(runPipelineRun.error); - process.exit(runPipelineRun.status) + console.error(`Unexpected parameter type: ${parameter.type}`) + process.exit(1) } - const interfaceJson = JSON.parse(runPipelineRun.stdout.toString()) + optionsContent += ` /** ${parameter.description} */\n` + const parameterType = interfaceJsonTypeToTypeScriptType.get(parameter.type) + optionsContent += ` ${camelCase(parameter.name)}?: ${parameterType}\n\n` + }) + optionsContent += `}\n\nexport default ${modulePascalCase}Options\n` + fs.writeFileSync(path.join(srcOutputDir, `${modulePascalCase}Options.ts`), optionsContent) - const moduleKebabCase = parsedPath.name - const moduleCamelCase = camelCase(parsedPath.name) - const modulePascalCase = `${moduleCamelCase[0].toUpperCase()}${moduleCamelCase.substring(1)}` + indexContent += `import ${modulePascalCase}Options from './${modulePascalCase}Options.js'\n` + indexContent += `export type { ${modulePascalCase}Options }\n\n` - // Result module - let resultContent = `interface ${modulePascalCase}Result {\n /** WebWorker used for computation */\n webWorker: Worker | null\n\n` - interfaceJson.outputs.forEach((output) => { - if (!interfaceJsonTypeToTypeScriptType.has(output.type)) { + } - console.error(`Unexpected output type: ${output.type}`) - process.exit(1) + // function module + let functionContent = 'import {\n' + const usedInterfaceTypes = new Set() + const pipelineComponents = ['inputs', 'outputs', 'parameters'] + pipelineComponents.forEach((pipelineComponent) => { + interfaceJson[pipelineComponent].forEach((value) => { + if (interfaceJsonTypeToInterfaceType.has(value.type)) { + const interfaceType = interfaceJsonTypeToInterfaceType.get(value.type) + if (!interfaceType.includes('File')) { + usedInterfaceTypes.add(interfaceType) } - resultContent += ` /** ${output.description} */\n` - const outputType = interfaceJsonTypeToTypeScriptType.get(output.type) - resultContent += ` ${camelCase(output.name)}: ${outputType}\n\n` - }) - resultContent += `}\n\nexport default ${modulePascalCase}Result\n` - fs.writeFileSync(path.join(srcOutputDir, `${modulePascalCase}Result.ts`), resultContent) - indexContent += `\n\nimport ${modulePascalCase}Result from './${modulePascalCase}Result.js'\n` - indexContent += `export type { ${modulePascalCase}Result }\n\n` - - // Options module - const haveParameters = !!interfaceJson.parameters.length - if (haveParameters) { - let optionsContent = `interface ${modulePascalCase}Options {\n` - interfaceJson.parameters.forEach((parameter) => { - if (parameter.name === 'memory-io') { - // Internal - return - } - if (!interfaceJsonTypeToTypeScriptType.has(parameter.type)) { - - console.error(`Unexpected parameter type: ${parameter.type}`) - process.exit(1) - } - optionsContent += ` /** ${parameter.description} */\n` - const parameterType = interfaceJsonTypeToTypeScriptType.get(parameter.type) - optionsContent += ` ${camelCase(parameter.name)}?: ${parameterType}\n\n` - }) - optionsContent += `}\n\nexport default ${modulePascalCase}Options\n` - fs.writeFileSync(path.join(srcOutputDir, `${modulePascalCase}Options.ts`), optionsContent) - - indexContent += `import ${modulePascalCase}Options from './${modulePascalCase}Options.js'\n` - indexContent += `export type { ${modulePascalCase}Options }\n\n` - } + }) + }) + usedInterfaceTypes.forEach((interfaceType) => { + functionContent += ` ${interfaceType},\n` + }) + functionContent += ` InterfaceTypes,\n` + if (forNode) { + functionContent += ` runPipelineNode\n` + } else { + functionContent += ` runPipeline\n` - // function module - let functionContent = 'import {\n' - const usedInterfaceTypes = new Set() - const pipelineComponents = ['inputs', 'outputs', 'parameters'] - pipelineComponents.forEach((pipelineComponent) => { - interfaceJson[pipelineComponent].forEach((value) => { - if (interfaceJsonTypeToInterfaceType.has(value.type)) { - const interfaceType = interfaceJsonTypeToInterfaceType.get(value.type) - if (!interfaceType.includes('File')) { - usedInterfaceTypes.add(interfaceType) - } - } - }) - }) - usedInterfaceTypes.forEach((interfaceType) => { - functionContent += ` ${interfaceType},\n` - }) - functionContent += ` InterfaceTypes,\n runPipeline\n} from 'itk-wasm'\n\n` - if (haveParameters) { - functionContent += `import ${modulePascalCase}Options from './${modulePascalCase}Options.js'\n` - } - functionContent += `import ${modulePascalCase}Result from './${modulePascalCase}Result.js'\n\n` + } + functionContent += `} from 'itk-wasm'\n\n` + if (haveParameters) { + functionContent += `import ${modulePascalCase}Options from './${modulePascalCase}Options.js'\n` + } + functionContent += `import ${modulePascalCase}${nodeText}Result from './${modulePascalCase}${nodeText}Result.js'\n\n` + if (forNode) { + functionContent += `\nimport path from 'path'\n\n` + } - functionContent += `/**\n * ${interfaceJson.description}\n` - interfaceJson.inputs.forEach((input) => { - if (!interfaceJsonTypeToTypeScriptType.has(input.type)) { + functionContent += `/**\n * ${interfaceJson.description}\n *\n` + interfaceJson.inputs.forEach((input) => { + if (!interfaceJsonTypeToTypeScriptType.has(input.type)) { - console.error(`Unexpected input type: ${input.type}`) - process.exit(1) - } - const typescriptType = interfaceJsonTypeToTypeScriptType.get(input.type) - functionContent += ` * @param {${typescriptType}} ${camelCase(input.name)} - ${input.description}\n` - }) - functionContent += ` * @returns {Promise<${modulePascalCase}Result>} - result object\n` - functionContent += ` */\n` - - functionContent += `async function ${moduleCamelCase}(\n webWorker: null | Worker,\n` - interfaceJson.inputs.forEach((input, index) => { - const typescriptType = interfaceJsonTypeToTypeScriptType.get(input.type) - const end = index === interfaceJson.inputs.length - 1 && !haveParameters ? `\n` : `,\n` - functionContent += ` ${camelCase(input.name)}: ${typescriptType}${end}` - }) - if (haveParameters) { - functionContent += ` options: ${modulePascalCase}Options = {})\n : Promise<${modulePascalCase}Result> {\n\n` + console.error(`Unexpected input type: ${input.type}`) + process.exit(1) + } + const typescriptType = interfaceJsonTypeToTypeScriptType.get(input.type) + functionContent += ` * @param {${typescriptType}} ${camelCase(input.name)} - ${input.description}\n` + }) + functionContent += ` *\n * @returns {Promise<${modulePascalCase}${nodeText}Result>} - result object\n` + functionContent += ` */\n` + + functionContent += `async function ${moduleCamelCase}${nodeText}(` + if (!forNode) { + functionContent += '\n webWorker: null | Worker,\n' + + } + interfaceJson.inputs.forEach((input, index) => { + const typescriptType = interfaceJsonTypeToTypeScriptType.get(input.type) + const end = index === interfaceJson.inputs.length - 1 && !haveParameters ? `\n` : `,\n` + functionContent += ` ${camelCase(input.name)}: ${typescriptType}${end}` + }) + if (haveParameters) { + functionContent += ` options: ${modulePascalCase}Options = {})\n : Promise<${modulePascalCase}${nodeText}Result> {\n\n` + + } else { + functionContent += `)\n : Promise<${modulePascalCase}${nodeText}Result> {\n\n` + } + functionContent += ` const desiredOutputs = [\n` + interfaceJson.outputs.forEach((output) => { + if (interfaceJsonTypeToInterfaceType.has(output.type)) { + const interfaceType = interfaceJsonTypeToInterfaceType.get(output.type) + functionContent += ` { type: InterfaceTypes.${interfaceType} },\n` + } + }) + functionContent += ` ]\n` + functionContent += ` const inputs = [\n` + interfaceJson.inputs.forEach((input, index) => { + if (interfaceJsonTypeToInterfaceType.has(input.type)) { + const interfaceType = interfaceJsonTypeToInterfaceType.get(input.type) + const camel = camelCase(input.name) + const data = interfaceType.includes('File') ? `{ data: ${camel}, path: "file${index.toString()}" } ` : camel + functionContent += ` { type: InterfaceTypes.${interfaceType}, data: ${data} },\n` + } + }) + functionContent += ` ]\n\n` + + let inputCount = 0 + functionContent += " const args = []\n" + functionContent += " // Inputs\n" + interfaceJson.inputs.forEach((input) => { + if (interfaceJsonTypeToInterfaceType.has(input.type)) { + const interfaceType = interfaceJsonTypeToInterfaceType.get(input.type) + const name = interfaceType.includes('File') ? `file${inputCount.toString()}` : inputCount.toString() + functionContent += ` args.push('${name}')\n` + inputCount++ + } else { + const camel = camelCase(input.name) + functionContent += ` args.push(${camel}.toString())\n` + } + }) + + let outputCount = 0 + functionContent += " // Outputs\n" + interfaceJson.outputs.forEach((output) => { + if (interfaceJsonTypeToInterfaceType.has(output.type)) { + const interfaceType = interfaceJsonTypeToInterfaceType.get(output.type) + const name = interfaceType.includes('File') ? `file${outputCount.toString()}` : outputCount.toString() + functionContent += ` args.push('${name}')\n` + outputCount++ + } else { + const camel = camelCase(output.name) + functionContent += ` args.push(${camel}.toString())\n` + } + }) + + functionContent += " // Options\n" + functionContent += " args.push('--memory-io')\n" + interfaceJson.parameters.forEach((parameter) => { + if (parameter.name === 'memory-io') { + // Internal + return + } + const camel = camelCase(parameter.name) + functionContent += ` if (options.${camel}) {\n` + if (parameter.type === "BOOL") { + functionContent += ` args.push('--${parameter.name}')\n` + } else { + if (interfaceJsonTypeToInterfaceType.has(parameter.type)) { + const interfaceType = interfaceJsonTypeToInterfaceType.get(parameter.type) + const name = interfaceType.includes('File') ? `file${inputCount.toString()}` : inputCount.toString() + functionContent += ` args.push('--${parameter.name}', '${name}')\n` + inputCount++ } else { - functionContent += `)\n : Promise<${modulePascalCase}Result> {\n\n` + functionContent += ` args.push('--${parameter.name}', options.${camel}.toString())\n` } + } + functionContent += ` }\n` + }) - functionContent += ` const desiredOutputs = [\n` - interfaceJson.outputs.forEach((output) => { - if (interfaceJsonTypeToInterfaceType.has(output.type)) { - const interfaceType = interfaceJsonTypeToInterfaceType.get(output.type) - functionContent += ` { type: InterfaceTypes.${interfaceType} },\n` - } - }) - functionContent += ` ]\n` - functionContent += ` const inputs = [\n` - interfaceJson.inputs.forEach((input, index) => { - if (interfaceJsonTypeToInterfaceType.has(input.type)) { - const interfaceType = interfaceJsonTypeToInterfaceType.get(input.type) - const camel = camelCase(input.name) - const data = interfaceType.includes('File') ? `{ data: ${camel}, path: "file${index.toString()}" } ` : camel - functionContent += ` { type: InterfaceTypes.${interfaceType}, data: ${data} },\n` - } - }) - functionContent += ` ]\n\n` - - let inputCount = 0 - functionContent += " const args = []\n" - functionContent += " // Inputs\n" - interfaceJson.inputs.forEach((input) => { - if (interfaceJsonTypeToInterfaceType.has(input.type)) { - const interfaceType = interfaceJsonTypeToInterfaceType.get(input.type) - const name = interfaceType.includes('File') ? `file${inputCount.toString()}` : inputCount.toString() - functionContent += ` args.push('${name}')\n` - inputCount++ - } else { - const camel = camelCase(input.name) - functionContent += ` args.push(${camel}.toString())\n` - } - }) - - let outputCount = 0 - functionContent += " // Outputs\n" - interfaceJson.outputs.forEach((output) => { - if (interfaceJsonTypeToInterfaceType.has(output.type)) { - const interfaceType = interfaceJsonTypeToInterfaceType.get(output.type) - const name = interfaceType.includes('File') ? `file${outputCount.toString()}` : outputCount.toString() - functionContent += ` args.push('${name}')\n` - outputCount++ - } else { - const camel = camelCase(output.name) - functionContent += ` args.push(${camel}.toString())\n` - } - }) - - functionContent += " // Options\n" - functionContent += " args.push('--memory-io')\n" - interfaceJson.parameters.forEach((parameter) => { - if (parameter.name === 'memory-io') { - // Internal - return - } - const camel = camelCase(parameter.name) - functionContent += ` if (options.${camel}) {\n` - if (parameter.type === "BOOL") { - functionContent += ` args.push('--${parameter.name}')\n` - } else { - if (interfaceJsonTypeToInterfaceType.has(parameter.type)) { - const interfaceType = interfaceJsonTypeToInterfaceType.get(parameter.type) - const name = interfaceType.includes('File') ? `file${inputCount.toString()}` : inputCount.toString() - functionContent += ` args.push('--${parameter.name}', '${name}')\n` - inputCount++ - } else { - functionContent += ` args.push('--${parameter.name}', options.${camel}.toString())\n` - } - } - functionContent += ` }\n` - }) - - functionContent += `\n const pipelinePath = '${moduleKebabCase}'\n\n` - - functionContent += ` const {\n webWorker: usedWebWorker,\n returnValue,\n stderr,\n outputs\n } = await runPipeline(webWorker, pipelinePath, args, desiredOutputs, inputs)\n` - functionContent += ' if (returnValue !== 0) {\n throw new Error(stderr)\n }\n\n' - - functionContent += ' const result = {\n webWorker: usedWebWorker as Worker,\n' - interfaceJson.outputs.forEach((output, index) => { - const camel = camelCase(output.name) - const interfaceType = interfaceJsonTypeToInterfaceType.get(output.type) - if (interfaceType.includes('Text') || interfaceType.includes('Binary')) { - functionContent += ` ${camel}: (outputs[${index.toString()}].data as ${interfaceType}).data,\n` - } else { - functionContent += ` ${camel}: outputs[${index.toString()}].data as ${interfaceType},\n` - } - }) - functionContent += ' }\n' - functionContent += ' return result\n' + if (forNode) { + functionContent += `\n const pipelinePath = path.join(path.dirname(import.meta.url.substring(7)), 'pipelines', '${moduleKebabCase}')\n\n` + functionContent += ` const {\n returnValue,\n stderr,\n outputs\n } = await runPipelineNode(pipelinePath, args, desiredOutputs, inputs)\n` + } else { + functionContent += `\n const pipelinePath = '${moduleKebabCase}'\n\n` + functionContent += ` const {\n webWorker: usedWebWorker,\n returnValue,\n stderr,\n outputs\n } = await runPipeline(webWorker, pipelinePath, args, desiredOutputs, inputs)\n` + } - functionContent += `}\n\nexport default ${moduleCamelCase}\n` - fs.writeFileSync(path.join(srcOutputDir, `${moduleCamelCase}.ts`), functionContent) - indexContent += `import ${moduleCamelCase} from './${moduleCamelCase}.js'\n` - indexContent += `export { ${moduleCamelCase} }\n` + functionContent += ' if (returnValue !== 0) {\n throw new Error(stderr)\n }\n\n' - }) - fs.writeFileSync(path.join(srcOutputDir, 'index.ts'), indexContent) + functionContent += ' const result = {\n' + if (!forNode) { + functionContent += ' webWorker: usedWebWorker as Worker,\n' + } + interfaceJson.outputs.forEach((output, index) => { + const camel = camelCase(output.name) + const interfaceType = interfaceJsonTypeToInterfaceType.get(output.type) + if (interfaceType.includes('Text') || interfaceType.includes('Binary')) { + functionContent += ` ${camel}: (outputs[${index.toString()}].data as ${interfaceType}).data,\n` + } else { + functionContent += ` ${camel}: outputs[${index.toString()}].data as ${interfaceType},\n` + } + }) + functionContent += ' }\n' + functionContent += ' return result\n' + + functionContent += `}\n\nexport default ${moduleCamelCase}${nodeText}\n` + fs.writeFileSync(path.join(srcOutputDir, `${moduleCamelCase}${nodeText}.ts`), functionContent) + indexContent += `import ${moduleCamelCase}${nodeText} from './${moduleCamelCase}${nodeText}.js'\n` + indexContent += `export { ${moduleCamelCase}${nodeText} }\n` + + }) + fs.writeFileSync(path.join(srcOutputDir, `index${nodeText}.ts`), indexContent) +} + + +function bindings(outputDir, wasmBinaries, options) { + const { buildDir } = processCommonOptions() + + try { + fs.mkdirSync(outputDir, { recursive: true }) + } catch (err) { + if (err.code !== 'EEXIST') throw err + } + + let srcOutputDir = outputDir + if (options.package) { + srcOutputDir = path.join(outputDir, 'src') + try { + fs.mkdirSync(srcOutputDir, { recursive: true }) + } catch (err) { + if (err.code !== 'EEXIST') throw err + } + } + + const language = options.language === undefined ? 'typescript' : options.language + switch (language) { + case 'typescript': { + typescriptBindings(srcOutputDir, buildDir, wasmBinaries, false) + typescriptBindings(srcOutputDir, buildDir, wasmBinaries, true) } break }