TypeScript 7 support and stub-resolution hardening (build-tsdoc 0.3.1)#44
TypeScript 7 support and stub-resolution hardening (build-tsdoc 0.3.1)#44amery wants to merge 5 commits into
Conversation
Type the defineBuildConfig result as BuildConfig[] and export the named binding, giving the default export an explicit type. Add a prepare step that runs the cross-test smoke over the built entry and falls back to dev:prepare when it is missing, with the @kagal/cross-test dependency it needs. Drop the @Kagal scope from the package name — it is a private example of build-tsdoc extraction, not a @Kagal package of its own. The name is workspace-internal, so the lockfile is unaffected. Set rootDir explicitly — src for the source build, the package root for the tools config — clearing the TypeScript 6.0 advisory that asks for it wherever outDir is set. Add a README describing the example: the consumer that runs extraction end to end on unbuild and the last JS-based compiler, the engine swap it exercises, and a snapshot of the toolchain versions. Signed-off-by: Alejandro Mery <[email protected]>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
📝 WalkthroughWalkthroughThe PR adds TypeScript 7 playground coverage, guards consumer compiler aliasing, derives declarations for stubbed bundled dependencies, adds ChangesBuild-tsdoc extraction flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Consumer
participant extractEntryManifest
participant TypeScript
participant redirectStubDependencies
participant api-extractor
Consumer->>extractEntryManifest: request API manifest extraction
extractEntryManifest->>TypeScript: resolve consumer compiler
extractEntryManifest->>redirectStubDependencies: inspect bundled dependency declarations
redirectStubDependencies-->>extractEntryManifest: return derived-declaration path mappings
extractEntryManifest->>api-extractor: prepare compiler and bundled package configuration
api-extractor-->>Consumer: produce API manifest
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/@kagal-build-tsdoc/src/extract.ts (2)
128-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
aeMain/bundledEntryresolution.The same three-line lookup (
requireHere.resolve('@microsoft/api-extractor')→createRequire(aeMain).resolve('typescript')) is repeated verbatim inpreferConsumerTypeScriptandloadAnalysisTypeScript. Extracting a single helper (e.g.resolveBundledTypeScriptEntry()) removes the duplication and the risk of the two call sites silently drifting apart later.♻️ Suggested helper extraction
+function resolveBundledTypeScriptEntry(): string { + const aeMain = requireHere.resolve('`@microsoft/api-extractor`'); + return createRequire(aeMain).resolve('typescript'); +} + function preferConsumerTypeScript(projectFolder: string): void { const consumerEntry = resolveFrom(projectFolder, 'typescript'); if (consumerEntry === undefined) { return; } - const aeMain = requireHere.resolve('`@microsoft/api-extractor`'); - const bundledEntry = createRequire(aeMain).resolve('typescript'); + const bundledEntry = resolveBundledTypeScriptEntry(); if (bundledEntry === consumerEntry) { return; } ... } function loadAnalysisTypeScript(): TypeScriptModule { - const aeMain = requireHere.resolve('`@microsoft/api-extractor`'); - const bundledEntry = createRequire(aeMain).resolve('typescript'); + const bundledEntry = resolveBundledTypeScriptEntry(); return requireHere(bundledEntry) as TypeScriptModule; }Also applies to: 166-169, 179-183
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/`@kagal-build-tsdoc/src/extract.ts around lines 128 - 129, Extract the duplicated `@microsoft/api-extractor` and bundled TypeScript resolution into a shared helper such as resolveBundledTypeScriptEntry(). Update preferConsumerTypeScript and loadAnalysisTypeScript to call that helper, removing their repeated aeMain/createRequire lookup while preserving the existing resolution behavior.
123-157: 🩺 Stability & Availability | 🔵 TrivialSticky process-global alias can leak across consumers in one process.
requireHere.cache[bundledEntry] = consumerModulemutates a process-wide module cache slot with no way to reset it. IfextractEntryManifestis ever invoked more than once in the same Node process for consumers pinned to different TypeScript engines (e.g. a monorepo script looping over packages), the first adoptable consumer's engine will stay aliased into api-extractor'stypescriptslot for every subsequent call — including one for a TS7 consumer that should fall back to the bundled compiler. The test suite works around exactly this ("own worker file: the engine is fixed at the first api-extractor load") but that only proves the hazard is real, not that production call sites are isolated the same way.Worth confirming callers of
extractEntryManifestalways run in a fresh process per consumer engine, or otherwise documenting/guarding this constraint.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/`@kagal-build-tsdoc/src/extract.ts around lines 123 - 157, Update the extractEntryManifest flow and preferConsumerTypeScript so the process-global TypeScript cache alias cannot leak between consumers. Preserve the original bundledEntry cache state before adopting a consumer compiler, and restore it after extraction (including fallback when no adoption occurs), or otherwise enforce/document a single-engine process constraint at the production entry point. Ensure later consumers can independently select their adoptable engine or the bundled compiler.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/playground-ts7/README.md`:
- Around line 65-68: Update the fenced error-log block in the README to declare
the text language, preserving the existing log contents.
In `@packages/`@kagal-build-tsdoc/src/redirect.ts:
- Around line 339-351: Update the diagnostic collection in the program emit flow
to use ts.getPreEmitDiagnostics(program), ensuring semantic diagnostics are
included alongside existing pre-emit checks. Keep the existing error filtering
and UnbuiltDependencyError construction unchanged.
---
Outside diff comments:
In `@packages/`@kagal-build-tsdoc/src/extract.ts:
- Around line 128-129: Extract the duplicated `@microsoft/api-extractor` and
bundled TypeScript resolution into a shared helper such as
resolveBundledTypeScriptEntry(). Update preferConsumerTypeScript and
loadAnalysisTypeScript to call that helper, removing their repeated
aeMain/createRequire lookup while preserving the existing resolution behavior.
- Around line 123-157: Update the extractEntryManifest flow and
preferConsumerTypeScript so the process-global TypeScript cache alias cannot
leak between consumers. Preserve the original bundledEntry cache state before
adopting a consumer compiler, and restore it after extraction (including
fallback when no adoption occurs), or otherwise enforce/document a single-engine
process constraint at the production entry point. Ensure later consumers can
independently select their adoptable engine or the bundled compiler.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4568d20e-6559-4e64-94de-989f6632c8c9
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
examples/playground-ts6/README.mdexamples/playground-ts6/build.config.tsexamples/playground-ts6/package.jsonexamples/playground-ts6/tsconfig.jsonexamples/playground-ts6/tsconfig.tools.jsonexamples/playground-ts7/README.mdexamples/playground-ts7/build.config.tsexamples/playground-ts7/package.jsonexamples/playground-ts7/src/index.tsexamples/playground-ts7/tsconfig.jsonexamples/playground-ts7/tsconfig.tools.jsonpackages/@kagal-build-tsdoc/CHANGELOG.mdpackages/@kagal-build-tsdoc/README.mdpackages/@kagal-build-tsdoc/package.jsonpackages/@kagal-build-tsdoc/src/__tests__/consumer-typescript-ts7.test.tspackages/@kagal-build-tsdoc/src/__tests__/fixtures/stub-re-export.tspackages/@kagal-build-tsdoc/src/__tests__/stub-re-export-bundled.test.tspackages/@kagal-build-tsdoc/src/__tests__/stub-re-export-consumer.test.tspackages/@kagal-build-tsdoc/src/errors.tspackages/@kagal-build-tsdoc/src/extract.tspackages/@kagal-build-tsdoc/src/index.tspackages/@kagal-build-tsdoc/src/redirect.ts
| const program = ts.createProgram(targets, options); | ||
| const emitted = program.emit(); | ||
| const failure = [ | ||
| ...program.getOptionsDiagnostics(), | ||
| ...program.getSyntacticDiagnostics(), | ||
| ...emitted.diagnostics, | ||
| ].find((d) => d.category === ts.DiagnosticCategory.Error); | ||
| if (failure !== undefined) { | ||
| throw new UnbuiltDependencyError( | ||
| dependency, | ||
| ts.flattenDiagnosticMessageText(failure.messageText, ' '), | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does ts.getPreEmitDiagnostics include semantic diagnostics, and does Program.emit() diagnostics exclude them, in TypeScript 5.9?
💡 Result:
Yes, ts.getPreEmitDiagnostics includes semantic diagnostics, and Program.emit diagnostics generally exclude them. ts.getPreEmitDiagnostics is an authoritative function in the TypeScript Compiler API designed to collect and return a comprehensive list of all diagnostics for a program [1]. This includes: - Config file parsing diagnostics [1][2] - Compiler options diagnostics [1][2] - Syntactic diagnostics [1][2] - Global diagnostics [1][2] - Semantic diagnostics [1][2] - Declaration diagnostics (if enabled) [1][2] In contrast, the diagnostics returned as part of the EmitResult object from Program.emit are specifically those generated during the emit process itself [3]. They do not include the full suite of semantic and syntactic errors that would have been identified prior to emit [4]. To obtain a complete view of all errors in a program, common practice involves concatenating the result of ts.getPreEmitDiagnostics(program) with the diagnostics returned from program.emit [5][6][7].
Citations:
- 1: https://github.com/microsoft/TypeScript/blob/0c2dea58c726ffee40b6caf833ff69ea5278aeb7/src/compiler/program.ts
- 2: Question: what is the difference between "diagnostics" and "preEmitDiagnostics?" dsherret/ts-morph#384
- 3: https://microsoft-typescript.mintlify.app/api/program
- 4: Emit diagnostics do not include semantic diagnostics dsherret/ts-morph#555
- 5: https://github.com/Microsoft/TypeScript-wiki/blob/main/Using-the-Compiler-API.md
- 6: https://github.com/microsoft/TypeScript-wiki/blob/756ece4f/Using-the-Compiler-API.md
- 7: https://github.com/microsoft/TypeScript-wiki/blob/master/Using-the-Compiler-API.md
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the relevant section of the file with line numbers.
sed -n '320,370p' packages/@kagal-build-tsdoc/src/redirect.ts | cat -n
# Find the TypeScript version(s) referenced by this package/workspace.
printf '\n--- package.json references ---\n'
rg -n '"typescript"|typescript@' -g 'package.json' -g 'pnpm-lock.yaml' -g 'yarn.lock' -g 'package-lock.json' .
# Show the package manifest for the module under review if present.
printf '\n--- package manifest ---\n'
cat packages/@kagal-build-tsdoc/package.json 2>/dev/null || trueRepository: kagal-dev/tsdoc
Length of output: 16881
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect any local code paths that already use pre-emit diagnostics.
rg -n "getPreEmitDiagnostics|getSyntacticDiagnostics|getOptionsDiagnostics|emit\(\)\.diagnostics" packages/@kagal-build-tsdoc/src
# Map the file structure around the reviewed module.
fd -H -t f . packages/@kagal-build-tsdoc/srcRepository: kagal-dev/tsdoc
Length of output: 1523
Include semantic diagnostics in this failure check
getOptionsDiagnostics() + getSyntacticDiagnostics() miss unresolved imports, undefined identifiers, and other semantic errors, so a broken stub can still emit declarations and pass the existsSync check. Use ts.getPreEmitDiagnostics(program) here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/`@kagal-build-tsdoc/src/redirect.ts around lines 339 - 351, Update
the diagnostic collection in the program emit flow to use
ts.getPreEmitDiagnostics(program), ensuring semantic diagnostics are included
alongside existing pre-emit checks. Keep the existing error filtering and
UnbuiltDependencyError construction unchanged.
Add examples/playground-ts7, a private workspace package that pins typescript ^7.0.0 over the same Point/distance/translate surface as playground-ts6. No @kagal/build-tsdoc hook is wired in, so it builds, stubs, and type-checks on its own. Build with obuild rather than unbuild, and set isolatedDeclarations to select rolldown-plugin-dts's oxc generator. TypeScript 7's main export is a version stub, not the compiler, so anything that imports typescript as a library crashes on it; the oxc generator is Rust and never loads it, emitting the declaration bundle where unbuild's rollup-plugin-dts dies. Carry no lint scripts. typescript-eslint reads the compiler off the same stub and has no TS7-safe path, so eslint cannot run here; the root ESLint exclusion of examples/* already keeps the repo-wide gate green. Add a README recording the toolchain wall and a snapshot of the pinned versions. Signed-off-by: Alejandro Mery <[email protected]>
Widen the typescript peer range to ^5.9.0 || ^6.0.0 || ^7.0.0, so a TypeScript 7 consumer is a supported consumer whose extraction runs on the bundled engine deliberately rather than by accident. Gate the consumer-compiler swap before it aliases: read the resolved typescript version and adopt only a compiler in the classic-API range (>=5.9 <7) that exposes createProgram and a version string. Anything else — including a TS7 version stub — is left to api-extractor's bundled compiler. This fixes the 0.3.0 crash where the ungated alias grafted a TS7 stub into api-extractor and died on ts.parseJsonConfigFileContent is not a function. Add a permanent row that resolves the ts7 example's typescript and asserts extraction falls back to the bundled engine, with the fixture's CONSUMER_TS7_ROOT / CONSUMER_TS7_VERSION beside the 6.x pair. Wire the extraction hook into examples/playground-ts7 so it exercises that fallback end to end: the obuild build now runs the hooks over the TS7 declarations and completes on the bundled engine. Signed-off-by: Alejandro Mery <[email protected]>
A bundled dependency left in its development-stub state ships a types entry that re-exports raw TypeScript source (as unbuild --stub writes). api-extractor can only analyse declarations, so following the stub aborted deep in its analyser with "Unable to determine semantic information". Before invoking api-extractor, derive real declarations from the stubbed source with the analysis compiler into node_modules/.cache/kagal-build-tsdoc/ and remap the dependency onto them through a tsconfig paths override extending the consumer's config, so the re-exported symbols are documented — TSDoc included — as if the dependency were built. Detect the stub by resolving what each re-export lands on, not by matching specifier patterns: modern unbuild stubs write the source specifier with a .js extension, so only resolution reveals it points at source. This covers the jiti-era .ts specifier, the modern .js-mapped specifier, and a types entry aimed straight at source. The mirror must replicate the dependency's layout — a canonical node_modules/<name>/ tail and a directory-targeted paths mapping — so the compiler stamps the derived files with the dependency's packageId and api-extractor attributes them to the package instead of dropping them as foreign. Force the emit options that would otherwise divert the declarations (declarationDir, outFile) or break under a sibling tsconfig (composite, incremental, noEmit). Add UnbuiltDependencyError — public and exported — for the one arm the redirect cannot rescue: stubbed source the compiler cannot compile. The message names the dependency to build first. Turn the former stub-explosion characterisation tests, which asserted the abort, into rows that assert the rescue on both the bundled and the adopted consumer engine, alongside rows for the modern .js-mapped stub, a types entry aimed at source, a dependency whose tsconfig sets declarationDir, the untouched built-dependency pass path, and the actionable error when the stubbed source does not parse. Signed-off-by: Alejandro Mery <[email protected]>
Bump @kagal/build-tsdoc to 0.3.1 and promote the existing [Unreleased] changelog entries under the new version with a 2026-07-13 release date, keeping an empty [Unreleased] placeholder on top. 0.3.1 is a patch: it adds TypeScript 7 to the supported peer range and stops two resolution-stub states — a TS7 consumer's version stub and a dependency's development stub — from crashing extraction. The CHANGELOG covers both. Once this PR is merged, tagging the resulting commit for 0.3.1 triggers publish.yml, which authenticates to npm via OIDC and publishes with Sigstore provenance. Signed-off-by: Alejandro Mery <[email protected]>
|
Force-pushed to fold the CodeRabbit review into the source commits — no
Base unchanged, full |
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/`@kagal-build-tsdoc/src/extract.ts:
- Around line 190-235: Update parseConsumerTsconfig to inspect the error field
returned by ts.readConfigFile before parsing config. Return undefined when an
error is present, while preserving the existing parsing and pathsBase behavior
for successfully read tsconfig files.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e9ea0f56-e2e0-497a-82f5-956430f65290
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
examples/playground-ts7/README.mdexamples/playground-ts7/build.config.tsexamples/playground-ts7/package.jsonexamples/playground-ts7/src/index.tsexamples/playground-ts7/tsconfig.jsonexamples/playground-ts7/tsconfig.tools.jsonpackages/@kagal-build-tsdoc/CHANGELOG.mdpackages/@kagal-build-tsdoc/README.mdpackages/@kagal-build-tsdoc/package.jsonpackages/@kagal-build-tsdoc/src/__tests__/consumer-typescript-ts7.test.tspackages/@kagal-build-tsdoc/src/__tests__/fixtures/stub-re-export.tspackages/@kagal-build-tsdoc/src/__tests__/stub-re-export-bundled.test.tspackages/@kagal-build-tsdoc/src/__tests__/stub-re-export-consumer.test.tspackages/@kagal-build-tsdoc/src/errors.tspackages/@kagal-build-tsdoc/src/extract.tspackages/@kagal-build-tsdoc/src/index.tspackages/@kagal-build-tsdoc/src/redirect.ts
| /** | ||
| * The compiler namespace analysis runs on: api-extractor's own | ||
| * `typescript` resolution, read *after* {@link loadAPIExtractor} | ||
| * has primed the cache — the consumer's adopted engine when the | ||
| * alias applied, the bundled compiler otherwise. The stub redirect | ||
| * derives declarations with this same engine so what it derives is | ||
| * what the analysis parses. | ||
| */ | ||
| function loadAnalysisTypeScript(): TypeScriptModule { | ||
| const bundledEntry = resolveBundledTypeScriptEntry(); | ||
| return requireHere(bundledEntry) as TypeScriptModule; | ||
| } | ||
|
|
||
| /** Consumer tsconfig, parsed to its effective compiler options. */ | ||
| interface ParsedTsconfig { | ||
| /** Effective options, extends chain applied. */ | ||
| options: TSModule.CompilerOptions | ||
| /** Base directory relative `paths` entries resolve against. */ | ||
| pathsBase: string | ||
| } | ||
|
|
||
| /** | ||
| * Parse the consumer's tsconfig with the analysis engine, for the | ||
| * stub redirect: detection resolves modules under the consumer's | ||
| * effective options, and any pre-existing `paths` must survive the | ||
| * override merge. `undefined` when the file cannot be read — | ||
| * api-extractor reports that on its own terms. | ||
| */ | ||
| function parseConsumerTsconfig( | ||
| ts: TypeScriptModule, | ||
| tsconfigPath: string, | ||
| ): ParsedTsconfig | undefined { | ||
| const { config } = | ||
| ts.readConfigFile(tsconfigPath, ts.sys.readFile) as | ||
| { config?: unknown }; | ||
| if (config === undefined) { | ||
| return undefined; | ||
| } | ||
| const basePath = path.dirname(tsconfigPath); | ||
| const parsed = ts.parseJsonConfigFileContent(config, ts.sys, basePath); | ||
| const pathsBase = parsed.options.pathsBasePath; | ||
| return { | ||
| options: parsed.options, | ||
| pathsBase: typeof pathsBase === 'string' ? pathsBase : basePath, | ||
| }; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
set -euo pipefail
printf '\n## extract.ts excerpt\n'
sed -n '160,260p' packages/@kagal-build-tsdoc/src/extract.ts
printf '\n## TypeScript version references\n'
rg -n '"typescript"|typescript@|from '\''typescript'\''' -g 'package.json' -g 'pnpm-lock.yaml' -g 'package-lock.json' -g 'yarn.lock' .
printf '\n## local declaration/source for readConfigFile if present\n'
rg -n "readConfigFile\\(" node_modules typescript . -g '*.d.ts' -g '*.ts' -g '*.js' 2>/dev/null | head -n 40Repository: kagal-dev/tsdoc
Length of output: 15912
🌐 Web query:
TypeScript readConfigFile unreadable file returns config error implementation parseConfigFileTextToJson
💡 Result:
In the TypeScript Compiler API, ts.readConfigFile and ts.parseConfigFileTextToJson handle file reading and parsing in distinct ways, and both require careful error checking to handle unreadable or malformed files [1][2]. ts.readConfigFile This function takes a file path and a readFile callback (typically ts.sys.readFile) [3][4]. If the file cannot be read or does not exist, the behavior depends on the provided callback, but generally, it returns an object containing an error property instead of throwing an exception [5][6]. The returned object has the signature: { config?: any; error?: Diagnostic } You must explicitly check for the presence of the error property before accessing the config property to avoid runtime errors [7][6]. ts.parseConfigFileTextToJson This function is a lower-level utility that parses raw JSON text into a TypeScript configuration object [1][2]. It does not perform any file I/O itself [2]. Its signature is: ts.parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } Similar to readConfigFile, this function returns an object containing either the parsed config or a Diagnostic object if the JSON text was malformed [8]. It is commonly used when you have already retrieved the file content manually and need to convert it into a TypeScript-compatible format [2]. Best Practices for Error Handling 1. Always Check for Diagnostics: After calling either function, always inspect the error field [7]. TypeScript diagnostics provide detailed information about why a file was unreadable or why the JSON was malformed [6]. 2. Use Compiler Host: For robust file resolution, it is recommended to use ts.findConfigFile combined with ts.readConfigFile, followed by ts.parseJsonConfigFileContent, rather than relying on manual file reading [3][4]. The latter function processes extended configurations and compiler options, which is the standard way to prepare a configuration for use with ts.createProgram [4]. 3. Diagnostic Reporting: If an error occurs, you can use the TypeScript diagnostic formatter (if needed) or simply log the diagnostic's messageText to understand the failure [6][3]. If you receive an error like "readFile is not a function," it typically indicates that the second argument passed to readConfigFile is not a valid function or is missing entirely [9]. Ensure you are passing ts.sys.readFile as the second argument to satisfy the function requirements [3][4].
Citations:
- 1: API for parsing *.json config files into a complete config objects microsoft/TypeScript#44573
- 2: https://stackoverflow.com/questions/67956755/how-to-compile-tsconfig-json-into-a-config-object-using-typescript-api
- 3: Document how to use tsconfig-related APIs in the wiki microsoft/TypeScript#56050
- 4: https://akousa.net/blog/typescript-compiler-api-guide
- 5: [Improvement] Let typescript load the config file, instead of tsconfig package kulshekhar/ts-jest#245
- 6: tsconfig.json parsing errors aren't helpful microsoft/TypeScript#2627
- 7: https://community.sonarsource.com/t/typescript-tsconfig-json-detection-fundamentally-broken/28893
- 8: https://jsr.io/@ts-morph/common/doc/~/ts.parseConfigFileTextToJson
- 9: Cannot read file 'tsconfig.json': readFile is not a function ToddThomson/tsproject#69
🌐 Web query:
site:github.com/microsoft/TypeScript readConfigFile implementation config error Diagnostic return object
💡 Result:
When using the TypeScript Compiler API, readConfigFile is a low-level utility function that reads a configuration file from disk but does not parse it into compiler options [1]. Because it only performs the initial read, it does not return a Diagnostic object directly in its primary result [1]. Instead, readConfigFile returns an object with the following structure: { config?: any; // The parsed JSON object, if successful error?: Diagnostic; // A diagnostic object, if an error occurred during reading or parsing } Common implementation details and error patterns include: 1. Low-Level Functionality: readConfigFile only reads the file content and parses the JSON [1]. It does not resolve extended configurations, handle default options, or validate the specific schema of a tsconfig.json file [1]. 2. Parsing Diagnostics: If you need to handle configuration errors, the error property of the returned object will contain a Diagnostic object if the file could not be read or if the JSON is malformed [2][3]. 3. Recommended Usage Flow: To correctly use the compiler options defined in a configuration file, you must pass the result of readConfigFile to subsequent API functions [1]. A robust implementation typically follows this pattern: const configFileName = ts.findConfigFile("./", ts.sys.fileExists, "tsconfig.json"); const configFile = ts.readConfigFile(configFileName, ts.sys.readFile); if (configFile.error) { // Handle the error using the Diagnostic object console.error(configFile.error.messageText); } else { // Parse the configuration properly const parsed = ts.parseJsonConfigFileContent( configFile.config, ts.sys, "./"); // Use parsed.options and check parsed.errors if needed let program = ts.createProgram(fileNames, parsed.options); } If you encounter unexpected errors, ensure your JSON file does not contain comments (which JSON.parse will reject) or Byte Order Marks (BOM), as readConfigFile may be sensitive to file encoding formatting [4]. If you need detailed diagnostics regarding specific compiler options, those are provided by parseJsonConfigFileContent rather than readConfigFile [1][5].
Citations:
- 1: Document how to use tsconfig-related APIs in the wiki microsoft/TypeScript#56050
- 2: tsconfig.json parsing errors aren't helpful microsoft/TypeScript#2627
- 3: microsoft/TypeScript@7a8238d
- 4: TypeScript wont read the tsconfig.json microsoft/TypeScript#41747
- 5: https://github.com/microsoft/TypeScript/blob/0c2dea58c726ffee40b6caf833ff69ea5278aeb7/src/compiler/program.ts
🌐 Web query:
TypeScript source readConfigFile returns config error raw tsconfig readConfigFile implementation
💡 Result:
In the TypeScript compiler API, the ts.readConfigFile function is designed to read and parse a tsconfig.json (or similar) file while handling TypeScript-specific JSON extensions, such as comments and trailing commas [1][2][3]. Implementation Details: The function signature is: ts.readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: ts.Diagnostic } 1. Return Value: Unlike standard JSON parsing, it returns an object containing either the parsed config object or an error object (of type ts.Diagnostic) [3]. It does not throw an error if the file is missing or contains invalid JSON; instead, it populates the error field [3]. 2. The readFile Callback: The function requires a second argument, a readFile callback, which performs the actual file system access [1][3]. A common source of errors (e.g., "readFile is not a function") arises when the host or the provided callback is incorrectly passed, missing, or incompatible with the expected interface [4][5]. 3. Scope: ts.readConfigFile only performs the initial reading and parsing of the file content into a raw JavaScript object [1][3]. It does not automatically resolve the extends property or convert compiler options into the internal ts.CompilerOptions format [1]. To fully resolve a tsconfig.json for use in a programmatic context, developers typically use ts.parseJsonConfigFileContent after reading the file with ts.readConfigFile [1][3]. This secondary step handles the extends inheritance, expands glob patterns, and validates the configuration [1][3].
Citations:
- 1: https://stackoverflow.com/questions/67956755/how-to-compile-tsconfig-json-into-a-config-object-using-typescript-api
- 2: https://stackoverflow.com/questions/68926611/why-read-tsconfig-json-using-readconfigfile-instead-of-directly-requiring-the-pa
- 3: https://npmx.dev/package/get-tsconfig/v/4.13.7
- 4: Cannot read file 'tsconfig.json': readFile is not a function ToddThomson/tsproject#69
- 5: fix(js): fix reading tsconfig.json on TypeScript 4.9 nrwl/nx#14380
🏁 Script executed:
set -euo pipefail
python3 - <<'PY'
import urllib.request
url = 'https://raw.githubusercontent.com/microsoft/TypeScript/v5.9.3/src/compiler/commandLineParser.ts'
try:
with urllib.request.urlopen(url, timeout=20) as r:
text = r.read().decode('utf-8', 'replace')
for needle in ['function readConfigFile', 'readConfigFile(', 'parseConfigFileTextToJson']:
idx = text.find(needle)
print(f'## {needle}')
if idx == -1:
print('not found')
else:
start = max(0, idx - 400)
end = min(len(text), idx + 1200)
print(text[start:end])
print()
except Exception as e:
print('ERROR:', repr(e))
PYRepository: kagal-dev/tsdoc
Length of output: 319
🏁 Script executed:
set -euo pipefail
python3 - <<'PY'
import ssl
import urllib.request
url = 'https://raw.githubusercontent.com/microsoft/TypeScript/v5.9.3/src/compiler/commandLineParser.ts'
ctx = ssl._create_unverified_context()
with urllib.request.urlopen(url, context=ctx, timeout=20) as r:
text = r.read().decode('utf-8', 'replace')
for needle in ['function readConfigFile', 'function parseConfigFileTextToJson', 'readFileText']:
idx = text.find(needle)
print(f'## {needle}')
if idx == -1:
print('not found')
else:
start = max(0, idx - 300)
end = min(len(text), idx + 1800)
print(text[start:end])
print()
PYRepository: kagal-dev/tsdoc
Length of output: 4448
Handle readConfigFile failures via error ts.readConfigFile(...) returns { config: {}, error } when the tsconfig can’t be read, so config === undefined never catches that case. This should return undefined on error instead of continuing with near-default compiler options.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/`@kagal-build-tsdoc/src/extract.ts around lines 190 - 235, Update
parseConsumerTsconfig to inspect the error field returned by ts.readConfigFile
before parsing config. Return undefined when an error is present, while
preserving the existing parsing and pathsBase behavior for successfully read
tsconfig files.
Summary
@kagal/build-tsdoc0.3.1 makes a TypeScript 7 project a supportedconsumer and repairs two ways extraction could crash on a resolution
that succeeds but yields the wrong semantic kind: a version stub where
a compiler was expected, and raw source where declarations were
expected. Both are now validated before the analysis engine sees them,
then handled — fall back, derive, or refuse with an actionable error —
instead of exploding deep inside api-extractor.
Consumer impact
typescriptpeerrange widens to
^5.9.0 || ^6.0.0 || ^7.0.0. A TS7 consumer'sextraction runs on the bundled analysis engine deliberately: TS7's
main export is a version stub, not the classic compiler API, so
there is no consumer engine left to adopt.
typescriptthe project resolved, ungated; a TS7 consumer got its version stub
grafted into api-extractor and died on
ts.parseJsonConfigFileContent is not a function. The swap is nowgated on the classic-API range (
>=5.9 <7, exposingcreateProgramand a
versionstring); anything outside it is left to the bundledcompiler.
extraction. When a bundled dependency's
typesre-exports rawsource (as
unbuild --stubwrites), declarations are derived fromthat source into
node_modules/.cache/kagal-build-tsdoc/and thedependency is remapped onto them through a
pathsoverrideextending the consumer's tsconfig — so its re-exported symbols are
documented, TSDoc included, as if it were built. The one case that
cannot be rescued — source the compiler cannot compile — raises the
new public
UnbuiltDependencyError, naming the dependency to buildfirst.
Changes
Engine and extraction:
peer range, the alias gate, and a permanent row asserting the TS7
fallback against the ts7 example's real install.
declarations — resolution-based stub detection (the modern stub
writes a
.js-mapped source specifier, so only resolving eachre-export reveals it), declaration derivation and remap, forced emit
options,
UnbuiltDependencyError, and rows that assert the rescue onboth the bundled and the adopted consumer engine.
promotion.
Example consumers:
BuildConfig[]typing, apreparesmoke step, the@kagalscopedropped from the private name, an explicit
rootDirclearing theTS 6.0 advisory, and a README.
typescript ^7.0.0pin built with obuild and the oxc declarationgenerator (Rust, never loads
typescript), carrying no lint becausetypescript-eslint has no TS7-safe path, with a README recording the
toolchain wall.
Why a patch
Under 0.x rules a minor is reserved for a breaking or significant
change; a widened peer range and two crash fixes are patch territory.
The CHANGELOG records both under 0.3.1.
Verification
The repo-wide gate is green on the rebased branch — frozen-lockfile
install, lint, type-check, build, and test across every workspace
project. build-tsdoc's own suite passes 73 tests, including the TS7
fallback row and the stub-rescue rows.
After merge
Tagging the merge commit for 0.3.1 triggers
publish.yml, whichauthenticates to npm via OIDC and publishes with Sigstore provenance.
Summary by CodeRabbit