npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nera-common

v0.36.0

Published

To make it easy for you to get started with GitLab, here's a list of recommended next steps.

Readme

nera-frontend-common

Getting started

To make it easy for you to get started with GitLab, here's a list of recommended next steps.

Already a pro? Just edit this README.md and make it your own. Want to make it easy? Use the template at the bottom!

Add your files

cd existing_repo
git remote add origin https://gitlab.com/fluxvane/nera-iam/nera-frontend-common.git
git branch -M develop
git push -uf origin develop

Integrate with your tools

Collaborate with your team

Test and Deploy

Use the built-in continuous integration in GitLab.


Editing this README

When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to makeareadme.com for this template.

Suggestions for a good README

Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.

Name

Choose a self-explaining name for your project.

Description

Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.

Badges

On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.

Visuals

Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.

Installation

Consumers pin nera-common to ONE exact version ("nera-common": "0.36.0"), never a caret range: on a 0.x package a caret (^0.21.2) resolves only >=0.21.2 <0.22.0, so the consumer is frozen on its old minor and silently misses every release (a breaking change bumps the MINOR while the major is 0).

After tagging a release, move every consumer in one step and let the lockfiles follow:

pnpm bump:consumers --install <consumerDir>...   # pins this package's version everywhere under each dir
pnpm bump:consumers --check <consumerDir>...     # exit 1 if any pin is a range or differs from this version

Usage

Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.

Support

Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.

Roadmap

If you have ideas for releases in the future, it is a good idea to list them in the README.

Contributing

State if you are open to contributions and what your requirements are for accepting them.

For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.

You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.

Authors and acknowledgment

Show your appreciation to those who have contributed to the project.

License

For open source projects, say how it is licensed.

Project status

If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork this project or volunteer to work on it as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.


Publishing the Angular components to the shared library

Status: PLAN — not implemented. This section documents what has to happen before the Angular components (DynamicTableComponent, PageHeaderComponent, BreadcrumbComponent) can leave the fe-web application and live in nera-common/angular like the other shared Angular code. Do NOT assume the components are published — they are not.

What is published today

nera-common/angular is built with tsc, not with ng-packagr:

tsc -p tsconfig.json          →  dist/angular/*.js + dist/angular/*.d.ts

That works for plain TypeScript Angular utilities — services, interceptors, pipes and helpers — because they carry no Angular compiler metadata:

| Already in nera-common/angular | File | | --- | --- | | BaseDestroy | src/angular/base-destroy.ts | | RestApiService | src/angular/rest-api.service.ts | | GenericCrudService | src/angular/generic-crud.service.ts | | pagination helpers | src/angular/pagination.ts | | HTTP param helpers | src/angular/http-params.ts | | list-page helpers | src/angular/list-page.ts | | customValidators / REGEX_VALIDATORS | src/angular/validators.ts | | interceptors, tokens, API response | src/angular/*.ts |

The validators are the reason they are described structurally (an AbstractControlLike with a value, a ValidatorFnLike): the angular subpath must not depend on @angular/forms, which is not a peer of this package. The returned validator is still assignable to Angular's ValidatorFn.

The entry point is self-contained

ng-packagr compiles every entry point on its own (the compiler's rootDir is the entry point's own folder), so the angular entry point may not import from the package's root entry point. The modules it shares with the root (lib/types/api, lib/errors/*, lib/api/notification-paths, lib/auth/*) therefore have an entry-point-local copy under src/angular/internal/. Keep those copies in sync with the canonical ones.

The ports a consumer binds

The components never depend on an app service — they take ports:

| Port | Token | Used for | | --- | --- | --- | | translator | NERA_TRANSLATOR | every label the components render (column labels, cell values, empty/error text) | | toast | NERA_TOAST_SERVICE | the table's copy-to-clipboard feedback | | timezone | NERA_TIMEZONE | the display zone of the shared date pipe |

providers: [
  {
    provide: NERA_TRANSLATOR,
    useFactory: (transloco: TranslocoService) => ({
      translate: (key: string, params?: Record<string, unknown>) =>
        transloco.translate(key, params),
      getActiveLang: () => transloco.getActiveLang(),
    }),
    deps: [TranslocoService],
  },
  { provide: NERA_TOAST_SERVICE, useExisting: ToastService },
  {
    provide: NERA_TIMEZONE,
    useFactory: () => ({ zone: () => inject(TimezoneService).zone() }),
  },
]

Without a translator the components render the key itself (the fallback an i18n library uses for an unknown key), so a consumer that has no i18n wiring yet still gets a usable surface.

Peers

@angular/core, @angular/common (already there) plus @angular/router and @angular/material — all optional (peerDependenciesMeta), because the breadcrumb's RouterLink and the table's Material modules are only needed by the apps that use those components.

Rollback

The release is tag-gated, so a bad angular artifact is fixed with a new release, not an unpublish. pnpm build:angular:tsc builds the subpath with plain tsc again (the pre-ng-packagr artifact shape: one file per module); point the "./angular" export map at ./dist/angular/index.tsc.js to cut such a release. The shared components are not part of that legacy build (a templateUrl template cannot be shipped by tsc) — the legacy barrel src/angular/index.tsc.ts leaves them out.

Verification

  • pnpm build → dist/angular/{fesm2022,index.d.ts,package.json}.
  • pnpm test → the Angular unit tests (vitest, import '@angular/compiler' plus a resolveComponentResources helper for the templateUrl components).
  • In a consumer app: npx ng build must succeed AOT — no NG0303 / "not a known element" in the console, which is the symptom of a tsc-built component.

The plan (steps, in order)

  1. Add the toolchain: ng-packagr + @angular/compiler-cli as devDependencies, and a src/angular/ng-package.json describing the secondary entry point ("lib": { "entryFile": "src/angular/public-api.ts" }).
  2. Split the build: keep tsc for the root / react / server subpaths and add a second build step for the Angular package (ng-packagr -p src/angular/ng-package.json → dist/angular/ with the FESM bundle and the .d.ts). The tsconfig.json used by tsc must then exclude the component sources, or they would be emitted twice.
  3. Move the components into the library (src/angular/components/…), stripping every app-specific dependency: no app/core/... imports, no feature services, no environment-specific configuration. The components must only import from @angular/core (and @angular/common / @angular/forms where they need them).
  4. Declare the peers: @angular/core, @angular/common and @angular/forms become (optional) peer dependencies of the package, with the matching peerDependenciesMeta entries — ng-packagr refuses to bundle them.
  5. Keep the barrel in sync: src/angular/index.ts stays the public surface (export * from './components/...'), and the exports map keeps pointing ./angular at the ng-packagr output.
  6. Migrate the consumers: fe-web imports the components from nera-common/angular and deletes its local copies; the other Angular apps can adopt them the same way. Each consumer bumps the pinned nera-common version — the release is tag-gated (x.y.z), as usual.
  7. Verify: npx ng-packagr -p src/angular/ng-package.json produces dist/angular with a FESM bundle and type declarations, and a consumer app compiles the components AOT (no NG0303 / "not a known element" in the console).

Risks / open questions

  • ng-packagr wants its own tsconfig (compilationMode: "partial") — the library's current tsc setup has to be reconciled with it.
  • The components in fe-web may pull in app-level services (i18n, permissions, API base). Those have to become injectable library services or inputs first, otherwise the move is not possible.
  • The angular subpath's public surface grows: everything exported from src/angular/index.ts becomes semver-relevant.

Plan: the IAM portal's table + page-scaffold variants, and the ng-packagr migration of the angular subpath (card 4830)

Status: Half A (React) is IMPLEMENTED; Half B (ng-packagr) is still a PLAN. Half A moved the IAM portal's table + page-scaffold variants into react/lib/components/data-table/resource-table and react/lib/components/scaffold; the portal now consumes them and its fork is deleted. Half B — moving the angular subpath to ng-packagr — is untouched and still a plan. The section above ("Publishing the Angular components to the shared library") is the general outline; this one is the file-level, config-level version of card 4830 and of the Angular half that goes with it.

Why two halves

Card 4830 is a React card. The IAM portal (modules/iam/nera-iam-portal, Next.js) keeps a 1 090-LOC fork of its own table + page-scaffold family because nera-common/react does not expose the variants the portal actually uses — so the fork keeps living. React components need no special build: the react subpath is compiled by plain tsc and can host them as they are.

The Angular half is the second blocker in the same area. nera-common/angular is compiled by tsc, and tsc cannot emit AOT components — so no Angular component (fe-web's DynamicTableComponent, PageHeaderComponent, BreadcrumbComponent) can ever live in the library, no matter how the code is written. Publishing them means moving that subpath to ng-packagr, which is specified in Half B below.

Half A and Half B are independent and can ship in separate releases.


Half A — React: the DataTable + scaffold variants the IAM portal needs

A.0 What shipped (the plan below is kept as the design record)

The plan's file table is the design record; this is what actually landed.

| Target in the kit | What it is | | --- | --- | | react/lib/components/data-table/resource-table/ | ResourceTable + its body / toolbar / pagination / row-action menu / confirm hook / labels / column utils / types. The fork's DataTable → ResourceTable, Column → ResourceColumn, RowAction → ResourceRowAction, BulkAction → ResourceBulkAction, DataTableLabels → ResourceTableLabels, DataTablePaginationLabels → ResourceTablePaginationLabels, DataTableProps → ResourceTableProps. | | react/lib/components/scaffold/PageHeaderStats.tsx | The portal's stat tile. | | react/lib/components/scaffold/PageLayout.tsx | Page, PageContent, Breadcrumb — new: the portal's shared/page/page-layout.tsx had already been deleted (91155703), so there was nothing to move. | | react/lib/components/scaffold/ListPage.tsx | The list page (header + stats + table), without the portal's next-intl calls — the translated copy arrives as props. | | PageHeader / PageScaffold | Gained stats?: ReactNode (the plan's slot) and gutter?: boolean (an app whose shell owns the horizontal padding renders the header flush). |

Decisions the plan left open:

  • The query shape. The plan asked for ONE spelling. ListQueryParams / SortOrder now live in the kit and the IAM portal re-exports them (@/shared/types/table); the portal no longer declares its own copy. It is deliberately NOT ListQueryState (the useListQuery hook's URL state, which speaks sortDirection): this one is the filter contract a table renders and an API consumes — sortOrder is what the shared filter schemas (UserFilterParamsSchema) speak.
  • The confirm dialog. The fork used the portal's own ConfirmDialog wrapper; the kit's ConfirmDialog already carries the reason prompt, so the moved code uses it directly (the wrapper only added a translated cancel label, which the table passes itself).
  • next-intl. The kit stays i18n-agnostic: the copy arrives as props and the portal keeps a thin useDataTableI18n() adapter.

A.1 The gap (verified against the portal's fork)

| What the IAM portal needs | nera-common/react today | | --- | --- | | column shape key / header / render | DataTableColumn = id / header / accessor (+ sortValue) | | bulk-actions bar inside the table | BulkActionsBar exists as a standalone scaffold piece; DataTable has no bulkActions prop and never renders it | | search toolbar | none — FilterBar is a separate scaffold piece, DataTable has no search | | date-range filter | none | | i18n label slots (labels, paginationLabels, actionsLabel, searchPlaceholder, emptyMessage / emptyHint) | only the useNeraUiLabels() context — no per-table overrides | | column visibility (hideBelow, className, headerClassName, minWidth) | none (the lib has size / truncate / monospace / numeric instead) | | PageHeaderStats | none (StatCard is a different shape) | | Page / PageContent / Breadcrumb | none (PageScaffold is a different composition) | | ListPage | none |

The lib's DataTable (TanStack-based, id/accessor) is kept as is — it is the shape sa-portal and photoqa already use. The portal's shape is added as a second, self-contained component so neither API has to grow a second set of column semantics.

A.2 The files that move

Source: modules/iam/nera-iam-portal/src/components/. Target: projects/nera-common/src/react/lib/components/.

| Source file | Target file | LOC | Change while moving | | --- | --- | --- | --- | | ui/data-table/types.ts | data-table/resource-table/types.ts | 123 | Column → ResourceColumn, ColumnVisibility → ResourceColumnVisibility, DataTableLabels → ResourceTableLabels, DataTableProps → ResourceTableProps; ConfirmConfig / ConfirmableAction keep their names (no clash in the lib) | | ui/data-table/default-labels.ts | data-table/resource-table/default-labels.ts | 32 | DEFAULT_LABELS → RESOURCE_TABLE_DEFAULT_LABELS, resolveLabels → resolveResourceTableLabels (exported name — a stable seam other components use) | | ui/data-table/column-utils.ts | data-table/resource-table/column-utils.ts | 18 | columnVisibilityClass, minWidthValue — unchanged | | ui/data-table/use-confirmable-action.ts | data-table/resource-table/use-confirmable-action.ts | 93 | unchanged | | ui/data-table/data-table.tsx | data-table/resource-table/resource-table.tsx | 123 | DataTable → ResourceTable | | ui/data-table/data-table-body.tsx | data-table/resource-table/resource-table-body.tsx | 229 | unchanged | | ui/data-table/data-table-toolbar.tsx | data-table/resource-table/resource-table-toolbar.tsx | 161 | search + date-range filter + bulk bar | | ui/data-table/data-table-pagination.tsx | data-table/resource-table/resource-table-pagination.tsx | 173 | unchanged | | ui/data-table/row-action-menu.tsx | data-table/resource-table/row-action-menu.tsx | 124 | unchanged | | ui/data-table/__tests__/use-confirmable-action.test.tsx | data-table/resource-table/__tests__/use-confirmable-action.test.tsx | 62 | unchanged | | shared/page/page-header.tsx (the PageHeaderStats half) | scaffold/PageHeaderStats.tsx | ~90 of 191 | new component; the portal's PageHeader itself is not moved — the portal migrates onto the lib's PageHeader | | shared/page/page-layout.tsx | scaffold/PageLayout.tsx | 105 | Page, PageContent, Breadcrumb | | shared/page/list-page.tsx | scaffold/ListPage.tsx | 275 | ListPage, ListPageProps, ListPageStat, ListPageTableProps | | shared/page/__tests__/list-page.test.tsx | scaffold/__tests__/list-page.test.tsx | 81 | unchanged | | shared/page/entity-detail-header.tsx | stays in the portal | 120 | it depends on @/features/auth (PermissionGuard) — portal-specific, not a shared primitive |

New public API (exported from src/react/index.ts through the existing data-table/ and scaffold/ barrels — no export-map change):

// data-table/resource-table/types.ts
export type ResourceColumnVisibility = 'always' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'

export interface ResourceColumn<T> {
  key: keyof T | string
  header: string
  render?: (row: T) => ReactNode
  sortable?: boolean
  hideBelow?: ResourceColumnVisibility
  className?: string
  headerClassName?: string
  minWidth?: number | string
}

export interface ResourceTableLabels { selected?: string; moreOptions?: string; searchAria?: string
  fromDate?: string; toDate?: string; dateRangeSeparator?: string; selectAll?: string
  selectRow?: string; confirmTitle?: string; confirmMessage?: string; cancel?: string }

export interface ResourceTablePaginationLabels { showing?: string; loading?: string; previous?: string
  next?: string; previousPage?: string; nextPage?: string; rowsPerPage?: string; perPage?: string }

export interface ResourceTableProps<T> { columns: ResourceColumn<T>[]; items: T[]; total: number
  initialQuery?: ListQueryParams; onQueryChange?: (query: ListQueryParams) => void
  toolbar?: ReactNode; getRowActions?: (row: T) => ResourceRowAction<T>[]
  bulkActions?: ResourceBulkAction<T>[]; showDateFilters?: boolean; hideSearch?: boolean
  isLoading?: boolean; emptyMessage?: string; emptyHint?: string; actionsLabel?: string
  searchPlaceholder?: string; paginationLabels?: ResourceTablePaginationLabels
  labels?: ResourceTableLabels; className?: string }

// scaffold
export { PageHeaderStats } from './PageHeaderStats'
export { Page, PageContent, Breadcrumb } from './PageLayout'
export { ListPage } from './ListPage'

PageHeader (and PageScaffold, which forwards to it) gains one slot: stats?: ReactNode — the portal renders its stat tiles under the title and the lib's header has no place for them today.

A.3 What must change while moving (the fork's app-level imports)

| Fork import | Replacement in the lib | | --- | --- | | cn from @/shared/utils | cn from nera-common (already in the lib) | | useTranslations from next-intl (4 files) | no next-intl in the lib — the copy arrives as props (labels, paginationLabels, actionsLabel, …). The portal keeps a thin useDataTableI18n() that maps its next-intl messages onto those props; that hook stays in the portal | | ConfirmDialog from @/components/shared/feedback/confirm-dialog | the lib has ConfirmDeleteDialog; the fork's dialog adds an optional reason prompt — either extend ConfirmDeleteDialog or add feedback/ConfirmDialog.tsx with the same ConfirmConfig contract | | useDebounce from @/shared/hooks/shared-ui-hooks | useDebounce from nera-common/react (already in the lib) | | ListQueryParams / SortOrder from @/shared/types/table | the lib's use-list-query (ListQueryState) — decide one shape: either the lib's ListQueryState is adopted, or ListQueryParams is added next to it. Do not ship two spellings of the same query object | | Button / Checkbox / Input / Skeleton from nera-common/react | already the lib's — no change |

A.4 Tests

Move the two existing test files with the code, and add:

  • data-table/resource-table/__tests__/resource-table.test.tsx — renders the table with a fake row set and asserts: the bulk bar appears with the selection count, the search box filters the rows, the date-range filter reports its range through onQueryChange, hideBelow adds the responsive class, labels / paginationLabels override the English fallbacks, and a Column with key + render renders the cell.
  • scaffold/__tests__/page-header-stats.test.tsx — the tile renders the value, and renders the skeleton while loading is true (never a fake 0).
  • scaffold/__tests__/page-layout.test.tsx — Page renders header + content, Breadcrumb marks the last item aria-current="page".
  • scaffold/__tests__/list-page.test.tsx — moved from the portal.

A.5 The portal migration (separate repo, separate MR)

  1. modules/iam/nera-iam-portal: switch the imports to nera-common/react, delete src/components/ui/data-table/ and the page-scaffold copies, keep the portal-only entity-detail-header.tsx and the useDataTableI18n hook.
  2. Bump the pinned nera-common version in the portal and tag the release (x.y.z — the release is tag-gated).
  3. The fork's files are deleted in the portal's MR only — the library keeps the components, so a rollback re-adds the portal copies without touching the lib.

Half B — Angular: moving the angular subpath to ng-packagr

B.1 What moves (and what has to be stripped first)

Source: projects/core/pfl01y25-fe-web/src/app/shared/components/ (the path in card 4830 — src/app/core/components/dynamic-table — does not exist; this is the real one).

| Source file | Target file | | --- | --- | | dynamic-table/dynamic-table.component.ts / .html / .scss | src/angular/components/dynamic-table/dynamic-table.component.ts / .html / .scss | | page-header/page-header.component.ts | src/angular/components/page-header/page-header.component.ts | | breadcrumb/breadcrumb.component.ts / .html | src/angular/components/breadcrumb/breadcrumb.component.ts / .html | | page-header/page-header.component.spec.ts | src/angular/components/page-header/__tests__/page-header.component.spec.ts |

The app-level dependencies of those components have to become library inputs / injected tokens before the move:

| App import (fe-web) | Becomes | | --- | --- | | IPagination from app/core/models/general.model | the lib's IPaginationMetadata (src/angular/api-response.ts) | | DEFAULT_PAGINATOR from app/shared/constants | the lib's DEFAULT_PAGINATOR (src/angular/pagination.ts) — already there | | AppDatePipe from app/shared/pipes/app-date.pipe | moves into the lib (src/angular/app-date.pipe.ts) | | formatMoney from app/shared/utils/money.utils | moves into the lib (src/angular/format.ts) | | ToastService from app/core/services/toast.service | an @Output() / callback input — the lib must not depend on an app service | | getAngularLocale from app/core/i18n/app-locale | an @Input() (locale) or an injection token | | TranslocoModule / TranslocoService | label @Input()s — the lib must not depend on Transloco (not a peer) | | @angular/material/* (table, paginator, button, icon, tooltip, progress-bar, progress-spinner) | a new optional peer dependency; or the components are re-written without Material | | @angular/router (RouterLink in the breadcrumb) | a new optional peer dependency, or plain <a href> |

B.2 The ng-packagr configuration

src/angular/ng-package.json (new):

{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/angular",
  "lib": { "entryFile": "index.ts" }
}

src/angular/tsconfig.lib.json (new):

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/angular",
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "bundler",
    "declaration": true,
    "types": []
  },
  "angularCompilerOptions": {
    "compilationMode": "partial",
    "strictTemplates": true
  },
  "include": ["**/*.ts", "**/*.html"],
  "exclude": ["**/*.spec.ts", "**/__tests__/**"]
}

package.json:

"scripts": {
  // the root tsc build stops emitting the angular subpath (see B.3)
  "build": "tsc -p tsconfig.json && ng-packagr -p src/angular/ng-package.json -c src/angular/tsconfig.lib.json && node dist/tokens/write-tokens-css.js",
  // kept until the ng-packagr output is proven in a consumer app — this is
  // the rollback switch (see B.6)
  "build:angular:tsc": "tsc -p tsconfig.angular-tsc.json"
}

devDependencies: ng-packagr + @angular/compiler-cli (both pinned to the Angular major the consumers run — ^20.x today). peerDependencies (+ the matching peerDependenciesMeta optional entries): @angular/core, @angular/common, ^20.0.0 (already there), plus @angular/forms, @angular/router, @angular/material — each only if the moved components still need them.

B.3 The blocker to clear first: cross-entry-point imports

ng-packagr requires every entry point to be self-contained — an entry point may not import from another entry point of the same package ("Importing from a secondary entry point is not allowed"). The angular entry point imports four modules from the ROOT entry point today:

| File | Imports | | --- | --- | | src/angular/api-response.ts | ../lib/types/api (type-only) | | src/angular/api-error.interceptor.ts | ../lib/errors/error-code-message (runtime) | | src/angular/notification-inbox.ts | ../lib/types/api (type-only) | | src/angular/notification-inbox.service.ts | ../lib/api/notification-paths (runtime) |

Resolution: the angular entry point gets its own copies of the three shared modules (the envelope type aliases, the error-code map, the notification path constants). The type-only copies cost nothing at runtime; the two runtime maps are small. Spike first: run ng-packagr -p src/angular/ng-package.json on the current sources and read the errors — if ng-packagr turns out to accept them, the duplication is unnecessary.

The root tsconfig.json must then exclude src/angular/** from the tsc build, or the two builds emit into dist/angular twice:

"exclude": ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**", "src/angular/**"]

Nothing outside src/angular imports src/angular (the root barrel does not re-export it), so the exclusion is safe.

B.4 The export map change

Before (today — tsc output, one file per module):

"./angular": {
  "types": "./dist/angular/index.d.ts",
  "import": "./dist/angular/index.js",
  "default": "./dist/angular/index.js"
}

After (ng-packagr output — one FESM bundle + the type declarations):

"./angular": {
  "types": "./dist/angular/index.d.ts",
  "import": "./dist/angular/fesm2022/<bundle>.mjs",
  "default": "./dist/angular/fesm2022/<bundle>.mjs"
}

<bundle> is the file ng-packagr actually emits (it derives the name from the package name and the entry point — read it from the build output, do not guess it into the map).

Consequences:

  • Deep imports break. nera-common/angular/rest-api.service resolves through the "./*" export map to ./dist/angular/rest-api.service.js, which no longer exists. No consumer does that today — the only importer of the subpath is fe-web, and it imports the mapped 'nera-common/angular' (14 files, 0 deep imports) — but the "./*" pattern stays in the map for the other subpaths, so the breakage is invisible until someone tries it.
  • The root "." export is untouched: the root barrel is still tsc-built.

B.5 Versioning impact

| Change | Semver | | --- | --- | | Half A (new React components + PageHeader.stats) | additive → minor | | Half B (the angular subpath's published artifact changes shape) | breaking for deep importers → minor as well, because the package is 0.x (the repo rule: while the major version is 0, a breaking change bumps the MINOR version) |

  • Version 0.33.0 → 0.34.0 for Half A. (0.33.0 already shipped — the shared Angular inbox client — so the plan's original "0.32.1 → 0.33.0" text is one release behind.) A consumer pinning ^0.33.0 does not pick 0.34.0 up (caret on a 0.x version pins the minor), so every consumer has to bump explicitly — that is the point of the minor bump.
  • The changelog entry must call out the angular subpath change explicitly (artifact shape, deep imports gone) — a consumer that deep-imports would otherwise find out at build time.
  • The release stays tag-gated (x.y.z), as usual; MAIN owns the version bump — this plan does not touch package.json's version.

B.6 Rollback

The release is tag-gated and npm's unpublish window is 72 h — do not rely on unpublishing a bad 0.33.0.

  1. Before the migration ships: keep the old build runnable (build:angular:tsc + the tsconfig.angular-tsc.json it needs) so a release can always be cut from the previous shape of the subpath.
  2. If the ng-packagr build breaks a consumer: revert the build script, the tsconfig.json exclusion and the "./angular" export-map entry in the library, and publish 0.33.1 with the tsc-built subpath. The consumers only bump their pin — no source change on their side.
  3. If a moved component misbehaves (AOT metadata, template compile error): keep the component in the library but do not delete the app's copy in the same release — the app switches to the library copy in a follow-up MR, so the rollback is "point the import back at the app copy".
  4. Half A rollback is independent: the new React components are additive, so a bad release is reverted by publishing 0.33.1 without them; the portal's fork is only deleted in the portal's own MR, so the portal can go back to it independently.

B.7 Verification

  • pnpm build in nera-common → dist/angular/{fesm2022,index.d.ts,package.json}.
  • pnpm test in nera-common → the Angular unit tests keep passing (vitest loads the JIT compiler explicitly — import '@angular/compiler' — the same trick the existing src/angular/__tests__ use).
  • In fe-web: npx ng build must succeed AOT — no NG0303 / "not a known element" in the console, which is the symptom of a tsc-built component.
  • In the IAM portal: pnpm build + pnpm test after the fork is deleted.

B.8 Order of work

  1. Half A in nera-common (new components + tests) → 0.34.0.
  2. Portal migration in modules/iam/nera-iam-portal (import from the lib, delete the fork) — its own repo, its own MR, its own version bump. The portal now pins [email protected].
  3. Half B in nera-common (ng-packagr) → 0.35.0. Half B does not depend on Half A.
  4. fe-web migration (import the Angular components, delete the app copies) — after Half B ships.