Files
iptvnator/docs/architecture/nx-workspace-boundaries.md
T
4grayandClaude Fable 5 e14b8ae8d9 feat(ci): enforce lint, guard coverage policy, add max-lines rule (#1117)
* fix(lint): resolve module-boundary and prefer-inject errors

Retag workspace-shell-util as type:data-access to match its injectable
services that depend on @iptvnator/services, and convert
RemoteControlService to inject(HttpClient).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(lint): enforce max-lines 400 with generated baseline

Add a max-lines ESLint error (hard cap 400 raw lines per TypeScript
file) per the repo file-size rule. The 134 pre-existing offenders are
baselined in tools/eslint/max-lines-baseline.mjs, regenerable via
generate-max-lines-baseline.mjs; the list should only shrink.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(ci): enforce lint on PRs and guard coverage policy drift

- Add a Lint job to ci.yml running nx run-many -t lint --all, so
  module-boundary tags, legacy-alias bans, and max-lines gate merges.
- Fix the root lint script (was linting only electron-backend).
- Add tools/coverage/check-coverage-policy.mjs: fails CI when a project
  with a test target is missing from coverage-policy.json; wired into
  coverage:ci as coverage:policy:check.
- Run Tier B/C unit tests in CI without coverage (list derived from the
  policy), so website/packaging/remote-control tests run on PRs.
- Replace the hand-picked 16-project test:unit:ci list with --all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: document CI lint enforcement and coverage policy guard

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): address bot review feedback on policy guard and baseline generator

- Drive Tier B/C validation from each policy entry's validationCommand
  (falling back to nx test), skipping projects with an e2e target since
  the E2E workflow already runs them (Codex).
- Fail when a Tier A entry has no test target (Greptile, adapted:
  checking all entries against test targets would false-positive on the
  intentionally spec-less e2e/mock-server tiers).
- Guard against missing JSON array in nx show projects output (Greptile).
- Scan .tsx files in the max-lines baseline generator to match the
  ESLint rule's file patterns (Greptile).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 10:06:45 +02:00

98 lines
3.5 KiB
Markdown

# Nx Workspace Boundaries
This document records the current monorepo boundary conventions for IPTVnator.
## Fresh Worktree Bootstrap
Install dependencies before using Nx discovery or targets:
```bash
pnpm install --frozen-lockfile
pnpm nx show projects
```
`pnpm nx show projects` depends on the workspace-local Nx packages under
`node_modules`. In a fresh worktree without dependencies it will fail before it
can inspect project metadata.
## Project Tags
Every Nx project should carry three tag families in `project.json`:
1. `scope:*` - ownership area, for example `scope:portal`, `scope:workspace`,
`scope:shared`, `scope:electron`, `scope:e2e`, or `scope:dev-tools`.
2. `domain:*` - product/runtime domain, for example `domain:xtream`,
`domain:stalker`, `domain:m3u`, `domain:playback`, `domain:web`, or
`domain:shared-runtime`.
3. `type:*` - architectural role, for example `type:app`, `type:e2e`,
`type:dev-app`, `type:feature`, `type:ui`, `type:data-access`,
`type:util`, `type:tool`, or `type:website`.
`eslint.config.mjs` uses these tags with `@nx/enforce-module-boundaries`.
When adding a project, choose tags before adding imports so dependency direction
is clear from the start.
## Import Aliases
Use scoped `@iptvnator/*` aliases from `tsconfig.base.json`.
Examples:
```ts
import { SettingsStore } from '@iptvnator/services';
import { Playlist } from '@iptvnator/shared/interfaces';
import { DialogService } from '@iptvnator/ui/components';
```
Do not introduce legacy bare aliases such as:
- `components`
- `m3u-state`
- `m3u-utils`
- `services`
- `shared-interfaces`
- `shared-portals`
- `remote-control`
- `database`
- `database-schema`
- `database-path-utils`
- `workspace-dashboard-feature`
- `workspace-dashboard-data-access`
The lint config blocks these aliases so new code uses the same visible
namespace and ownership convention.
Buildable libraries that have a local `package.json` should use the same public
name as their scoped alias. Nx uses `package.json.name` when it rewrites
buildable dependency paths to `dist/` during `@nx/js:tsc` builds.
## Dependency Direction
- `type:feature` may use `type:feature`, `type:ui`, `type:data-access`, and
`type:util`.
- `type:ui` may use `type:ui`, `type:data-access`, and `type:util`.
- `type:data-access` may use `type:data-access` and `type:util`.
- `type:util` may use only `type:util`.
If a change needs a dependency in the opposite direction, move the shared
contract into a lower-level library instead of weakening boundaries.
Portal collection orchestration that reads/writes favorites, recent items, live
playback, or EPG data belongs in `libs/portal/shared/data-access`, not
`libs/portal/shared/util`. That keeps pure collection helpers importable by
Xtream/Stalker data-access libraries while allowing shared UI to use
provider-specific collection services without creating cycles.
Note: `workspace-shell-util` (`libs/workspace/shell/util`) is tagged
`type:data-access` despite its path. It exports injectable services such as
`WorkspaceStartupPreferencesService` that depend on `@iptvnator/services`, and
it must stay eagerly importable from `apps/web/src/app/app.routes.ts` without
pulling the lazy-loaded workspace shell feature bundle into the initial chunk.
## CI Enforcement
The `Lint` job in `.github/workflows/ci.yml` runs
`pnpm nx run-many --target=lint --all` on every PR, so
`@nx/enforce-module-boundaries` violations, legacy bare-alias imports, and
`max-lines` violations fail CI. Run `pnpm run lint` locally before pushing.