feat: support per-target output roots#2055
Conversation
|
@zerone0x Thanks for the contribution! The feature looks good and CI is green, but this PR currently has merge conflicts with |
dyoshikawa
left a comment
There was a problem hiding this comment.
Thanks for this — the per-target outputRoots support is a nice improvement, and the validation path (validateOutputRoot) is correctly reused for both the array and object forms, so no traversal issue was introduced there.
Two things that need to be resolved before this can merge:
src/lib/import.tswasn't updated. All eightimport*Corefunctions (lines 75, 118, 164, 206, 247, 289, 336, 385) still callconfig.getOutputRoots()[0] ?? "."with no target argument, even though each function already has atool: ToolTargetparameter available. Once someone configures per-targetoutputRoots,rulesync importwill read from the wrong directory for any target that isn't first in the flattened/deduped array.generate.tswas fully migrated toconfig.getOutputRoots(toolTarget)butimport.tswas missed — these two should stay in sync.- The branch currently has a merge conflict with
main(per the maintainer comment above) and can't be merged as-is — needs a rebase.
A couple of smaller things worth addressing too: docs/guide/configuration.md and docs/api/programmatic-api.md still describe outputRoots as array-only and don't mention the new per-target object form. And test coverage for the new behavior stops at the Config/ConfigResolver level — generate.test.ts's getOutputRoots mock is argument-agnostic (mockReturnValue), so it wouldn't catch a regression to the old no-argument call, and there's no test for the "Unknown outputRoots target" validation error or for a target that's missing from the map.
Left a couple of inline notes on smaller robustness points too.
|
|
||
| if (target) { | ||
| const targetOutputRoots = this.outputRoots[target]; | ||
| if (targetOutputRoots === undefined) return []; |
There was a problem hiding this comment.
When a target that is present in targets has no key in the per-target outputRoots map, this returns [] and the generate.ts loop for that target silently runs zero times — no warning, no error. Worth either falling back to a default (e.g. process.cwd()) or emitting a logger.warn similar to warnUnsupportedTargets, so a typo in the map does not silently produce no output for a target.
| roots.forEach((outputRoot) => { | ||
| validateOutputRoot(outputRoot); | ||
| }); | ||
| resolvedOutputRoots[target as ToolTarget] = Array.isArray(targetOutputRoots) |
There was a problem hiding this comment.
resolvedOutputRoots is built as a plain {} and populated via bracket assignment. A config with a key literally named __proto__ would hit the Object.prototype.__proto__ setter instead of creating an own property, so the entry silently disappears from Object.keys() and never reaches validateObjectFormOutputRootKeys's unknown-key check (it just gets dropped instead of raising the intended error). Not exploitable beyond this local object, but building it with Object.create(null) (or explicitly rejecting __proto__/constructor/prototype keys) would make malformed config fail loudly instead of silently.
| } | ||
| } | ||
|
|
||
| private validateObjectFormOutputRootKeys(outputRoots: OutputRoots): void { |
There was a problem hiding this comment.
This duplicates validateObjectFormTargetKeys above almost exactly (same Array.isArray guard, same new Set(ALL_TOOL_TARGETS), same unknown-key loop — differing only in the wildcard handling and the error message). Might be worth factoring into a shared helper, e.g. assertKnownTargetKeys(obj, { allowWildcard, fieldLabel }).
Summary
outputRootsto be configured as a per-target map in addition to the existing array formconfig.getOutputRoots(toolTarget)so each target writes only to its configured rootsTests
PATH=/tmp/rulesync-bin:$PATH corepack pnpm exec vitest run src/config/config-resolver.test.ts --silent=truePATH=/tmp/rulesync-bin:$PATH corepack pnpm run typecheckPATH=/tmp/rulesync-bin:$PATH corepack pnpm run fmt:checkPATH=/tmp/rulesync-bin:$PATH corepack pnpm run oxlintFixes #2039