Skip to content

feat: support per-target output roots#2055

Open
zerone0x wants to merge 1 commit into
dyoshikawa:mainfrom
zerone0x:fix-outputroots-per-target
Open

feat: support per-target output roots#2055
zerone0x wants to merge 1 commit into
dyoshikawa:mainfrom
zerone0x:fix-outputroots-per-target

Conversation

@zerone0x

Copy link
Copy Markdown
Contributor

Summary

  • allow outputRoots to be configured as a per-target map in addition to the existing array form
  • resolve and validate per-target output roots while preserving global-mode behavior
  • route generation through config.getOutputRoots(toolTarget) so each target writes only to its configured roots

Tests

  • PATH=/tmp/rulesync-bin:$PATH corepack pnpm exec vitest run src/config/config-resolver.test.ts --silent=true
  • PATH=/tmp/rulesync-bin:$PATH corepack pnpm run typecheck
  • PATH=/tmp/rulesync-bin:$PATH corepack pnpm run fmt:check
  • PATH=/tmp/rulesync-bin:$PATH corepack pnpm run oxlint

Fixes #2039

@dyoshikawa

Copy link
Copy Markdown
Owner

@zerone0x Thanks for the contribution! The feature looks good and CI is green, but this PR currently has merge conflicts with main and can't be merged as-is. Could you rebase onto the latest main and resolve the conflicts? Once that's done, I'll re-review and merge. Thanks!

@dyoshikawa dyoshikawa left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

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:

  1. src/lib/import.ts wasn't updated. All eight import*Core functions (lines 75, 118, 164, 206, 247, 289, 336, 385) still call config.getOutputRoots()[0] ?? "." with no target argument, even though each function already has a tool: ToolTarget parameter available. Once someone configures per-target outputRoots, rulesync import will read from the wrong directory for any target that isn't first in the flattened/deduped array. generate.ts was fully migrated to config.getOutputRoots(toolTarget) but import.ts was missed — these two should stay in sync.
  2. 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.

Comment thread src/config/config.ts

if (target) {
const targetOutputRoots = this.outputRoots[target];
if (targetOutputRoots === undefined) return [];

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

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.

Comment thread src/config/config.ts
}
}

private validateObjectFormOutputRootKeys(outputRoots: OutputRoots): void {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

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 }).

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.

Allow different outputRoots per target

2 participants