Skip to content

Implementing a CWL exporter for tool-spec - #2

Open
mmaelicke wants to merge 2 commits into
mainfrom
2025-11-21-v9kg-Um52D
Open

Implementing a CWL exporter for tool-spec#2
mmaelicke wants to merge 2 commits into
mainfrom
2025-11-21-v9kg-Um52D

Conversation

@mmaelicke

@mmaelicke mmaelicke commented Nov 21, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added CWL export format (v1.2 & v1.1) with preview and download.
    • Introduced CWL configuration (outputs list, version, base command, container image).
    • Added CWL export UI: version selector, manage outputs (add/edit/remove), base command and container inputs, and export preview.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Nov 21, 2025

Copy link
Copy Markdown

Walkthrough

Adds a new CWL exporter and supporting types, registers it in the export registry, and extends the export UI/page to configure and invoke CWL exports (version, outputs, base command, container). Implements CWL v1.2/v1.1 YAML generation, validation, and download behavior.

Changes

Cohort / File(s) Summary
CWL Exporter Implementation
src/lib/exporters/cwl-exporter.ts
New CwlExporter class (extends BaseExporter): public props (format, displayName, description, mimeType, fileExtension); export, validate, download methods; many private helpers to build inputs/outputs, map types, generate command sequence (inputs.json + Docker), and produce CWL YAML with v1.2/v1.1 handling.
Export Configuration Types
src/lib/unified-metadata.ts
Added CwlOutput and CwlExportConfig interfaces (cwlVersion, outputs, baseCommand, container).
Registry Integration
src/lib/exporters/export-registry.ts
Imported CwlExporter and added a CWL entry to the export formats registry (id cwl, display name, description, icon, exporter instance).
UI and State Management
src/routes/+page.svelte
Added CWL-specific UI/state (cwlOutputs, cwlVersion, cwlBaseCommand, cwlContainer); wired preview and download flows to pass CwlExportConfig to exporter; added UI controls for version, outputs list (add/remove/edit name/type/glob/label/doc), base command, and container.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as +page.svelte
    participant Exporter as CwlExporter
    participant YAML as "CWL YAML"

    User->>UI: select "CWL" and configure (version, outputs, base, container)
    User->>UI: trigger export/preview
    UI->>Exporter: export(metadata, cwlConfig)
    Exporter->>Exporter: buildInputs(metadata)
    Exporter->>Exporter: buildOutputs(cwlConfig.outputs)
    Exporter->>Exporter: generateCommandSequence(metadata, cwlConfig, container)
    Exporter->>Exporter: assemble CWL doc (id,label,doc,inputs,outputs,baseCommand,requirements)
    Exporter->>YAML: dump YAML string
    YAML-->>UI: return YAML
    UI->>User: show preview or download file
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • src/lib/exporters/cwl-exporter.ts — correctness of CWL v1.2 vs v1.1 metadata, type mappings, InputBinding and InitialWorkDirRequirement generation, and Docker command construction.
    • src/routes/+page.svelte — UI state consistency and correct propagation of CwlExportConfig into preview/download flows.
    • src/lib/unified-metadata.ts — types align with exporter expectations and runtime usage.
    • src/lib/exporters/export-registry.ts — ensure registry entry initialization order and icon/reference correctness.

Poem

🐰 I nibble specs and spin a spool,
CWL strings stitched into a tool.
YAML leaves in tidy rows,
Docker hums where workflow goes.
Hop, export — portable and cool!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implementing a CWL exporter for tool-spec' accurately describes the main change: adding a new CWL (Common Workflow Language) exporter class with supporting configuration and UI integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-11-21-v9kg-Um52D

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/routes/+page.svelte (1)

611-757: CWL configuration UI is clear; consider minimal validation feedback

The CWL block (version selector, outputs, base command, container) matches the config shape and mirrors the Galaxy UX nicely. Since the exporter enforces required name/type/glob fields, you might optionally add lightweight inline validation indicators (e.g., mark missing required fields before export) to avoid users discovering issues only via runtime errors, but this is non-blocking.

src/lib/exporters/cwl-exporter.ts (2)

383-421: Validation logic is reasonable; consider surfacing warnings in UI

The validate method correctly enforces “at least one input or output” and checks required fields on outputs, while treating missing outputs as a warning via console.warn. The core checks are appropriate.

If you later wire validation into the UI, consider returning structured warnings alongside errors instead of relying solely on console.warn, so the Svelte layer can display them explicitly. Not required for this PR.


229-252: generateInputBinding is currently unused

generateInputBinding isn’t referenced anywhere, and you explicitly avoid inputBinding for tool-spec tools. Unless you plan to use it in the near term, you could remove it or add a short comment indicating it’s reserved for future non–tool-spec exporters to keep the class lean.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ef6109 and c7586fb.

📒 Files selected for processing (4)
  • src/lib/exporters/cwl-exporter.ts (1 hunks)
  • src/lib/exporters/export-registry.ts (2 hunks)
  • src/lib/unified-metadata.ts (1 hunks)
  • src/routes/+page.svelte (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/lib/exporters/export-registry.ts (1)
src/lib/exporters/cwl-exporter.ts (1)
  • CwlExporter (8-439)
src/lib/exporters/cwl-exporter.ts (1)
src/lib/unified-metadata.ts (3)
  • UnifiedSoftwareMetadata (106-139)
  • CwlExportConfig (99-104)
  • ToolSpecParameter (39-48)
🔇 Additional comments (8)
src/lib/exporters/export-registry.ts (1)

5-30: CWL exporter registration looks consistent and non-disruptive

The new CwlExporter import and cwl entry integrate cleanly with the existing registry: unique id, descriptive name/description, and use of a dedicated exporter instance, without impacting the helper functions that consume exportFormats.

src/lib/unified-metadata.ts (1)

91-105: CWL config types align with exporter and UI usage

CwlOutput and CwlExportConfig match how the CWL exporter and Svelte page consume them (outputs array, version union, optional baseCommand/container). Allowing type as a union with string leaves room for advanced CWL types while covering common cases.

src/routes/+page.svelte (4)

6-7: CWL state and initialization mirror Galaxy behavior appropriately

The added CWL-specific state (cwlOutputs, cwlVersion, cwlBaseCommand, cwlContainer) and initialization from metadata.galaxyConfig / repository.fullName reuse the same patterns as Galaxy, giving sensible defaults while allowing override. This keeps the mental model consistent across formats.

Also applies to: 40-45, 68-77


138-167: Export handler correctly passes CWL config through exporter

The selectedExportFormat === 'cwl' branch builds a CwlExportConfig with version, outputs, and optional baseCommand/container, then forwards it to download. Container fallback to metadata.galaxyConfig?.container mirrors the defaulting in the exporter and Galaxy UI, so behavior should be predictable.


175-204: Preview path mirrors CWL export configuration

getCurrentExportData constructs the same CwlExportConfig as handleExport for the cwl branch. Keeping these in sync avoids “preview vs. downloaded file” discrepancies, which is especially important while iterating on CWL settings.


229-247: CWL output helpers are straightforward and type-safe

addCwlOutput, removeCwlOutput, and updateCwlOutput follow the same immutable update pattern used for Galaxy outputs, and keyof CwlOutput keeps field names aligned with the shared type. The default added output (File, *.out) provides a useful starting point.

src/lib/exporters/cwl-exporter.ts (2)

426-438: Download override follows existing pattern and looks correct

The download method simply threads the CWL config through export, wraps the result in a Blob, and triggers an anchor click. This aligns with typical browser download patterns and with how other exporters in the codebase are expected to behave.


15-48: CWL v1.2 support for $namespaces and schema.org metadata is confirmed

The CWL v1.2 specification explicitly allows implementation-extension metadata fields using namespace prefixes declared in $namespaces. Schema.org namespace usage is recommended in the spec and shown in examples. cwltool and other ecosystem tools (CWL Viewer, community documentation) accept and recommend s:author, s:license, and s:version metadata. cwltool treats these as non-execution extensions and will run the document successfully, though it may historically emit benign warnings about unrecognized extension fields.

Your CWL document construction—including the $namespaces and s: metadata fields—aligns with spec expectations and common runner behavior. The code is ready to proceed.

Comment on lines +145 to +177
private buildInputs(data: UnifiedSoftwareMetadata): Record<string, any> {
const inputs: Record<string, any> = {};

if (!data.toolSpec.parameters) {
return inputs;
}

// Tool-spec tools read inputs from inputs.json, not command-line arguments
// So we don't add inputBinding - inputs will be staged as files
// and a preprocessing step (user-provided or custom) will generate inputs.json
for (const [paramName, paramDef] of Object.entries(data.toolSpec.parameters)) {
const input: any = {
type: this.mapToolSpecTypeToCwl(paramDef),
label: paramName.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
};

if (paramDef.description) {
input.doc = paramDef.description;
}

// Note: No inputBinding - tool-spec doesn't use command-line args
// Inputs will be staged and used to generate inputs.json

// Add default value if available
if (paramDef.default !== undefined && paramDef.default !== null) {
input.default = paramDef.default;
}

inputs[paramName] = input;
}

return inputs;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Type mapping is reasonable; optional/array semantics for enums could be improved

The mapping from ToolSpecParameter to CWL types (stringstring, integerint, assetFile, etc.) is sensible, and handling of arrays via { type: 'array', items: ... } is fine for most cases.

Two potential refinements (non-blocking):

  1. Optional enums: for type: 'enum' you currently ignore paramDef.optional; CWL usually models nullable enums as a union like ['null', { type: 'enum', symbols: [...] }]. Right now, optional enums will appear required in the CWL layer.

  2. Optional arrays: paramDef.array && paramDef.optional yields items: ['null', 'string'] (or similar), which allows null elements rather than an optional parameter. The more canonical encoding would be something like ['null', { type: 'array', items: 'string' }].

These are spec-alignment details and don’t block initial adoption but could improve correctness with strict CWL validation.

Please confirm the preferred encoding for optional enum and array types with your CWL target environment before changing this, as implementations may differ slightly.

Also applies to: 182-224


🌐 Web query:

What is the recommended CWL v1.1/v1.2 representation for optional enum and optional array parameters in CommandLineTool inputs?

💡 Result:

Recommended approach: make the input type a union with "null" plus the enum/array schema (or use the shorthand ? where allowed). Examples:

  • Optional enum (explicit schema):
    type:

    • "null"
    • name: format_choices
      type: enum
      symbols: [auto, fasta, fastq]
      (equivalently you can use the nullable shorthand where appropriate: format_choices? ). [1][2]
  • Optional array (explicit schema):
    type:

    • "null"
    • type: array
      items: string
      (or use string[]? shorthand when the implementation accepts it). [3][1]

References:

  • CWL v1.2 CommandLineTool / CommandInputEnumSchema & CommandInputArraySchema. [1]
  • CWL user guide example showing nullable enum in inputs. [2]
  • CWL user guide section on array inputs (array and [] forms). [3]

:::


🏁 Script executed:

#!/bin/bash
# Find and display the mapToolSpecTypeToCwl function in cwl-exporter.ts
rg -n "mapToolSpecTypeToCwl" src/lib/exporters/cwl-exporter.ts -A 50 -B 2

Length of output: 2050


🏁 Script executed:

#!/bin/bash
# Also search for enum and array handling to understand current implementation
rg -n "type.*enum|type.*array" src/lib/exporters/cwl-exporter.ts -B 3 -A 3

Length of output: 280


🏁 Script executed:

#!/bin/bash
# Get lines 182-224 mentioned in review
sed -n '182,224p' src/lib/exporters/cwl-exporter.ts

Length of output: 807


Fix enum and array handling to match CWL spec for optional parameters

The current implementation has two bugs:

  1. Optional enums (line 198-202): Early return prevents optional check. Optional enums should be type: ['null', { type: 'enum', symbols: [...] }] but currently output { type: 'enum', symbols: [...] } (missing null union).

  2. Optional arrays (lines 211-220): Order of operations is wrong. When both optional and array are true, the code produces { type: 'array', items: ['null', 'string'] } (null at item level), but spec requires type: ['null', { type: 'array', items: 'string' }] (null at outer type level).

Fix approach:

  • For enums: Remove early return and let enum fall through to optional/array handling
  • For arrays: Apply array wrapping first, then wrap final result with null union if optional
🤖 Prompt for AI Agents
In src/lib/exporters/cwl-exporter.ts around lines 145 to 177, the enum and array
handling for optional parameters is incorrect: remove the early return for enum
types so enums fall through to the optional/array logic (so optional enums
become type: ['null', { type: 'enum', symbols: [...] }]), and change the
wrapping order for arrays so you first construct the array type ({ type:
'array', items: <innerType> }) and only then, if optional is true, wrap the
whole array type in a null-union (['null', <arrayType>]) instead of placing
'null' inside items. Ensure the same pattern applies when the inner type is an
enum or primitive.

Comment on lines +285 to +304
private generateCommandSequence(data: UnifiedSoftwareMetadata, config: CwlExportConfig, container?: string): string | undefined {
if (!container) {
return undefined;
}

const commands: string[] = [];

// Step 1: Generate inputs.json from CWL inputs
// We'll use a Python script that reads CWL input values
if (data.toolSpec.parameters && Object.keys(data.toolSpec.parameters).length > 0) {
commands.push(this.generateInputsJsonCommand(data));
} else {
commands.push('mkdir -p inputs && echo "{}" > inputs/inputs.json');
}

// Step 2: Run docker with proper mountpoints and RUN_TOOL
commands.push(this.generateDockerCommand(data, config, container));

return commands.join(' && ');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Docker command paths should rely on runtime.outdir rather than escaped $(pwd)

The docker run command is built as:

let dockerCmd = `docker run --rm -v "\$(pwd)/inputs:/data/in:ro" -v "\$(runtime.outdir)/outputs:/data/out:rw" -e RUN_TOOL=${toolName} ${container}`;

Issues:

  • \$(pwd) will be passed literally to sh -c (the backslash escapes the $ in the shell), so pwd is never executed and the host path becomes "$(pwd)/inputs", which is almost certainly invalid.
  • The same backslash before $(runtime.outdir) is unnecessary and may interfere with CWL expression interpolation, depending on the runner.

Given CWL already provides runtime.outdir, you can avoid $(pwd) entirely and lean on it for both mounts, e.g.:

- let dockerCmd = `docker run --rm -v "\$(pwd)/inputs:/data/in:ro" -v "\$(runtime.outdir)/outputs:/data/out:rw" -e RUN_TOOL=${toolName} ${container}`;
+ let dockerCmd =
+   'docker run --rm' +
+   ' -v "$(runtime.outdir)/inputs:/data/in:ro"' +
+   ' -v "$(runtime.outdir)/outputs:/data/out:rw"' +
+   ` -e RUN_TOOL=${toolName} ${container}`;

This relies on CWL to interpolate $(runtime.outdir) to the working directory path before the shell runs, and avoids shell-level $(...) substitution conflicts.

Please verify with your target CWL runner that $(runtime.outdir) is expanded as expected inside argument strings and that the resulting docker -v paths are valid.

Also applies to: 362-381


🌐 Web query:

Does CWL v1.1/v1.2 guarantee that `$(runtime.outdir)` used inside an `arguments` string is expanded before the command is executed, and is it standard practice to mount `$(runtime.outdir)/...` into nested docker containers?

💡 Result:

Short answers

  • Does CWL guarantee $(runtime.outdir) in arguments is expanded before the command runs? — Yes: CWL expressions (e.g. $(runtime.outdir)) are specified to be evaluated by the runtime and may be used in arguments; when running in Docker the platform is expected to rewrite runtime.outdir to a valid path inside the container. However the spec also permits implementations to do deferred/opaque resolution of runtime fields (i.e. provide an opaque string) so behaviour can vary by runner. [1][2]

  • Is it standard practice to mount $(runtime.outdir)/... into nested Docker containers? — No: CWL itself does not require or standardize mounting into nested containers. Making host-mounted directories visible to a container started from inside another container requires Docker-in-Docker or careful bind-propagation (rshared/rslave) and is an implementation/platform concern rather than a CWL-level guarantee. If you need nested-container access, arrange host-volume mounts or use DinD/appropriate bind-propagation on the runner. [1][3]

Sources

  • CWL CommandLineTool / expressions / runtime fields (v1.2). [1]
  • CWL user guide example showing arguments: ["-d", $(runtime.outdir)]. [2]
  • Docker bind-mount and bind-propagation notes (rshared/rslave) for making mounts visible to nested containers. [3]

Now let me inspect the actual code to verify the implementation details:

Based on the comprehensive web search results, I can now verify the technical concerns in the review comment. Let me now inspect the actual code to confirm the exact issue:

[/run_scripts]

I need to check if there are results from the script. Let me try a more specific approach:

[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Locate and display the cwl-exporter.ts file content around the mentioned lines
fd "cwl-exporter.ts" -type f --exec cat {} \; | head -400 | tail -120

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for generateDockerCommand or similar methods that construct docker run commands
rg -n "docker run" --type ts -A 3 -B 3 | head -80

Length of output: 1015


Now let me inspect the actual code around both line ranges mentioned:

[/run_scripts]

[/run_scripts]


🏁 Script executed:

#!/bin/bash
# View lines around 285-304 to see the first method
sed -n '280,310p' src/lib/exporters/cwl-exporter.ts

Length of output: 1028


🏁 Script executed:

#!/bin/bash
# View lines around 362-381 to see the second method and full context
sed -n '355,390p' src/lib/exporters/cwl-exporter.ts

Length of output: 1309


Let me verify if there are other instances of this problematic pattern:

[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Search for all instances of docker run with backslash escaping patterns
rg -n '\\\$\(' src/lib/exporters/cwl-exporter.ts

Length of output: 154


Fix Docker command escaping in generateDockerCommand method (line 370)

Verification confirms the backslash escaping breaks both shell and CWL expression evaluation:

  • CWL expressions are evaluated by the runtime and may be used in arguments; implementations must rewrite runtime.outdir to a valid path inside the container
  • Backslash-escaping \$(pwd) prevents shell substitution; the literal string $(pwd) is passed, not the current working directory
  • Backslash-escaping \$(runtime.outdir) interferes with CWL expression interpolation
  • CWL user guide examples show bare $(runtime.outdir) without escaping in arguments

Change line 370 from:

let dockerCmd = `docker run --rm -v "\$(pwd)/inputs:/data/in:ro" -v "\$(runtime.outdir)/outputs:/data/out:rw" -e RUN_TOOL=${toolName} ${container}`;

To:

let dockerCmd =
  'docker run --rm' +
  ' -v "$(runtime.outdir)/inputs:/data/in:ro"' +
  ' -v "$(runtime.outdir)/outputs:/data/out:rw"' +
  ` -e RUN_TOOL=${toolName} ${container}`;

This avoids shell-level substitution conflicts and allows CWL to properly interpolate runtime.outdir before command execution.

🤖 Prompt for AI Agents
In src/lib/exporters/cwl-exporter.ts around lines 285-304, the Docker command
built in generateDockerCommand incorrectly backslash-escapes $(pwd) and
$(runtime.outdir), preventing shell substitution and CWL expression
interpolation; update the command construction to stop escaping those
expressions and use CWL-friendly quoted expressions (e.g.,
"$(runtime.outdir)/...") for -v mounts, and build the string via concatenation
or template literals so the RUN_TOOL env var is injected normally (no
backslashes) — replace the existing escaped string at the dockerCmd assignment
(near line 370) with a concatenated/quoted form that uses
"$(runtime.outdir)/inputs:/data/in:ro" and
"$(runtime.outdir)/outputs:/data/out:rw" and `-e RUN_TOOL=${toolName}
${container}` so CWL can interpolate runtime.outdir at execution time.

Comment thread src/lib/exporters/cwl-exporter.ts
Comment thread src/lib/exporters/cwl-exporter.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/lib/exporters/cwl-exporter.ts (3)

182-224: [Duplicate] Enum and array type handling bugs remain unfixed from previous review

The two bugs flagged in the previous review are still present:

  1. Optional enums (lines 198-202): The early return prevents optional enums from being wrapped with null. Optional enums should produce ['null', { type: 'enum', symbols: [...] }] but currently produce { type: 'enum', symbols: [...] }.

  2. Optional arrays (lines 210-221): When both optional and array are true, line 212 creates baseType = ['null', 'string'], then lines 216-221 return { type: 'array', items: ['null', 'string'] }. This allows null array elements rather than an optional array parameter. The CWL spec requires ['null', { type: 'array', items: 'string' }] (null at outer type level).

Fix approach:

  • Remove the early return for enums (make it set baseType instead of returning)
  • Apply array wrapping first, then wrap the final result with null union if optional

Apply this fix:

 private mapToolSpecTypeToCwl(paramDef: ToolSpecParameter): any {
-	let baseType: string | string[];
+	let baseType: any;
 	
 	switch (paramDef.type) {
 		case 'string':
 			baseType = 'string';
 			break;
 		case 'integer':
 			baseType = 'int';
 			break;
 		case 'float':
 			baseType = 'float';
 			break;
 		case 'boolean':
 			baseType = 'boolean';
 			break;
 		case 'enum':
-			return {
+			baseType = {
 				type: 'enum',
 				symbols: paramDef.values || []
 			};
-		case 'asset':
+			break;
+		case 'asset':
 			baseType = 'File'; // Default to File, could be Directory
 			break;
 		default:
 			baseType = 'string';
 	}
 	
-	// Handle optional (nullable)
-	if (paramDef.optional) {
-		baseType = ['null', baseType as string];
-	}
-	
 	// Handle array
 	if (paramDef.array) {
-		return {
+		baseType = {
 			type: 'array',
 			items: baseType
 		};
 	}
 	
+	// Handle optional (nullable) - must be after array handling
+	if (paramDef.optional) {
+		baseType = ['null', baseType];
+	}
+	
 	return baseType;
 }

312-345: [Duplicate] Shell-based JSON generation remains unsafe for special characters

The bug flagged in the previous review is still present. Line 323 escapes parameter names, but CWL substitutes $(inputs.paramName) with actual parameter values before the shell command executes. If a value contains quotes, backslashes, or newlines, the resulting JSON will be invalid.

Example with value hello"world:

printf '{\n  "param": "hello"world"\n}'  # Invalid JSON - breaks at the quote

As discussed in the previous review thread, the portable solution is to use InitialWorkDirRequirement to create both the directory and the inputs.json file. CWL's expression language can safely construct the file content without shell escaping issues. Your current generateInputsJsonListing only creates an empty directory; it should also include a file entry for inputs/inputs.json with the content built using CWL expressions.

See the previous review thread for the detailed implementation suggested by the reviewer.


366-385: [Duplicate] Docker command escaping breaks CWL expression evaluation

The bug flagged in the previous review is still present at line 374. The backslash-escaped expressions \$(pwd) and \$(runtime.outdir) prevent proper evaluation:

  1. \$(pwd) prevents shell substitution - the literal string "$(pwd)/inputs" is passed to docker (not a valid path)
  2. \$(runtime.outdir) interferes with CWL expression interpolation

CWL expressions should be bare (without backslashes) so the CWL runtime can interpolate them before the shell executes the command. The previous review confirmed this with CWL documentation.

Apply this fix to line 374:

-	let dockerCmd = `docker run --rm -v "\$(pwd)/inputs:/data/in:ro" -v "\$(runtime.outdir)/outputs:/data/out:rw" -e RUN_TOOL=${toolName} ${container}`;
+	let dockerCmd =
+		'docker run --rm' +
+		' -v "$(runtime.outdir)/inputs:/data/in:ro"' +
+		' -v "$(runtime.outdir)/outputs:/data/out:rw"' +
+		` -e RUN_TOOL=${toolName} ${container}`;

This uses $(runtime.outdir) for both mounts (CWL provides the working directory) and removes the backslashes so CWL can properly interpolate the expression.

🧹 Nitpick comments (1)
src/lib/exporters/cwl-exporter.ts (1)

229-252: Consider removing unused generateInputBinding method

This method is never called in the codebase. The comment at line 165 explicitly states "No inputBinding - tool-spec doesn't use command-line args", confirming that input bindings are not used for tool-spec tools. Since the method serves no purpose and adds maintenance burden, consider removing it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7586fb and aa4ed24.

📒 Files selected for processing (1)
  • src/lib/exporters/cwl-exporter.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/lib/exporters/cwl-exporter.ts (1)
src/lib/unified-metadata.ts (3)
  • UnifiedSoftwareMetadata (106-139)
  • CwlExportConfig (99-104)
  • ToolSpecParameter (39-48)
🔇 Additional comments (2)
src/lib/exporters/cwl-exporter.ts (2)

351-360: InitialWorkDirRequirement syntax fixed, but consider expanding to create JSON file

The syntax issue from the previous review has been fixed - the code now correctly uses CWL spec properties (class, basename, listing, writable) instead of the invalid entry/entryname keys.

However, as discussed in the previous review thread, this method could be expanded to create the inputs.json file directly using CWL expressions, which would eliminate the shell-based JSON generation vulnerability at lines 312-345. The reviewer provided a detailed implementation in the previous thread showing how to add a file entry to the listing with JSON content built from CWL expressions like $(inputs.paramName).

Based on learnings from the previous review.


430-442: LGTM - Download implementation is correct

The download method properly exports the CWL content, creates a blob with the correct MIME type, and triggers a browser download with a sanitized filename. The implementation follows standard patterns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant