Skip to content

refactor: replace hand-rolled helpers with the library calls that already ship - #414

Merged
Vivswan merged 1 commit into
mainfrom
wt/lib-swaps
Sep 23, 2026
Merged

Vivswan merged 1 commit into
mainfrom
wt/lib-swaps

Conversation

@Vivswan

@Vivswan Vivswan commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

What this changes

Six hand-rolled helpers become the library call that already ships, and three duplicated helpers get one home each.

swap                 before (hand-rolled line)                                                    after (library call)
zod issue path       issue.path.map((p) => typeof p === "number" ? `[${p}]` : `.${p}`).join("")   z.core.toDotPath([key, ...issue.path])
deep equality        JSON.stringify(liveComparable) === JSON.stringify(declaredValue)             isDeepStrictEqual(liveComparable, declaredValue)
set equality         setA.size === setB.size && [...setA].every((e) => setB.has(e))               setA.size === setB.size && setA.isSubsetOf(setB)
query string         Object.entries(query).map(([k, v]) => `${enc(k)}=${enc(v)}`).join("&")       new URLSearchParams(query)
YAML file name       /\.ya?ml$/.test(name)  ...  name.replace(YAML_EXT, "")                       YAML_EXTENSIONS.has(extname(name))  ...  parse(name).name
regex escape         text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")                                 RegExp.escape(text)   (scripts and tests only)

duplicate            was                                                                          now
valueAt              engine/validate.ts and sections/shared/list-section.ts                       list-section.ts exports the Object.hasOwn variant; validate.ts imports it
unreconcilable       environments/branch-policies.ts and environments/protection-rules.ts         one builder in environments/endpoints.ts, the list and entry nouns as a parameter
escapeRe             .github/scripts/lib/generated-regions.ts                                     deleted; every caller uses RegExp.escape

Before / After

Same input, same output for everything the code receives today, except these three.

input                                       before                                after
["parameters", "my-key"] on an issue path   parameters.my-key                     parameters["my-key"]
{ labels: "needs review" } as a query       labels=needs%20review                 labels=needs+review
repos-dir file named exactly ".yml"         error: "owner/" is not a slug         warning: not a .yml/.yaml file, ignored
  • toDotPath brackets a key with a non-word character. No shape today puts such a key on an issue path, so no pinned message changed: every existing pin (labels[1].color has no empty state, rulesets[0].rules[0]: parameters.grouping_strategy: ..., (body)) renders the same.
  • URLSearchParams writes a space as + (application/x-www-form-urlencoded); GitHub decodes + and %20 alike. It also percent-encodes !'()~, which decode the same.
  • A dotfile has no extension for extname, so a file named exactly .yml is ignored with the not-a-YAML warning instead of failing as the empty slug.

How

  • tsconfig lib: ES2022 -> ES2025 for the Set.prototype.isSubsetOf and RegExp.escape types; target stays ES2022. Node 22.14 (the engines floor) ships isSubsetOf; RegExp.escape is called only from .github/scripts and test/, which run under bun 1.4.2 (.bun-version).
  • No architecture.yml edit: validate.ts importing from sections/shared rides the existing engine -> sections edge.
  • The unreconcilable home is environments/endpoints.ts because it imported nothing from its sibling modules, so it cannot close a cycle with them; a first draft in nested.ts made a runtime cycle (nested.ts imports both callers), which the review caught.

Proof

  • Gates: typecheck, knip, lint, lint:arch green.
  • Unit tests: bun test test/engine test/sections test/discovery test/github test/scripts test/docs test/flows/snapshot.test.ts test/e2e/foundation.test.ts -> 2826 pass, 0 fail (rerun after the rebase onto the folded test layout); no test assertion edited.
  • e2e: bun run test:e2e --sections custom_properties,secret_scanning_custom_patterns,rulesets -> 42/42; the full e2e once, because validate.ts renders every section's parse messages -> 368/368.
  • Per-swap control: each old expression and its replacement were run on the same inputs (the table below); every row matches except the three listed above.
  • Import-order control: bun -e 'import "./src/sections/environments/branch-policies.ts"; import "./src/sections/environments/protection-rules.ts"' succeeds (it threw on the nested.ts draft).
Technical details

Per-swap unchanged-output evidence (old expression vs new, same input):

