Skip to content

feat: unified config package with typed tool sections - #2

Merged
Mearman merged 23 commits into
mainfrom
feat/config-envelope
Sep 29, 2026
Merged

Mearman merged 23 commits into
mainfrom
feat/config-envelope

Conversation

@Mearman

@Mearman Mearman commented Sep 29, 2026

Copy link
Copy Markdown
Member

Builds @exadev/config on cosmiconfig-extends: tool-owned typed sections composed in an authoring file with withSections(toolA, toolB)({ ... }), loadSection resolving a section from exadev.config.ts or a standalone <name>.config.ts (defined in both is an error naming both files), the shared layout section matching the options of the workspace architecture rules in @exadev/eslint-config, and a doctor function with an exadev-config doctor command for sections no installed or listed tool owns.

Refs #1

A descriptor whose name type is string (a bare Section annotation) gave the
section map a string index signature, which switched off the excess-property
check for every key of the config.
withSections now rejects such a descriptor, one named after an envelope key,
two that share a name, and an empty list, each with a message stating the
reason.
defineSection rejects an envelope key name in the tool's own build.
cosmiconfig-extends hands back the whole module namespace when a module has
no default export, and null for a null default.
Both then read as a config that defines nothing, so loadSection returned
undefined and doctor reported a clean directory for a configuration that
was never applied.
The explorer's TypeScript loaders now refuse both; an empty file or a
default export of undefined remains an empty config.
loadSection returned undefined for a nonexistent directory or one that is
a file, and doctor and the exadev-config command reported success for a
mistyped --cwd, so the check could never go red for that input.
Both now throw, and the command exits with the failure status.
The standalone file <name>.config.ts is the native config file of many tools,
so a section called eslint or vitest read the tool's own config as its
section, or failed as defined in both files.
The standalone file is now exadev.<name>.config.ts, which no tool owns.

The .mts and .cts extensions that cosmiconfig-extends loads were silently
ignored.
Both file kinds now accept .ts, .mts and .cts, and a file present under more
than one extension throws.
The defined-in-both error says the unified side may come through extends,
since the preset is not known at that point.
…ere is no config

The usage text advertises --help for the doctor command, but parseArgs
rejected it as an unknown option.
Running the command with no arguments printed the usage and exited 0.
It now exits with the failure status.
A directory with no unified config exited 0 in silence, which made a clean
exit weak evidence; the command now says nothing was checked.
… manifest keys

doctor ignored the exadevConfig field of the project's own package.json, so
a tool repository dogfooding its section saw it reported as unowned.
The manifest check also rejected any key besides sections, which would make
every consumer on an older version fail as soon as a tool adds one; it now
checks sections and ignores the rest.
loadSection throws ConfigValidationError, but the class was reachable only
by importing cosmiconfig-extends, which a consumer under strict node_modules
cannot do without adding it as a direct dependency.
Exact pins on the published package's runtime dependencies stop a consumer
taking a cosmiconfig-extends patch without a new release here, and install a
second copy beside any other 1.x version, where ConfigValidationError
differs between the copies and instanceof fails.
The release age exclusion now names the package rather than one version.
…tory's own

The check installed whatever zod was latest at run time, so the job was not
reproducible.
The authoring type is the schema's input type, so rules the schema enforces
only at runtime pass the type checker, and the excess-property check applies
only to an object literal passed directly.
The packaged-install sentence now says which check runs the command.
The authoring test's option list is described as the subset it is.
…its decisions

The coherence messages quoted group names, patterns and pair members from the
config, against the loader design that error messages do not echo input.
They now name the path and the rule.
The layout schema doc and README state the two other deliberate behaviours:
a key written as undefined is rejected where the ESLint reader accepts it,
and the relational rules wait for a valid shape.
The Check contract says a relational guard may report fewer problems.
…erage of the files it loads

The alias shim re-exported this package's source, so jiti evaluated a second
copy that coverage attributed to the same files, reporting covered functions
as uncovered.
The shim is a stand-in now, since the test is about where the import
resolves.
Hooks get the same timeout as tests, because the authoring type test
compiles its fixtures in one.
A project package.json whose dependency fields were not objects, and an
installed package.json that was not an object, were skipped, so doctor
reported a clean result for input it could not read.
Both are now validated and named in the error, with every problem listed.
The dependency fields of installed packages are still not read.
Descriptors in the section unit tests are built in a hook, because a module
level call that throws fails the suite at load with no failing test, which a
mutation run counts as a survivor.
The layout schema vendor and the manifest field name are asserted.
The explorer no longer sets a cache option that a fresh explorer per call
made unobservable, and takes its module name from the unified file base, so
the two cannot drift.
The command resolves the working directory through process.cwd, whose
fallback was indistinguishable from resolving an empty path.
The mutation job no longer runs on pull requests or pushes and no longer
gates required checks or the release job. The script and its threshold are
unchanged for manual runs.
@Mearman
Mearman marked this pull request as ready for review September 29, 2026 23:34
@Mearman
Mearman merged commit cfb7c53 into main Sep 29, 2026
12 checks passed
@Mearman
Mearman deleted the feat/config-envelope branch September 29, 2026 23:34
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-09-29T23:41:45.536988Z 7c57cbe Draft marked ready

Security findings

Advisory findings (1)

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@chatgpt-codex-connector chatgpt-codex-connector 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.

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: 7c57cbef98

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread .github/workflows/ci.yml
commitlint:
name: Commitlint
needs: determine-runner
runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ Codex Security Review · Automatically triggered

P1 Badge Security: Keep pull requests off the self-hosted runner fleet

When fork PR workflows are approved or auto-run and the shared fleet is available, keep every pull-request job on a hosted runner. pull_request evaluates the PR-controlled workflow revision, so an attacker can change the repository-local determine-runner.yml or this runs-on expression to select the self-hosted fleet; the fork-aware action is mutable before scheduling. CI then checks out the PR and executes pnpm install and repository scripts, yielding arbitrary code execution on that host. The read-only token and hosted release job limit direct publication but do not protect the runner. Enforce hosted runners for PRs or an immutable gate plus runner-group restrictions.

Useful? React with 👍 / 👎.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant