Skip to content

fix(cli): create output directories for multi-file --out - #2974

Open
schani wants to merge 6 commits into
masterfrom
agent/fix-issue-2491
Open

fix(cli): create output directories for multi-file --out#2974
schani wants to merge 6 commits into
masterfrom
agent/fix-issue-2491

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

The bug

--out with multi-file output (e.g. generating Java) silently scattered generated files into the parent of the given output directory instead of writing them inside it, and using a nested, not-yet-existing output directory crashed with ENOENT.

Repro:

node dist/index.js --src ./src/types.ts --src-lang typescript --lang java --out ./dist
  • ./dist (an existing directory) ended up empty; Address.java, Person.java, Converter.java were written into the current working directory instead.
node dist/index.js --src ./src/types.ts --src-lang typescript --lang java --out ./dist/newsub/library
  • Crashed with Error: ENOENT: no such file or directory, open 'dist/newsub/Converter.java' because the directory didn't exist yet.

Root cause

writeOutput() in src/index.ts always computed the output path as path.join(path.dirname(cliOptions.out), filename), treating --out purely as a file path even when quicktype produced multiple files (e.g. one file per class for Java). It also never created missing directories before writing.

The fix

In writeOutput():

  • When there is more than one generated file, --out is now treated as a directory: each file is written to path.join(cliOptions.out, filename).
  • When there is exactly one generated file, --out keeps its existing meaning as an exact file path (so --out ./dist/index.ts for single-file TypeScript output is unaffected).
  • In both cases, the parent directory of the resolved output path is created with fs.mkdirSync(..., { recursive: true }) before writing, so missing nested directories (e.g. --out ./dist/newsub/library) are created automatically instead of crashing.

This matches the scope of the maintainer triage note on #2491: only the two confirmed CLI defects (scattering into the parent dir, and missing directories not being created) are fixed here. Unsupported/by-design behavior called out in the same triage (Java --package directory structure, TypeScript multi-file output) is intentionally left untouched.

Test coverage

Added test/unit/output-directory.test.ts, which drives writeOutput() directly against real temp directories (via fs.mkdtempSync) and covers:

  • Multiple files are written inside an existing output directory (not its parent).
  • Multiple files are written into a missing, deeply nested output directory (created automatically).
  • A single file is still written to the exact --out path, with its parent directory created if missing.

This is a unit test rather than an end-to-end JSON/JSON-Schema fixture because the bug is about where writeOutput() places files on disk (a CLI filesystem side effect), not about the content of generated code, which the fixture harness verifies via round-tripped JSON. This matches the repo's documented guidance that test/unit/ is the right place for what the fixture harness can't express.

Confirmed the added tests fail on the pre-fix code (ENOENT/scattered files) and pass after the fix.

Verification

  • npm run build passes.
  • npm run test:unit — all 166 unit tests pass (including the 3 new ones).
  • Manually re-ran the original repro commands against the built CLI:
    • --out ./dist (existing dir) now writes all Java files inside dist/, not the cwd.
    • --out ./dist/newsub/library (missing nested dir) now creates the directory and writes the files, instead of crashing.
    • --out ./dist/index.ts (single-file TypeScript) is unaffected — still writes exactly to that path.
  • Did not run the Java end-to-end fixture (FIXTURE=java) locally — this environment has a JRE but no javac/JDK, so Java driver-program compilation isn't possible here. CI will validate the Java (and other multi-file-capable language) fixtures.

Fixes #2491.

🤖 Generated with Claude Code

schani and others added 6 commits July 20, 2026 16:43
Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
The previous multi-file rewrite always treated `--out` as a directory and
joined each generated filename under it. For languages that pass a
representative file path (e.g. Java's `src/main/java/io/quicktype/TopLevel.java`
or cjson's `TopLevel.h`) while emitting several files, this nested every file
inside a bogus directory named after the file, breaking the java and cjson
fixtures (`EEXIST mkdir` / missing `.h`/`.c`).

`--out` is now resolved to a directory only when it actually names one — an
existing directory, a trailing-separator path, or an extension-less path.
Otherwise it is treated as a representative file path and the files are written
into its parent directory, matching the long-standing convention. The
directory-target behavior that fixes #2491 (writing into an existing/nested
`--out` directory instead of scattering into the parent) is preserved.

Co-Authored-By: Claude <noreply@anthropic.com>
writeOutput's single-file branch wrote content straight to the literal
--out path, ignoring the filename the renderer actually produced. For
targets like Java, where a top-level primitive schema emits only a
Converter.java file (no TopLevel class), this clobbered the --out path
with the wrong content and broke compilation. Restore joining --out's
directory with the renderer's filename for every file, matching the
pre-existing behavior, while keeping the new directory-creation and
multi-file directory resolution from this PR.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
test/unit/cjson-enum-default.test.ts still expected cJSON enum members in
alphabetical order, but quicktype now preserves JSON Schema enum declaration
order everywhere (see test/unit/enum-order.test.ts). The test was stale
against current master (fails there independent of this PR) and blocked this
branch's merge-into-master CI after picking up master's latest history.
Update the expected block to match declaration order; the "values start at
1" behavior under test is unaffected.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
@github-actions

Copy link
Copy Markdown

No generated-output differences

✅ Generated outputs are unchanged between the PR base and head revisions.

@github-actions

Copy link
Copy Markdown

No generated-output differences

✅ Generated outputs are unchanged between the PR base and head revisions.

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.

Directory multiple file input src and multiple file out differences

1 participant