refactor(core-web): TS strict mode across all 44 projects — completes epic #35932 - #37198
Open
nicobytes wants to merge 200 commits into
Open
refactor(core-web): TS strict mode across all 44 projects — completes epic #35932#37198nicobytes wants to merge 200 commits into
nicobytes wants to merge 200 commits into
Conversation
`sdk-types` needs no code change: `libs/sdk/types/tsconfig.json` has carried `strict: true` plus the four extra safety flags since the library was created (#31967), and `tsc -p tsconfig.lib.json --noEmit` passes with zero errors. It is already enforced too. Because `tsconfig.lib.json` sets `"declaration": true`, `@rollup/plugin-typescript` sits in the Rollup chain and reports type diagnostics, so `sdk-types:build` fails on a strict violation — verified by removing a constructor assignment and watching the build report TS2564. CI builds every project via the `build-test` execution in `core-web/pom.xml`, so the gate already runs on each PR. A dedicated `typecheck` target would be redundant. `lint` does not catch this: ESLint reports lint rules, not TS diagnostics. What was actually missing is documentation, so the remaining 42 projects in epic #35932 have a pattern to follow: - Add a `## TypeScript Strict Mode` section covering the per-project flags, what enforces them, and the Vite exception (esbuild skips type checking, which is why the Nx Vite plugin infers a separate `typecheck` target). - Fix the line that forbade `"strict": true` in project tsconfigs. It sat under the Jest config guidance but read as a blanket ban, contradicted `docs/frontend/TYPESCRIPT_STANDARDS.md`, and blocked the epic outright. The restriction now points at `tsconfig.spec.json`, which is what it meant. Closes #35935 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add the standard per-project strict flags to `libs/sdk/create-app/tsconfig.json`, following the pattern established in #36879 (dotcms-models). `tsconfig.base.json` is left at `strict: false`. Two errors surfaced, both from flags beyond plain `strict`: - `src/index.ts:393` — `process.env.DEBUG` needs bracket access under `noPropertyAccessFromIndexSignature` (TS4111). It is the only `process.env.*` dot access in the project. - `src/utils/index.ts:41` — `fetchWithRetry` tripped `noImplicitReturns` (TS7030). The loop returns on success and throws on the last attempt, but with `retries < 1` the loop never runs and the function fell through returning `undefined`. Its only caller already guarded with `if (res && ...)`, so nothing broke in practice, but the signature was lying. Throwing after the loop closes the gap and narrows the return type. No build or CI wiring needed. The `@nx/esbuild:esbuild` executor type-checks before bundling — `skipTypeCheck` defaults to false and is not overridden — and CI already builds this project via `nx run-many -t build` (`build-test` in core-web/pom.xml). The same build runs in the SDK release pipeline (`cicd_release-sdk.yml` → `nx run-many --projects='sdk-*'`), so the flags are enforced on every release. Verified: tsc clean on lib and spec; `nx run sdk-create-app:build/lint/test` green; `nx affected -t build,lint` green; `node dist/libs/sdk/create-app/index.js --help` still works. Negative test — reverting the DEBUG fix makes `nx run sdk-create-app:build` fail with TS4111, confirming the gate is real. Closes #35938 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add the standard per-project strict flags to `libs/dotcms-js/tsconfig.json`, following the pattern from #36879 (dotcms-models), and resolve the 38 errors they surface across 11 files. `tsconfig.base.json` stays at `strict: false`. Notable type corrections rather than mechanical silencing: - `Auth.loginAsUser` was typed `User` but the code has always passed `null` when nobody is impersonating, and every consumer already guards with `auth.loginAsUser || auth.user`. Corrected to `User | null`. - `StringUtils.getLine` and `HttpRequestUtils.getQueryStringParam` both document "null if it does not exist" but were typed `string`. Corrected. - `RoutingService.getPortletURL` returns `Map.get()`, so `string | undefined`. - `SiteService.switchSiteById` emits `of(null)` when no site is found, so `Observable<Site | null>`. Its one consumer already handles null. - `ResponseView` now models `HttpResponse.body` as nullable instead of assigning `null` into a non-nullable field inside a `try/catch` that could never throw. The dead try/catch is removed. - `LoginService.urls` is typed by inference instead of `Record<string, string>`, which keeps dot access valid and gives each endpoint a named property. Two definite-assignment assertions were used, each with a TODO: `_auth` and `selectedSite` are assigned during init but not in the constructor. Modelling them as `| undefined` is the truthful type, but their public getters (`auth`, `currentSite`) are consumed by already-strict projects, so widening them is a public-API change that belongs in its own issue. No new `any`, `@ts-ignore`, or `@ts-expect-error`. Verified: - `tsc -p libs/dotcms-js/tsconfig.lib.json --noEmit` — 0 errors - All six already-strict consumers build green (data-access, global-store, portlets-dot-analytics, portlets-dot-analytics-data-access, portlets-dot-locales-portlet, utils-testing) - `data-access` typecheck went from 106 errors to 68, with zero new errors introduced — the honest types upstream remove noise downstream - `dotcms-ui` typecheck clean apart from a pre-existing missing `dotcms-webcomponents/loader` dist - `nx format:check` green; dotcms-js lint went from 42 to 41 problems Note: this project has no `build` target and is tag-excluded from lint and test, so nothing in CI verifies these flags. That was an explicit scoping decision — no `typecheck` target or CI gate was added. See `specs/35939-dotcms-js-strict-mode/spec.md`. Closes #35939 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add the standard per-project strict flags to `libs/utils/tsconfig.json`, following the pattern from #36879 (dotcms-models), and resolve the 32 errors they surface across 3 files. `tsconfig.base.json` stays at `strict: false`. The flags also propagate to `tsconfig.spec.json`, which surfaced 17 further errors in the spec files (baseline was 0). Those are fixed here too rather than left as a regression. Notable changes: - `EMPTY_FIELD` assigned `null` to 18 members that `DotCMSContentTypeField` declares non-nullable. Replaced with zero values of the declared types. Nothing compares those members to `null` strictly — consumers use falsy checks such as `isNewField`'s `!field.id` — so `''`, `0` and `false` behave identically at runtime. - `clazz` has no zero value (`DotCMSClazz` is a union of concrete Java class names), so `EMPTY_FIELD` and `EMPTY_SYSTEM_FIELD` are now typed `Omit<DotCMSContentTypeField, 'clazz'>`. They are partial templates, not valid fields, and the type now says so. The derived `COLUMN_FIELD`, `ROW_FIELD` and `TAB_FIELD` already supply their own `clazz`. - `getFieldsWithoutLayout` used a truthy `.filter()` that does not narrow the optional `row.columns`. Replaced with a type predicate, which clears the TS2532 and both TS2769 errors without a cast. - `ellipsizeText` accepted `null`/`undefined` at runtime — its own guard and its tests document that — but declared `string` and `number`. Widened to match, with an explicit `limit == null` check so the later comparisons narrow. - `fallbackErrorMessages` typed `{ [key: number]: string }`, mirroring the identical declaration already in `libs/data-access/.../dot-upload.service.ts`. - `dot-utils.ts` uses bracket access for the six `DotCMSContentlet` index-signature reads in `getImageAssetUrl`. No new `any`, `@ts-ignore`, or `@ts-expect-error`. The nine `as unknown as` casts added are all in spec files, on inputs the tests deliberately pass as invalid, matching the idiom those files already used. Verified: - `tsc -p libs/utils/tsconfig.lib.json --noEmit` — 0 errors (from 32) - `tsc -p libs/utils/tsconfig.spec.json --noEmit` — 0 errors (from 17) - `data-access` typecheck went from 68 errors to 36, zero new - `utils-testing` unchanged at 1 pre-existing error (missing jasmine types) - `dotcms-ui` typecheck clean apart from a pre-existing missing `dotcms-webcomponents/loader` dist - `nx format:check` green Note: `utils` has no `build` target and is tag-excluded from lint and test, so nothing in CI verifies these flags — the same accepted trade-off as #35939. Closes #35940 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses three review comments on #36957. 1. `dot-content-types.mock.ts` — real regression, now fixed. `dotcmsContentTypeFieldBasicMock` spreads `EMPTY_SYSTEM_FIELD`, which #35940 retyped to `Omit<DotCMSContentTypeField, 'clazz'>`, leaving the mock without a required property (TS2741). It now supplies `clazz: DotCMSClazzes.TEXT`; callers that care already override it. Why the original verification missed it: `libs/utils-testing/tsconfig.lib.json` declares `"types": ["jasmine"]` and that package is not installed, so tsc emits `TS2688: Cannot find type definition file for 'jasmine'` and stops before semantic checking. The "1 error before, 1 after" measurement reported in #35940 therefore proved nothing — nothing was being checked. Running with `--types node` reveals 33 errors, including the TS2741. It is 32 after this fix. Verified the runtime-value change, since the mock has ~103 consumers whose tests do run in CI: `clazz` went `null` (pre-PR) → absent (#35940) → `TEXT`. `FieldUtil.isRow`/`isColumn`/`isTabDivider` compare for equality and return false for all three, and there is no `!field.clazz` or `=== null` check anywhere. Test runs: `default-value-property` 7/7, `dot-content-types-edit` 545 passed across 48 suites, `data-access` 751 passed across 79 suites. 2. `sdk-create-app/src/utils/index.ts` — the throw said "requires at least 1 retry", but `retries` is the total attempt count (`for (i = 0; i < retries)`), so `retries = 1` means one attempt and zero retries. Reworded to "attempt" and the ambiguity noted in the comment. 3. `core-web/CLAUDE.md` — the verify snippet hard-coded `libs/<project>/tsconfig.lib.json`, which resolves for neither nested projects (`libs/sdk/create-app`, which has no `tsconfig.lib.json`) nor apps (`tsconfig.app.json`). Replaced with a `<projectRoot>` placeholder plus the two caveats, a reminder that `tsconfig.spec.json` inherits the flags, and a warning about unresolved `types` entries masking all semantic diagnostics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`sdk-uve` needs no change for [08/44]. The six strict flags have been in `libs/sdk/uve/tsconfig.json` since the library was created (`277cbbc8f7`, #31242, Feb 2025) as a verbatim copy of `sdk-client`'s config, `tsc --noEmit` is clean on both lib and spec, and there are zero `any`, `@ts-ignore` or non-null assertions across 4518 lines. It is also genuinely enforced, which is what separated `sdk-types` from `dotcms-js` and `utils`. `rollup.config.cjs` sets `compiler: 'babel'`, but that governs only transpilation — `@nx/rollup`'s `withNx` always inserts a TypeScript plugin with `check`/`noEmitOnError` tied to `skipTypeCheck`, which this project does not set. Two of the three type-checking paths run in CI, and the `build-test` execution in `core-web/pom.xml` has no `<skip>` element, so it cannot be turned off. Issue closed as completed with the evidence; not linked to PR #36957 since there is no diff and that PR did not resolve it. Also records an incidental finding, left unfixed: `tsconfig.base.json:104` maps `@dotcms/uve/types` to a file that does not exist, and nothing imports it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`sdk-client` needs no change for [09/44]. The six strict flags are already in `libs/sdk/client/tsconfig.json`, `tsc --noEmit` is clean on both lib and spec, and there are zero `any`, `@ts-ignore` or non-null assertions across 9600 lines of production source. Enforcement is unambiguous here, unlike the sibling projects that needed an argument: `rollup.config.cjs` sets `compiler: 'tsc'` against `tsconfig.lib.json` with no `skipTypeCheck`, so the build compiles with tsc directly against the strict config. `tags` is empty and the `build-test` execution in `core-web/pom.xml` has no `<skip>` element, so that build runs on every PR and gates every SDK release. Issue closed as completed with the evidence; not linked to PR #36957 since there is no diff and that PR did not resolve it. Also records an emerging pattern for the remaining issues: every `libs/sdk/*` project checked so far is already strict and already enforced — they share a tsconfig lineage (sdk-uve's config is a verbatim copy of this one) and all build through Nx executors that type-check. The unfinished work is concentrated in the non-SDK libraries and the apps. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two factual corrections to the specs for #35941 and #35942, and one observation, all surfaced by a follow-up review. 1. Published version was wrong. Both specs quoted the version from the local `package.json` (`@dotcms/uve` 1.1.1, `@dotcms/client` 1.2.0) as if that were what ships. It is not: the SDK release action rewrites the version to the dotCMS release tag under ADR-0019 date lockstep. npm `latest` for both is 26.8.7-1 (197 and 262 published versions respectively). Both specs now say so explicitly, and the corresponding GitHub issue comments have been edited. 2. `sdk-client` has 6 dependents, not 4, and 5 of them are strict rather than 3. The Nx graph query used for the original count missed `sdk-experiments` and `sdk-create-app`. The lone non-strict consumer is `portlets-edit-ema-portlet`, which reaches into `@dotcms/client/internal`. 3. New observation, out of scope for the rollout: `build:js` in both `sdk-client` and `sdk-uve` emits an artifact that is committed to git (`html/js/editor-js/sdk-editor.js` and `ext/uve/dot-uve.js`), but that target is invoked by neither `core-web/pom.xml` nor any workflow. If the source changes and nobody runs it by hand, the committed file drifts out of sync and nothing notices. The verdicts for both issues are unchanged — both projects remain already strict-compliant and enforced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#35943 Groundwork for #35943. Strict is **not** enabled yet — 276 type errors remain across 42 files and Stencil type-checks during `build`, so flipping the flag before they are fixed would turn CI red. This lands the part that is correct on its own and leaves the build green. Stencil declares runtime-injected members without initializers, which collides with `strictPropertyInitialization`. Handled by decorator kind rather than uniformly, because the choice is not cosmetic: - `@Event` (57), `@Element` (27), `@State` (25) → definite assignment `!`. These are internal; the Stencil runtime assigns them and they do not appear in the generated public API. - `@Prop` (30) → optional `?` instead. Using `!` here made Stencil emit those props as **required** in `components.d.ts` — 28 of them — which is a breaking change for any TS/JSX consumer. With `?` the generated API moves the other way, from required to optional, which is backward compatible. Also `dot-binary-text-field`'s `value` prop was `= null` with no annotation, so under strict TS inferred its type as `null` and the generated API narrowed from `any` to `null`. It is assigned `''` and file URLs at runtime, so it is now typed `string | null` — still a narrowing from `any`, but an accurate one. `components.d.ts` and one readme are regenerated build output and are included so the repo matches what the build produces. Two things worth recording for whoever finishes this: - Do **not** put `"ignoreDeprecations": "6.0"` in this tsconfig. Stencil bundles TypeScript 5.8.3, which only accepts `"5.0"` and fails the build with `Invalid value for '--ignoreDeprecations'`. The repo's tsc is 6.0.3 and needs `"6.0"` to see past the deprecated `baseUrl` / `moduleResolution`, so pass it on the CLI. Without it, tsc aborts on TS5101/TS5107 before any semantic checking and reports a misleading 2 errors. - Unlike `utils` and `dotcms-js`, this project has no `skip:build`, so the Stencil build is a real CI gate. Strict has to reach 0 in one go. Verified: `nx run dotcms-webcomponents:build` green from a cleared `.stencil` cache; `dotcms-ui` typechecks with 0 errors; `nx format:check` green; no `!` on any member without a Stencil decorator (checked by script). Refs #35943 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…text-field `5ea14e8cf8` typed `dot-binary-text-field`'s `value` prop as `string | null`. That was wrong and broke the Stencil build: `handleFilePaste` assigns a `File` to it (line 105), alongside the strings assigned elsewhere. Corrected to `string | File | null`. The error was missed because the verification builds were reading Stencil's `.stencil` cache. `nx run <project>:build --skip-nx-cache` skips only the Nx cache, not Stencil's own, so a build can report green against stale output. Delete `libs/dotcms-webcomponents/.stencil` before trusting a result. Verified with both caches cleared (`.stencil` removed and `nx reset`): `nx run dotcms-webcomponents:build` green, `dotcms-ui` typechecks with 0 errors, `nx format:check` green. Refs #35943 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nary-text-field Both `5ea14e8cf8` (`string | null`) and `0117273504` (`string | File | null`) were wrong, and the second broke the Stencil build. The prop is genuinely contradictory at runtime and `any` was hiding it: `handleFilePaste` assigns a `File` to it (line 105), other paths assign strings, and the template passes it straight to an `<input value>`, which accepts `string | number | string[]` and therefore neither. No annotation describes the current code correctly — the render path has to be fixed first, which belongs to the strict-mode work rather than to this groundwork. Reverted to the original untyped `= null` and left a TODO(#35943) recording the contradiction so the next person does not re-annotate it and hit the same wall. Verified green on two consecutive builds with both `.stencil` and the Nx cache cleared. The earlier green readings that let this through were stale Stencil cache: `--skip-nx-cache` does not clear `libs/dotcms-webcomponents/.stencil`. Refs #35943 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`libs/utils-testing/tsconfig.json` has carried all six strict flags (plus
`strictTemplates`) for some time, but they were inert: `tsconfig.lib.json`
declared `"types": ["jasmine"]` and that package is not installed, so tsc
emitted `TS2688: Cannot find type definition file for 'jasmine'` and **stopped
before semantic checking**. The project reported exactly one error no matter
what the code did.
The reference is stale — nothing here uses jasmine, two files use `jest.*`
APIs, and `@types/jest` is installed. Changed to `"types": ["jest"]`, which
both removes the abort and drops 27 spurious `Cannot find name 'jest'` errors,
leaving 5 real ones:
- `clean-up-dialog.ts` — untyped `fixture` param. Typed structurally as
`{ nativeElement: unknown }` rather than importing Angular's
`ComponentFixture`, since only that one property is touched.
- `dot-page-state.service.mock.ts` — `_lock: boolean = null`, now
`boolean | null`.
- `dot-page-tools.mock.ts` — three mock entries carried a `tags` array that
`DotPageTool` does not declare. Nothing reads `.tags` off a page tool
anywhere in the repo, so the dead field was removed rather than added to the
model in `dotcms-models`.
`tsc -p libs/utils-testing/tsconfig.lib.json --noEmit` now exits 0 with no CLI
overrides — the check is real rather than short-circuited.
Verified across the consumers of the touched mocks (`cleanUpDialog` in 7 files,
the page-tools mock in 3): `data-access` 751 passed, `edit-ema-ui` 338 passed,
`dotcms-ui` typechecks with 0 errors, `nx format:check` green.
Note this project still has no build target and is tagged `skip:test` /
`skip:lint`, so nothing in CI runs this check — the same gap recorded for
`dotcms-js` (#35939) and `utils` (#35940).
Closes #35944
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`strict: true` was already present; the five companion flags were not. Adding them surfaced 14 errors, all `TS4111` — dot access on a type carrying an index signature — resolved with bracket notation. Two origins, same fix: - 13 come from `node.attrs`, declared `Record<string, any>` in `@dotcms/types`. That type is left alone: block editor attributes really are dynamic, and it lives in a layer-0 project whose consumers would all be affected. - 1 comes from CSS Modules (`styles.row` in `Row.tsx`), whose generated type is also a `Record<string, string>`. No behaviour change — bracket access compiles to the same property lookup. Unlike `dotcms-js`, `utils` and `utils-testing`, the flags here are genuinely enforced. Proved rather than assumed: reverting one access to dot notation fails the build with `@rollup/plugin-typescript TS4111`, so TypeScript is in the Rollup chain. The project carries no `skip:` tags, so CI builds, lints and tests it on every PR, and the same build runs in the SDK release pipeline. One error remains under plain `tsc` and is expected: `Cannot find module 'virtual:sdk-version'` in `sdk-client`. It is a Vite virtual module that raw `tsc` cannot resolve but the build can; it predates this change and is unrelated to strict mode. Verified: `sdk-react` build, lint and test green; `sdk-experiments` (its only internal dependent) builds green; `nx format:check` green; no new `any`, `@ts-ignore` or `@ts-expect-error`. Closes #35945 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Turn on noPropertyAccessFromIndexSignature, noImplicitOverride, noImplicitReturns, noFallthroughCasesInSwitch, and forceConsistentCasingInFileNames for the analytics lib's tsconfig, and update code/tests to satisfy the new diagnostics (bracket-notation dataset access, renamed ANALYTICS_CONTENTLET_CLASS constant, typed jest mocks, and a corrected mock Location/event payload shape). - Update CLAUDE.md and dotcms-webcomponents tsconfig comments to reflect the current strict-mode error count and affected file.
) sdk-angular already carried all six strict flags plus Angular's strictTemplates/strictInjectionParameters/strictInputAccessModifiers, so no flags were added. The real defect was dead config: both tsconfigs referenced a `next/` directory that existed and was removed (refs added 2025-03-21 in 09e879b). One reference was fatal. tsconfig.spec.json listed `next/test-setup.ts` in `files`, so tsc aborted with TS6053 before semantic checking — that config had never completed a single semantic pass, and any error count taken from it was meaningless. A non-matching `include` glob is harmless; a missing `files` entry is not, which is why tsconfig.lib.json kept working. Removed the dangling references from both files. Both configs now report 0 own errors; the single remaining error is the pre-existing, unrelated `virtual:sdk-version` from sdk-client. Verified the build is a real gate rather than assuming it: a deliberate type error in dotcms.store.ts fails `nx run sdk-angular:build` with TS2322 (exit 1), since @nx/angular:package runs ngtsc. Probe reverted. Contrast sdk-analytics (#35946), where the Vite build ignores type errors. lint clean; test unchanged at 21 suites / 234 tests, which is the guard that nothing dropped out of the program. Also documents the TS6053 masking variant in core-web/CLAUDE.md alongside the existing TS2688 one, and restores the #35946 spec write-up that the issue comment references. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
data-access has carried all six strict flags plus Angular's strict template options for some time, and they were doing nothing: the project has no `build` target, so its own tsconfig is never read, and its 27 dependents compile these sources under their own non-strict configs. 36 errors sat in a layer-3 shared services hub with CI green. Production source (36): - paginator: 8 uninitialised fields given zero values; header reads guarded with `?? ''` (same NaN outcome); `setLinks` widened to `string | null` since its body already handled null; `Links` given an index signature because the Link-header parser stores arbitrary `rel` values. `_sortOrder` deliberately stays optional — defaulting it to OrderDirection.ASC would be a behaviour change, as getParams() gates the `direction` param on truthiness and ASC === 1 is truthy. - dot-page-state: widened six declarations that were simply wrong. The key one is handleSetPageStateFailed, which ends in `map(() => undefined)` and so really emits undefined — meaning the caller's `[null, null]` destructuring default is load-bearing, not dead code. Typing it honestly made the switchMap typable; it now destructures explicitly. `if (page)` became `if (page && user)`, which forkJoin already guaranteed. - dot-router, dot-localstorage, dot-content-types-info: nullable getters, localStorage reads, and a string index narrowed to keyof. Specs (47): 25 came from three untyped jest.fn mocks on a fake Router that inferred zero parameters. Two were real bugs hidden behind disabled suites — dot-global-message imported DotMessageService from a module that does not export it, and dot-ai.service.ts re-exported a type without `export type`. dot-page-layout was testing a payload shape production never sends. The content-drive fixture used a removed `offset` field copied from the model's own stale JSDoc example, which is fixed too. No new `any`, no @ts-ignore / @ts-expect-error. Verified: tsc 36→0 (lib) and 84→0 (spec); lint clean; test unchanged at 754 passing. Blast radius measured across all 7 strict dependents — zero new errors, 218 removed, and global-store, portlets-dot-analytics-data-access and portlets-dot-experiments-data-access went 36→0, having carried nothing but this library's leakage. Runtime guard: dotcms-ui 820 tests and ui 2184 tests pass. Still unenforced by CI, consistent with #35939 and #35940. Also documents that ts-jest runs transpile-only whenever tsconfig.spec.json sets isolatedModules, so no `test` target type-checks specs anywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Correction found while working #35948. The #35947 write-up claimed jest-preset-angular/ts-jest type-checks spec files, and that sdk-angular's 234 passing tests therefore evidenced type-cleanliness. It does not: ts-jest copies TypeScript's `isolatedModules` into its own transpile-only switch (config-set.js:229) and then skips building the language-service host that diagnostics require (ts-compiler.js:74). data-access is the counterexample that exposed it — same setup, 84 tsc errors alongside 754 passing tests. The #35947 verdict stands; it was measured with `tsc -p` directly, not inferred from the test run. Only the justification was wrong. Because CLAUDE.md mandates `isolatedModules: true` in every tsconfig.spec.json, this holds repo-wide: no project's `test` target type-checks its specs. Documented alongside the existing TS2688 and TS6053 masking notes, since it is the same trap in a third form — a green signal that checked nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tsconfig.json carried only `strict`. Measured the other five on the CLI before committing them: 0 errors on both tsconfig.lib.json and tsconfig.spec.json, so nothing was hiding behind them. Pure flags change. Enforcement confirmed by negative test rather than assumed — despite an inferred `typecheck` target, this project builds with Rollup, so @rollup/plugin-typescript is in the chain and a deliberate type error fails `nx run sdk-experiments:build` with TS2322 (exit 1). Probe reverted. lint, test (48) and build all green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#35952 #35954) Four projects verified against the rollout bar. All four already carried all six flags, and none was masked by a TS6053/TS2688 config error, so their counts were real. Three needed no change at all — portlets-dot-locales-data-access (#35950), portlets-dot-experiments-data-access (#35952) and, after the one fix below, portlets-dot-analytics-data-access (#35954). Two of them reported 36 errors before #35948; every one was data-access leaking through imports, so fixing that library took them to 0 with nothing touched here. The one real defect was in global-store (#35951): src/index.ts re-exported WebSocketStatus with `export {}`, but it is a type, so under isolatedModules that is TS1205. global-store's own configs never reported it — no spec there imports ./index, so the file was never in its own program. It only surfaced from consumers, showing up as the single spec error attributed to portlets-dot-analytics-data-access. `export type` fixes both issues at once. Generalisation worth carrying: a barrel file can hold an isolatedModules error that only its consumers ever see. Second instance after dot-ai.service.ts in #35948. None of the four is enforced — no build target, and :test does not type-check since isolatedModules puts ts-jest in transpile-only mode. tsc clean on lib and spec for all four; lint and test green (global-store 187 tests, analytics-data-access 217). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Partial progress on ui, the rollout's biggest bottleneck: ~109 of its
errors leak into each of its 26 dependents, so this unblocks a large part
of the epic. tsconfig.lib.json is now at 0 (from 122); tsconfig.spec.json
is at 123 (from 427) and continues in a follow-up commit.
Flags added to libs/ui/tsconfig.json (it had none of the six).
Production fixes follow the policies agreed for the epic — zero values for
TS2564 where one exists, definite assignment only for FormGroup/Observable/
ViewChild, and guards rather than assertions where null is reachable:
- dot-icon's `size` deliberately became `size?: number` instead of `= 0`.
The template binds [style.font-size.px]="size", so 0 would have rendered
invisible icons where undefined inherits.
- dot-sidebar, dot-dropdown, dot-site-selector, dot-container-options and
dot-trim-input all inject their host with { optional: true } and then used
it unguarded. They now guard.
- Types that were simply wrong: `formEl` was declared HTMLFormElement while
the template says #formEl="ngForm" (it is an NgForm); getVariableIndexChanged
declared `number` while its own JSDoc documented `number | null`;
getDefaultBundle returned null under a non-nullable type.
- Real defect found: dot-add-to-bundle invoked getDefaultBundle twice for the
same value. Now once.
- tsconfig.lib.json excluded a non-existent src/test.ts but not test-setup.ts,
**/*.test.ts or __mocks__/, so test files were compiled into the library
program. That alone accounted for 15 errors.
- htmldiff-js ships no types; added a minimal module declaration rather than
silencing the import.
Specs went 427 -> 123 mostly by asserting at the point of declaration rather
than at every use: 53 usages of one `select` local came from a single line, so
95 declaration-level assertions cleared roughly 300 errors.
One behaviour change worth naming: the gravatar directive now clears the
PrimeNG avatar with `undefined` instead of `null`, because Avatar declares
image?/label? as optional strings and null was never assignable. Five spec
assertions moved from toBeNull to toBeUndefined to match.
lint clean (8 pre-existing warnings); test unchanged at 81 suites / 820
passing, verified against the pre-change baseline.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#35960) Also fixes an htmldiff-js type leak that was inflating every consumer of libs/ui by one error. The ambient declaration added for htmldiff-js in #35953 lived under libs/ui/src, so it was only in ui's own program; every project that compiles ui's sources still reported TS7016. Registering it through tsconfig.base.json makes it global. It sits in a root-level types/ folder rather than inside a project, because mapping it into libs/ui made @nx/enforce-module-boundaries demand a relative import. Same shape as the known virtual:sdk-version leak from libs/sdk/client — an ambient declaration parked next to its consumer instead of somewhere every program can see it. portlets-dot-usage (#35969): added the two missing flags; one real error, a UsageSummary fixture missing the required lastUpdated. edit-content-bridge (#35960): added the missing `strict`; two real errors. The dialog ref is now captured in a local so its non-nullness is evident instead of asserted. In the spec, `reconcileOnFormEvent` is assigned inside a nested callback, which TypeScript's control-flow analysis cannot see, so it narrowed the variable to `null` and reported it as not callable; definite assignment states the intent. Both projects: tsc clean on lib and spec, lint clean, tests green (97 and 21). ui unchanged at 820 passing. Neither is enforced — no build target, and :test does not type-check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
19 errors, and 8 of them shared one root cause: dispatchLoading's switch had no default branch, so under noImplicitReturns the updater's return type included undefined, stopped resolving as a one-argument updater, and every one of its six call sites reported TS2554 "Expected 0 arguments". An unknown loader should leave state untouched, so `default: return state` fixes the TS7030, the TS2345 and all six call sites at once. The rest: definite assignment for a ViewChild and two fields built in ngOnInit, a form control the component creates itself, two implicit-any parameters, and chart.js tick callbacks that receive `string | number` rather than `number`. Also guards two conditions in libs/ui's dot-action-menu-button template. `actions` is an optional @input used as `actions.length`, which had never been null-checked: libs/ui has no build target, so its templates are only verified when a consuming app compiles them, and turning on `strict` here is what made ngtsc check them. Guarded with `actions?.length`, which is exactly equivalent for both conditions. Worth noting for the remaining apps: `tsc -p` does not check templates, so per-project error counts taken that way miss this class entirely. Only a build does. app and spec configs at 0, lint clean, and dotcdn:build passes — this project has a build target, so unlike most of the rollout its flags are enforced. ui unchanged at 820 tests passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts f51e02f and ba7183e. The @import -> @use migration of libs/dotcms-scss/jsp turned out to need structural surgery on legacy Dojo styles: seven @extend rules cross module boundaries, and four of them target #cell-actions, which lives in the very ancestor (backend/_common.scss) that loads the files extending it. The module system rejects that as a cycle. Breaking it means relocating five cross-cutting extend targets, which moves where their rules are emitted inside an 11,341-line stylesheet that has no automated coverage - and with that, the byte-identical diff that was the only objective check available here stops being achievable. Since another session is working this branch in parallel, restructuring legacy JSP styles here is not worth the risk, so the whole JSP zone goes back to its baseline and is deferred. That zone is exactly what the two reverted commits touched: the root sass CLI is invoked by three commands, all of them in libs/dotcms-scss/project.json and all of them compiling the JSP bundle. Angular resolves its own sass through @angular/build and Stencil through @stencil/sass, so neither is affected by the root version. With 1.56.2 restored the bundle again compiles without warnings - the older compiler does not know these deprecations - and darken()/ceil() go back to their original form. Kept from the JSP zone: the .tagLink back-port in c019aa8. It changes no shipped artifact, and without it the next regeneration of dotcms.css would silently revert the fix from PR #35667. Verified: compiling dotcms.scss with 1.56.2 still reproduces the committed dotcms.css byte for byte. Still shipped: the dotcms-webcomponents migration and the Angular dead-import cleanup, which are what actually emitted deprecation warnings on every build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 tasks
Removes now-unnecessary optional chaining/nullish coalescing on values Angular's strict templates prove non-null, makes a few `!:` fields properly optional where they can be undefined before initialization, and turns on `extendedDiagnostics.defaultCategory: error` in tsconfig.base.json so future template diagnostics fail the build instead of warning silently.
…nputs
`@Input() x!: T` asserts a value the compiler never verifies, so a caller that
omits the binding leaves the property undefined with nothing to catch it. Two
such inputs (`DotPortletToolbarComponent.actions`,
`DotSelectSeoToolComponent.device`) were already failing that way in tests.
Ran `@angular/core:signal-input-migration` over the affected areas and kept only
the components that carried the pattern, reverting the collateral sweep of
optional `@Input()`s. Counts: `@Input({required: true}) x!:` 8 -> 0 (semantically
identical, both compiler-enforced) and `@Input() x!:` 97 -> 38.
The migration reads `!` as "required", which this codebase mostly contradicts:
of the seven inputs the compiler could check, all seven had call sites that never
bound them. Each converted input was therefore decided individually — 30 are
genuinely required (every call site binds them, now enforced) and 5 are optional
(`src`, `mapItems`, `velocityVar`, `blocks`, `dotShowOnNotFound`).
Three latent defects surfaced along the way:
- `DotListingDataTableComponent` read `url` in its constructor, before inputs
exist, so `paginatorService.url` was always assigned undefined; `ngOnInit`
already set it correctly, so the constructor line is gone.
- `dot-container-permissions.component.spec` built the component under test while
treating it as the host, overwriting the input it meant to bind.
- `DotCMSBlockEditorRendererComponent` validated `blocks` twice.
`@dotcms/angular` note: `DotCMSEditableTextComponent.contentlet` and `.fieldName`
are now required, which is breaking for consumers that never bound them and needs
a version decision. `DotCMSBlockEditorRendererComponent.blocks` stays optional —
the component renders an error branch for a missing value by design, matching the
-native renderer.
The 38 remaining are left as they are: 16 of them (NodeViewRenderer, asset-form,
suggestions) are assigned through `AngularRenderer.updateProps`, which does
`instance[key] = value` and would overwrite a signal; converting those first
requires moving that helper to `componentRef.setInput`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`@angular-eslint/prefer-signals` now runs as an error for `preferInputSignals`, so a component added from here on must declare inputs with `input()` / `input.required()`. The query and readonly-property halves of the rule stay off to keep this to inputs alone. The 219 decorators already in the tree are recorded in per-project `eslint-suppressions.json` baselines (ESLint 9.24+) rather than 219 inline disable comments. `nx lint` picks them up with no extra flag because its inferred target runs `eslint .` with the project root as cwd. A single workspace-root baseline does not work, and neither does passing `--suppressions-location` through `targetDefaults`: the plugin's inferred `command` wins over it. Verified end to end — `nx lint ui` is clean as it stands and fails as soon as a new `@Input()` appears. Suppressions are counted per file, so adding an input to a file that already has suppressed ones reports every occurrence in that file, not just the new one. The build fails either way; the extra lines are not new violations. The baselines are generated output, so Prettier ignores them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`nx affected -t build` reports success while printing eleven TypeScript errors from sdk-vue: Vite builds with esbuild, which does not type check, and vite-plugin-dts prints diagnostics without failing. Nx infers a separate `typecheck` target for those projects and CI never ran it, so the errors were invisible. Ten of them are TS4111, from the `noPropertyAccessFromIndexSignature` that 66d73c6 turned on workspace-wide. Nine projects were opted out of that flag at the time; libs/sdk/vue was missed and never fixed. Its `node.attrs` reads now use bracket notation rather than opting the project out, which would only bank the debt. The eleventh is TS2307 on `virtual:sdk-version`. The declaration existed but sat in `libs/sdk/client/src/`, and an ambient declaration only covers the project that includes it — consumers pull those sources in through a path mapping, which does not carry the sibling `.d.ts`. It moves to `types/` behind a `paths` entry, the same shape `htmldiff-js` and `jstat` already use. Unlike those two, its importer is a buildable library, so `@nx/enforce-module-boundaries` reads the mapping as a buildable -> non-buildable edge; the module id is allow-listed since there is no real dependency to enforce. The gate itself is a `typecheck-test` execution beside `lint-test`, not part of `build-test`: it validates rather than producing artifacts, so it belongs in generate-resources with `affected` and `${skip.validate}`, and `build` stays whole and unskippable for the Docker image. It runs before lint because it covers four projects against lint's forty-seven and finishes in roughly half the time. The root `core-web` project is tagged skip:typecheck. Its target is `tsc --noEmit -p tsconfig.base.json`, which compiles the entire workspace as one program and reports ~2570 errors; it has never passed and is not a per-project gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3a8fc87 created throwaway `tsconfig.strictprobe.json` files to measure how many template errors each project would report under strictTemplates, and ignored them so they could not be committed by accident. #37120 is closed and no probe file is left in the tree, so the rule matches nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both come from the regex passes in d56b8e1 and neither could fail a test run, because `nx test` transpiles specs without type checking: - editor.directive.spec.ts asserted `component.value()` on `TestFormComponent`, whose `value` is a plain string. The pass matched on the name being a signal input elsewhere in the workspace. - dot-device-selector-seo.component.spec.ts called `fixture.componentRef.setInput` in a spec whose fixture is named `fixtureHost`. `hideSocialMedia` and `currentUser` are signal inputs now, so they are set through the host's bindings instead; `hideSocialMedia` had none and gains one. Found by running `tsc --noEmit` over the spec configs of the projects that have no build target of their own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`nx test` runs on ts-jest with `isolatedModules`, which transpiles without building a program, so a spec could carry type errors indefinitely while its suite stayed green. Library sources are already checked transitively when a consuming app builds them; specs were the gap. Scanning the 37 projects that have no build target of their own turned up 19 errors across 8 of them, hidden for exactly that reason. A local plugin infers the target instead of ~30 `project.json` declarations. The gap being closed was itself created by a project getting missed, and a per-project declaration would let the next one slip the same way. Projects carrying a `vite.config.*` keep the `typecheck` `@nx/vite/plugin` infers for them — the plugin steps aside rather than overwriting it, which matters for sdk-vue, whose target runs `vue-tsc`. `typecheck-test` in pom.xml already runs the affected set before lint, so the new targets join CI with no further wiring. What the scan surfaced, now fixed: - dot-publishing-queue: five specs set an aliased signal input through Spectator's `props`, which is typed on the class property name but applied through `setInput`, which takes the public alias. The two disagree, so the input was never set and four tests were passing without exercising the path. They go through `setInput` now. - dotcms-js: `tsconfig.spec.json` asked for `@types/jasmine`, which this workspace does not install, so tsc aborted on TS2688 and had never reached the code. With `types` pointing at jest, two real errors appeared behind the abort. - dot-users: a service mock returned the list shape where the detail shape was declared. - dot-agents: `target: es2022` without a matching `lib`, so `Array.prototype.at` was missing when it compiled edit-ema/ui sources through a path mapping. - sdk-react, dot-textarea-content, dot-ai-config-detail: fixtures typed as `never` or as object literals that do not satisfy the parameter. - dot-container-permissions, dot-container-selector, dot-iframe-dialog, name-property: local annotations that no longer matched what the spec builds or what the component exposes. mcp-server's specs opt out of `noPropertyAccessFromIndexSignature` and `noImplicitOverride` with the same TODO libs/sdk/ai and apps/ai-evals already carry; `strict` itself passes there. 50 projects, 27 seconds, zero errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
XML forbids `--` inside a comment, and the note added with `typecheck-test` in aa13165 spelled out `tsc --noEmit`. Maven could not read the POM at all, so the PR build failed before reaching any target. Verified with a real `mvnw generate-resources -Dskip.validate=false`: typecheck-test (50 projects) then lint-test (47) then format-test, BUILD SUCCESS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The shell spec mocked `contextMenu` as null, so `$contextMenuData().showAddToBundle` threw and 123 tests failed. The store never reaches that state: it starts with a context menu object and every method patches an object in, which is why the template reads it without a guard and why the compiler types the signal as non-nullable. Missed during the strict-template work because the verification runs named the project `portlets-dot-content-drive-portlet`. The real name is `portlets-content-drive`, and Nx runs the projects it matched without warning about the one it did not — the trap already written up in core-web/CLAUDE.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The PR build failed on sdk-ai, mcp-server and sdk-experiments even though all three pass locally. `typecheck-test` sits in generate-resources, ahead of `build-test` in compile, so on a fresh runner the artifacts those three read had not been produced yet: - sdk-ai reads `src/generated/spec.json`, which is gitignored and comes from its `generate-spec` target. Its `build` and `test` already declare that dependency; the inferred `typecheck` did not. - mcp-server consumes `@dotcms/ai/spec`, so the generator has to run on its dependency. - sdk-experiments resolves its SDK peers against `dist/` through its own `paths` block (the @nx/rollup workaround described in CLAUDE.md), so those libs have to be built. Each declares the dependency it actually has rather than moving the gate after the build, which would cost its fail-fast position ahead of lint. Nx caches the extra work, so `build-test` still hits cache afterwards. Reproduced by deleting `dist/` and `libs/sdk/ai/src/generated`, which failed exactly as CI did, then verified green from that same state: 50 projects and the 5 tasks they depend on. Full `mvnw generate-resources -Dskip.validate=false` reaches BUILD SUCCESS through typecheck-test, lint-test and format-test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every e2e spec failed with `Cannot find module '@pages'`. Playwright 1.36 ignores the whole tsconfig `paths` map unless `baseUrl` is set — `transform.js` opens with `if (!tsconfig.tsConfigPath || !tsconfig.baseUrl) return;` — and 7854580 dropped `baseUrl` from apps/dotcms-ui-e2e/tsconfig.json along with every other one, for the TypeScript 7.0 deadline. TypeScript itself resolves those aliases without it; only Playwright's own resolver did not. Restoring `baseUrl` would have meant carrying `ignoreDeprecations: "6.0"` beside it, adding a second exception to the rule that CLAUDE.md states plainly. 1.62 resolves `paths` on its own, so the deprecated pair is not needed at all and the tsconfig stays as the strict-mode work left it. The jump spans 26 minor versions, so the specs were checked against the new types: `tsc -p` reports 25 errors on 1.62 against 31 on 1.36 — all pre-existing strict-mode debt in that project, six of which the upgrade happens to fix. Nothing gates them today; the project has neither a build nor a typecheck target. Verified: `playwright test --list` enumerates 141 tests across 30 files with no module errors, lint passes, typecheck stays green for 50 projects, and the 17 affected builds still pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Access protected `$canAddChildren`/`$addChildrenTooltip` signals via bracket notation since strict mode now enforces member visibility in tests - Use existing jest.fn() mocks instead of reassigning service methods, and drive store state through mocked selectors instead of patchState - Type `permissions`/`build()` params explicitly and seed fixture state from the store's own initial state to avoid drift - Add missing `_body` field required by the response type and drop an unused import now flagged under strict checks
`store` is the real DotContentDriveStore instance, only *typed* as `SpyObject<...>`, so `store.currentSite.mockReturnValue(...)` threw "is not a function" at runtime — currentSite is a signal, not a jest mock. Go back to patchState. The reason it was swapped out is real: SpyObject's mocked member types no longer match `WritableStateSource` structurally, so strict mode rejects the store as arg 1. Cast it back at the call site instead, which keeps the assertion honest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nicobytes
force-pushed
the
35932-enable-strict-mode-v3
branch
from
August 26, 2026 01:38
587296b to
d3470a6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Supersedes #36957 and #37197. #37197 was blocked at merge by
required_signatures: 159 of its176 commits predated the SSH-signing setup and could not be signed in place, because the org-wide
2026-08-24_incident-responseruleset forbids both direct pushes and force pushes to existingbranches. This branch is that same history with all 176 commits re-signed — trees, authorship,
dates and messages are byte-identical (verified commit-by-commit); only the committer and signature
changed, which is inherent to any rewrite.
What
Thirty-three steps of the strict-mode rollout (epic #35932), plus groundwork on one more (
dotcms-webcomponents, not closed):edit-contentdotcms-binary-field-builderinclude: []), so measuring that file reported a fake zeroportlets-dot-query-tool-portletnoImplicitReturns-without-strictcombination caught aTS7030I introduced inedit-contentedit-ema-uitemplate-builderdotcms-block-editorTS2688). Unmasking them surfaced a template defect that only a build can seeblock-editorOmitthat silently erased every member of PrimeNG'sMenuItemdotcdnswitchdefault. First app to go strict — surfaced template errors inlibs/uiportlets-dot-usagehtmldiff-jstype leakportlets-dot-tags-portletcatchbindingportlets-dot-locales-portletDynamicDialogRefannotations,of(null)forObservable<void>portlets-dot-es-search-portletjest.fn()assigned onto signal-typed membersportlets-dot-categories-portletedit-content-bridgeportlets-dot-analytics-data-accessglobal-store's barrelportlets-dot-experiments-data-accessglobal-storeexport typethat also closed #35954portlets-dot-locales-data-accesssdk-experimentsportlets-dot-plugins-portletmoduleResolution: node10broke 256 imports. Also corrects the portlets guide that recommended itportlets-dot-analytics@types/d3-*addedcontent-drive-uinew-block-editorEditorViewsourced from@tiptap/pm/view;@types/turndownaddeduidata-accessbuildtarget) — fixes the 36 lib + 47 spec errors hiding behind themsdk-angularnext/tsconfig refs that madetsconfig.spec.jsonunverifiable (TS6053)sdk-analyticsTS4111in source and 29 in specs (14 pre-existing)sdk-reactTS4111index-signature accessesutils-testingtypes: ["jasmine"]aborted all type checkingutilsstrict+ fix the 32 (+17 spec) resulting type errorsdotcms-jsstrict+ fix the 38 resulting type errorssdk-create-appstrict+ fix the 2 resulting type errorssdk-typesAll three add the standard six flags to the project's own
tsconfig.json, following the pattern established in #36879 (dotcms-models).tsconfig.base.jsonstays at"strict": false— the rollout never flips it globally.dotcms-js(#35939)The largest of the three: 38 errors across 11 files, in a layer-1 core library with 20 dependent projects, including the
dotcms-uiadmin app. Six of those dependents are already strict, so this library's loose types were leaking uncertainty into projects that had opted into rigour.Most fixes correct types that were simply wrong, rather than silencing the compiler:
Auth.loginAsUserUsernullwhen nobody is impersonating, and every consumer already guards withauth.loginAsUser || auth.user. NowUser | null.StringUtils.getLinestringstring | null.HttpRequestUtils.getQueryStringParamstringRoutingService.getPortletURLstringMap.get(). Nowstring | undefined.SiteService.switchSiteByIdObservable<Site>of(null)when no site is found. NowObservable<Site | null>; its one consumer already handled null.ResponseView.bodyJsonObjectDotCMSResponse<T>HttpResponse.body, which is nullable. The surroundingtry/catchcould never throw and has been removed.LoginService.urlsmoved fromRecord<string, string>to inference-typed, which resolves all 8TS4111errors at once and gives each endpoint a named property.Two definite-assignment assertions were used, each with a
TODO:LoginService._authandSiteService.selectedSiteare assigned during init but not in the constructor. Modelling them as| undefinedis the truthful type, but their public getters (auth,currentSite) are consumed by already-strict projects, so widening them is a public-API change that belongs in its own issue.No new
any,@ts-ignore, or@ts-expect-erroranywhere in this PR.Blast-radius verification
data-access(a strict consumer) went from 106 type errors to 68, with zero new errors introduced — the honest types upstream remove noise downstream.dotcms-uitypechecks clean apart from a pre-existing missingdotcms-webcomponents/loaderdist.utils(#35940)32 errors across only 3 files, plus 17 more that appeared in the spec files once the flags propagated through
tsconfig.spec.json(baseline there was 0). Both are fixed here — leaving the spec errors would have shipped a regression.The bulk was one constant.
EMPTY_FIELDassignednullto 18 members thatDotCMSContentTypeFielddeclares non-nullable:nullstrictly — consumers use falsy checks such asisNewField's!field.id— so'',0andfalsebehave identically at runtime.clazzhas no zero value (DotCMSClazzis a union of concrete Java class names), soEMPTY_FIELDandEMPTY_SYSTEM_FIELDare nowOmit<DotCMSContentTypeField, 'clazz'>. They are partial templates, not valid fields, and the type now says so. The derivedCOLUMN_FIELD/ROW_FIELD/TAB_FIELDalready supply their ownclazz, so they remain complete.Other fixes:
getFieldsWithoutLayout.filter()did not narrow the optionalrow.columns. A type predicate clears theTS2532and bothTS2769without a cast.ellipsizeTextnull/undefinedat runtime — its own guard and its tests say so — but declaredstring/number. Widened to match, with an explicitlimit == nullcheck so later comparisons narrow.fallbackErrorMessages{ [key: number]: string }, mirroring the identical declaration already inlibs/data-access/.../dot-upload.service.ts.dot-utils.tsDotCMSContentletindex-signature reads ingetImageAssetUrl.dot-asset.service.tspromisesand the twofetchAssetparams.The nine
as unknown ascasts added are all in spec files, on inputs the tests deliberately pass as invalid, matching the idiom those files already used.Blast-radius verification
data-access(strict consumer) went from 68 type errors to 36, zero new.utils-testing(strict) unchanged at its 1 pre-existing error — theOmitdid not break itsEMPTY_SYSTEM_FIELDspread.sdk-create-app(#35938)Two errors, both from flags beyond plain
strict:src/index.ts:393—process.env.DEBUGneeds bracket access undernoPropertyAccessFromIndexSignature(TS4111). It is the onlyprocess.env.*dot access in the project.src/utils/index.ts:41—fetchWithRetrytrippednoImplicitReturns(TS7030). The loop returns on success and throws on the last attempt, but withretries < 1the loop never runs and the function fell through returningundefined. Its only caller (isDotcmsRunning,src/index.ts:506) already guarded withif (res && …), so nothing broke in practice — but the signature was lying. Throwing after the loop closes the gap and narrows the return type toPromise<AxiosResponse>.No build or CI wiring was needed here. The
@nx/esbuild:esbuildexecutor type-checks before bundling (skipTypeCheckdefaults tofalseand is not overridden), and CI already builds this project vianx run-many -t build(build-testincore-web/pom.xml). The same build runs in the SDK release pipeline (cicd_release-sdk.yml→nx run-many --projects='sdk-*'), so the flags are enforced on every release.sdk-types(#35935)libs/sdk/types/tsconfig.jsonhas carriedstrict: trueplus the four extra safety flags since the library was created (#31967), andtsc --noEmitpasses with zero errors. It is also already enforced:tsconfig.lib.jsonsets"declaration": true, so@rollup/plugin-typescriptsits in the Rollup chain and fails the build on a strict violation.So no code change was required. What was missing was documentation, added here to
core-web/CLAUDE.md:## TypeScript Strict Modesection covering the per-project flags, what actually enforces them, and the Vite exception (esbuild skips type checking, which is why the Nx Vite plugin infers a separatetypechecktarget)."strict": truein project tsconfigs. It sat under the Jest config guidance but read as a blanket ban, contradicteddocs/frontend/TYPESCRIPT_STANDARDS.md, and blocked the epic outright. The restriction now points attsconfig.spec.json, which is what it meant.utils-testing(#35944)The six strict flags were already in
tsconfig.json— but completely inert.tsconfig.lib.jsondeclared"types": ["jasmine"], that package is not installed, sotscemittedTS2688: Cannot find type definition file for 'jasmine'and stopped before semantic checking. The project reported exactly one error regardless of what the code did.The reference was stale: nothing uses jasmine, two files use
jest.*, and@types/jestis installed. Switching to"types": ["jest"]removed the abort and 27 spuriousCannot find name 'jest'errors, leaving 5 real ones:clean-up-dialog.tsfixtureparam → typed structurally as{ nativeElement: unknown }, since only that property is touched (no need to pull in Angular'sComponentFixture)dot-page-state.service.mock.ts_lock: boolean = null→boolean | nulldot-page-tools.mock.ts×3tagsarray thatDotPageTooldoes not declare. Verified nothing in the repo reads.tagsoff a page tool, so the dead field was removed rather than added to the model indotcms-modelstsc -p libs/utils-testing/tsconfig.lib.json --noEmitnow exits 0 with no CLI overrides — the check is real rather than short-circuited.Verified across consumers of the touched mocks (
cleanUpDialogin 7 files, page-tools mock in 3):data-access751 tests passed,edit-ema-ui338 passed.dotcms-webcomponents(#35943) — groundwork only, not closedStrict is not enabled here. ~250 errors remain across 38 files, and unlike the other projects this one has no
skip:build, so Stencil type-checks it on every PR — flipping the flag early turns CI red. What landed is the part that is correct on its own.The decorator split, which is the load-bearing decision. Stencil declares runtime-injected members without initializers, colliding with
strictPropertyInitialization(139 of the original 375 errors). The fix cannot be uniform:@Event!EventEmitter@Element!@State!@Prop?Using
!on@Propmade Stencil emit 28 props as required incomponents.d.ts— breaking for any TS/JSX consumer. With?the generated API moves required → optional, which is backward compatible. Measured in the generated file, not assumed.Two traps recorded on the issue
Stencil under-reports. Its build shows ~10 files / ~39 errors per run, not the total. Measured at the same commit: Stencil 39 errors / 10 files vs
tsc250 / 38. Size this work withtsc, not with build output.--skip-nx-cachedoes not clear Stencil's cache. Builds can report green against stale.stenciloutput. This bit me:0117273504annotated a prop, passed a "clean" build, and was actually broken — reverted inf22afce383after verifying twice with.stenciland the Nx cache cleared.That prop (
dot-binary-text-field'svalue) is genuinely contradictory:handleFilePasteassigns aFile, other paths assign strings, and the template feeds it to an<input value>that accepts neither. No annotation describes the current code — the render path has to be fixed first. Left untyped with aTODO(#35943)so it is not re-annotated in isolation.sdk-react(#35945)strict: truewas already present; the five companion flags were not. Adding them surfaced 14 errors, allTS4111— dot access on a type carrying an index signature — resolved with bracket notation. Two origins, same fix:node.attrs, declaredRecord<string, any>in@dotcms/types. That type is deliberately left alone: block editor attributes really are dynamic, and it lives in a layer-0 project whose consumers would all be affected.styles.rowinRow.tsx), whose generated type is also aRecord<string, string>.No behaviour change — bracket access compiles to the same property lookup.
The flags here are genuinely enforced, and that was proved rather than assumed. Reverting one access to dot notation fails the build with
@rollup/plugin-typescript TS4111, confirming TypeScript sits in the Rollup chain. The project carries noskip:tags, so CI builds, lints and tests it on every PR, and the same build runs in the SDK release pipeline.sdk-analytics(#35946)Same starting shape as
sdk-react:strict: truealready present, the five companion flags absent. But this one is not enforced, and that was established by test rather than inference.18 errors in production source, all
TS4111fromnoPropertyAccessFromIndexSignature— dot access onHTMLElement.dataset(DOMStringMap) and on aRecord<string, unknown>of payload properties. Bracket notation throughout, reads and writes alike. Spread acrossdot-analytics.utils.ts(10),dot-analytics.click-tracker.ts(4),dot-analytics.impression-tracker.ts(3),dot-analytics.click.utils.ts(1).29 errors in specs — 15 more of the same mechanical
datasetfix, plus 14 that were pre-existing drift rather than strict-mode fallout. Confirmed pre-existing: they persist identically under--strict false. Two independent gaps had let them accumulate unseen — the inferredtypechecktarget runs onlytsconfig.lib.json, andjest.config.tstransforms viababel-jest, which strips types without checking them.ANALYTICS_CONTENTLET_CLASSno longer exported — renamed toCONTENTLET_CLASSdeviceinsidedataand omitted requiredlocale_idjest.fn()inferringneverformockResolvedValue/mockRejectedValueLocationmock missinghostjest.spyOn(...).mockImplementation()called with no argumentmockInitializeinferred as zero-argresult.custom— not onEnrichedTrackPayloadTS2589excessively deep instantiationThe pageview fixture was the instructive one: with
devicemisplaced andlocale_idmissing, thepageviewmember of theDotCMSEventunion stopped matching, so TypeScript fell through to the impression member and reported a misleading "doc_encodingdoes not exist onDotCMSContentImpressionPageData". One coherent fix cleared four errors. Fixtures were corrected rather than production types widened; no source bug hid behind any of them.So
sdk-analyticsjoinsdotcms-jsandutilsas strict but unenforced. Wiringnx affected -t typecheckintocore-web/pom.xmlwas deliberately left out — it is monorepo-wide and belongs to the epic, not to project 13 of 44. Both gaps are raised on #35932.This also corrects the pattern proposed in #35942 — that every
libs/sdk/*project was already strict and already enforced. That holds for the Rollup-built SDK libs, which type-check through@rollup/plugin-typescript(assdk-reactproved). It does not hold for Vite-built ones:sdk-analyticsinheritedstrictfrom the shared tsconfig lineage but neither the other five flags nor a type-checking build.0 internal dependents — the only references to
@dotcms/analyticsoutside the lib are doc comments inlibs/sdk/uve/src/internal/constants.ts. No blast radius.sdk-angular(#35947)No flags were added — all six were already there, plus Angular's
strictTemplates,strictInjectionParametersandstrictInputAccessModifiers. Re-adding them would have been a cosmetic diff. The real defect was dead config.Both
tsconfig.lib.jsonandtsconfig.spec.jsonreferenced anext/directory that existed and was removed — the references landed on 2025-03-21 (09e879b2ac) and outlived the directory. One of them was fatal:tscaborts on that before semantic checking, sotsconfig.spec.jsonhad never completed a single semantic pass and any error count taken from it was meaningless. The asymmetry is the lesson: a non-matchingincludeglob is harmless, a missingfilesentry is fatal — which is whytsconfig.lib.json, whosenext/references were only ininclude/exclude, kept working.Removed the dangling references from both. Both configs now report 0 own errors; the one remaining error in each is the pre-existing, unrelated
virtual:sdk-versionfromsdk-clientdocumented in thesdk-reactsection above.The spec config coming out clean was predicted, not lucky:
jest-preset-angular@17→ts-jest@29.4.6with diagnostics enabled and transpile-only unset already type-checked all 21 spec files against these exactcompilerOptions— just per-file, never as a whole program. That is the opposite ofsdk-analyticsbelow, wherebabel-jeststripped types and hid 14 errors. Same rollout, two projects, and the test transformer decided whether anything was checked at all.Production source is clean without escape hatches: 0
@ts-ignore/@ts-expect-error, 0 non-null assertions, and 2anys that are the same exported declaration (DynamicComponentEntity = Promise<Type<any>>,lib/models/index.ts:12).Type<any>is idiomatic Angular for dynamically-loaded components and the type is public API, so narrowing it is a separate change, not strict-mode work. 0 internal dependents.CLAUDE.mdnow documents theTS6053masking variant next to the existingTS2688one. Two of the fourteen projects triaged so far were masked this way — #35944 viaTS2688, #35947 viaTS6053— so error counts from the remaining projects should not be trusted until their tsconfigs are checked for this.data-access(#35948)First non-isolated project in the rollout: 27 direct dependents, 6 of them already strict.
All six flags had been in
libs/data-access/tsconfig.jsonfor some time, and they were completely inert. The project has nobuildtarget, so its own tsconfig is never read by anything, and its 27 dependents compile these sources under their own non-strict configs. So 36 errors sat in a layer-3 shared services hub with CI fully green — matching the 106 → 68 → 36 drift measured incidentally in thedotcms-jsandutilssections above.tsconfig.lib.jsontsconfig.spec.jsonProduction source (36)
paginator.service.ts(14) — 8 uninitialised fields given zero values; four header reads take?? ''(identicalNaNoutcome);private setLinks(linksString: string)widened tostring | nullsince its body already didlinksString?.split(',') || []; the file-localinterface Linksgained an index signature because theLink-header parser stores whateverrelthe server sends.dot-page-state.service.ts(12) — six declarations widened because they were simply wrong; the service really does emitnull. The interesting one ishandleSetPageStateFailed, declaredObservable<DotHttpErrorHandled>but ending inmap(() => undefined). Because it genuinely emitsundefined, the caller's= [null, null]destructuring default is reachable and load-bearing, not dead code. Declaring the honest type made the wholeswitchMaptypable; it now destructures explicitly instead of fighting an annotation.if (page)becameif (page && user)— whichforkJoinalready guaranteed.dot-router(5),dot-localstorage(3),dot-content-types-info(2) — nullable getters (previousUrl,storedRedirectUrl),localStoragereads, and a string index narrowed tokeyof.Specs (47)
25 came from three lines. The fake
Routerdeclarednavigate = jest.fn(() => ...), which infers zero parameters, so everytoHaveBeenCalledWith(...)was aTS2554.Two of the rest were real bugs hiding behind disabled suites:
dot-global-message.service.spec.tsimportedDotMessageServicefromdot-alert-confirm.service, which does not export it. The suite isxdescribed, so it never ran.dot-ai.service.ts— a production file — didexport { DotAiProviderConfig }on a type, invalid underisolatedModules. Only the spec config sets that flag, so only it surfaced the error.Also:
dot-page-layout.service.spec.tswas callingsave(id, mockDotLayout()), butsavetakes aDotTemplateDesignerand posts it verbatim — the spec was testing a payload shape production never sends (edit-ema-layout.component.ts:111sends the real one). And thedot-content-drivefixture used anoffsetfield removed fromDotContentDriveSearchRequest, copied from the model's own stale JSDoc example, which is fixed here too.dot-personasnow reuses the existingmockDotPersonafrom@dotcms/utils-testinginstead of hand-rolling 21 fields.No new
any, no@ts-ignore/@ts-expect-erroranywhere in the diff.Blast radius — measured, not assumed
Every strict dependent was counted before and after. Zero new errors, 218 removed, and three went fully clean because they were carrying nothing but this library's leakage:
global-storeportlets-dot-analytics-data-accessportlets-dot-experiments-data-accessimage-editorportlets-dot-analyticsportlets-dot-locales-portletutils-testingThis is the "high leverage" the issue predicted, quantified.
A repo-wide finding:
testnever type-checks specsdata-accessusesjest-preset-angular→ts-jest@29.4.6againsttsconfig.spec.json, which looks like it type-checks. It does not, because that tsconfig setsisolatedModules: true:ts-jest/.../config/config-set.js:229reads TypeScript'sisolatedModulesinto ts-jest's own flag.ts-jest/.../compiler/ts-compiler.js:74builds the language-service host onlyif (!isolatedModules)._doTypeChecking()needs that host forgetSemanticDiagnostics.data-accessis the proof: 84tscerrors alongside 754 passing tests. Sincecore-web/CLAUDE.mdmandatesisolatedModules: truein everytsconfig.spec.json, no project'stesttarget type-checks its specs anywhere in this monorepo — so "tests pass" has never been evidence of spec type-cleanliness. This corrects the justification given in thesdk-angularsection above (that verdict was separately confirmed withtsc -p, so it stands). Removing the flag would enable checking monorepo-wide and is left to the epic.Batch two: twelve more projects (#35949 #35950 #35951 #35952 #35954 #35960 #35962 #35963 #35965 #35968 #35969 #35970)
Bottom-up, and the ordering mattered more than the raw counts suggested.
libs/uifirst, because it was the bottleneck (#35953 — still open)uihad none of the six flags and 549 own errors, and ~109 of them leaked into each of its 26 dependents. Clearing its library program collapsed the portlets that follow:uiportlets-dot-usageportlets-dot-tags-portletportlets-dot-locales-portletportlets-dot-es-search-portletportlets-dot-categories-portletportlets-dot-analyticscontent-drive-uiui'stsconfig.lib.jsonis at 0 (from 122) and itstsconfig.spec.jsonat 90 (from 427), so #35953 stays open. Notable findings there:dot-icon'ssizebecamesize?: numberrather than= 0, because the template binds[style.font-size.px]="size"and0would have rendered invisible icons whereundefinedinherits.dot-sidebar,dot-dropdown,dot-site-selector,dot-container-optionsanddot-trim-inputall inject their host with{ optional: true }and then used it unguarded. They now guard.formElwas declaredHTMLFormElementwhile the template says#formEl="ngForm";getVariableIndexChangeddeclarednumberwhile its own JSDoc documentednumber | null.dot-add-to-bundleinvokedgetDefaultBundletwice for the same value.tsconfig.lib.jsonexcluded a non-existentsrc/test.tsbut nottest-setup.ts,**/*.test.tsor__mocks__/, so test files were being compiled into the library program — 15 errors by itself.selectlocal came from a single line.Already compliant, verified rather than assumed
#35950
portlets-dot-locales-data-accessand #35952portlets-dot-experiments-data-accessneeded no change: all six flags present, both configs at 0, and neither masked by aTS6053/TS2688config error. Both reported 36 errors before #35948 — purelydata-accessleaking.#35949
sdk-experimentswas a flags-only change. The cost was measured on the CLI before committing (0 with all six), and enforcement was proved by negative test: it builds with Rollup, so a deliberate type error fails the build with@rollup/plugin-typescript TS2322.Barrel files that only their consumers could see
#35951
global-storere-exportedWebSocketStatus— a type — withexport {}, which isTS1205underisolatedModules. Its own configs never reported it, because no spec there imports./index, so the file was never in its own program. It surfaced only from consumers, showing up as the single spec error attributed to #35954portlets-dot-analytics-data-access. Oneexport typeclosed both issues.Third and fourth instances of this shape followed in
dot-analytics's two barrels. A barrel can carry anisolatedModuleserror that only its consumers ever see — worth a repo-wide sweep, raised on #35932.An ambient declaration in the wrong place
The
htmldiff-jsdeclaration added foruilived underlibs/ui/src, so it was only inui's own program and every consumer still reportedTS7016. It is now registered throughtsconfig.base.jsonfrom a root-leveltypes/folder — insidelibs/uimade@nx/enforce-module-boundariesdemand a relative import. Same shape as the knownvirtual:sdk-versionleak fromlibs/sdk/client.Signal stores are the dominant spec pattern
#35968
dot-tags(18 of 22), #35963dot-es-search(all 28) and #35962dot-categories(15 of 31) were all the same theme: aSignal<T>does not structurally overlap ajest.Mock, so casts must route throughunknown, and assignments ofjest.fn()onto signal-typed members must state the type they stand in for.dot-categoriesalso had fixtures incomplete in two directions —DotCMSAPIResponseneeds four fields besideentity(now a sharedAPI_ENVELOPErather than repeated nine times) andDotCategoryDeleteResultneedsdeletedCount— plus four calls passingEventwhereopenRowMenutakes aMouseEvent.Wrong annotations, not loose ones
#35965
dot-locales: both dialog refs were annotatedDynamicDialogRef, butDialogService.open()is typed as possibly null in this PrimeNG version. Its store spec mockedObservable<void>methods withof(null).#35960
edit-content-bridge: the dialog ref is now captured in a local so its non-nullness is evident rather than asserted. In its spec,reconcileOnFormEventis assigned inside a nested callback, which control-flow analysis cannot see, so TypeScript narrowed it back tonulland called it not callable.#35969
portlets-dot-usage: one fixture missingUsageSummary.lastUpdated.The first app, and what it revealed about templates
#35970
dotcdnhad 19 errors, and 8 shared one cause:dispatchLoading'sswitchhad nodefault, so undernoImplicitReturnsthe updater's return type includedundefined, it stopped resolving as a one-argument updater, and all six call sites reportedTS2554: Expected 0 arguments.default: return statefixed all eight.More importantly, its build failed on
libs/ui's templates, not ondotcdn:libs/uihas nobuildtarget, so its templates had never been null-checked — they are only verified when a consuming app compiles them.dotcdnandedit-content-bridgeboth havebuildtargets, so their flags are genuinely enforced. The rest of this batch is not — nobuildtarget, and:testdoes not type-check.Dependencies
Added
@types/d3-scale,@types/d3-selectionand@types/d3-shape. All three d3 packages are direct dependencies with no bundled types, so those imports were implicitlyany. Maintained DefinitelyTyped packages, so installing beats hand-declaring the modules.Batch three:
libs/uifinished, plus four more (#35953 #35956 #35959 #35961 #35966)libs/ui(#35953) — 549 → 0, and it unblocked most of what followedBoth configs are now clean:
tsconfig.lib.json122 → 0,tsconfig.spec.json427 → 0. ~109 of its errors leaked into each of its 26 dependents, so clearing it collapsed seven projects from 219–275 down to 0–59.portlets-dot-tags-portlethad 1 error of its own, not 238.Highlights beyond the mechanical work:
DotLocaleTagPipeguards withif (!languageId || !languagesMap),DotRelativeDatePipewithconst time = date || Date.now(), andonAssignChange/onCommentChangewith?? ''— all three declared non-nullable parameters, making those guards unreachable and the specs that assert the null behaviour uncompilable.DotLanguageVariableEntrydeclared every language's value as always present; the API omits languages without a variable and the component already reads them with?.value.dot-browsing.service.specimportedSiteEntity, which no longer exists —dot-site.model.tssays to useDotSite, andcreateFakeSitealready returns it.dot-add-to-bundleinvokedgetDefaultBundletwice for the same value; five directives injected their host with{ optional: true }and used it unguarded;formElwas declaredHTMLFormElementwhile the template says#formEl="ngForm".tsconfig.lib.jsonexcluded a non-existentsrc/test.tsbut nottest-setup.ts,**/*.test.tsor__mocks__/— 15 errors by itself.Two techniques worth reusing: fix at the point of declaration (53 usages of one
selectlocal came from a single line; 95 declaration-level assertions cleared ~300 spec errors), and read each TS2564 site rather than applying the policy blindly —dot-icon'ssizebecamesize?: numberinstead of= 0, because the template binds[style.font-size.px]="size"and0renders invisible icons whereundefinedinherits.dot-plugins(#35966) — reported 733, had 63tsconfig.spec.jsonusedmodule: "commonjs"+moduleResolution: "node10", andtsconfig.jsonwas missingmoduleResolution: "bundler".node10cannot resolve the@dotcms/*subpath exports, so 256 imports failed and everything downstream collapsed tounknown(227TS2571, 115TS18046, 85TS2339). Aligning both withdot-tagstook it from 733 to 1.libs/portlets/CLAUDE.mdwas telling people to configure it that way. Its anti-patterns table said to omitstrict: true("causes issues with Angular compiler") and to use"module": "commonjs"intsconfig.spec.json— whiledot-tags, which the same guide calls the canonical reference, carries bothstrict: trueandmodule: "preserve"and compiles clean. Corrected, so the next portlet does not repeat it.dot-analytics(#35961) andcontent-drive-ui(#35959)Spectator's typed
propsdisagrees with Angular for aliased signal inputs. Components declare$tableState = input.required({ alias: 'tableState' }). Spectator'sInferInputSignalskeys off the field name; Angular'ssetInputrequires the alias. The specs passed the alias under anas unknowncast that made the props bagunknown, so removing it surfacedTS2561with a "did you mean$tableState" hint — and following that hint broke 12 tests. The alias wins; the cast is narrowed to the props type derived from the factory, with a comment naming the conflict.A drop with no destination.
dot-tree-folder'sonDropread the nullable$activeDropNode()and emitted it astargetFolder, which both payload types declare non-null. A drop outside any folder emitted an invalid event; it now returns early.Also: two more barrels re-exporting types with
export {}(third and fourth instances after #35948 and #35951), and@types/d3-scale/@types/d3-selection/@types/d3-shapeadded — direct dependencies with no bundled types.new-block-editor(#35956)38 of 60 were
TS4111on TipTap node attrs, converted from the exact positionstscreports.EditorViewis annotated from@tiptap/pm/view, not top-levelprosemirror-view— the file's own comment explains that TipTap 3.x nests its own copy and mixing the two yieldsTS2322; the comment now names the correct source instead of saying the import is avoided entirely. Three plugin state fields hadinit: () => null, pinning the state type tonull.@types/turndownadded.On my own mistakes in this batch
Three self-inflicted breakages, all from over-broad regexes, all caught by running the suites:
contentlet?.asset→contentlet?['asset'](invalid syntax, which then masked every other error), a(view)replacement that hit call sites as well as declarations, and — the one that mattered — "completing" a fixture that deliberately omittedAction.name, which is exactly the case its test asserts on.tscwas happy with that last one; only the test caught it.Dependencies added in this batch
@types/d3-scale,@types/d3-selection,@types/d3-shape,@types/turndown. All four are direct dependencies whose imports were implicitlyany; these are maintained DefinitelyTyped packages, so installing them beats hand-declaring the modules.Important
Three batch write-ups omitted here to fit GitHub's 65 536-character body limit. The full
text for each is in the body of #36957, which is unchanged and stays open for reference:
block-editor([22/44] Enable TS strict mode in block-editor #35955): the four wrong declarations behind 442 errorsedit-ema-ui([38/44] Enable TS strict mode in edit-ema-ui #35971),template-builder([25/44] Enable TS strict mode in template-builder #35958)edit-content([41/44] Enable TS strict mode in edit-content #35974) and the two it blocked ([43/44] Enable TS strict mode in dotcms-binary-field-builder #35976, [34/44] Enable TS strict mode in portlets-dot-query-tool-portlet #35967)Nothing in the code differs — only this description is shortened.
Test plan
edit-ema-ui(#35971) andtemplate-builder(#35958)tsc -p— 0 on lib and spec for both (from 119 and 141)edit-ema-ui:test— 20 suites / 343 tests green;template-builder:test— 15 suites / 148 tests unchangedtemplate-builder's 27 inherited errors traced tomocks.tsin the lib build, not to utils-testingDotLayout.sidebarand thedata-accesspayload signature:dotcms-models,data-access,ui,block-editor,edit-ema-ui,utils-testing,utils,global-storeall still 0dotcms-block-editor(#35973)tsc -p— 0 ontsconfig.app.json,tsconfig.spec.jsonandtsconfig.editor.json, all three of which previously aborted onTS2688nx run dotcms-block-editor:build— clean (it failed first, on a template defecttsc -pcannot see)libs/block-editorunaffected: 0/0, tests unchanged at 16 suites / 37 by namelibs/new-block-editorunaffected: 0edit-content:teststill green at 112 suites / 2218 tests after removing the poisoning augmentationblock-editor(#35955)tsc -p libs/block-editor/tsconfig.lib.json --noEmit— 0 errors (from 296)tsc -p libs/block-editor/tsconfig.spec.json --noEmit— 0 errors (from 443); 442 own errors deduped across bothnx run block-editor:test— unchanged at 16 suites / 37 tests failing, verified by failing-test name: identical 38 entries before and afternx run block-editor:lint— unchanged at 11 errors, all in files this branch does not touch (git diff origin/mainconfirms)82dbf4c9ad: zero new errors, three go to 0buildtarget on this project, sotsc -pis the gate — stated on the issue rather than implieddotcms-jspnpm exec tsc -p libs/dotcms-js/tsconfig.lib.json --noEmit— 0 errors (from 38)data-access,global-store,portlets-dot-analytics,portlets-dot-analytics-data-access,portlets-dot-locales-portlet,utils-testingdata-accesstypecheck: 106 → 68 errors, zero newdotcms-uitypecheck clean (one pre-existing unrelated error)dotcms-jslint went from 42 to 41 problems (still tag-excluded)sdk-create-apptsc --noEmitclean on lib and specnx run sdk-create-app:build / :lint / :testgreennode dist/libs/sdk/create-app/index.js --helpworksDEBUGfix makesnx run sdk-create-app:buildfail with TS4111 — confirming the build gate is realsdk-analyticstsc --noEmitclean ontsconfig.lib.json(from 18) andtsconfig.spec.json(from 29)nx run sdk-analytics:typecheck / :lint / :build / :build:standalonegreennx run sdk-analytics:test— 15 suites, 314 tests passed. Since jest never type-checked these specs, this was the real regression check on the fixture editsnx run sdk-analytics:build— this project's build is not a gatesdk-angulartsc -p tsconfig.spec.json --noEmitnow completes a semantic pass at all (previouslyTS6053), 0 own errorstsc -p tsconfig.lib.json --noEmit0 own errorsnx run sdk-angular:lintclean;:buildgreennx run sdk-angular:testunchanged at 21 suites / 234 tests — the guard that no file dropped out of the programnx run sdk-angular:build(TS2322, exit 1) — ngtsc gates this projectdata-accesstsc -p tsconfig.lib.json --noEmit36 → 0;tsc -p tsconfig.spec.json --noEmit84 → 0nx run data-access:lintclean;:testunchanged at 79 suites / 754 testsdotcms-ui820 tests andui2184 tests passnx affected -t buildgreen for all 6 affected projectsBatch two
tsc -pclean on lib and spec for [16/44] Enable TS strict mode in sdk-experiments #35949 [17/44] Enable TS strict mode in portlets-dot-locales-data-access #35950 [18/44] Enable TS strict mode in global-store #35951 [19/44] Enable TS strict mode in portlets-dot-experiments-data-access #35952 [21/44] Enable TS strict mode in portlets-dot-analytics-data-access #35954 [27/44] Enable TS strict mode in edit-content-bridge #35960 [29/44] Enable TS strict mode in portlets-dot-categories-portlet #35962 [30/44] Enable TS strict mode in portlets-dot-es-search-portlet #35963 [32/44] Enable TS strict mode in portlets-dot-locales-portlet #35965 [35/44] Enable TS strict mode in portlets-dot-tags-portlet #35968 [36/44] Enable TS strict mode in portlets-dot-usage #35969 [37/44] Enable TS strict mode in dotcdn #35970libs/uilibrary program 0; specs 90 and still open as [20/44] Enable TS strict mode in ui #35953dotcdn:buildandedit-content-bridge:buildpass — the two enforced projects in this batchsdk-experiments:build(Rollup TS2322)Batch three
libs/uiboth configs 0 (122 and 427 before); lint clean; 81 suites / 820 tests unchangeddot-plugins733 → 0;new-block-editor60 → 0;dot-analytics42 → 0;content-drive-ui59 → 0libs/uilanded, re-verified the ten already-closed projects plusdotcdnfor regressionsmoduleResolution;dot-pluginswas the only one misconfiguredBoth
pnpm exec nx format:check --base=origin/maingreenany/@ts-ignore/@ts-expect-error(verified by diff grep)Correction: a verification false negative (review follow-up)
A review comment caught a real regression this PR introduced, and the reason it slipped through matters for how the numbers above should be read.
libs/utils-testing/tsconfig.lib.jsondeclares"types": ["jasmine"], and that package is not installed.tsctherefore emitsTS2688: Cannot find type definition file for 'jasmine'and stops before semantic checking. Sotsc -p libs/utils-testing/tsconfig.lib.json --noEmitreports exactly one error no matter what the code does.The
utilssection originally reported "utils-testingunchanged at 1 pre-existing error" as evidence of no regression. That measurement proved nothing — nothing was being type-checked. Running the same config with--types nodereveals 33 errors, including a genuineTS2741caused by retypingEMPTY_SYSTEM_FIELDtoOmit<DotCMSContentTypeField, 'clazz'>: the mock atdot-content-types.mock.ts:71spreads it and never suppliesclazz.Fixed by giving the mock
clazz: DotCMSClazzes.TEXT; that config is now at 32 errors, all pre-existing and unrelated.Because the mock has ~103 consumers whose tests do run in CI, the runtime-value change was verified rather than assumed —
clazzwentnull(pre-PR) → absent (this PR) →TEXT:FieldUtil.isRow/isColumn/isTabDividercompare for equality and returnfalsefor all three values.!field.clazzorfield.clazz === nullanywhere in the repo.default-value-property7/7;dot-content-types-edit545 passed across 48 suites;data-access751 passed across 79 suites.The
data-accessfigures reported elsewhere in this PR (106 → 68 fordotcms-js, 68 → 36 forutils) are not affected — that project has no unresolvedtypesentry, so those runs were doing real semantic checking.core-web/CLAUDE.mdnow documents this masking behaviour so the next person does not repeat it.Other two comments
sdk-create-app— the throw said "requires at least 1 retry", butretriesis the total attempt count (for (i = 0; i < retries; i++)), soretries = 1is one attempt and zero retries. Reworded to "attempt".CLAUDE.mdverify snippet — hard-codedlibs/<project>/tsconfig.lib.json, which resolves for neither nested projects (libs/sdk/create-app, which has notsconfig.lib.json) nor apps (tsconfig.app.json). Replaced with a<projectRoot>placeholder and both caveats.Notes for reviewers
Three sibling issues in this rollout turned out not to need the work as written, and were resolved separately:
dotcms) and [04/44] Enable TS strict mode in dot-layout-grid #35937 (dot-layout-grid) — closed as not applicable. Both are dead libraries with zero consumers that do not compile today; removal is tracked in Remove dead core-web libraries (libs/dotcms, libs/dot-layout-grid) #36950.typescript-strict-plugin/tsc-strictapproach that was dropped — the bootstrap [00] Setup typescript-strict-plugin baseline + CI gate #35933 closed without the plugin ever landing. Sub-issues still referencingnpx tsc-strictor// @ts-strict-ignorecarry stale acceptance criteria; [06/44] Enable TS strict mode in dotcms-js #35939's were corrected on the issue.Batch seven: the last five projects (#35943 #35957 #35964 #35972 #35975 #35977)
This batch finishes the epic.
dotcms-uiwas the largest single project in it — 1005 errors across app and spec.dotcms-webcomponents(#35943) — now closedThe groundwork section above left this open. 104 errors to 0, then the flags on. It type-checks twice: once by the workspace's TypeScript 6.0.3 and once by Stencil, which bundles its own 5.8.3. TS6 re-declared
Node.textContentas an asymmetric accessor —get(): string,set(value: string | null)— soelement.textContent.replace(...)is clean under 6 andObject is possibly 'null'under 5.8. The project reached 0 ontsc -pand the Stencil build still failed. Where two compilers check the same sources, the build is the gate.dot-rules(#35957),portlets-dot-experiments-portlet(#35964),portlets-content-drive(#35972),portlets-edit-ema-portlet(#35975)dot-experiments: 81 lib + 289 spec. Two tsconfig defects first — a deadincludeand a missingit-specexclude.content-drive: 270 errors.edit-ema/portlet: needed its test-onlymocks.tsexcluded from the lib config before the count meant anything.dot-rules: flags on, lib + spec to 0.dotcms-ui(#35977) — 1005 → 0Production source reached 0 first, then the specs. The findings worth a reviewer's time:
Real defects, fixed:
DotContentTypeComponentStore.saveCopyDialogassetSelected$(string | null) and passed that straight tosaveCopyContentType, so a submit with nothing selected sentnullas the content type to copy. The error-path test was reaching the effect without selecting — which is what surfaced itDotAutocompleteTagsComponent.addItemthis.value.unshift(this.value.pop())unshiftsundefinedback into the tag array when the list is empty, andgetStringifyLabelsthen reads.labeloff itshouldClearDropdown(): booleandropdown && options.length && …—options.lengthis a number, so a function declared: booleancould return0SearchableDropdownComponent.action@Input() action!: (event: Event) => voidclaimed the input is always bound; only one of its three hosts binds it, and the template's@if (action)is what handles the other two. Reported by the Angular compiler as TS2774, always trueSignatures corrected at their source rather than at the call:
DotRouterService.currentPortlet(always setsid, three callers were paying for the optional);portletReload$(a bareSubject, sounknown);DotEventsService.listenandPaginatorService.getWithOffset/getCurrentPageleft unparameterised at four sites;LoginService.watchUsertyped(params?: unknown)while its body always calls with anAuth;DotNavLogoService.setLogo, whose ownnavLogo?.startsWithhad already assumed a nullable argument;ActionHeaderDeleteOptions.confirmHeader/confirmMessage, optional while feeding a confirm dialog that requires both — and nothing in the repo suppliesdeleteOptionsat all.DataTableColumn.icon?: (any) => stringwas not theanytype — it is a parameter namedanywith no type at all, which is why TypeScript reportedTS7051rather than an implicit-any. Nobody had ever read it.The app build found six errors
tsc -pcannot seetscdoes not check templates.strictTemplatesbeing off does not stop a project's ownstrictNullChecksfrom applying to template expressions, and the Angular compiler is the only thing that evaluates them. All six were real: two optional-sitesaccesses, a nullableform.get, an unnarrowed secondoptions()call, the@if (action)above, andrunningExperiment.scheduling.endDateinedit-ema/portlet— a library that measures 0 on its own configs, because a build-less library's templates are checked only when an app compiles them.tsconfig.editor.jsonalso reached 0I expected to record this as a known limitation. It compiles what
tsconfig.app.jsonexcludes, and that is where things had been rotting unseen:index.tsbarrels andcomponents.ts— re-exporting NgModules and a component deleted in the standalone migration. Zero importers; none could have compiled. Removed.PrimeNGConfigis nowPrimeNGand itsrippleflag is a signal.libs/block-editor/NodeViewRenderer.ts, not this project's file. It declaresoverride decorations!: readonly DecorationWithType[]— clean under block-editor's es2015 target, where a class field is an assignment, andTS2612under dotcms-ui's ES2022, whereuseDefineForClassFieldsis on by default and the same field emits adefinePropertythat shadows the base value withundefined.declare(which TypeScript will not accept alongsideoverride) states the intent and emits nothing, so both configs agree. A new variant of "flags interact across projects": here it is the target that interacts.One shared helper replaces eight casts
aliasedPropsinapps/dotcms-ui/src/app/test/. Spectator keyspropsby the class property name ($field) while theComponentRef.setInputunderneath needs the alias (field), so a component following this repo's$name+{ alias }convention cannot express its inputs throughpropsat all. One spec already carried a comment saying exactly that.Verification for this batch
dotcms-uitsconfig.app.json/spec/editornx run dotcms-ui:build:productionnx run dotcms-ui:testnx run dotcms-ui:lintuidotcms-models,data-access,ui,block-editor,edit-ema/portlet,edit-contenttsc -pat 0data-access's fourPushPublishServicefailures andblock-editor's 16/37 reproduce identically atHEAD— confirmed against a stashed tree, not assumed.Correction: the commit trailers on the final batch cite the wrong issue
The commits for
dotcms-uiare tagged(#35933). That is [00] Setup typescript-strict-plugin baseline + CI gate, which was already closed before this batch started. The correct issue is #35977 [44/44] Enable TS strict mode in dotcms-ui, and theClosesline below is right.I did not rewrite the trailers — the branch is pushed and shared, and the
Closeslines are what drive the automation. Flagging it so the trailer/issue mismatch is not a surprise in review or ingit log.Follow-ups filed rather than folded in
Four issues under epic #32713, so none of this rides along in an already-large PR:
strictTemplatesin the four apps. Measured: 430 errors across 373 files, and 231 of those files are inlibs/— it is a ~27-library project, not an app change. TheTODO(#35930)gating it points at an issue that is already closedtsconfig.base.jsonto"strict": true— which is also how five of those eleven came to be created non-strictas unknown as DotCMSContentTypeondotcmsContentTypeBasicMock, which lets ~20 fields of the most-used content-type fixture disagree with the model unchecked. Found because two specs overrodehostafter the spread, putting the assignment outside the cast.bind(this)on both add and remove), a drop guard that comparesundefined === nulland so has never fired, andcleanTemplateItemdeletingtypebefore testing itCloses #35943
Closes #35957
Closes #35964
Closes #35972
Closes #35975
Closes #35977
Closes #35971
Closes #35958
Closes #35973
Closes #35955
Closes #35966
Closes #35961
Closes #35959
Closes #35956
Closes #35953
Closes #35970
Closes #35969
Closes #35968
Closes #35965
Closes #35963
Closes #35962
Closes #35960
Closes #35954
Closes #35952
Closes #35951
Closes #35950
Closes #35949
Closes #35948
Closes #35947
Closes #35946
Closes #35945
Closes #35974
Closes #35976
Closes #35967
Closes #35944
Closes #35940
Closes #35939
Closes #35938
Closes #35935