toDotPath        labels [1,"color"]            labels[1].color        labels[1].color        same
toDotPath        pages []                      pages                  pages                  same
toDotPath        live body []                  (body)                 (body)                 same
toDotPath        ["parameters","operator"]     parameters.operator    parameters.operator    same
isDeepStrictEqual ["a","b"] vs ["a","b"]       true                   true                   same
isDeepStrictEqual ["b","a"] vs ["a","b"]       false                  false                  same
isDeepStrictEqual [] vs []                     true                   true                   same
isDeepStrictEqual 1 vs "1"                     false                  false                  same
isSubsetOf       {x,y} vs {y,x}                true                   true                   same
isSubsetOf       {x} vs {x,y}                  false                  false                  same
isSubsetOf       {x,y} vs {x,z}                false                  false                  same
URLSearchParams  per_page=100, page=1          per_page=100&page=1    per_page=100&page=1    same
URLSearchParams  q: "a&b=c"                    q=a%26b%3Dc            q=a%26b%3Dc            same
extname/parse    repo.yml, repo.yaml, a.b.yml  repo, repo, a.b        repo, repo, a.b        same
extname/parse    notes.md, README              rejected               rejected               same
RegExp.escape    "| Input | Description |"     matches literally      matches literally      same
RegExp.escape    "a.b*c(d)"                    matches literally      matches literally      same

Line accounting (git diff --numstat origin/main...HEAD, 22 files, +85/-119):

 5   5  .github/scripts/gen-action-docs.ts
10  15  .github/scripts/gen-docs.ts
 0   5  .github/scripts/lib/generated-regions.ts
 6   6  src/discovery/central.ts
 5  19  src/engine/validate.ts
 1   4  src/sections/contract/endpoints.ts
 2   5  src/sections/contract/live.ts
 1   5  src/sections/contract/permissions.ts
 1   1  src/sections/custom_properties/index.ts
 9  10  src/sections/environments/branch-policies.ts
13   0  src/sections/environments/endpoints.ts
 4   9  src/sections/environments/protection-rules.ts
 3   9  src/sections/rulesets/schema.ts
 2   1  src/sections/secret_scanning_custom_patterns/index.ts
 1   2  src/sections/secret_scanning_custom_patterns/schema.ts
 3   2  src/sections/shared/list-section.ts
 1   3  test/docs/markdown.ts
 6   3  test/e2e/foundation.test.ts
 3   4  test/flows/snapshot.test.ts
 3   4  test/scripts/release-pipeline.test.ts
 5   6  test/sections/docs-registry.test.ts
 1   1  tsconfig.json

Reviewer note, deliberately left as is:

  • src/cli/actions.ts hand-rolls @actions/core's workflow-command escaping and setOutput heredoc by recorded decision (bundle weight; @actions/core is a devDependency).
  • src/discovery/discover.ts compileExcludePattern vs path.matchesGlob: matchesGlob changes the exclude semantics (case-insensitive, * crossing /); candidate only.
  • src/github/api.ts ClientAnswer tri-state vs a neverthrow Result at 55 sites: design-level, the owner's call.
  • The two src/ regex-escape copies (secret_scanning_custom_patterns/compilable-form.ts, discovery/discover.ts) stay: Node 22.14, which engines admits, lacks RegExp.escape.
  • src/plain-data.ts would be a closer conceptual home for valueAt, but the sections layer may not import it under architecture.yml; recorded, not built.
  • The tsconfig lib bump adds no ES2025 type to any public signature; the package smoke still compiles the consumer at target es2022.

Codex rubber-duck: two passes; the first found the nested.ts import cycle, the second clean.

BEGIN_COMMIT_OVERRIDE
refactor: replace hand-rolled helpers with the library calls that already ship

zod issue paths render through z.core.toDotPath at the four sites that each spelled their own join, so a key with a non-word character reads as ["my-key"] instead of .my-key.
The secret scanning pattern comparison uses isDeepStrictEqual from node:util instead of JSON.stringify equality.
The two set-equality helpers use Set.prototype.isSubsetOf, and the tsconfig lib moves from ES2022 to ES2025 for its types while the target stays ES2022.
The REST query string is built by URLSearchParams, which writes a space as + where encodeURIComponent wrote %20; GitHub decodes both.
Central-mode file names go through extname and parse from node:path instead of a YAML extension regex.
valueAt has one home in the shared list-section module, the two environments modules share one unreconcilable message builder, and the generator scripts call RegExp.escape instead of a local escapeRe.
END_COMMIT_OVERRIDE

Copilot AI balanced review requested due to automatic review settings September 22, 2026 09:38
@github-actions

Copy link
Copy Markdown
Contributor

File size check

0 over a hard cap (fails), 30 warning(s).

File Size Tier Cap
.github/scripts/release-pipeline.ts:6 156 chars warn 150
.github/scripts/release-pipeline.ts:453 151 chars warn 150
.github/scripts/release-pipeline.ts:1247 153 chars warn 150
.github/scripts/release-pipeline.ts:1 34 comment lines (header) warn 25
.github/scripts/release-pipeline.ts:449 14 comment lines warn 10
.github/workflows/post-green.yml:29 153 chars warn 150
.github/workflows/update-release-pr.yml:109 14 comment lines warn 10
docs/upgrading/v2-to-v3.md 1276 lines warn 1040
src/engine/layers.ts:217 157 chars warn 150
src/flows/settings-write.ts:142 159 chars warn 150
src/flows/settings-write.ts:30 11 comment lines warn 10
src/flows/snapshot.ts:189 13 comment lines warn 10
src/github/secret-scan.ts:42 11 comment lines warn 10
src/schema.ts:186 153 chars warn 150
src/schema.ts:197 176 chars warn 150
src/sections/contract/errors.ts:14 14 comment lines warn 10
src/sections/contract/module.ts:963 185 chars warn 150
src/sections/contract/module.ts:627 12 comment lines warn 10
src/sections/contract/module.ts:897 12 comment lines warn 10
src/sections/secret_scanning_custom_patterns/compilable-form.ts:382 161 chars warn 150
src/sections/shared/roles.ts:43 13 comment lines warn 10
src/types.ts:16 156 chars warn 150
test/docs/guides.test.ts:451 155 chars warn 150
test/e2e/generators.ts 2633 lines warn 2560
test/e2e/generators.ts:1703 166 chars warn 150
test/e2e/generators.ts:1857 161 chars warn 150
test/e2e/generators.ts:1666 12 comment lines warn 10
test/engine/execute.test.ts:617 152 chars warn 150
test/flows/merge-parity.test.ts:63 164 chars warn 150
test/scripts/auto-fix-allowlist.test.ts:8 12 comment lines warn 10

Split the file, wrap the line, shorten or exempt the comment, or list the path in .file-size-allow.local with a # reason.

5 managed file(s) skipped; repo-platform owns them.

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

The refactors preserve supported behavior and are backed by comprehensive validation and test coverage.

Review effort: Balanced
Findings: None

What changed in this PR

Replaces custom helpers with standard library APIs and consolidates duplicated utilities without changing supported behavior.

Changes:

  • Uses native path, equality, query, set, and regex helpers.
  • Centralizes shared value lookup and environment error construction.
  • Updates ES library typings while retaining the ES2022 target.
File Description
tsconfig.json Enables ES2025 API typings.
.github/​scripts/​gen-action-docs.ts Uses RegExp.escape.
.github/​scripts/​gen-docs.ts Uses RegExp.escape.
.github/​scripts/​lib/​generated-regions.ts Removes the custom regex escaping helper.
src/​discovery/​central.ts Uses Node path APIs for YAML filenames.
src/​engine/​validate.ts Reuses path lookup and Zod path formatting.
src/​sections/​contract/​endpoints.ts Uses URLSearchParams.
src/​sections/​contract/​live.ts Uses Zod path formatting.
src/​sections/​contract/​permissions.ts Uses Set.isSubsetOf.
src/​sections/​custom_properties/​index.ts Uses native set comparison.
src/​sections/​environments/​branch-policies.ts Reuses the shared failure builder.
src/​sections/​environments/​endpoints.ts Adds the shared failure builder.
src/​sections/​environments/​protection-rules.ts Reuses the shared failure builder.
src/​sections/​rulesets/​schema.ts Uses Zod path formatting.
src/​sections/​secret_scanning_custom_patterns/​index.ts Uses strict deep equality.
src/​sections/​secret_scanning_custom_patterns/​schema.ts Uses Zod path formatting.
src/​sections/​shared/​list-section.ts Exports the shared own-property lookup.
test/​docs/​markdown.ts Uses native regex escaping.
test/​e2e/​foundation.test.ts Uses native regex escaping.
test/​flows/​snapshot.test.ts Uses native regex escaping.
test/​scripts/​release-pipeline.test.ts Uses native regex escaping.
test/​sections/​docs-registry.test.ts Uses native regex escaping.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…eady ship

zod issue paths render through z.core.toDotPath at the four sites that each spelled their own join, so a key with a non-word character reads as ["my-key"] instead of .my-key.
The secret scanning pattern comparison uses isDeepStrictEqual from node:util instead of JSON.stringify equality.
The two set-equality helpers use Set.prototype.isSubsetOf, and the tsconfig lib moves from ES2022 to ES2025 for its types while the target stays ES2022.
The REST query string is built by URLSearchParams, which writes a space as + where encodeURIComponent wrote %20; GitHub decodes both.
Central-mode file names go through extname and parse from node:path instead of a YAML extension regex.
valueAt has one home in the shared list-section module, the two environments modules share one unreconcilable message builder, and the generator scripts call RegExp.escape instead of a local escapeRe.
Copilot AI review requested due to automatic review settings September 22, 2026 09:53

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

The refactors preserve supported behavior and introduce no unresolved correctness issues.

Review effort: Balanced
Findings: None

@Vivswan Vivswan added the merge-when-green Owner approved: merge once every gate is green label Sep 22, 2026
@Vivswan
Vivswan merged commit 8043159 into main Sep 23, 2026
35 checks passed
@Vivswan
Vivswan deleted the wt/lib-swaps branch September 23, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-when-green Owner approved: merge once every gate is green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants