* fix(content-manager): guard repeatable field .map() crash on relation modal close
useField default value=[] doesn't protect against null. When a relation
modal closes, field value is briefly null causing value.map() to throw.
Fixes#26401
* fix: revert Input.tsx guard to resolve e2e test failures
* fix(content-manager): restore repeatable fix after Input revert typo
Repair the accidental Input.tsx regression from the e2e revert, stabilize
the null array guard with useMemo, and add front tests for null/undefined
field values.
Fixes#26401
---------
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
Co-authored-by: Ben Irvin <ben@innerdvations.com>
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: documentId shown in combobox when entry title set to numeric field
* test(content-manager): add unit tests for getRelationLabel numeric titles
Cover string and integer main fields, documentId fallback, and id guard for #25622.
---------
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
Co-authored-by: Ben Irvin <ben@innerdvations.com>
`strapi develop` runs `develop.ts` in the cluster primary first, which
imports `@strapi/core`, `@strapi/typescript-utils`, `@strapi/utils`,
`chokidar`, `./create-build-context`, and `./staticFiles` at module top.
None of these are needed in the primary process before `cluster.fork()`
fires — they only run inside the worker (or in the conditional admin
build branch).
Introduce a small generic `lazy<T>(spec)` helper at the top of the file
and convert the six worker-only imports to lazy require calls. Type
annotations are preserved via type-only imports. Call sites change from
`createStrapi(...)` to `core().createStrapi(...)`, etc.
`fs`, `path`, `cluster`, `checkRequiredDependencies`, and `getTimer` /
`prettyTime` stay eager — they're used in the primary block.
~200 ms saved on every `strapi develop` boot.
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
cleanupDistDirectory wipes everything in dist/ except build/ on every
dev restart. tsconfig.json ships with `incremental: true`, so tsc writes
dist/tsconfig.tsbuildinfo on first compile — but that file is then
deleted on the next restart, silently invalidating tsc's incremental
cache. Net effect: every dev restart pays for a full TypeScript compile.
Add *.tsbuildinfo to the cleanup filter. The cache file is regenerated
by tsc if missing, so the change is fully backward-compatible.
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
Two small dev-loop wins:
- `node/core/dependencies.ts`: `checkRequiredDependencies` re-reads
`package.json`, walks every PEER_DEP, runs `resolveFrom` and
`semver.satisfies` on every dev start (~50–100 ms). Cache the
successful-pass outcome by SHA-1 of `package.json` at
`node_modules/.strapi/deps-check.hash`. Cache lives inside
`node_modules` so it's already gitignored and wiped on reinstall;
full check still runs on miss.
- `node/create-build-context.ts`: the "Including the following ENV
variables" preamble is one-line `logger.info` -> `logger.debug`.
Useful when investigating admin builds, noise on every dev start.
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
`Strapi.ts` and `compile.ts` both eagerly import `@strapi/typescript-utils`
at module top, but only need it conditionally:
- `Strapi.ts` calls `resolveOutDirSync` inside the `db` factory, and only
when `database.settings.useTypescriptMigrations` is true (default false).
- `compile.ts` is loaded as part of `@strapi/core` but only invoked from
`compileStrapi()` (develop / build CLI paths).
Defer the require to first call site via the lazy-fn pattern. ~100 ms on
every boot of `@strapi/core`.
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
* 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>
Scope pre-transfer entity deletion to user content types when --only content
or --exclude config skips the configuration stage, so internal models like
strapi::core-store are not wiped without being restored.
Fixes#23967
Two cron / migration providers eagerly require modules they only need
at runtime:
- `services/cron.ts` imports `node-schedule` (~90 ms via `Job` class)
even when `server.cron` is empty. Defer the require to first
`add()` / `start()`.
- `database/src/migrations/{internal,users}.ts` instantiate `Umzug`
immediately in their factory, pulling in the inquirer and
`@rushstack/ts-command-line` chain. Move the construction inside a
lazy `provider()` closure so the require only fires on `shouldRun`,
`up`, or `down`.
~100 ms saved on every boot of a project without active cron tasks
or pending migrations (the common case).
* fix: resolve ajv ReDoS vulnerability by forcing ajv@8.18.0
Fixes#25999
Snyk reported ajv@8.13.0 as vulnerable to Regular Expression Denial of Service (ReDoS)
(CVSS 8.2, CWE-1333). The vulnerability exists in the transitive dependency chain:
@strapi/database -> umzug@3.8.1 -> @rushstack/ts-command-line@4.23.1
-> @rushstack/terminal@0.14.3 -> @rushstack/node-core-library@5.10.0
-> ajv@~8.13.0
This fix forces all ajv v8 dependencies to the patched 8.18.0 version via Yarn resolutions,
which prevents the vulnerable 8.13.0 from being installed.
* fix: bump ajv resolution to 8.20.0
Scope v8 resolutions to patched 8.20.0 without overriding eslint's
ajv v6 dependency. Bump the direct @strapi/database ajv pin to match.
---------
Co-authored-by: Ben Irvin <ben@innerdvations.com>
Several document-service API suites only need a minimal article schema and
fixtures but were loading the full shared resource pack, making setup and
teardown slow enough to flake on CI hook timeouts.
* 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>
Convert the docs Docusaurus config files to TypeScript to match the
modern Docusaurus scaffold and give editors correct per-file types:
- docusaurus.config.js -> .ts (typed Config / Preset options, ESM)
- sidebars.js -> .ts (typed SidebarsConfig)
- remark-design-system-links.js -> .ts (typed Transformer<Root>)
- babel.config.js: ESM export, @docusaurus/babel preset (3.x split)
- add docs/tsconfig.json extending @docusaurus/tsconfig (editor-only,
not used by docusaurus build)
- add @docusaurus/babel, @docusaurus/tsconfig, @types/mdast deps
Also adds "docs" to the root jsconfig.json excludes, since docs is a
standalone Docusaurus project that now owns its own config.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
* fix(i18n): preserve non-localized field inheritance across unsaved locale revisits
* fix(content-manager): sort availableLocales with the default locale first
* fix(content-manager): align non-localized prefill filter with server semantics
* fix(i18n): e2e tests
* fix(core): preserve createdBy/updatedBy on drafts created by discard-drafts migration
Stage 1 of the v4→v5 discard-drafts migration now copies join-column foreign
keys when cloning published rows to drafts, so creator metadata is no longer
NULL on the new draft rows. Adds a focused migration test and extends the
complex example migration validator to catch this regression.
Fixes#26460
* fix(core): skip virtual join columns in discard-drafts stage 1
i18n localizations reuses document_id as a virtual join column; copying it
alongside the scalar duplicated the column in INSERT…SELECT and broke v4→v5
migration. Dedupe clone columns and apply the same filter in later stages.
* fix: add missing translation, remove non existing ones and translate existing ones
* chore: reverse changes in admin/es
* fix: fix tab issue in i18n/admin/src/translations/en.json
* fix(i18n): restore author es.json after merge conflict resolution
Remove AI translation keys that were incorrectly merged into i18n es.json;
keep the author's intended i18n plugin Spanish changes from the original PR.
* fix(i18n): align locale hint key and trim translation scope
Use the canonical name.description hint id in CreateLocale, restore
develop ES strings instead of rewritten copy, migrate hint text to the
new key for locales that only had the legacy id, and drop the EN
locales.label entry that changed the picker label and broke E2E tests.
---------
Co-authored-by: Ben Irvin <ben@innerdvations.com>
* Use query builder init in deleteMany
* test(api): deleteMany respects filters combined with relation
* fix(database): pick filter params in deleteMany init
Apply _q, where, and filters through init like count instead of where-only
or full findMany params, avoiding populate/pagination surprises. Expand API
and unit tests for relation filters, count parity, and ignored param keys.
* chore(database): clarify deleteMany relation cleanup TODO
Replace the vague bulk-query TODO with a concrete note that deleteMany
still skips per-row deleteRelations unlike single delete().
---------
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
Co-authored-by: Ben Irvin <ben@innerdvations.com>
* fix(homepage): homepage count-documents slow on large D&P tables
* chore: add api test
* test(homepage): add i18n count-documents document_id semantics tests
Assert per-document_id buckets for never-published multi-locale drafts,
partial locale publish states, and mutually exclusive published vs modified.
---------
Co-authored-by: Ben Irvin <ben@innerdvations.com>
* chore: fix dependabot cooldown config for github-actions
Remove unsupported semver cooldown fields from the github-actions
ecosystem entry; only default-days is valid for that ecosystem.
* chore: comments
* 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>
Replace GitHub's npm_and_yarn mega-PR with per-family security groups,
re-enable throttled version updates with cooldowns, and expand dependency
families for webpack, vite, rollup, and related tooling.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: unable to access content manager page with required and private field
* fix: unable to access content manager page with required and private field
* test(content-manager): cover private field exclusion in validation populate
Extend getPopulateForValidation tests for top-level scalars, media, and
dynamic zones so private required attributes stay out of query fields.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Ben Irvin <ben@innerdvations.com>
Co-authored-by: Cursor <cursoragent@cursor.com>