✨ feat(nx): automate the workspace with Nx - #226
Open
roninjin10 wants to merge 10 commits into
Open
Conversation
added 10 commits
August 18, 2026 14:36
namedInputs model this repo: sharedGlobals covers the root configs that invalidate everything, production excludes the test and lint surface. targetDefaults make the existing package scripts cacheable with correct inputs; release selects the engine group as a fixed release train.
The plugin maps smthrs.group onto group:* project tags and infers a cacheable fmt target from dprint.json. The generator is the Nx equivalent of the NewPackage rule: it scaffolds a conventional package with no project.json, reading the manifest shape from an existing sibling.
engine may depend on engine only; agent on engine and agent; tooling on everything; packages never on apps. The known kernel/platform-browser runtime cycle and the dev-time BUILD.ts cycles through @smthrs/targets are allowlisted so the gate catches new violations.
…s targets wasm-repro is deliberately cache: false: the gate proves a from-source rebuild reproduces the committed bytes on this host, and a remote cache hit from another host would mask the nondeterminism it catches.
Wired for Nx Cloud (NX_CLOUD_ACCESS_TOKEN) but functional without it via actions/cache over .nx/cache; connecting the cloud workspace is one documented command for a human.
packages/chain's tests dynamically import @smthrs/memory as a test technique; the boundary rule reads any dynamic import as a lazy-loading boundary and forbids the static src imports. Exempt the specifier, and record the finding in the comparison doc.
The branch point predated 39bd390 (restores the smthrs bin the rename mangled) and ceb784b (reformats the READMEs dprint was failing). The adoption section claimed both failures reproduce on a pristine origin/main; they no longer do. Record the rebase and the post-rebase re-verification: the docs-parity gate is green, dprint passes on the 14 formerly failing packages, and the first post-rebase fmt run invalidated exactly the 14 packages whose READMEs changed.
roninjin10
force-pushed
the
build-compare/nx
branch
from
August 18, 2026 21:46
572ac73 to
0d1372b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A complete, modern Nx 23.1.1 (
nx@latest) setup for the flows workspace, built to be compared side by side with the Turborepo and Bazel sibling PRs and with the in-repoBUILD.tssystem. Nothing existing was deleted or rewritten: the pnpm scripts,BUILD.ts,ci.yml, andrelease.ymlare untouched, and the Nx lane runs next to them. The full analysis is indocs/build-systems/nx.md— it is the real deliverable.What was wired
nx.json, no hand-writtenproject.jsonexcept the crate):@nx/vitestis registered against everyvitest.config.ts(under Nx 23's merge order the package.json script wins thetestname collision, so the script stays the command — the plugin contributes config-aware hashing; the doc details this),@nx/viteis registered for Vite builds,@nx/js/typescriptis registered with inference disabled (why below). Packagecheck/lint/test/build/circular/coveragescripts become cacheable targets throughtargetDefaults. One project needed aproject.json:crates/flows-jj, because it is not an npm package — it modelscargo-fmt,cargo-clippy,cargo-test, and the wasm reproducibility gate with explicit inputs.namedInputsthat model this repo:sharedGlobalscovers the root configs that invalidate everything (tsconfig.base.json,eslint.jsdoc.js,eslint.boundaries.js,pnpm-workspace.yaml,rust-toolchain.toml);productionexcludes the test/lint surface.check/testuse["default", "^default"]— not^production— because cross-package imports resolve to source here (exports: { ".": "./src/index.ts" }), so dependency sources must be in the cache key. Verified live: editingpnpm-workspace.yamlre-hashed and re-ran all 51 test tasks.smthrs.group(eslint.boundaries.js+ a two-line edit in each of the 45 package eslint configs): the local plugintools/nx/plugin.tsmaps the manifest field ontogroup:engine|agent|toolingtags, and@nx/enforce-module-boundariesencodes the real constraint — engine (the published train) depends on engine only; agent on engine+agent; tooling on everything; packages never on apps. The current graph is clean (66 engine→engine edges, zero engine→agent/tooling), and the rule is verified firing on synthetic violations.nx release(nx.json#release): fixed release group overtag:group:engine, conventional-commits versioning,updateDependents: autofor internal range retargeting, workspace changelog,v{version}tags.release.ymlstays; this is the alternative path. Dry-run output below..github/workflows/nx.yml):nrwl/nx-set-shas@v4,nx affected -t check,lint,fmt,circular,test,build --parallel=2,actions/cacheover.nx/cache. Remote caching: Nx Cloud is the current recommended path and needs an account we do not have. The workflow is fully wired for it — the one human step ispnpm exec nx connect(writesnxCloudId) plus adding theNX_CLOUD_ACCESS_TOKENsecret. Until then the local cache carries CI. The OSS self-hosted options (community S3/GCS plugins) are unofficial; Powerpack is gone as a separate product in Nx 23.tools/nx): tags fromsmthrs.group, plus a cacheablefmttarget (dprint check) inferred fromdprint.json— the half of the repo's lint gate the eslint plugin can't express.new-packagegenerator (nx g ./tools/nx:new-package): the Nx equivalent of theNewPackagerule. It scaffolds a conventional package with noproject.jsonand reads the manifest shape (script set, exports, publishConfig, pinned devDependencies) from an existing sibling so the scaffold can't drift. Verified: the generated package passedcheck, lint, fmt, test, buildimmediately with zero Nx-specific files..nxignore(keeps thevendor/jjsubmodule out of the file index),nx graph,nx watch(no config needed),nx migrateas the upgrade path.Measured numbers (this worktree, Apple Silicon)
check+fmt+circular— 123 tasks, 47 projectsbuild— 44 projectstest— 51 projects,--parallel=2nx affectedafter a one-linepackages/crypto/src/index.tschangenx affectedafter a one-linepackages/triggers(leaf) changenx release --dry-runcompletes end to end: resolves 0.1.0 from disk (no git tags exist yet), derives a minor bump from conventional commits, plans the fixed engine train at 0.1.1 with internal ranges retargeted, renders the workspace changelog, and skips publish.nx graph --filerenders the 52-project graph. The crate targets run (cargo-fmtverified locally).What does not work, precisely
@nx/eslintinference is not registered. Its inferred command is hard-coded toeslint .; these flat configs deliberately cover onlysrc, andeslint .fails on every package (parse errors oneslint.config.jsandscripts/*.mjs, which nofilesblock configures). The command can't be overridden without per-project files, solinttargets stay script-based. The Nx-specific lint value (boundaries) is wired directly instead.@nx/vitestatomizer runs one target per test file; packages withcoverage.enabled: trueenforce suite-level thresholds, so atomized runs fail their own coverage gate. The plugin also bakes the config'sreportsDirectoryinto outputs at graph time — these configs usejoin(tmpdir(), …-${process.pid}), producing escaping outputs like{workspaceRoot}/../../../../tmp/flows-core-coverage-54087with a dead PID.nx sync/@nx/js/typescriptdoesn't fit this workspace. The repo brief said project references are in use; they are not — zeroreferencesin any tsconfig, by design (source-first resolution). The sync generator's model is composite solution-style TS:nx sync:checkreports all 51 projects as missing root references whilenx syncwrites"references": [](its composite filter drops everything), leaving the gate permanently red. Wiring it for real means converting 45 packages to composite projects — a change to build semantics, not Nx config. The plugin is registered with inference disabled and the doc explains why.@nx/vite/@nx/vitestare scoped topackages/**. They boot configs through Node ESM at graph time; the apps use bundler-style extensionless imports (apps/ui/vite.config.ts→apps/shared/src/NativeAgent.tsimports./Cardswith no extension), which crashes graph construction. Apps keep script targets.wasm-reprois modeled withcache: falseon purpose — the gate proves a from-source rebuild reproduces the committed bytes on this host, and a remote cache hit from another host would mask exactly the nondeterminism it catches. It only runs onx86_64-unknown-linux-gnu(the script refuses other hosts), so on this Mac it is verified by configuration inspection, not execution.node_modulesis ambient. These are the three placesBUILD.tsis strictly more expressive, and the doc details each.Pre-existing issues this branch surfaced (not caused by it)
At the original branch point, two gates were red on
origin/mainitself, verified byte-identical on a pristine worktree:dprint checkfailed on 14 packages' READMEs, andpnpm exec smthrswas not found after a fresh install (the package rename had mangled thebuild-clibin name to"smithers build"while the docs-parity CI step still callssmthrs). Both were fixed onmainafter the branch point (39bd390drestores the bin,ceb784b6reformats the READMEs). This branch is rebased onto those fixes and re-verified:pnpm exec smthrs docs '//...'runs green,dprint checkpasses on the 14 formerly failing packages, and the first post-rebasenx run-many -t fmtre-ran exactly the 14 packages whose READMEs changed and hit the cache for the other 31 — theinputsmodel tracking reality.Still true and still worth knowing:
@smthrs/kernel⇄@smthrs/platform-browseris a real runtime dependency cycle (each lists the other independencies). pnpm tolerates it; Nx's^buildtask graph refused to schedule it. Builds here don't consume dependency artifacts, so the ordering edge is dropped and the cycle is allowlisted in the boundary rule with a comment.Verdict versus
BUILD.tsNx wins on change-impact analysis (
nx affectedis real and correct here), cache ergonomics, the graph as a visible product, boundaries-as-lint, and not having to operate a cache Worker. Our system wins on the typed toolchain as a value, generated root files as drift-checked graph outputs, declared secrets,node_modulesas a target, and type-checked target construction — with Nx, a wronginputsentry is a silent stale hit that nobody catches. The doc's migration section recommends adopting the nx.yml lane in advisory mode (the same pattern as the repo's ownsmthrs-shadowlane) and explicitly recommends against adopting solution-style references, test atomization, oreslint .inference unless the underlying repo conventions change first.🤖 Generated with Claude Code