Even with the lazy-fn pattern in Strapi.ts and compile.ts (#26266),
deep tracing shows @strapi/typescript-utils still loads at boot via
three independent paths:
- `services/metrics/sender.ts` top-imports tsUtils for two
`isUsingTypeScriptSync` calls inside the sender factory. Even when
telemetry is disabled, importing the file loads tsUtils.
- `services/metrics/index.ts` calls `createSender(strapi)`
unconditionally, then `wrapWithRateLimit`. Both run when telemetry
is disabled, even though their result is never used.
- `admin/server/src/controllers/admin.ts` destructures
`{ isUsingTypeScript } = tsUtils` at the module top, firing the
require unconditionally. The function is only consumed by
`GET /admin/project-type`.
- `core/src/Strapi.ts` calls `tsUtils.resolveOutDirSync(...)` in the
db factory even when `useTypescriptMigrations` is the default false,
so the result is computed and thrown away.
Surgical changes:
- `metrics/sender.ts`: tsUtils becomes a lazy fn so loading the file
is free.
- `metrics/index.ts`: gate `createSender(strapi)` and
`wrapWithRateLimit(...)` behind `!isDisabled`.
- `admin.ts`: tsUtils becomes a lazy fn; isUsingTypeScript is a thin
wrapper that resolves the require on first call.
- `Strapi.ts`: `useTypescriptMigrations` check is hoisted; tsUtils
resolves only when the flag is true.
Measured (interleaved A/B, 12 runs each, fresh quickstart):
- typescript-utils loads at boot: 17 -> 0
- median wall-clock: 1803 ms -> 1696 ms (-107 ms, above noise)
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
Co-authored-by: Ben Irvin <ben@innerdvations.com>
* fix(admin): use ISO 639-1 da for Danish admin locale
Rename admin translation bundles from dk to da, normalize legacy dk in
app config, custom translation keys, localStorage, and stored user language.
Closes#25761
* fix(admin): add dk json fallback and admin user language migration
Register internal migration admin::migrate-prefered-language-dk-to-da to
rewrite admin_users.prefered_language from dk to da.
Export importLocaleJsonWithLegacyDkFallback from strapi-admin and use it in
core loadAdminTrads plus every bundled plugin registerTrads so third-party
plugins that still ship only dk.json keep loading when the UI locale is da.
Add @strapi/admin devDependency where the helper is imported from JS or
packages that did not already list it.
Refs #26322
* fix(types): add importLocaleJson to strapi-admin registerTrads typing
* fix(admin): expand plugin locale list and merge under canonical codes
Moves legacy dk handling into StrapiApp.loadTrads (flatMap + uniq + normalize merge) so registerTrads keeps a stable API without importLocaleJson. Core admin bundles use try/catch dynamic imports per locale code.
* fix(admin): route plugin translations through importLocaleJson
Pass StrapiApp.importLocaleJson into registerTrads so core and third-party plugins share legacy locale JSON fallback (da -> dk) and one-shot deprecation warnings. Relax admin test console.warn filter for those messages. Refs #26322.
* fix(admin): keep plugin locale fallback internal
---------
Co-authored-by: Bassel Kanso <basselkanso82@gmail.com>
* chore(admin): remove punycode dependency
WHATWG URL handles IDN natively on Node >=20, so the punycode call
before `new URL()` is redundant.
Per Node docs (https://nodejs.org/api/url.html#new-urlinput-base):
Unicode characters appearing within the host name of input will be
automatically converted to ASCII using the Punycode algorithm.
const myURL = new URL('https://測試');
// https://xn--g6w251d/
Drops `punycode` and `@types/punycode` from packages/core/admin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(typescript): guard pathExistsSync against undefined config path
`getConfigPath` may return `undefined` when no tsconfig matches; passing
that into `fse.pathExistsSync` triggers DEP0187 on Node >=22:
[DEP0187] DeprecationWarning: Passing invalid argument types to
fs.existsSync is deprecated
Short-circuit to `false` when the path is not resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
* feat(admin): add documentation helper link in HeaderLayout
Revival of #23328 (by @Mcastres), rebased on develop with review feedback
addressed and additional improvements:
- Replace hand-rolled find with react-router matchRoutes for proper path
matching and specificity ranking
- Update doc URLs to canonical docs.strapi.io paths (drop redirected
/user-docs/* and /dev-docs/* entries)
- Restore section anchors that were lost during doc-site reorganization
(#providers, #email-templates, #advanced-settings, profile, rbac)
- Align array path entries with actual registered admin routes (rename
/plugins/content-type-builder, /plugins/content-releases, settings
prefix for purchase-content-releases and list-plugins, drop dead
/marketplace external link)
- Add opt-out flag admin.flags.docLinks for white-label deployments
(requested by @derrickmehaffy)
- Memoize getMatchingDocLink result per pathname
- Place doc link button next to primary action via gap on parent Flex
(per @remidej review)
- Drop async from getMatchingDocLink, derive docLink synchronously from
pathname (per @remidej review)
Closes#23328
Co-authored-by: Maxime Castres <17828745+Mcastres@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(admin): move DocLink type to getMatchingDocLink module
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(admin): add docLinks flag to EE Window strapi type
The EE admin custom.d.ts redeclares window.strapi.flags and was missing
the docLinks property, causing test:ts:front to fail with TS2339 in
HeaderLayout.tsx.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add getMachingDocLink tests
---------
Co-authored-by: Maxime Castres <17828745+Mcastres@users.noreply.github.com>
Co-authored-by: Ben Irvin <ben@innerdvations.com>
* fix(admin): redirect active tab to login on session expiry
Two complementary changes that close the gap between server-side session
invalidation and client-side UX, fixes#26163:
1. **Active-tab redirect on 401.** baseQuery's 401 handler now invokes a
session-expired callback in addition to dispatching `logoutAction`.
AuthProvider registers `clearStateAndLogout` as that callback so the
tab that originated the failing request is navigated to /auth/login —
not just other tabs (which already react via the `storage` event).
2. **JWT-exp idle timer.** AuthProvider schedules a one-shot timer at the
access token's `exp` claim. While the user is active, every API call
that hits a 401 transparently refreshes the token (existing behavior in
getFetchClient.withTokenRefresh), which updates Redux and reschedules
the timer to the new exp. If no API activity occurs during the access
token lifespan, the timer fires and the user is logged out — matching
the configured server-side idle behavior without a separate activity
tracker.
The mechanism is wired through a new `setOnSessionExpired` /
`triggerSessionExpired` pair in getFetchClient.ts (mirroring the existing
`setOnTokenUpdate` registry), keeping the React layer as the only place
that owns navigation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(admin): extract idle-session logout into testable hook
Per review feedback, move the JWT exp decoding into utils/jwt.ts and
the one-shot logout effect into a useIdleSessionLogout hook so both
have unit coverage. Hardens the decoder with type checks, base64url
padding, and a Number.isFinite guard on exp.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(admin): gate expiresIn deprecation on user auth options
getTokenOptions merges defaultJwtOptions, so options.expiresIn is always
set even when the project never configured admin.auth.options.expiresIn.
That made the bootstrap deprecation warn on every startup.
Read raw admin.auth.options via a small helper next to the token service
and cover it with unit tests.
Refs https://github.com/strapi/strapi/issues/25989
* chore: prettier
---------
Co-authored-by: Ben Irvin <ben@innerdvations.com>
* fix: remove year 2041 limit on date/datetime pickers (closes#26180)
The @strapi/design-system DatePicker defaults to currentYear + 15 as maxDate,
capping year selection at 2041. Explicitly pass maxDate={new Date(9999, 11, 31)}
before the props spread so users can still override it, but the default is now
effectively unlimited.
* fix: lower date/datetime picker max year to 2099
---------
Co-authored-by: Adrien L <3693028+Adzouz@users.noreply.github.com>
* fix(review-workflows): add assignee to list view filters
* fix: improve perf and add assignee filters options
* fix: preload with data when filter exists
* fix: centralize and export type from content manager
* fix: assignee filter limit to 10 items
* chore(eslint): migrate .eslintrc + .eslintignore to .eslintrc.cjs
Consolidates the per-package ESLint setup into a single typed
`.eslintrc.cjs` file across the monorepo (177 files touched).
Why
- `.eslintignore` is removed in ESLint v9/v10. Folding ignore patterns
into `ignorePatterns` inside the rc file is the supported migration
path and unblocks future ESLint upgrades (which will likely be needed
alongside the in-flight TypeScript 6 migration).
- `.cjs` extension is explicit about module format, so the configs keep
working if any package later adopts `"type": "module"` (no CJS/ESM
surprises).
- Wrapping each config in `// @ts-check` + a `Linter.Config`-typed
`const config` block gives editor autocomplete and catches typos.
Surfaced two real duplicates in
`eslint-config-custom/back/typescript.js`
(`node/no-unsupported-features/es-syntax`,
`node/no-missing-import`), now deduplicated.
- `extends` references switched from the `custom/*` shorthand to the
explicit `eslint-config-custom/*` package name. ESLint resolves both,
but the explicit form makes the source unambiguous.
- Added `coverage/` to `ignorePatterns` so coverage output produced
inside a workspace is not linted.
Misc
- `examples/kitchensink` had ESLint configured but no `lint` script;
added one so the config actually runs.
- `nx.json` hash inputs updated:
- removed `.eslintignore` (no longer exists)
- replaced `{projectRoot}/.eslintrc.js` with
`{projectRoot}/**/.eslintrc.cjs` so nested package configs are also
part of the cache key (the previous glob missed them).
- `packages/utils/upgrade/tsconfig.eslint.json` include path updated
from `.eslintrc.js` to `.eslintrc.cjs`.
Verification
- `eslint --print-config` resolves cleanly on sampled back/front
packages (admin, users-permissions, upload).
- Full `pnpm -r lint` is clean.
* chore(eslint): set tsconfigRootDir for vscode-eslint typed linting
CLI lint resolved tsconfig paths via cwd, but vscode-eslint runs from
the workspace root and could not locate per-package tsconfigs. Set
parserOptions.tsconfigRootDir to __dirname in each package's
.eslintrc.cjs so the editor resolves typed-lint configs correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(eslint): self-ignore configs, scope coverage to jest packages
- Replace `env: { es6: true }` workaround with `.eslintrc.cjs` in
ignorePatterns so eslint never lints the config file itself
(root cause of the "const is not a keyword" parse error in es5)
- Add `.eslintrc.cjs` to ignorePatterns across all configs that
were missing it
- Restrict `coverage/` ignorePatterns to workspace roots that
actually run jest; drop from non-test packages and admin/server
subdir configs (tests run from the workspace root)
* chore(deps): bump msw to 1.3.5 and typedoc to 0.25.13
Widens TypeScript peer ranges in msw (>= 4.4.x) and typedoc (adds 5.4.x),
unblocking a future bump to typescript 5.6 required by react-intl 7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(deps): bump msw to 2.13.4 across admin test suites
Migrates 8 workspaces (admin, content-manager, content-releases, upload,
review-workflows, plugin-i18n, plugin-documentation, plugin-users-permissions)
from msw 1.3.5 to 2.13.4; bumps email's unused dep for consistency.
Handler changes:
- rest.X((req, res, ctx) => res(ctx.json(X))) → http.X(() => HttpResponse.json(X))
- res(ctx.status(N)) → new HttpResponse(null, { status: N })
- res.once(...) → http.X(path, handler, { once: true })
- res.networkError(...) → HttpResponse.error()
- await req.json<T>() / casts → http.METHOD<Params, Body>(...) generics
- RequestHandler → HttpHandler
- Empty 200 responses use HttpResponse.json({}) to avoid cross-realm
SyntaxError instanceof check in getFetchClient.ts
Jest setup (per msw frequent-issues guide):
- jest-preset.front.js: customExportConditions: [''] so msw/node resolves
under jsdom; .mjs swc transform; ESM allow-list for msw deps
(rettime, @open-draft, outvariant, until-async, etc.)
- packages/admin-test-utils/src/environment.ts: polyfill Node fetch +
streams + BroadcastChannel + URL into jsdom global. Blob/File/FormData
intentionally left as jsdom's — Strapi app code threads them through
jsdom-only APIs (FileReader, Image, HTMLFormElement) where realm matters.
Comment in env.ts documents the trade-off.
Verified vs develop+msw@1.3.5 baseline: 0 net regressions across all 7
workspaces with msw test files; TypeScript clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): bump typedoc to 0.25.13"
The typedoc bump was bundled with the msw 1.3.5 bump in 39d169cfbb but
isn't required for the msw v2 migration. Reverting the docs/ and
core/types typedoc dep back to 0.25.10 to keep this branch focused on
the msw upgrade.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(tests): type msw handler params via generics
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(tests): address review feedback on msw v2 migration
- env.ts: snapshot jsdom globals in setup(), restore in teardown() so
per-test mutations cannot leak across files in a Jest worker.
- env.ts: collapse polyfill list and value map into a single NODE_GLOBALS
source of truth; document the fetch trade-off (Node undici vs jsdom).
- handlers: replace `<never, Body>` generics with `<Record<string, never>,
Body>` (msw v2 convention; `{}` blocked by ban-types).
- email: drop unused msw devDep entirely (zero source usage in workspace).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(tests): use realm-agnostic SyntaxError check; drop URL polyfill
Two follow-ups to the msw v2 migration:
- `getFetchClient.responseInterceptor` now matches `error?.name === 'SyntaxError'`
rather than `instanceof SyntaxError`. Constructor identity differs across JS realms
(under jsdom + Node fetch, an empty 200 throws a Node-realm `SyntaxError` that
fails the jsdom-realm `instanceof` check; the same is true in browsers when the
Response originates from a service worker / iframe). Name comparison is realm-
agnostic. With this in place, empty 200 handlers can return `new HttpResponse()`
again (closer to v1's `res(ctx.status(200))`) instead of the `HttpResponse.json({})`
workaround.
- Drop `URL` / `URLSearchParams` from the env.ts polyfill list. jsdom already
provides browser-spec versions, and Node's `URL.createObjectURL` only accepts
Node-realm `Blob` — replacing it broke the "from computer" upload path in
`LogoInput.tsx`. MSW v2's interceptor doesn't require these to come from Node.
env.ts comments restructured: all polyfill rationale lives in one top-of-file block
so the body is short and the trade-offs are documented in one place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(tests): switch content-type-builder jest env to shared polyfilled env
`aiClient.test.ts` constructs `new Response(...)` which jsdom does not provide
spec-compliantly. Align CTB with the other admin test suites by using
`@strapi/admin-test-utils/environment`, which polyfills Node's
`fetch`/`Request`/`Response`/streams into the jsdom global.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(tests): make msw v2 + undici body + jsdom realms work together
Three regressions surfaced by running the full front test suite under
jsdom + Node's undici fetch polyfill:
1. `TypeError: Body is unusable: Body has already been read` in every
handler doing `await request.json()`. MSW v2's `RequestHandler.run()`
calls `request.clone()` before invoking the resolver (to keep a copy
for post-hoc logging). Under Node's undici, `Request.clone()` tees the
underlying `ReadableStream` and locks the original body, so the
resolver's `.json()` throws. Fix in `environment.ts`: wrap the global
`Request` constructor to stash the stringified body on every instance
and patch `Request.prototype.json` / `.text` to return the stash when
present. Handlers now get the body regardless of how many times msw
clones internally.
2. `expect.any(Array)` and `toStrictEqual` failing with "serializes to
the same string" against msw response bodies. `Response.prototype.json`
runs Node's `JSON.parse`, so the resulting objects carry Node-realm
`Object`/`Array` prototypes; jsdom's `instanceof`/strict-equality
matchers reject them. Patch `Response.prototype.json` to parse with
the jsdom-realm `JSON`.
3. `useWidgets > should handle network errors gracefully` expected the
toast text "Network error", which msw v1's `res.networkError('Network
error')` surfaced verbatim. v2's `HttpResponse.error()` drops the
custom message and fetch throws `TypeError: Failed to fetch`; update
the assertion accordingly.
Also: `UIDInput` uses `jest.useFakeTimers()`. Adding `doNotFake:
['queueMicrotask', 'setImmediate']` keeps per-test runtime at ~1s
instead of ~10s (plain fake timers stall undici's internal microtasks
between request emission and handler resolution), and an `afterEach`
restores real timers so a failing test cannot leak fake timers into
the next one.
Verified with `nx run-many --target=test:front --parallel=1` across all
12 projects — all suites green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(admin): assert toast container in Widgets error tests
Replace text-match (`findByText('Server error' | 'Failed to fetch')`) with
`[data-sonner-toast]` container assertion. Decouples the test from toast
copy and from msw v1/v2 network-error message differences.
Addresses @jhoward1994's review comment on PR #26065.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(admin): assert toast text, scope msw handlers, use jsdom env
- Widgets tests: match actual toast text (Server error / fetch failure
regex) instead of data-sonner-toast container.
- ConfirmBulkActionDialog: mark msw runtime handlers { once: true } so
they do not leak across cases.
- content-type-builder: jest testEnvironment back to jsdom.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(tests): split jest env patches into modules
Refactor packages/admin-test-utils/src/environment.ts by extracting
the msw v2 + undici body + jsdom realm shims into dedicated modules
under src/patches/ (node-globals, request-body-stash,
response-json-realm, types).
Co-Authored-By: Jamie Howard <jhoward1994@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: sort based on publish status
* chore: remove redundant `any` type assertions
* fix: override sort if `status` filter is present
* fix(content-manager): status sort ignored hasPublishedVersion and publicationStatusFilter
* fix(utils): remove unused STATUS_ATTRIBUTE constant
* fix(content-manager): missing deps in useEffect for status sort cleanup
---------
Co-authored-by: Ziyi <daydreamnation@live.com>