Build1 distinct publisher3 min readPublished
One developer merged five shared repositories into a provider monorepo in June 2026. The load-bearing rule is that no shared package reads process.env, so values like table names arrive as arguments from whichever product is consuming them.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
A shared package that calls process.env takes a dependency on a name in somebody else's deployment. Nothing in the signature says so. The table name is just a string the module hopes to find the first time it runs, inside whichever product loaded it. Uehara's rule inverts the direction: environment-dependent values, table names among them, are injected from the consumer, which is why he names it the design decision that mattered most [10]. The dependency then sits in the call site, the compiler checks it, and two products can name their tables differently without the package learning either name.
That is the same rule as the line he put in the README: the foundation holds no runtime, and the API, admin UI and data live alongside the product [8]. Only reused code goes in the provider; the screens and the actual data-access implementations stay on the consumer side [9]. A package that reads env is carrying a fragment of runtime in with the library, because it assumes a process it does not own.
The migration ledger reads as a real tradeoff, not a clean win. The v1.0.0 move changed about 60 files on the consuming side and let him delete 15 files of block-editor implementation the product had been holding locally [11]. That is four files touched per duplicate file retired [18]. He is explicit that the output of sharing is the disappearance of the duplicated copies rather than any new shared code [12]. The second consumer should come cheaper, but only if the injected-parameter surface does not move for it. If product two needs a value product one never passed, you pay a change on both sides and a release in between.
This is one developer's workspace, written up in his own post [20], and the pieces had been drifting for a while: the utility toolkit dates to August 2024, the block editor to June 2026 [13][16], roughly 22 months apart [19], with no consistency in dependency direction or publish target across them [2]. The file counts transfer if your duplicates are still copies of one implementation. They do not if the copies have forked, because then those 15 deletions are a merge instead. The process.env ban is cheap for him because he owns both sides of the boundary. In a multi-team org the same rule means publishing a config surface, versioning it, and telling callers when a parameter appears.
Option C on his shortlist was to do nothing and keep the local copies, ruled out because block-editor copies were already accumulating on the product side [6]. It was never a real candidate, just the default state that kept running while the decision got made.
What makes the env rule worth copying is that it is checkable by machine. A CI step that greps packages/ for process.env and fails on any hit enforces the boundary without a review conversation. He does not say he wires that up, and his counts are one person's counts, but that check costs the same line of YAML at any headcount. The part that scales with repository count rather than consumer count is the other half of option B: CI and versioning pulled into one place, with everything published as @org/* to GitHub Packages [5].
Ranked by verification strength, evidence, and original report placement.
A shared package does not read process.env: environment-dependent values such as table names are injected from the consumer. The author calls this the design decision that mattered most, and says it lets the package be portable to any environment.
As of June 2026 the author had five separate repositories holding shared logic: a utility toolkit, DynamoDB helpers, a rate limiter, a block-editor engine, and notifications.
Each of the five repositories was born at a different time, was referenced from a different product, and had no consistency in its dependency direction or publish target.
Products referenced the shared logic inconsistently: some via public npm, some via GitHub Packages, some copied locally, so every update meant hunting for which copy was the source of truth.
Option A was to keep each repository independent and keep publishing separately: low migration cost, but CI, versioning and dependency graphs for five repositories, separately, forever.
Option B was to consolidate into a single monorepo published as @org/* to GitHub Packages, which pulls CI and versioning into one place.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 30, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
A four-app workspace is where pnpm's symlink store starts paying for its migration cost1 distinct publisher
build
Claude Code walks the whole process table to inherit one shell's environment1 distinct publisher
build
Four Layers, One Status Code: Testing Tenant Scope on a Generated Prisma Route1 distinct publisher
build
A missing package.json line broke every pnpm user of a React dashboard template1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One author, his own repositories
Two artefacts are shown and both are fragments: a README row asserting that table names come from the consumer, which stops mid-table, and a four-line consumer .npmrc. Everything else — the June 25 date, the 60-changed and 15-deleted file counts, the January 2026 v1.0.2 — is checkable by exactly one person, and the repositories themselves stay behind an @org placeholder. The design argument is stronger than its documentation: the DYNAMODB_TABLE-versus-TABLE_NAME conflict and the Amplify SSR env behaviour stand on their own logic, not on the author's word.
Two consumers, both his own
Adoption is real but its ceiling is one developer's workspace: five packages under one roof, one v1.0.0 migration completed, and exactly one package — the block editor — with two consumers at once. The oldest package sits on public npm, so it is at least reachable by strangers; the @org/* set on GitHub Packages is not. The author is explicit that below two consumers the exercise does not pay, and by his own count he has just crossed that line once.
Small claims for small proof
Rare balance: the strongest sentence in the piece argues against doing the work. Consolidating for a single consumer is called not worth the effort, the benefit is defined as deletion rather than new capability, and the migration cost is disclosed at four files edited per duplicate retired. The one reach is "portable to any environment" from a sample of two products — but the rule is argued from mechanism, not from results, so it is not asking to be believed on evidence it lacks.
Reputation, not revenue
The author is writing about his own products on a developer-blogging platform, which buys standing rather than sales: nothing here is for hire, no vendor is named favourably, and the registries and tools cited — GitHub Packages, npm, pnpm — are commodity choices with no affiliation on offer. The pressure that does exist is the usual one on a build-story: a migration you chose is easier to narrate as having worked, and the only person who could report the parts that did not is the same person counting the files.
Believable, unverifiable
Split verdict. The engineering rule can be judged on the page — it either survives the DYNAMODB_TABLE-versus-TABLE_NAME test or it does not, and it survives — so our read on the design is firm. Our read on the scale is not: one publisher, one participant, self-reported counts, and a fifth of the evidence trail cut off mid-sentence. Confidence lands mid-range because the useful part happens not to depend on the unverifiable part.