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

3.5 KiB

Nx Workspace Boundaries

This document records the current monorepo boundary conventions for IPTVnator.

Fresh Worktree Bootstrap

Install dependencies before using Nx discovery or targets:

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:

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.