chore(nx): enforce scoped workspace boundaries

This commit is contained in:
4gray committed 2026-05-15 02:28:12 +02:00
1 parent 8f79b25f52
commit 6de47d4b82
338 files changed
+925 -589

No files matched your search

@@ -0,0 +1,44 @@
---
name: iptvnator-nx-architecture
description: Repository-specific Nx monorepo structure, library placement rules, scoped path aliases, and migration guardrails for portal/workspace/app code.
---
# IPTVnator Nx Architecture
Use this skill when deciding where code belongs, extracting libraries, changing imports, editing project tags, or refactoring portal/workspace/app boundaries.
## Project Shape
- `apps/web`: Angular renderer application.
- `apps/electron-backend`: Electron main process and native/runtime integration.
- `apps/*-e2e`: Playwright E2E projects.
- `apps/*-mock-server`: local development and E2E mock servers.
- `libs/playlist/*`: M3U/import/shared playlist functionality.
- `libs/portal/*`: Xtream, Stalker, and provider-neutral portal functionality.
- `libs/workspace/*`: workspace shell and dashboard.
- `libs/ui/*`: provider-neutral UI, playback, EPG, remote control, and pipes.
- `libs/shared/*`: contracts, database, and pure utility code.
## Import Policy
- Use scoped aliases from `tsconfig.base.json`, for example `@iptvnator/services` and `@iptvnator/shared/interfaces`.
- Do not reintroduce legacy bare aliases such as `services`, `components`, or `shared-interfaces`.
- Prefer library public APIs (`src/index.ts`) over deep imports unless a sub-entrypoint is explicitly configured.
- Keep app-only code in `apps/*`; shared behavior belongs in a domain library.
## Tag Policy
- Every project should have `scope:*`, `domain:*`, and `type:*` tags.
- Use `type:feature` for route/component orchestration, `type:data-access` for stores/API/persistence, `type:ui` for reusable components, and `type:util` for pure helpers/contracts.
- Keep `type:data-access` from depending on `type:feature` or `type:ui`.
- Add or update `@nx/enforce-module-boundaries` constraints when introducing new tag families.
## Validation
```bash
pnpm nx show projects
pnpm nx lint <project>
pnpm nx test <project>
```
When dependencies are not installed in a fresh worktree, run `pnpm install --frozen-lockfile` first.
@@ -0,0 +1,31 @@
---
name: iptvnator-sqlite-db-worker
description: Repository-specific guidance for Electron non-EPG SQLite worker boundaries, request-scoped DB progress events, and validation for slow DB operations.
---
# IPTVnator SQLite DB Worker
Use this skill when changing Electron SQLite operations, worker-backed database flows, DB progress events, or Xtream/playlist import/search/delete persistence.
## Ownership
- Runtime worker client: `apps/electron-backend/src/app/services/database-worker-client.ts`
- Worker protocol: `apps/electron-backend/src/app/workers/database-worker.types.ts`
- Worker dispatcher: `apps/electron-backend/src/app/workers/database.worker.ts`
- SQL operation modules: `apps/electron-backend/src/app/database/operations/`
- Shared schema/path helpers: `libs/shared/database/src/`
- Architecture doc: `docs/architecture/sqlite-db-worker.md`
## Rules
- Keep heavy non-EPG SQLite work off the Electron main thread.
- Keep SQL-heavy logic in operation modules; keep the worker entrypoint as dispatcher/orchestration.
- Emit request-scoped `DB_OPERATION_EVENT` progress for long-running operations.
- Preserve cancellation as cooperative and chunk-based.
- Do not break existing preload API method names without a coordinated renderer migration.
## Validation
- Run `pnpm nx test electron-backend` for worker/client/operation changes.
- Run targeted Electron E2E for import, search, delete, backup/restore, or downloads flows when touched.
- Use `IPTVNATOR_TRACE_DB=1` or `IPTVNATOR_TRACE_SQL=1` for manual debugging.
@@ -0,0 +1,31 @@
---
name: iptvnator-theme-style
description: Theme architecture, design tokens, shared SCSS library, portal header/sidebar patterns, Electron drag regions, and cross-portal style consistency.
---
# IPTVnator Theme Style
Use this skill when changing SCSS tokens, shared layout mixins, portal headers, sidebars, detail views, or Electron draggable regions.
## Shared Style Sources
- `libs/ui/styles/_index.scss`
- `libs/ui/styles/_portal-layout.scss`
- `libs/ui/styles/_content-grid.scss`
- `libs/ui/styles/_portal-sidebar.scss`
- `libs/ui/styles/_panel-header.scss`
- `libs/ui/styles/_detail-view-actions.scss`
- `docs/architecture/iptvnator-ui-guidelines.md`
## Rules
- Prefer shared SCSS mixins and Material system tokens over local hard-coded colors.
- Keep Electron drag behavior explicit: interactive controls need `app-region: no-drag`.
- Do not duplicate large SCSS files across feature libraries; extract a shared partial or mixin.
- Use relative SCSS imports only when no stable shared entrypoint exists.
- Check Xtream, Stalker, M3U, and workspace views for cross-portal style drift when editing shared patterns.
## Validation
- Run lint/tests for the affected UI project.
- Manually inspect the changed screen in light and dark themes for layout, selection, and drag-region regressions.
@@ -0,0 +1,29 @@
---
name: iptvnator-ui-design
description: Repository-specific UI design guidance for IPTVnator channel rows, EPG views, settings surfaces, shared selection styles, and light/dark theme consistency.
---
# IPTVnator UI Design
Use this skill when changing user-visible Angular UI in the IPTVnator app, especially channel lists, EPG panels, settings, playlist surfaces, and portal shared components.
## Principles
- Prefer dense, scannable application UI over marketing-style layouts.
- Preserve existing Material 3 token usage and light/dark theme behavior.
- Keep repeated selection states aligned with existing `--app-selection-*` tokens.
- Use existing shared UI components before adding local one-off markup.
- Avoid large visual rewrites in behavior-focused changes.
## Checklist
1. Inspect the nearest existing component and shared UI library before editing.
2. Check both light and dark theme styles when touching colors, borders, hover states, or selection states.
3. Keep text and controls within fixed-width rows from resizing the layout.
4. Prefer existing components from `@iptvnator/ui/components`, `@iptvnator/portal/shared/ui`, and `@iptvnator/ui/epg`.
5. Add or update focused component tests for changed interaction states.
## Validation
- Run the affected Nx project test target.
- For visible workflow changes, run the closest Playwright E2E target or manually verify through Electron CDP as documented in `AGENTS.md`.
+30
View File
@@ -0,0 +1,30 @@
---
name: xtream-electron
description: IPTVnator's Electron-first Xtream implementation, including feature/data-access boundaries, worker-backed DB flows, and Xtream loading/progress UX.
---
# Xtream Electron
Use this skill when working on Xtream routes, stores, data sources, import/search/delete behavior, or Electron-backed Xtream playback and persistence.
## Key Areas
- Feature UI: `libs/portal/xtream/feature/src/lib/`
- Data access: `libs/portal/xtream/data-access/src/lib/`
- Shared portal utilities: `libs/portal/shared/util/src/lib/`
- Shared portal UI: `libs/portal/shared/ui/src/lib/`
- Electron DB events: `apps/electron-backend/src/app/events/database/`
- DB worker operations: `apps/electron-backend/src/app/database/operations/`
## Rules
- Keep provider-specific API/cache behavior in `portal/xtream/data-access`.
- Keep reusable layout and collection UI in `portal/shared/ui` or `portal/shared/util`.
- Prefer worker-backed DB operations for large imports, global search, delete, and restore.
- Preserve request cancellation and progress reporting for long imports.
- Validate both PWA and Electron data-source paths when changing Xtream APIs.
## Validation
- Run `pnpm nx test portal-xtream-data-access` and `pnpm nx test portal-xtream-feature` for store/UI changes.
- Run `pnpm nx run web-e2e:e2e-ci--src/xtream.e2e.ts` or the closest Electron E2E target for user-visible Xtream workflow changes.
+3 -1
View File
@@ -73,7 +73,9 @@ Thumbs.db
.cursor
.agent
.claude
.codex
.codex/*
!.codex/skills/
!.codex/skills/**
.plans
# Native Electron addons
+10 -1
View File
@@ -9,6 +9,15 @@ This file provides guidance to coding agents working in this repository.
- Use the filename pattern `YYYY-MM-DD-short-topic.md` such as `.plans/2026-03-12-channel-filtering.md`.
- If the intended filename already exists, append a numeric suffix such as `-2`, `-3`, and so on.
## Agent Bootstrap
- In a fresh worktree, run `pnpm install --frozen-lockfile` before relying on Nx project discovery, lint, test, or build commands. Without `node_modules`, `pnpm nx show projects` will fail because the local Nx modules are unavailable.
- After dependencies are installed, verify workspace discovery with `pnpm nx show projects`.
- Use scoped path aliases from `tsconfig.base.json` such as `@iptvnator/services`, `@iptvnator/shared/interfaces`, and `@iptvnator/ui/components`. Do not add new imports from legacy bare aliases such as `services`, `shared-interfaces`, `components`, `m3u-state`, or `database`.
- Every Nx project should keep `scope:*`, `domain:*`, and `type:*` tags in `project.json` so `@nx/enforce-module-boundaries` remains useful for humans and agents.
- See `docs/architecture/nx-workspace-boundaries.md` for the current Nx tag and alias policy.
- Repository-specific skills are committed under `.codex/skills/`. If an external agent does not support skills, treat those files as concise ownership docs.
## Documentation After Changes
- After implementing a meaningful change, agents must assess whether canonical repo docs need updates before considering the task complete.
@@ -150,7 +159,7 @@ Key files:
## General Guidelines for working with Nx
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first - it has patterns for querying projects, targets, and dependencies
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first when it is available - it has patterns for querying projects, targets, and dependencies. If it is unavailable, use `pnpm nx show projects`, `pnpm nx graph`, and project `project.json` files directly.
- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly
- Prefix nx commands with the workspace's package manager (e.g., `pnpm nx build`, `npm exec nx test`) - avoids using globally installed CLI
- You have access to the Nx MCP server and its tools, use them to help the user
+15 -1
View File
@@ -45,6 +45,20 @@ IPTVnator is a cross-platform IPTV player application built with Angular and Ele
## Development Commands
### Agent Bootstrap
```bash
pnpm install --frozen-lockfile
pnpm nx show projects
```
- Run the install step in a fresh worktree before relying on Nx discovery, lint, test, or build commands. Without `node_modules`, local Nx modules are unavailable.
- Use scoped path aliases from `tsconfig.base.json` such as `@iptvnator/services`, `@iptvnator/shared/interfaces`, and `@iptvnator/ui/components`.
- Do not add new imports from legacy bare aliases such as `services`, `shared-interfaces`, `components`, `m3u-state`, or `database`.
- Every Nx project should keep `scope:*`, `domain:*`, and `type:*` tags in `project.json`.
- See `docs/architecture/nx-workspace-boundaries.md` for the current Nx tag and alias policy.
- Repository-specific skills are committed under `.codex/skills/`. If Claude Code does not load skills directly, treat those files as concise ownership docs.
### Building and Serving
```bash
@@ -689,7 +703,7 @@ No formal migration system yet. Schema changes are applied via raw SQL in `conne
## General Guidelines for working with Nx
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first - it has patterns for querying projects, targets, and dependencies
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first when it is available - it has patterns for querying projects, targets, and dependencies. If it is unavailable, use `pnpm nx show projects`, `pnpm nx graph`, and project `project.json` files directly.
- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly
- Prefix nx commands with the workspace's package manager (e.g., `pnpm nx build`, `npm exec nx test`) - avoids using globally installed CLI
- You have access to the Nx MCP server and its tools, use them to help the user
+2 -1
View File
@@ -12,5 +12,6 @@
"e2e": {
"dependsOn": ["electron-backend:build-e2e"]
}
}
},
"tags": ["scope:e2e", "domain:electron-main", "type:e2e"]
}
+5 -5
View File
@@ -77,23 +77,23 @@ async function buildWorker() {
sourcemap: !isProduction,
minify: isProduction,
alias: {
'shared-interfaces': path.join(
'@iptvnator/shared/interfaces': path.join(
__dirname,
'../../libs/shared/interfaces/src/index.ts'
),
'm3u-utils': path.join(
'@iptvnator/shared/m3u-utils': path.join(
__dirname,
'../../libs/shared/m3u-utils/src/index.ts'
),
'database': path.join(
'@iptvnator/shared/database': path.join(
__dirname,
'../../libs/shared/database/src/index.ts'
),
'database-schema': path.join(
'@iptvnator/shared/database/schema': path.join(
__dirname,
'../../libs/shared/database/src/lib/schema.ts'
),
'database-path-utils': path.join(
'@iptvnator/shared/database/path-utils': path.join(
__dirname,
'../../libs/shared/database/src/lib/path-utils.ts'
),
+2 -1
View File
@@ -179,5 +179,6 @@
"jestConfig": "apps/electron-backend/jest.config.ts"
}
}
}
},
"tags": ["scope:electron", "domain:electron-main", "type:app"]
}
@@ -9,7 +9,7 @@ import type {
PlaylistRefreshPayload,
ResolvedPortalPlayback,
XtreamCategory,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import {
DEBUG_TRACE_EVENT_CHANNEL,
isRendererApiTraceEnabled,
@@ -1,4 +1,4 @@
import { __databaseConnectionTestHooks } from 'database';
import { __databaseConnectionTestHooks } from '@iptvnator/shared/database';
function statementContaining(pattern: string): string {
const statement =
@@ -10,5 +10,5 @@ export {
closeDatabase,
type DatabaseInstance,
type DatabaseOptions,
} from 'database';
} from '@iptvnator/shared/database';
@@ -1,4 +1,4 @@
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
export type AppDatabase = BetterSQLite3Database<typeof schema>;
@@ -1,5 +1,5 @@
import type { AppDatabase } from '../database.types';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import { getCategories, saveCategories } from './category.operations';
function createDbMock(existingCount = 0) {
@@ -1,5 +1,5 @@
import { and, eq, inArray, sql } from 'drizzle-orm';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
type XtreamCategoryInput = {
@@ -20,7 +20,7 @@ jest.mock('drizzle-orm', () => ({
sqlMock(strings, ...values),
}));
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
import { setContentBackdropIfMissing } from './content-backdrop.operations';
@@ -1,5 +1,5 @@
import { and, eq, sql } from 'drizzle-orm';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
export async function persistContentBackdropIfMissing(
@@ -18,7 +18,7 @@ jest.mock('drizzle-orm', () => ({
sql: jest.fn(),
}));
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
import { getContentByXtreamId, saveContent } from './content.operations';
@@ -1,5 +1,5 @@
import { and, asc, desc, eq, inArray, or, sql } from 'drizzle-orm';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
import {
checkpointOperation,
@@ -1,5 +1,5 @@
import { and, asc, desc, eq, inArray, sql } from 'drizzle-orm';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
import {
chunkValues,
@@ -1,5 +1,5 @@
import { and, desc, eq, sql } from 'drizzle-orm';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
type PlaylistType = 'xtream' | 'stalker' | 'm3u-file' | 'm3u-text' | 'm3u-url';
@@ -1,5 +1,5 @@
import { eq, inArray } from 'drizzle-orm';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
import {
checkpointOperation,
@@ -1,5 +1,5 @@
import { and, desc, eq, inArray, sql } from 'drizzle-orm';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type { AppDatabase } from '../database.types';
import { persistContentBackdropIfMissing } from './content-backdrop.operations';
@@ -1,10 +1,10 @@
import { and, eq, inArray } from 'drizzle-orm';
import * as schema from 'database-schema';
import * as schema from '@iptvnator/shared/database/schema';
import type {
XtreamBackupFavoriteItem,
XtreamBackupHiddenCategory,
XtreamBackupRecentlyViewedItem,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import type { AppDatabase } from '../database.types';
import {
checkpointOperation,
@@ -36,4 +36,4 @@ export {
type NewPlaybackPosition,
type Download,
type NewDownload,
} from 'database';
} from '@iptvnator/shared/database';
@@ -20,7 +20,7 @@ import {
EmbeddedMpvBounds,
EmbeddedMpvRecordingStartOptions,
ResolvedPortalPlayback,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import {
EmbeddedMpvNativeService,
embeddedMpvNativeService,
@@ -1,7 +1,7 @@
import { and, eq, gte, inArray, lte, sql } from 'drizzle-orm';
import { app, BrowserWindow, ipcMain } from 'electron';
import * as path from 'path';
import { EpgChannelMetadata, EpgProgram } from 'shared-interfaces';
import { EpgChannelMetadata, EpgProgram } from '@iptvnator/shared/interfaces';
import { pathToFileURL } from 'url';
import { Worker } from 'worker_threads';
import { getDatabase } from '../database/connection';
@@ -1,4 +1,4 @@
import { ExternalPlayerSession } from 'shared-interfaces';
import { ExternalPlayerSession } from '@iptvnator/shared/interfaces';
import { ExternalPlayerSessionRegistry } from './external-player-session-registry';
describe('ExternalPlayerSessionRegistry', () => {
@@ -2,7 +2,7 @@ import {
ExternalPlayerName,
ExternalPlayerSession,
PlayerContentInfo,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
interface CreateExternalPlayerSessionOptions {
player: ExternalPlayerName;
@@ -6,7 +6,7 @@ import {
ExternalPlayerSession,
parseExternalPlayerArguments,
type ExternalPlayerArgumentsInput,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import App from '../app';
import {
MPV_PLAYER_ARGUMENTS,
@@ -6,7 +6,7 @@
import axios from 'axios';
import { app, dialog, ipcMain, WebContents } from 'electron';
import { parse } from 'iptv-playlist-parser';
import { createPlaylistObject, getFilenameFromUrl } from 'm3u-utils';
import { createPlaylistObject, getFilenameFromUrl } from '@iptvnator/shared/m3u-utils';
import { readFile, writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { pathToFileURL } from 'url';
@@ -19,7 +19,7 @@ import {
Playlist,
PlaylistRefreshEvent,
PlaylistRefreshPayload,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import { resolveWorkerRuntimeBootstrap } from '../workers/worker-runtime-paths';
import type {
PlaylistRefreshWorkerMessage,
@@ -1,5 +1,5 @@
import { BrowserWindow } from 'electron';
import { PORTAL_DEBUG_EVENT, PortalDebugEvent } from 'shared-interfaces';
import { PORTAL_DEBUG_EVENT, PortalDebugEvent } from '@iptvnator/shared/interfaces';
import { environment } from '../../environments/environment';
const MAX_DEBUG_DEPTH = 6;
@@ -1,5 +1,5 @@
import { ipcMain } from 'electron';
import { normalizeExternalPlayerArguments } from 'shared-interfaces';
import { normalizeExternalPlayerArguments } from '@iptvnator/shared/interfaces';
import {
MPV_PLAYER_ARGUMENTS,
MPV_REUSE_INSTANCE,
@@ -6,7 +6,7 @@
import axios, { AxiosRequestConfig } from 'axios';
import { createHash } from 'crypto';
import { ipcMain } from 'electron';
import { PortalDebugEvent, STALKER_REQUEST } from 'shared-interfaces';
import { PortalDebugEvent, STALKER_REQUEST } from '@iptvnator/shared/interfaces';
import { rememberStalkerPlaybackContext } from '../services/stalker-playback-context.service';
import { emitPortalDebugEvent } from './portal-debug.events';
@@ -1,4 +1,4 @@
import { XTREAM_CANCEL_SESSION } from 'shared-interfaces';
import { XTREAM_CANCEL_SESSION } from '@iptvnator/shared/interfaces';
const registeredHandlers = new Map<string, (...args: unknown[]) => unknown>();
const axiosMock = Object.assign(jest.fn(), {
@@ -5,7 +5,7 @@
import axios, { AxiosRequestConfig } from 'axios';
import { ipcMain } from 'electron';
import { PortalDebugEvent, XTREAM_CANCEL_SESSION } from 'shared-interfaces';
import { PortalDebugEvent, XTREAM_CANCEL_SESSION } from '@iptvnator/shared/interfaces';
import { emitPortalDebugEvent } from './portal-debug.events';
export default class XtreamEvents {
@@ -2,7 +2,7 @@ import type {
EmbeddedMpvBounds,
EmbeddedMpvSessionStatus,
ResolvedPortalPlayback,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import path from 'path';
@@ -22,7 +22,7 @@ import {
EmbeddedMpvSupport,
EMBEDDED_MPV_SESSION_UPDATE,
ResolvedPortalPlayback,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
interface NativeEmbeddedMpvSessionSnapshot {
status: EmbeddedMpvSessionStatus;
@@ -1,5 +1,5 @@
import { Conf } from 'electron-conf/main';
import { getElectronConfigDirectory } from 'database';
import { getElectronConfigDirectory } from '@iptvnator/shared/database';
export const WINDOW_BOUNDS = 'WINDOW_BOUNDS';
export const MPV_PLAYER_PATH = 'MPV_PLAYER_PATH';
@@ -1,6 +1,6 @@
import type BetterSqlite3 from 'better-sqlite3';
import * as schema from 'database-schema';
import { getIptvnatorDatabasePath } from 'database-path-utils';
import * as schema from '@iptvnator/shared/database/schema';
import { getIptvnatorDatabasePath } from '@iptvnator/shared/database/path-utils';
import { workerData } from 'worker_threads';
import type { AppDatabase } from '../database/database.types';
import {
@@ -6,7 +6,7 @@ import { parentPort } from 'worker_threads';
import type {
XtreamBackupFavoriteItem,
XtreamBackupRecentlyViewedItem,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import type {
DbOperationEvent,
DbWorkerIncomingMessage,
@@ -1,6 +1,6 @@
import type BetterSqlite3 from 'better-sqlite3';
import { existsSync, mkdirSync } from 'fs';
import { getIptvnatorDatabasePath } from 'database-path-utils';
import { getIptvnatorDatabasePath } from '@iptvnator/shared/database/path-utils';
import { Readable } from 'stream';
import { parentPort, workerData } from 'worker_threads';
import { createGunzip } from 'zlib';
@@ -2,12 +2,12 @@ import axios from 'axios';
import { parentPort } from 'worker_threads';
import { parse } from 'iptv-playlist-parser';
import { readFile } from 'node:fs/promises';
import { createPlaylistObject, getFilenameFromUrl } from 'm3u-utils';
import { createPlaylistObject, getFilenameFromUrl } from '@iptvnator/shared/m3u-utils';
import type {
Playlist,
PlaylistRefreshEvent,
PlaylistRefreshPayload,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import type {
PlaylistRefreshWorkerIncomingMessage,
PlaylistRefreshWorkerMessage,
@@ -1,4 +1,4 @@
import type { PlaylistRefreshEvent, PlaylistRefreshPayload } from 'shared-interfaces';
import type { PlaylistRefreshEvent, PlaylistRefreshPayload } from '@iptvnator/shared/interfaces';
export interface PlaylistRefreshWorkerRequestMessage {
type: 'request';
+1 -1
View File
@@ -1,5 +1,5 @@
import { app, BrowserWindow } from 'electron';
import { getElectronUserDataPath } from 'database';
import { getElectronUserDataPath } from '@iptvnator/shared/database';
import fixPath from 'fix-path';
import App from './app/app';
import { initDatabase } from './app/database/connection';
+1 -1
View File
@@ -9,7 +9,7 @@
"enabled": false
}
},
"tags": [],
"tags": ["scope:remote-control", "domain:playback", "type:app"],
"targets": {
"build": {
"executor": "@angular/build:application",
+1 -1
View File
@@ -1,5 +1,5 @@
import { Component, OnInit, inject } from '@angular/core';
import { RemoteControlComponent } from 'remote-control';
import { RemoteControlComponent } from '@iptvnator/ui/remote-control';
import { TranslateService } from '@ngx-translate/core';
@Component({
+1 -1
View File
@@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/stalker-mock-server/src",
"tags": ["scope:dev-tools"],
"tags": ["scope:dev-tools", "domain:stalker", "type:dev-app"],
"targets": {
"serve": {
"continuous": true,
+2 -1
View File
@@ -11,5 +11,6 @@
"// targets": "to see all targets run: nx show project web-e2e --web",
"targets": {
"e2e": {}
}
},
"tags": ["scope:e2e", "domain:web", "type:e2e"]
}
+1 -1
View File
@@ -9,7 +9,7 @@
"enabled": false
}
},
"tags": ["scope:app", "type:app"],
"tags": ["scope:app", "domain:web", "type:app"],
"targets": {
"build": {
"executor": "@angular/build:application",
+3 -3
View File
@@ -8,7 +8,7 @@ import { EpgService } from '@iptvnator/epg/data-access';
import { WORKSPACE_SHELL_ACTIONS } from '@iptvnator/workspace/shell/util';
import { MockProvider } from 'ng-mocks';
import { EMPTY, of } from 'rxjs';
import { DataService } from 'services';
import { DataService } from '@iptvnator/services';
import {
Language,
Settings,
@@ -17,8 +17,8 @@ import {
StreamFormat,
Theme,
VideoPlayer,
} from 'shared-interfaces';
import { PlaylistActions } from 'm3u-state';
} from '@iptvnator/shared/interfaces';
import { PlaylistActions } from '@iptvnator/m3u-state';
import { AppComponent } from './app.component';
import { ElectronServiceStub } from './services/electron.service.stub';
import { SettingsService } from './services/settings.service';
+3 -3
View File
@@ -7,9 +7,9 @@ import { TranslateService } from '@ngx-translate/core';
import { EpgService } from '@iptvnator/epg/data-access';
import { WORKSPACE_SHELL_ACTIONS } from '@iptvnator/workspace/shell/util';
import { EpgProgressPanelComponent } from '@iptvnator/ui/epg/progress-panel';
import { PlaylistActions, selectAllPlaylistsMeta } from 'm3u-state';
import { PlaylistActions, selectAllPlaylistsMeta } from '@iptvnator/m3u-state';
import { filter, take } from 'rxjs';
import { DataService, SettingsStore } from 'services';
import { DataService, SettingsStore } from '@iptvnator/services';
import {
AUTO_UPDATE_PLAYLISTS,
Language,
@@ -17,7 +17,7 @@ import {
Settings,
STORE_KEY,
Theme,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import { SettingsService } from './services/settings.service';
@Component({
+3 -3
View File
@@ -18,7 +18,7 @@ import { provideStore } from '@ngrx/store';
import { provideStoreDevtools } from '@ngrx/store-devtools';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { PlaylistEffects, playlistReducer } from 'm3u-state';
import { PlaylistEffects, playlistReducer } from '@iptvnator/m3u-state';
import { NgxIndexedDBModule, NgxIndexedDBService } from 'ngx-indexed-db';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import {
@@ -27,8 +27,8 @@ import {
} from '@iptvnator/portal/shared/util';
import { PLAYLIST_PLAYER_ACTIONS } from '@iptvnator/playlist/shared/util';
import { provideXtreamDataSource } from '@iptvnator/portal/xtream/data-access';
import { DataService } from 'services';
import { dbConfig } from 'shared-interfaces';
import { DataService } from '@iptvnator/services';
import { dbConfig } from '@iptvnator/shared/interfaces';
import { AppConfig } from '../environments/environment';
import { routes } from './app.routes';
import { ElectronService } from './services/electron.service';
+1 -1
View File
@@ -40,7 +40,7 @@ export const routes: Routes = [
path: 'dashboard',
canActivate: [dashboardAccessGuard],
loadComponent: () =>
import('workspace-dashboard-feature').then(
import('@iptvnator/workspace/dashboard/feature').then(
(c) => c.WorkspaceDashboardRailsComponent
),
},
@@ -1,7 +1,7 @@
import { Component, signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ResolvedPortalPlayback } from 'shared-interfaces';
import { ResolvedPortalPlayback } from '@iptvnator/shared/interfaces';
import { EmbeddedMpvOverlayVisibilityService } from '../../../../libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-overlay-visibility.service';
import { EmbeddedMpvPlayerComponent } from '../../../../libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component';
import { EmbeddedMpvSessionController } from '../../../../libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-session-controller';
@@ -14,7 +14,7 @@ import {
} from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { selectAllPlaylistsMeta } from 'm3u-state';
import { selectAllPlaylistsMeta } from '@iptvnator/m3u-state';
import { EmptyStateComponent } from '@iptvnator/playlist/shared/ui';
import { PlaylistContextFacade } from '@iptvnator/playlist/shared/util';
import {
@@ -2,4 +2,4 @@ export {
DownloadsService,
type DownloadItem,
type DownloadStatus,
} from 'services';
} from '@iptvnator/services';
@@ -2,8 +2,8 @@ import { inject, Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { PlaylistActions } from 'm3u-state';
import { DataService } from 'services';
import { PlaylistActions } from '@iptvnator/m3u-state';
import { DataService } from '@iptvnator/services';
import {
AUTO_UPDATE_PLAYLISTS,
ERROR,
@@ -13,7 +13,7 @@ import {
XTREAM_REQUEST,
XTREAM_RESPONSE,
XtreamCodeActions,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import { AppConfig } from '../../environments/environment';
import {
createPortalDebugRequestContext,
@@ -1,4 +1,4 @@
import { ExternalPlayerSession } from 'shared-interfaces';
import { ExternalPlayerSession } from '@iptvnator/shared/interfaces';
import { ExternalPlaybackService } from './external-playback.service';
describe('ExternalPlaybackService', () => {
@@ -1,5 +1,5 @@
import { computed, Injectable, signal } from '@angular/core';
import { ExternalPlayerSession, PlayerContentInfo } from 'shared-interfaces';
import { ExternalPlayerSession, PlayerContentInfo } from '@iptvnator/shared/interfaces';
@Injectable({
providedIn: 'root',
@@ -1,7 +1,7 @@
import { TestBed } from '@angular/core/testing';
import { MatDialog } from '@angular/material/dialog';
import { DataService } from 'services';
import { ExternalPlayerSession, VideoPlayer } from 'shared-interfaces';
import { DataService } from '@iptvnator/services';
import { ExternalPlayerSession, VideoPlayer } from '@iptvnator/shared/interfaces';
import { SettingsStore } from './settings-store.service';
import { PlayerService } from './player.service';
+3 -3
View File
@@ -1,7 +1,7 @@
import { Injectable, inject } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ExternalPlayerInfoDialogComponent } from '@iptvnator/ui/playback/external-player-info-dialog';
import { DataService } from 'services';
import { DataService } from '@iptvnator/services';
import {
ExternalPlayerSession,
OPEN_MPV_PLAYER,
@@ -9,8 +9,8 @@ import {
PlayerContentInfo,
ResolvedPortalPlayback,
VideoPlayer,
} from 'shared-interfaces';
import type { ExternalPlayerName } from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import type { ExternalPlayerName } from '@iptvnator/shared/interfaces';
import { SettingsStore } from './settings-store.service';
@Injectable({
@@ -6,7 +6,7 @@ import {
PortalNavigationActions,
} from '@iptvnator/portal/shared/util';
import { XtreamStore } from '@iptvnator/portal/xtream/data-access';
import { Playlist } from 'shared-interfaces';
import { Playlist } from '@iptvnator/shared/interfaces';
import { SettingsComponent } from '../settings/settings.component';
@Injectable({ providedIn: 'root' })
+3 -3
View File
@@ -4,9 +4,9 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { SwUpdate } from '@angular/service-worker';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { PlaylistActions } from 'm3u-state';
import { PlaylistActions } from '@iptvnator/m3u-state';
import { catchError, firstValueFrom, throwError } from 'rxjs';
import { DataService } from 'services';
import { DataService } from '@iptvnator/services';
import {
ERROR,
Playlist,
@@ -16,7 +16,7 @@ import {
XtreamCodeActions,
XTREAM_REQUEST,
XTREAM_RESPONSE,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import { AppConfig } from '../../environments/environment';
import {
createPortalDebugErrorEvent,
@@ -1 +1 @@
export { SettingsStore } from 'services';
export { SettingsStore } from '@iptvnator/services';
@@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import { StorageMap } from '@ngx-pwa/local-storage';
import { of } from 'rxjs';
import { STORE_KEY, Theme } from 'shared-interfaces';
import { STORE_KEY, Theme } from '@iptvnator/shared/interfaces';
import { SettingsService } from './settings.service';
describe('Service: Settings', () => {
@@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { StorageMap } from '@ngx-pwa/local-storage';
import { catchError, map, Observable } from 'rxjs';
import { STORE_KEY, Theme } from 'shared-interfaces';
import { STORE_KEY, Theme } from '@iptvnator/shared/interfaces';
const PRERELEASE_KEYWORDS = [
'beta',
@@ -6,7 +6,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { CoverSize, Language, Theme } from 'shared-interfaces';
import { CoverSize, Language, Theme } from '@iptvnator/shared/interfaces';
import {
CoverSizeOption,
StartupBehaviorOption,
@@ -3,7 +3,7 @@ import {
StartupBehavior,
Theme,
VideoPlayer,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import {
CoverSizeOption,
SettingsPlayerOption,
@@ -108,7 +108,7 @@ export function buildSettingsSectionNavItems(
visible: isDesktop,
},
{
id: 'remote-control',
id: '@iptvnator/ui/remote-control',
label: 'SETTINGS.NAV_REMOTE',
icon: 'smartphone',
visible: isDesktop,
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { StreamFormat, VideoPlayer } from 'shared-interfaces';
import { StreamFormat, VideoPlayer } from '@iptvnator/shared/interfaces';
import { SettingsPlaybackSectionComponent } from './settings-playback-section.component';
const MPV_PATH_DESCRIPTION =
@@ -8,7 +8,7 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { StreamFormat, VideoPlayer } from 'shared-interfaces';
import { StreamFormat, VideoPlayer } from '@iptvnator/shared/interfaces';
import { SettingsPlayerOption } from './settings.models';
@Component({
@@ -22,14 +22,14 @@ import { EpgService } from '@iptvnator/epg/data-access';
import { Store } from '@ngrx/store';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { DialogService } from 'components';
import { DialogService } from '@iptvnator/ui/components';
import { MockModule, MockProvider } from 'ng-mocks';
import {
DatabaseService,
DataService,
PlaylistBackupService,
PlaylistsService,
} from 'services';
} from '@iptvnator/services';
import {
EmbeddedMpvSupport,
Language,
@@ -38,7 +38,7 @@ import {
StreamFormat,
Theme,
VideoPlayer,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import { SettingsComponent } from './settings.component';
import { signal } from '@angular/core';
@@ -47,7 +47,7 @@ import {
PlaylistActions,
selectAllPlaylistsMeta,
selectIsEpgAvailable,
} from 'm3u-state';
} from '@iptvnator/m3u-state';
import { NgxIndexedDBService } from 'ngx-indexed-db';
import { from, of } from 'rxjs';
import { ElectronServiceStub } from '../services/electron.service.stub';
@@ -28,12 +28,12 @@ import { EpgService } from '@iptvnator/epg/data-access';
import { SettingsContextService } from '@iptvnator/workspace/shell/util';
import { Store } from '@ngrx/store';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { DialogService } from 'components';
import { DialogService } from '@iptvnator/ui/components';
import {
PlaylistActions,
selectAllPlaylistsMeta,
selectIsEpgAvailable,
} from 'm3u-state';
} from '@iptvnator/m3u-state';
import { firstValueFrom, take } from 'rxjs';
import {
DatabaseService,
@@ -42,7 +42,7 @@ import {
PlaylistBackupImportSummary,
PlaylistBackupService,
PlaylistsService,
} from 'services';
} from '@iptvnator/services';
import {
EmbeddedMpvSupport,
CoverSize,
@@ -52,7 +52,7 @@ import {
StreamFormat,
Theme,
VideoPlayer,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import { SettingsStore } from '../services/settings-store.service';
import { SettingsService } from './../services/settings.service';
import { SettingsAboutSectionComponent } from './settings-about-section.component';
+1 -1
View File
@@ -3,7 +3,7 @@ import {
StartupBehavior,
Theme,
VideoPlayer,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
export interface SettingsSection {
id: string;
+2 -2
View File
@@ -23,7 +23,7 @@ import {
XtreamBackupHiddenCategory,
XtreamBackupRecentlyViewedItem,
XtreamCategory,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import {
DbOperationEvent,
GlobalFavoriteItem,
@@ -32,7 +32,7 @@ import {
GlobalSearchResult,
XCategoryFromDb,
XtreamContent,
} from 'services';
} from '@iptvnator/services';
type JsonObject = Record<string, unknown>;
type JsonArray = unknown[];
+30 -30
View File
@@ -1,33 +1,33 @@
{
"name": "website",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/website/src",
"tags": ["type:website"],
"targets": {
"build": {
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/dist/apps/website"],
"inputs": ["production", "^production"],
"options": {
"command": "pnpm exec astro build",
"cwd": "apps/website"
}
},
"serve": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm exec astro dev --port 4321",
"cwd": "apps/website"
}
},
"preview": {
"executor": "nx:run-commands",
"dependsOn": ["build"],
"options": {
"command": "pnpm exec astro preview --port 4322",
"cwd": "apps/website"
}
"name": "website",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/website/src",
"tags": ["scope:website", "domain:marketing", "type:website"],
"targets": {
"build": {
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/dist/apps/website"],
"inputs": ["production", "^production"],
"options": {
"command": "pnpm exec astro build",
"cwd": "apps/website"
}
},
"serve": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm exec astro dev --port 4321",
"cwd": "apps/website"
}
},
"preview": {
"executor": "nx:run-commands",
"dependsOn": ["build"],
"options": {
"command": "pnpm exec astro preview --port 4322",
"cwd": "apps/website"
}
}
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/xtream-mock-server/src",
"tags": ["scope:dev-tools"],
"tags": ["scope:dev-tools", "domain:xtream", "type:dev-app"],
"targets": {
"serve": {
"continuous": true,
+1 -1
View File
@@ -354,7 +354,7 @@ class EpgService {
- The shared EPG UI only shows the archive replay badge when the host confirms
that the selected M3U channel has a playable replay scheme. Archive days
alone are not enough.
- M3U catch-up support is resolved in `m3u-utils` from channel metadata and
- M3U catch-up support is resolved in `@iptvnator/shared/m3u-utils` from channel metadata and
the archived program start time.
- Supported replay precedence:
1. `catchup.source` if it is an HTTP(S) URL. IPTVNator rewrites or appends
@@ -0,0 +1,78 @@
# 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.
+2 -2
View File
@@ -349,8 +349,8 @@ renderer can actually paint the loading state instead of freezing.
The worker build also aliases:
1. `database-schema`
2. `database-path-utils`
1. `@iptvnator/shared/database/schema`
2. `@iptvnator/shared/database/path-utils`
These aliases avoid importing the shared database barrel from inside the worker,
which would otherwise pull in runtime code that assumes the main Electron
+52
View File
@@ -1,5 +1,20 @@
import nx from '@nx/eslint-plugin';
const legacyBareAliases = [
'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',
];
export default [
...nx.configs['flat/base'],
...nx.configs['flat/typescript'],
@@ -25,6 +40,28 @@ export default [
'type:util',
],
},
{
sourceTag: 'type:e2e',
onlyDependOnLibsWithTags: [
'type:feature',
'type:ui',
'type:data-access',
'type:util',
],
},
{
sourceTag: 'type:dev-app',
onlyDependOnLibsWithTags: [
'type:feature',
'type:ui',
'type:data-access',
'type:util',
],
},
{
sourceTag: 'type:website',
onlyDependOnLibsWithTags: ['type:ui', 'type:util'],
},
{
sourceTag: 'type:feature',
onlyDependOnLibsWithTags: [
@@ -175,6 +212,21 @@ export default [
],
},
],
'no-restricted-imports': [
'error',
{
paths: legacyBareAliases.map((name) => ({
name,
message:
'Use the scoped @iptvnator/* path alias instead of the legacy bare alias.',
})),
patterns: legacyBareAliases.map((name) => ({
group: [`${name}/*`],
message:
'Use the scoped @iptvnator/* path alias instead of the legacy bare alias.',
})),
},
],
},
},
{
@@ -1,4 +1,4 @@
import { EpgProgram } from 'shared-interfaces';
import { EpgProgram } from '@iptvnator/shared/interfaces';
import { normalizeEpgPrograms } from './epg-program-normalization.util';
function buildProgram(overrides: Partial<EpgProgram> = {}): EpgProgram {
@@ -1,4 +1,4 @@
import { EpgProgram } from 'shared-interfaces';
import { EpgProgram } from '@iptvnator/shared/interfaces';
function toIsoDate(value: string | undefined): string | null {
if (!value) {
+1 -1
View File
@@ -3,7 +3,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, forkJoin, from, Observable, of } from 'rxjs';
import { catchError, map, tap, timeout } from 'rxjs/operators';
import { EpgChannelMetadata, EpgProgram } from 'shared-interfaces';
import { EpgChannelMetadata, EpgProgram } from '@iptvnator/shared/interfaces';
import { normalizeEpgPrograms } from './epg-program-normalization.util';
interface CachedProgram {
+1 -1
View File
@@ -1,5 +1,5 @@
export default {
displayName: 'm3u-state',
displayName: '@iptvnator/m3u-state',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../coverage/libs/m3u-state',
+1 -1
View File
@@ -1,5 +1,5 @@
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Channel, EpgProgram, Playlist, PlaylistMeta } from 'shared-interfaces';
import { Channel, EpgProgram, Playlist, PlaylistMeta } from '@iptvnator/shared/interfaces';
export const PlaylistActions = createActionGroup({
source: 'Playlists',
@@ -1,4 +1,4 @@
import { Channel } from 'shared-interfaces';
import { Channel } from '@iptvnator/shared/interfaces';
import { resolveChannelEpgLookupKey } from './channel-epg-lookup.util';
function buildChannel(overrides: Partial<Channel> = {}): Channel {
@@ -1,4 +1,4 @@
import { Channel } from 'shared-interfaces';
import { Channel } from '@iptvnator/shared/interfaces';
export function resolveChannelEpgLookupKey(
channel: Channel | null | undefined
+1 -1
View File
@@ -1,4 +1,4 @@
import { Channel } from 'shared-interfaces';
import { Channel } from '@iptvnator/shared/interfaces';
import { buildExternalPlayerPayload } from './external-player-payload.util';
describe('buildExternalPlayerPayload', () => {
+3 -3
View File
@@ -2,7 +2,7 @@ import { inject, Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { EpgService } from '@iptvnator/epg/data-access';
import { resolveM3uCatchupUrl } from 'm3u-utils';
import { resolveM3uCatchupUrl } from '@iptvnator/shared/m3u-utils';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { StorageMap } from '@ngx-pwa/local-storage';
@@ -18,7 +18,7 @@ import {
tap,
withLatestFrom,
} from 'rxjs';
import { DataService, PlaylistsService } from 'services';
import { DataService, PlaylistsService } from '@iptvnator/services';
import {
OPEN_MPV_PLAYER,
OPEN_VLC_PLAYER,
@@ -26,7 +26,7 @@ import {
Playlist,
STORE_KEY,
VideoPlayer,
} from 'shared-interfaces';
} from '@iptvnator/shared/interfaces';
import {
ChannelActions,
EpgActions,
@@ -1,4 +1,4 @@
import { Channel } from 'shared-interfaces';
import { Channel } from '@iptvnator/shared/interfaces';
export function buildExternalPlayerPayload(
activeChannel: Channel | undefined | null,
+1 -1
View File
@@ -1,5 +1,5 @@
import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
import { PlaylistMeta } from 'shared-interfaces';
import { PlaylistMeta } from '@iptvnator/shared/interfaces';
export const playlistsAdapter: EntityAdapter<PlaylistMeta> =
createEntityAdapter<PlaylistMeta>({
@@ -1,5 +1,5 @@
import { createReducer } from '@ngrx/store';
import { Channel } from 'shared-interfaces';
import { Channel } from '@iptvnator/shared/interfaces';
import { ChannelActions } from '../actions';
import { initialState } from '../state';
import { channelReducers } from './channel.reducers';
@@ -1,5 +1,5 @@
import { on } from '@ngrx/store';
import { Channel } from 'shared-interfaces';
import { Channel } from '@iptvnator/shared/interfaces';
import { ChannelActions } from '../actions';
import { PlaylistState } from '../state';
@@ -1,5 +1,5 @@
import { on } from '@ngrx/store';
import { Channel } from 'shared-interfaces';
import { Channel } from '@iptvnator/shared/interfaces';
import { EpgActions } from '../actions';
import { PlaylistState } from '../state';
@@ -3,7 +3,7 @@ import { PlaylistActions } from '../actions';
import { playlistsAdapter } from '../playlists.state';
import { initialState } from '../state';
import { playlistReducers } from './playlist.reducers';
import { Channel, Playlist, PlaylistMeta } from 'shared-interfaces';
import { Channel, Playlist, PlaylistMeta } from '@iptvnator/shared/interfaces';
const reducer = createReducer(initialState, ...playlistReducers);
@@ -1,5 +1,5 @@
import { on } from '@ngrx/store';
import { Channel } from 'shared-interfaces';
import { Channel } from '@iptvnator/shared/interfaces';
import { PlaylistActions } from '../actions';
import { playlistsAdapter } from '../playlists.state';
import { PlaylistState } from '../state';
+1 -1
View File
@@ -1,4 +1,4 @@
import { PlaylistMeta } from 'shared-interfaces';
import { PlaylistMeta } from '@iptvnator/shared/interfaces';
import { PlaylistState } from './state';
import { selectActivePlaylist, selectChannelsLoading } from './selectors';
Loaded 100 of 338 files, more files were not shown because too many files have changed in this diff. Show more