mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
* fix(build): include shared UI stylesheets in Nx cache inputs `libs/ui/styles` held shared SCSS partials but had no `project.json`, so its files belonged to no Nx project and were absent from every task hash. Editing a partial and running `pnpm nx build web` reported 4 of 4 tasks cached and shipped the previous CSS — a silent wrong build rather than a failure. Nx derives its project graph from TypeScript imports only, so a relative Sass `@use` that crosses a project root creates no edge. Verified directly: after adding the project but before declaring anything, `ui-styles` still had zero dependents in the graph. Make it the `ui-styles` project (no targets — it exists to be hashed) and declare `implicitDependencies` on the 8 consumers. Chosen over adding the path to `sharedGlobals`, which would put shared styles into every project's hash and make a one-line SCSS tweak mark the whole workspace affected. A styles edit now marks 15 projects affected and leaves electron-backend, website, the mock servers and the shared libs alone. `libs/ui/styles` was the only projectless directory holding files under `libs/` or `apps/`. Add `pnpm run styles:inputs:validate` to keep it closed: it resolves every relative stylesheet import against Nx's real project graph and fails when one escapes the input closure of a build that compiles it, naming the project to declare. It exits 1 with 21 diagnostics on the pre-fix tree. Imports of `apps/web/src/nav-list.scss` are deliberately accepted — `web` already hashes that file, and a lib -> app edge would make the graph cyclic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(build): spawn git without a shell in the stylesheet check `execSync("git ls-files '*.scss'")` runs through `cmd.exe` on Windows, where single quotes are literal characters rather than quoting. Git received the pathspec with the quotes intact, matched nothing and exited 0, so `styles:inputs:validate` reported success after checking zero stylesheets — silently disabling the check for Windows developers while staying green. Spawn with `execFileSync` so no shell is involved and git expands its own pathspec; verified to return the identical 133 files. Both this and the eslint glob trap next to it in the docs report success while covering nothing, so also make an empty scan fail rather than pass: the workspace always contains SCSS, and a listing that returns none means the scan broke. Reported by Codex review on #1360. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(styles): move nav-list partial into ui-styles (#1361) * fix(build): count every target of a comma-separated Sass @import `@import` is the only rule that takes a list, and the scan read just its first target. A later entry crossing an Nx project boundary escaped the cache key while the check still reported success — the same silent-pass failure the tool exists to prevent. Parse every target of an `@import` list. The obvious "read all quoted strings" fix trades one silent gap for a phantom one, so the rule decides: `@use`/`@forward` load exactly one module and a quoted string after it is `with (...)` configuration, and `url(...)` stays a plain CSS import the browser resolves at runtime. Neither is a module Sass compiles. The workspace has no relative `@import` at all today, so the scan still finds the same 42 imports across 133 files; this closes the gap before someone writes one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
216 lines
9.1 KiB
Markdown
216 lines
9.1 KiB
Markdown
# Nx Workspace Boundaries
|
|
|
|
This document records the current monorepo placement, tagging, and validation
|
|
contract for IPTVnator. Nx discovery is the canonical project inventory; avoid
|
|
copying an exhaustive project list into documentation.
|
|
|
|
## Fresh Worktree Bootstrap and Discovery
|
|
|
|
Install dependencies before relying on Nx:
|
|
|
|
```bash
|
|
pnpm install --frozen-lockfile
|
|
pnpm nx show projects
|
|
```
|
|
|
|
`pnpm nx show projects` requires the workspace-local Nx packages in
|
|
`node_modules`. Inspect project ownership and available validation targets
|
|
before choosing commands:
|
|
|
|
```bash
|
|
pnpm nx show project <name>
|
|
pnpm nx show projects --withTarget test
|
|
pnpm nx show projects --withTarget e2e
|
|
```
|
|
|
|
Do not invent a `test`, `build`, or `e2e` target because a similarly named
|
|
project has one. Run affected lint/test/build targets that exist and the closest
|
|
available E2E target for the changed behavior.
|
|
|
|
## Nx Dependency Updates
|
|
|
|
Keep `nx` and every official `@nx/*` package on the same exact version. Run
|
|
`pnpm run deps:nx:validate` after any manifest or lockfile update; CI runs the
|
|
same policy check and rejects both direct specifier drift and multiple resolved
|
|
Nx versions.
|
|
|
|
Dependabot groups routine minor and patch Nx updates when possible. A security
|
|
update may still contain only the vulnerable package, so replace an incomplete
|
|
Dependabot PR with a coordinated maintainer update instead of editing the bot
|
|
branch:
|
|
|
|
```bash
|
|
pnpm nx migrate nx@<target> --skipInstall
|
|
pnpm install --no-frozen-lockfile
|
|
pnpm nx migrate --run-migrations
|
|
pnpm run deps:nx:validate
|
|
```
|
|
|
|
Omit `pnpm nx migrate --run-migrations` when the first command reports that no
|
|
migrations exist. Major Nx updates always use this manual workflow and the
|
|
resulting PR runs the full CI pipeline.
|
|
|
|
## Placement Decision
|
|
|
|
- `apps/` owns runtime applications, development servers, E2E applications,
|
|
and provider mock servers.
|
|
- `libs/` owns reusable code grouped by product domain and architectural role.
|
|
- `tools/` owns repository automation such as lint, packaging, release, and
|
|
repository-skill validation. Nx projects there use `scope:tools`.
|
|
|
|
Inside `libs/`, choose the role before the path:
|
|
|
|
- `type:feature` owns routes, screens, and feature orchestration.
|
|
- `type:ui` owns reusable visual components.
|
|
- `type:data-access` owns injectable state, API access, persistence, and
|
|
orchestration.
|
|
- `type:util` is the destination for new pure helpers and contracts only.
|
|
|
|
For example, provider-neutral collection services that coordinate favorites,
|
|
recents, EPG, or playback persistence belong in
|
|
`libs/portal/shared/data-access`. Pure collection types and transformations stay
|
|
in `libs/portal/shared/util`, while reusable collection views stay in
|
|
`libs/portal/shared/ui`. Existing injectable or stateful services in a `util`
|
|
path are legacy debt, not precedent for new placement.
|
|
|
|
## Project Tags
|
|
|
|
Every Nx project keeps one tag from each family in `project.json`:
|
|
|
|
1. `scope:*` records ownership, such as `scope:portal`, `scope:workspace`,
|
|
`scope:shared`, `scope:electron`, `scope:e2e`, or `scope:tools`.
|
|
2. `domain:*` records the product/runtime domain.
|
|
3. `type:*` records the architectural role.
|
|
|
|
`eslint.config.mjs` enforces these type directions:
|
|
|
|
| Source tag | Allowed dependency type tags |
|
|
| ------------------ | ------------------------------ |
|
|
| `type:app` | feature, UI, data-access, util |
|
|
| `type:e2e` | feature, UI, data-access, util |
|
|
| `type:dev-app` | feature, UI, data-access, util |
|
|
| `type:website` | UI, util |
|
|
| `type:feature` | feature, UI, data-access, util |
|
|
| `type:ui` | UI, data-access, util |
|
|
| `type:data-access` | data-access, util |
|
|
| `type:util` | util |
|
|
|
|
Domain constraints in the same rule are additive to type constraints. If an
|
|
import violates either family, move the contract or implementation to its
|
|
proper owner instead of weakening a constraint.
|
|
|
|
`workspace-shell-util` is a deliberate path/tag exception:
|
|
`libs/workspace/shell/util` is tagged `type:data-access` because it exports
|
|
injectable services that depend on `@iptvnator/services`. The web app imports
|
|
those services eagerly from `apps/web/src/app/app.routes.ts` without pulling
|
|
the lazy workspace shell feature into the initial bundle.
|
|
|
|
## Import Aliases and Public APIs
|
|
|
|
Use scoped aliases from `tsconfig.base.json` and expose public imports through a
|
|
library's `src/index.ts`. Do not introduce legacy bare aliases such as
|
|
`services`, `components`, `shared-interfaces`, or `database`, and avoid deep
|
|
imports unless a sub-entrypoint is explicitly configured.
|
|
|
|
For a buildable library that has a local `package.json`, its `name` must match
|
|
the scoped alias. Nx uses that package name when rewriting buildable dependency
|
|
paths to `dist/` during `@nx/js:tsc` builds.
|
|
|
|
## Shared Stylesheets and Cache Inputs
|
|
|
|
Nx derives the project graph from TypeScript imports. A relative Sass `@use`
|
|
that crosses a project root creates **no** graph edge, so without an explicit
|
|
declaration the imported partial belongs to no task's input set. The build then
|
|
reports a cache hit for a stylesheet edit and serves the previous CSS — a
|
|
silent wrong build rather than a failure.
|
|
|
|
Two rules keep that from happening:
|
|
|
|
1. A directory whose files are consumed by another project is itself an Nx
|
|
project. Shared partials live in `libs/ui/styles`, project `ui-styles`,
|
|
tagged `scope:shared`, `domain:shared-ui`, `type:ui`. It declares no targets;
|
|
it exists so its files are hashed.
|
|
2. Every consumer declares the dependency Nx cannot infer:
|
|
|
|
```json
|
|
"implicitDependencies": ["ui-styles"]
|
|
```
|
|
|
|
`@nx/enforce-module-boundaries` does not read stylesheets, so tag directions are
|
|
not enforced here — keep consumers at `type:feature` or `type:ui`, both of which
|
|
may depend on `type:ui`.
|
|
|
|
Importing a partial that the consuming **application** owns is a different case
|
|
and needs no declaration, because that partial already sits inside the app's own
|
|
build inputs. It is still the wrong direction, and it is the one case the two
|
|
rules above cannot repair: a lib → app edge would make the graph cyclic, since
|
|
the app already depends on those libraries. Move the partial into `ui-styles`
|
|
instead. No library stylesheet imports from `apps/` today — keep it that way.
|
|
|
|
`pnpm run styles:inputs:validate` enforces both rules. It resolves every
|
|
relative `@use`/`@forward`/`@import` in the workspace against Nx's own project
|
|
graph and fails when an imported stylesheet sits outside the input closure of a
|
|
build that compiles it, naming the project to declare. Comment-only example
|
|
paths are ignored, so the documentation blocks inside the shared partials do not
|
|
register as broken imports. CI runs it in the `unit-and-typecheck` job.
|
|
|
|
Only a module Sass actually compiles counts as an input. `@import` is the one
|
|
rule that takes a comma-separated list, and **every** target in it is a separate
|
|
dependency — reading just the first would let a later cross-project target
|
|
escape the cache key while the check still passed. A quoted string after the
|
|
module in `@use`/`@forward` belongs to a `with (...)` configuration and is a
|
|
value, and `url(...)` stays a plain CSS import the browser resolves at runtime;
|
|
neither is a build input, and treating either as one would report a phantom
|
|
broken import.
|
|
|
|
Verify a suspected caching gap directly — add a comment to a partial, run the
|
|
consuming build, and confirm the task runs instead of reporting a cache hit:
|
|
|
|
```bash
|
|
pnpm nx build web --verbose
|
|
```
|
|
|
|
## TypeScript File Size
|
|
|
|
`tools/eslint/max-lines-config.mjs` is the single source of truth:
|
|
|
|
- production TypeScript should stay below 300 lines and has a hard maximum of
|
|
400;
|
|
- tests, E2E specs, and E2E infrastructure have a maximum of 1200;
|
|
- blank lines and comments are not counted.
|
|
|
|
Pre-existing violations live in
|
|
`tools/eslint/max-lines-baseline.mjs`. That baseline may only shrink. After
|
|
splitting a baselined file, run
|
|
`node tools/eslint/generate-max-lines-baseline.mjs`; never add a new file to the
|
|
baseline. A genuinely inseparable new file needs a justified file-wide
|
|
directive, which the generator deliberately skips.
|
|
|
|
## Command-Based Lint Targets
|
|
|
|
Quote recursive globs so POSIX and Windows hosts lint the same files:
|
|
|
|
```bash
|
|
eslint "apps/<project>/**/*.ts"
|
|
find apps/<project> -name '*.ts' | wc -l
|
|
```
|
|
|
|
An unquoted `**` can expand to a shallow subset on POSIX while still returning
|
|
success. After editing such a target, compare ESLint's linted-file count with
|
|
the `find` count.
|
|
|
|
Repository tooling in `tools/` has the mirror-image trap: Node's `execSync`
|
|
runs through `cmd.exe` on Windows, where single quotes are literal characters
|
|
rather than quoting, so a POSIX-quoted pattern reaches the program intact and
|
|
matches nothing. Spawn without a shell — `execFileSync('git', ['ls-files',
|
|
'*.scss'])` — and let the program expand its own patterns. Both traps report
|
|
success while covering nothing, so a check that scans an empty file set must
|
|
fail rather than pass.
|
|
|
|
## CI Enforcement
|
|
|
|
The CI lint job runs affected projects on pull requests and all projects on
|
|
master pushes. Root config or lockfile changes affect every project, so module
|
|
boundaries, legacy-alias restrictions, and max-lines enforcement apply across
|
|
the workspace.
|