@strapi/content-manager 5.56.0 → 5.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/admin/components/ConfigurationForm/Form.js +3 -3
  2. package/dist/admin/components/ConfigurationForm/Form.js.map +1 -1
  3. package/dist/admin/components/ConfigurationForm/Form.mjs +2 -2
  4. package/dist/admin/components/ConfigurationForm/Form.mjs.map +1 -1
  5. package/dist/admin/components/RelativeTime.js +9 -0
  6. package/dist/admin/components/RelativeTime.js.map +1 -1
  7. package/dist/admin/components/RelativeTime.mjs +10 -1
  8. package/dist/admin/components/RelativeTime.mjs.map +1 -1
  9. package/dist/admin/features/DocumentRBAC.js +0 -3
  10. package/dist/admin/features/DocumentRBAC.js.map +1 -1
  11. package/dist/admin/features/DocumentRBAC.mjs +1 -4
  12. package/dist/admin/features/DocumentRBAC.mjs.map +1 -1
  13. package/dist/admin/history/components/VersionContent.js +3 -3
  14. package/dist/admin/history/components/VersionContent.js.map +1 -1
  15. package/dist/admin/history/components/VersionContent.mjs +2 -2
  16. package/dist/admin/history/components/VersionContent.mjs.map +1 -1
  17. package/dist/admin/history/pages/History.js +2 -1
  18. package/dist/admin/history/pages/History.js.map +1 -1
  19. package/dist/admin/history/pages/History.mjs +3 -2
  20. package/dist/admin/history/pages/History.mjs.map +1 -1
  21. package/dist/admin/pages/EditView/EditViewPage.js +2 -2
  22. package/dist/admin/pages/EditView/EditViewPage.js.map +1 -1
  23. package/dist/admin/pages/EditView/EditViewPage.mjs +2 -2
  24. package/dist/admin/pages/EditView/EditViewPage.mjs.map +1 -1
  25. package/dist/admin/pages/EditView/components/DocumentActions.js +37 -2
  26. package/dist/admin/pages/EditView/components/DocumentActions.js.map +1 -1
  27. package/dist/admin/pages/EditView/components/DocumentActions.mjs +37 -3
  28. package/dist/admin/pages/EditView/components/DocumentActions.mjs.map +1 -1
  29. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.js +1 -0
  30. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.js.map +1 -1
  31. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.mjs +1 -0
  32. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.mjs.map +1 -1
  33. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js +3 -3
  34. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js.map +1 -1
  35. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs +2 -2
  36. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs.map +1 -1
  37. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js +30 -2
  38. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js.map +1 -1
  39. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs +31 -4
  40. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs.map +1 -1
  41. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js +40 -8
  42. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js.map +1 -1
  43. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs +41 -9
  44. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs.map +1 -1
  45. package/dist/admin/pages/EditView/utils/data.js +3 -3
  46. package/dist/admin/pages/EditView/utils/data.js.map +1 -1
  47. package/dist/admin/pages/EditView/utils/data.mjs +2 -2
  48. package/dist/admin/pages/EditView/utils/data.mjs.map +1 -1
  49. package/dist/admin/pages/ListView/ListViewPage.js +4 -3
  50. package/dist/admin/pages/ListView/ListViewPage.js.map +1 -1
  51. package/dist/admin/pages/ListView/ListViewPage.mjs +4 -3
  52. package/dist/admin/pages/ListView/ListViewPage.mjs.map +1 -1
  53. package/dist/admin/preview/pages/Preview.js +2 -1
  54. package/dist/admin/preview/pages/Preview.js.map +1 -1
  55. package/dist/admin/preview/pages/Preview.mjs +3 -2
  56. package/dist/admin/preview/pages/Preview.mjs.map +1 -1
  57. package/dist/admin/services/documents.js +3 -2
  58. package/dist/admin/services/documents.js.map +1 -1
  59. package/dist/admin/services/documents.mjs +2 -2
  60. package/dist/admin/services/documents.mjs.map +1 -1
  61. package/dist/admin/services/relations.js +3 -0
  62. package/dist/admin/services/relations.js.map +1 -1
  63. package/dist/admin/services/relations.mjs +3 -0
  64. package/dist/admin/services/relations.mjs.map +1 -1
  65. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +2 -1
  66. package/dist/admin/src/pages/EditView/components/FormInputs/Relations/RelationModal.d.ts +19 -2
  67. package/dist/admin/src/services/documents.d.ts +10 -2
  68. package/dist/admin/translations/en.json.js +2 -0
  69. package/dist/admin/translations/en.json.js.map +1 -1
  70. package/dist/admin/translations/en.json.mjs +2 -0
  71. package/dist/admin/translations/en.json.mjs.map +1 -1
  72. package/dist/admin/utils/validation.js +3 -3
  73. package/dist/admin/utils/validation.js.map +1 -1
  74. package/dist/admin/utils/validation.mjs +2 -2
  75. package/dist/admin/utils/validation.mjs.map +1 -1
  76. package/dist/server/controllers/collection-types.js +4 -4
  77. package/dist/server/controllers/collection-types.js.map +1 -1
  78. package/dist/server/controllers/collection-types.mjs +4 -4
  79. package/dist/server/controllers/collection-types.mjs.map +1 -1
  80. package/dist/server/controllers/content-types.js +5 -7
  81. package/dist/server/controllers/content-types.js.map +1 -1
  82. package/dist/server/controllers/content-types.mjs +5 -7
  83. package/dist/server/controllers/content-types.mjs.map +1 -1
  84. package/dist/server/controllers/relations.js +19 -15
  85. package/dist/server/controllers/relations.js.map +1 -1
  86. package/dist/server/controllers/relations.mjs +17 -13
  87. package/dist/server/controllers/relations.mjs.map +1 -1
  88. package/dist/server/controllers/utils/clone.js +3 -4
  89. package/dist/server/controllers/utils/clone.js.map +1 -1
  90. package/dist/server/controllers/utils/clone.mjs +4 -5
  91. package/dist/server/controllers/utils/clone.mjs.map +1 -1
  92. package/dist/server/controllers/validation/index.js +1 -1
  93. package/dist/server/controllers/validation/index.mjs +1 -1
  94. package/dist/server/history/controllers/history-version.js +3 -3
  95. package/dist/server/history/controllers/history-version.js.map +1 -1
  96. package/dist/server/history/controllers/history-version.mjs +3 -3
  97. package/dist/server/history/controllers/history-version.mjs.map +1 -1
  98. package/dist/server/history/entitlements.js +26 -0
  99. package/dist/server/history/entitlements.js.map +1 -0
  100. package/dist/server/history/entitlements.mjs +24 -0
  101. package/dist/server/history/entitlements.mjs.map +1 -0
  102. package/dist/server/history/index.js +3 -0
  103. package/dist/server/history/index.js.map +1 -1
  104. package/dist/server/history/index.mjs +3 -0
  105. package/dist/server/history/index.mjs.map +1 -1
  106. package/dist/server/history/services/history.js +4 -4
  107. package/dist/server/history/services/history.js.map +1 -1
  108. package/dist/server/history/services/history.mjs +4 -4
  109. package/dist/server/history/services/history.mjs.map +1 -1
  110. package/dist/server/history/services/lifecycles.js +8 -8
  111. package/dist/server/history/services/lifecycles.js.map +1 -1
  112. package/dist/server/history/services/lifecycles.mjs +3 -3
  113. package/dist/server/history/services/lifecycles.mjs.map +1 -1
  114. package/dist/server/history/services/utils.js +26 -18
  115. package/dist/server/history/services/utils.js.map +1 -1
  116. package/dist/server/history/services/utils.mjs +23 -17
  117. package/dist/server/history/services/utils.mjs.map +1 -1
  118. package/dist/server/mcp/permissions.js +2 -2
  119. package/dist/server/mcp/permissions.js.map +1 -1
  120. package/dist/server/mcp/permissions.mjs +2 -2
  121. package/dist/server/mcp/permissions.mjs.map +1 -1
  122. package/dist/server/mcp/register-content-manager-mcp-tools.js +7 -5
  123. package/dist/server/mcp/register-content-manager-mcp-tools.js.map +1 -1
  124. package/dist/server/mcp/register-content-manager-mcp-tools.mjs +7 -5
  125. package/dist/server/mcp/register-content-manager-mcp-tools.mjs.map +1 -1
  126. package/dist/server/preview/controllers/previewScript.js +30 -15
  127. package/dist/server/preview/controllers/validation/preview.js +3 -3
  128. package/dist/server/preview/controllers/validation/preview.js.map +1 -1
  129. package/dist/server/preview/controllers/validation/preview.mjs +3 -3
  130. package/dist/server/preview/controllers/validation/preview.mjs.map +1 -1
  131. package/dist/server/services/components.js +3 -3
  132. package/dist/server/services/components.js.map +1 -1
  133. package/dist/server/services/components.mjs +3 -3
  134. package/dist/server/services/components.mjs.map +1 -1
  135. package/dist/server/services/content-structure.js +2 -2
  136. package/dist/server/services/content-structure.js.map +1 -1
  137. package/dist/server/services/content-structure.mjs +2 -2
  138. package/dist/server/services/content-structure.mjs.map +1 -1
  139. package/dist/server/services/content-types.js +3 -3
  140. package/dist/server/services/content-types.js.map +1 -1
  141. package/dist/server/services/content-types.mjs +3 -3
  142. package/dist/server/services/content-types.mjs.map +1 -1
  143. package/dist/server/services/data-mapper.js +3 -3
  144. package/dist/server/services/data-mapper.js.map +1 -1
  145. package/dist/server/services/data-mapper.mjs +3 -3
  146. package/dist/server/services/data-mapper.mjs.map +1 -1
  147. package/dist/server/services/document-manager.js +6 -6
  148. package/dist/server/services/document-manager.js.map +1 -1
  149. package/dist/server/services/document-manager.mjs +6 -6
  150. package/dist/server/services/document-manager.mjs.map +1 -1
  151. package/dist/server/services/document-metadata.js +29 -31
  152. package/dist/server/services/document-metadata.js.map +1 -1
  153. package/dist/server/services/document-metadata.mjs +29 -31
  154. package/dist/server/services/document-metadata.mjs.map +1 -1
  155. package/dist/server/services/metrics.js +3 -3
  156. package/dist/server/services/metrics.js.map +1 -1
  157. package/dist/server/services/metrics.mjs +3 -3
  158. package/dist/server/services/metrics.mjs.map +1 -1
  159. package/dist/server/services/permission-checker.js +2 -2
  160. package/dist/server/services/permission-checker.js.map +1 -1
  161. package/dist/server/services/permission-checker.mjs +1 -1
  162. package/dist/server/services/permission-checker.mjs.map +1 -1
  163. package/dist/server/services/permission.js +2 -3
  164. package/dist/server/services/permission.js.map +1 -1
  165. package/dist/server/services/permission.mjs +2 -3
  166. package/dist/server/services/permission.mjs.map +1 -1
  167. package/dist/server/services/populate-builder.js +4 -4
  168. package/dist/server/services/populate-builder.js.map +1 -1
  169. package/dist/server/services/populate-builder.mjs +4 -4
  170. package/dist/server/services/populate-builder.mjs.map +1 -1
  171. package/dist/server/services/utils/configuration/attributes.js +1 -2
  172. package/dist/server/services/utils/configuration/attributes.js.map +1 -1
  173. package/dist/server/services/utils/configuration/attributes.mjs +2 -3
  174. package/dist/server/services/utils/configuration/attributes.mjs.map +1 -1
  175. package/dist/server/services/utils/configuration/settings.js +4 -7
  176. package/dist/server/services/utils/configuration/settings.js.map +1 -1
  177. package/dist/server/services/utils/configuration/settings.mjs +2 -5
  178. package/dist/server/services/utils/configuration/settings.mjs.map +1 -1
  179. package/dist/server/services/utils/draft.js +8 -7
  180. package/dist/server/services/utils/draft.js.map +1 -1
  181. package/dist/server/services/utils/draft.mjs +6 -5
  182. package/dist/server/services/utils/draft.mjs.map +1 -1
  183. package/dist/server/services/utils/populate.js +10 -11
  184. package/dist/server/services/utils/populate.js.map +1 -1
  185. package/dist/server/services/utils/populate.mjs +9 -10
  186. package/dist/server/services/utils/populate.mjs.map +1 -1
  187. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  188. package/dist/server/src/history/controllers/history-version.d.ts.map +1 -1
  189. package/dist/server/src/history/entitlements.d.ts +9 -0
  190. package/dist/server/src/history/entitlements.d.ts.map +1 -0
  191. package/dist/server/src/history/index.d.ts.map +1 -1
  192. package/dist/server/src/history/services/utils.d.ts +8 -2
  193. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  194. package/dist/server/src/index.d.ts +1 -1
  195. package/dist/server/src/index.d.ts.map +1 -1
  196. package/dist/server/src/mcp/permissions.d.ts +1 -1
  197. package/dist/server/src/mcp/permissions.d.ts.map +1 -1
  198. package/dist/server/src/mcp/register-content-manager-mcp-tools.d.ts +3 -1
  199. package/dist/server/src/mcp/register-content-manager-mcp-tools.d.ts.map +1 -1
  200. package/dist/server/src/mcp/types.d.ts +2 -2
  201. package/dist/server/src/services/content-types.d.ts.map +1 -1
  202. package/dist/server/src/services/data-mapper.d.ts +1 -1
  203. package/dist/server/src/services/data-mapper.d.ts.map +1 -1
  204. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  205. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  206. package/dist/server/src/services/index.d.ts +1 -1
  207. package/dist/server/src/services/index.d.ts.map +1 -1
  208. package/dist/server/src/services/metrics.d.ts.map +1 -1
  209. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  210. package/dist/server/src/services/permission.d.ts.map +1 -1
  211. package/dist/server/src/services/populate-builder.d.ts.map +1 -1
  212. package/dist/server/src/services/utils/configuration/index.d.ts +10 -1
  213. package/dist/server/src/services/utils/configuration/index.d.ts.map +1 -1
  214. package/dist/server/src/services/utils/configuration/settings.d.ts +10 -1
  215. package/dist/server/src/services/utils/configuration/settings.d.ts.map +1 -1
  216. package/dist/server/src/services/utils/draft.d.ts.map +1 -1
  217. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  218. package/dist/shared/contracts/relations.d.ts +1 -0
  219. package/dist/shared/contracts/relations.d.ts.map +1 -1
  220. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","sources":["../../../server/src/mcp/permissions.ts"],"sourcesContent":["import { z } from '@strapi/utils';\nimport type { Core, Modules, Struct, UID } from '@strapi/types';\n\nimport { getService } from '../utils';\nimport type { ContentManagerModelForMcp } from './types';\n\n/** Returns true if the content type identified by `uid` has the i18n `localized` plugin option enabled. */\nexport const isContentTypeLocalized = (strapi: Core.Strapi, uid: string): boolean => {\n const ct = strapi.contentTypes?.[uid as UID.ContentType];\n if (ct === undefined) return false;\n return (\n (ct as { pluginOptions?: { i18n?: { localized?: boolean } } }).pluginOptions?.i18n\n ?.localized === true\n );\n};\n\nconst localeDefaultDescription = (\n defaultLocale: string | null,\n allowedLocales: readonly string[]\n): string => {\n if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {\n return `Defaults to \"${defaultLocale}\".`;\n }\n\n return 'Defaults to the default locale.';\n};\n\n/**\n * Builds the base locale Zod schema for a derived MCP tool input.\n * When `localeCodes` is provided, constrains the field to a `z.enum` of available codes\n * with an optional default; otherwise falls back to a plain optional string.\n */\nexport const buildLocaleSchema = (\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null\n): z.ZodTypeAny => {\n if (localeCodes !== null && localeCodes.length > 0) {\n let schema: z.ZodTypeAny = z.enum(localeCodes).optional();\n\n if (defaultLocale !== null && localeCodes.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Available: ${localeCodes.join(', ')}. ${localeDefaultDescription(defaultLocale, localeCodes)}`\n );\n }\n\n return z\n .string()\n .optional()\n .describe('Locale code (e.g. \"en\", \"fr\"). Defaults to the default locale.');\n};\n\n/**\n * Narrows the base locale schema to only locales the session is permitted to access\n * for the given action + uid combination.\n *\n * Returns the base schema unchanged when:\n * - localeCodes is null (i18n not installed)\n * - the content type is not localized\n * - all installed locales are permitted for this action\n *\n * Returns z.never().optional() when no locales are permitted, keeping the tool\n * registered but signalling no valid locale input.\n */\nexport const resolvePermittedLocaleSchema = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext,\n action: string,\n uid: string,\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null,\n baseLocaleSchema: z.ZodTypeAny\n): z.ZodTypeAny => {\n if (localeCodes === null) return baseLocaleSchema;\n\n const isLocalized = isContentTypeLocalized(strapi, uid);\n if (isLocalized === false) {\n return z.string().optional().describe('This content type is not localized. Locale is ignored.');\n }\n\n const permissionChecker = getService('permission-checker').create({\n userAbility: context.userAbility,\n model: uid,\n });\n const permitted = getPermittedLocales(permissionChecker, action, localeCodes);\n if (permitted === null) return baseLocaleSchema;\n if (permitted.length === 0) {\n return z.never().optional().describe('No locale access for this action.');\n }\n\n let schema: z.ZodTypeAny = z.enum(permitted).optional();\n\n if (defaultLocale !== null && permitted.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Permitted: ${permitted.join(', ')}. ${localeDefaultDescription(defaultLocale, permitted)}`\n );\n};\n\n/**\n * Recursively resolves leaf field paths for a component, matching the nested\n * path format used by CASL rules (e.g. 'SEO.title', 'SEO.og.image').\n *\n * The admin RBAC system decomposes component attrs into nested paths and removes\n * the parent key — so checking `ability.can(action, uid, 'SEO')` returns false\n * even when the user has full access to the component's sub-fields.\n */\nexport const getComponentLeafPaths = (\n strapi: Core.Strapi,\n componentUid: string,\n prefix: string,\n visited: Set<string> = new Set()\n): string[] => {\n if (visited.has(componentUid) === true) return [prefix];\n\n type ComponentEntry = { attributes: Record<string, { type: string; component?: string }> };\n const component = (strapi.components as unknown as Record<string, ComponentEntry | undefined>)[\n componentUid\n ];\n if (component === undefined) return [prefix];\n\n visited.add(componentUid);\n const paths: string[] = [];\n\n for (const [key, attr] of Object.entries(component.attributes)) {\n if (key === 'id') {\n // skip system id field — it is not a user-facing permission path\n // eslint-disable-next-line no-continue\n continue;\n }\n const fieldPath = `${prefix}.${key}`;\n\n if (attr.type === 'component' && attr.component !== undefined) {\n paths.push(...getComponentLeafPaths(strapi, attr.component, fieldPath, visited));\n } else {\n paths.push(fieldPath);\n }\n }\n\n visited.delete(componentUid);\n\n return paths.length > 0 ? paths : [prefix];\n};\n\n/**\n * Returns the subset of attribute keys the session may access for `action` on `uid`.\n * Returns `null` when all fields are permitted (caller should skip field filtering).\n * Component attributes are resolved to their nested leaf paths before checking CASL rules.\n */\nexport const getPermittedFields = (\n strapi: Core.Strapi,\n userAbility: Modules.MCP.McpHandlerContext['userAbility'],\n action: string,\n uid: string,\n attributes: Struct.SchemaAttributes\n): Set<string> | null => {\n const allKeys = Object.keys(attributes);\n const permitted = allKeys.filter((key) => {\n if (userAbility.can(action, uid, key) === true) return true;\n\n // Component attrs: CASL rules use nested paths (e.g. 'SEO.title').\n // Check if at least one sub-field path is permitted.\n const attr = attributes[key] as { type: string; component?: string };\n if (attr.type === 'component' && attr.component !== undefined) {\n const leafPaths = getComponentLeafPaths(strapi, attr.component, key);\n return leafPaths.some((path) => userAbility.can(action, uid, path) === true);\n }\n\n return false;\n });\n\n if (permitted.length === allKeys.length) {\n return null;\n }\n\n return new Set(permitted);\n};\n\n/**\n * Filters `localeCodes` to only those the session may access for `action`.\n * Returns `null` when all locales are permitted (caller should use the unfiltered base schema).\n * Returns an empty tuple-like array when no locale is permitted.\n */\nexport const getPermittedLocales = (\n permissionChecker: { cannot: (action: string, entity?: unknown) => boolean },\n action: string,\n localeCodes: [string, ...string[]]\n): [string, ...string[]] | null => {\n const permitted = localeCodes.filter(\n (code) => permissionChecker.cannot(action, { locale: code }) === false\n );\n\n if (permitted.length === localeCodes.length) {\n return null;\n }\n\n return permitted.length > 0\n ? (permitted as [string, ...string[]])\n : ([] as unknown as [string, ...string[]]);\n};\n\n// Re-export for use in handler files (avoids needing to import from permissions in each handler)\nexport type { ContentManagerModelForMcp };\n"],"names":["isContentTypeLocalized","strapi","uid","ct","contentTypes","undefined","pluginOptions","i18n","localized","localeDefaultDescription","defaultLocale","allowedLocales","includes","buildLocaleSchema","localeCodes","length","schema","z","enum","optional","default","describe","join","string","resolvePermittedLocaleSchema","context","action","baseLocaleSchema","isLocalized","permissionChecker","getService","create","userAbility","model","permitted","getPermittedLocales","never","getComponentLeafPaths","componentUid","prefix","visited","Set","has","component","components","add","paths","key","attr","Object","entries","attributes","fieldPath","type","push","delete","getPermittedFields","allKeys","keys","filter","can","leafPaths","some","path","code","cannot","locale"],"mappings":"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"}
1
+ {"version":3,"file":"permissions.js","sources":["../../../server/src/mcp/permissions.ts"],"sourcesContent":["import { z } from '@strapi/utils';\nimport type { Core, Modules, Struct, UID } from '@strapi/types';\n\nimport { getService } from '../utils';\nimport type { ContentManagerModelForMcp } from './types';\n\n/** Returns true if the registered localization provider reports the content type identified by `uid` as localized. */\nexport const isContentTypeLocalized = (strapi: Core.Strapi, uid: string): boolean => {\n const ct = strapi.contentTypes?.[uid as UID.ContentType];\n if (ct === undefined) return false;\n return strapi.localization.isLocalizedContentType(ct);\n};\n\nconst localeDefaultDescription = (\n defaultLocale: string | null,\n allowedLocales: readonly string[]\n): string => {\n if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {\n return `Defaults to \"${defaultLocale}\".`;\n }\n\n return 'Defaults to the default locale.';\n};\n\n/**\n * Builds the base locale Zod schema for a derived MCP tool input.\n * When `localeCodes` is provided, constrains the field to a `z.enum` of available codes\n * with an optional default; otherwise falls back to a plain optional string.\n */\nexport const buildLocaleSchema = (\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null\n): z.ZodTypeAny => {\n if (localeCodes !== null && localeCodes.length > 0) {\n let schema: z.ZodTypeAny = z.enum(localeCodes).optional();\n\n if (defaultLocale !== null && localeCodes.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Available: ${localeCodes.join(', ')}. ${localeDefaultDescription(defaultLocale, localeCodes)}`\n );\n }\n\n return z\n .string()\n .optional()\n .describe('Locale code (e.g. \"en\", \"fr\"). Defaults to the default locale.');\n};\n\n/**\n * Narrows the base locale schema to only locales the session is permitted to access\n * for the given action + uid combination.\n *\n * Returns the base schema unchanged when:\n * - localeCodes is null (i18n not installed)\n * - the content type is not localized\n * - all installed locales are permitted for this action\n *\n * Returns z.never().optional() when no locales are permitted, keeping the tool\n * registered but signalling no valid locale input.\n */\nexport const resolvePermittedLocaleSchema = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext,\n action: string,\n uid: string,\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null,\n baseLocaleSchema: z.ZodTypeAny\n): z.ZodTypeAny => {\n if (localeCodes === null) return baseLocaleSchema;\n\n const isLocalized = isContentTypeLocalized(strapi, uid);\n if (isLocalized === false) {\n return z.string().optional().describe('This content type is not localized. Locale is ignored.');\n }\n\n const permissionChecker = getService('permission-checker').create({\n userAbility: context.userAbility,\n model: uid,\n });\n const permitted = getPermittedLocales(permissionChecker, action, localeCodes);\n if (permitted === null) return baseLocaleSchema;\n if (permitted.length === 0) {\n return z.never().optional().describe('No locale access for this action.');\n }\n\n let schema: z.ZodTypeAny = z.enum(permitted).optional();\n\n if (defaultLocale !== null && permitted.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Permitted: ${permitted.join(', ')}. ${localeDefaultDescription(defaultLocale, permitted)}`\n );\n};\n\n/**\n * Recursively resolves leaf field paths for a component, matching the nested\n * path format used by CASL rules (e.g. 'SEO.title', 'SEO.og.image').\n *\n * The admin RBAC system decomposes component attrs into nested paths and removes\n * the parent key — so checking `ability.can(action, uid, 'SEO')` returns false\n * even when the user has full access to the component's sub-fields.\n */\nexport const getComponentLeafPaths = (\n strapi: Core.Strapi,\n componentUid: string,\n prefix: string,\n visited: Set<string> = new Set()\n): string[] => {\n if (visited.has(componentUid) === true) return [prefix];\n\n type ComponentEntry = { attributes: Record<string, { type: string; component?: string }> };\n const component = (strapi.components as unknown as Record<string, ComponentEntry | undefined>)[\n componentUid\n ];\n if (component === undefined) return [prefix];\n\n visited.add(componentUid);\n const paths: string[] = [];\n\n for (const [key, attr] of Object.entries(component.attributes)) {\n if (key === 'id') {\n // skip system id field — it is not a user-facing permission path\n // eslint-disable-next-line no-continue\n continue;\n }\n const fieldPath = `${prefix}.${key}`;\n\n if (attr.type === 'component' && attr.component !== undefined) {\n paths.push(...getComponentLeafPaths(strapi, attr.component, fieldPath, visited));\n } else {\n paths.push(fieldPath);\n }\n }\n\n visited.delete(componentUid);\n\n return paths.length > 0 ? paths : [prefix];\n};\n\n/**\n * Returns the subset of attribute keys the session may access for `action` on `uid`.\n * Returns `null` when all fields are permitted (caller should skip field filtering).\n * Component attributes are resolved to their nested leaf paths before checking CASL rules.\n */\nexport const getPermittedFields = (\n strapi: Core.Strapi,\n userAbility: Modules.MCP.McpHandlerContext['userAbility'],\n action: string,\n uid: string,\n attributes: Struct.SchemaAttributes\n): Set<string> | null => {\n const allKeys = Object.keys(attributes);\n const permitted = allKeys.filter((key) => {\n if (userAbility.can(action, uid, key) === true) return true;\n\n // Component attrs: CASL rules use nested paths (e.g. 'SEO.title').\n // Check if at least one sub-field path is permitted.\n const attr = attributes[key] as { type: string; component?: string };\n if (attr.type === 'component' && attr.component !== undefined) {\n const leafPaths = getComponentLeafPaths(strapi, attr.component, key);\n return leafPaths.some((path) => userAbility.can(action, uid, path) === true);\n }\n\n return false;\n });\n\n if (permitted.length === allKeys.length) {\n return null;\n }\n\n return new Set(permitted);\n};\n\n/**\n * Filters `localeCodes` to only those the session may access for `action`.\n * Returns `null` when all locales are permitted (caller should use the unfiltered base schema).\n * Returns an empty tuple-like array when no locale is permitted.\n */\nexport const getPermittedLocales = (\n permissionChecker: { cannot: (action: string, entity?: unknown) => boolean },\n action: string,\n localeCodes: [string, ...string[]]\n): [string, ...string[]] | null => {\n const permitted = localeCodes.filter(\n (code) => permissionChecker.cannot(action, { locale: code }) === false\n );\n\n if (permitted.length === localeCodes.length) {\n return null;\n }\n\n return permitted.length > 0\n ? (permitted as [string, ...string[]])\n : ([] as unknown as [string, ...string[]]);\n};\n\n// Re-export for use in handler files (avoids needing to import from permissions in each handler)\nexport type { ContentManagerModelForMcp };\n"],"names":["isContentTypeLocalized","strapi","uid","ct","contentTypes","undefined","localization","isLocalizedContentType","localeDefaultDescription","defaultLocale","allowedLocales","includes","buildLocaleSchema","localeCodes","length","schema","z","enum","optional","default","describe","join","string","resolvePermittedLocaleSchema","context","action","baseLocaleSchema","isLocalized","permissionChecker","getService","create","userAbility","model","permitted","getPermittedLocales","never","getComponentLeafPaths","componentUid","prefix","visited","Set","has","component","components","add","paths","key","attr","Object","entries","attributes","fieldPath","type","push","delete","getPermittedFields","allKeys","keys","filter","can","leafPaths","some","path","code","cannot","locale"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { z } from '@strapi/utils';
2
2
  import { getService } from '../utils/index.mjs';
3
3
 
4
- /** Returns true if the content type identified by `uid` has the i18n `localized` plugin option enabled. */ const isContentTypeLocalized = (strapi, uid)=>{
4
+ /** Returns true if the registered localization provider reports the content type identified by `uid` as localized. */ const isContentTypeLocalized = (strapi, uid)=>{
5
5
  const ct = strapi.contentTypes?.[uid];
6
6
  if (ct === undefined) return false;
7
- return ct.pluginOptions?.i18n?.localized === true;
7
+ return strapi.localization.isLocalizedContentType(ct);
8
8
  };
9
9
  const localeDefaultDescription = (defaultLocale, allowedLocales)=>{
10
10
  if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.mjs","sources":["../../../server/src/mcp/permissions.ts"],"sourcesContent":["import { z } from '@strapi/utils';\nimport type { Core, Modules, Struct, UID } from '@strapi/types';\n\nimport { getService } from '../utils';\nimport type { ContentManagerModelForMcp } from './types';\n\n/** Returns true if the content type identified by `uid` has the i18n `localized` plugin option enabled. */\nexport const isContentTypeLocalized = (strapi: Core.Strapi, uid: string): boolean => {\n const ct = strapi.contentTypes?.[uid as UID.ContentType];\n if (ct === undefined) return false;\n return (\n (ct as { pluginOptions?: { i18n?: { localized?: boolean } } }).pluginOptions?.i18n\n ?.localized === true\n );\n};\n\nconst localeDefaultDescription = (\n defaultLocale: string | null,\n allowedLocales: readonly string[]\n): string => {\n if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {\n return `Defaults to \"${defaultLocale}\".`;\n }\n\n return 'Defaults to the default locale.';\n};\n\n/**\n * Builds the base locale Zod schema for a derived MCP tool input.\n * When `localeCodes` is provided, constrains the field to a `z.enum` of available codes\n * with an optional default; otherwise falls back to a plain optional string.\n */\nexport const buildLocaleSchema = (\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null\n): z.ZodTypeAny => {\n if (localeCodes !== null && localeCodes.length > 0) {\n let schema: z.ZodTypeAny = z.enum(localeCodes).optional();\n\n if (defaultLocale !== null && localeCodes.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Available: ${localeCodes.join(', ')}. ${localeDefaultDescription(defaultLocale, localeCodes)}`\n );\n }\n\n return z\n .string()\n .optional()\n .describe('Locale code (e.g. \"en\", \"fr\"). Defaults to the default locale.');\n};\n\n/**\n * Narrows the base locale schema to only locales the session is permitted to access\n * for the given action + uid combination.\n *\n * Returns the base schema unchanged when:\n * - localeCodes is null (i18n not installed)\n * - the content type is not localized\n * - all installed locales are permitted for this action\n *\n * Returns z.never().optional() when no locales are permitted, keeping the tool\n * registered but signalling no valid locale input.\n */\nexport const resolvePermittedLocaleSchema = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext,\n action: string,\n uid: string,\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null,\n baseLocaleSchema: z.ZodTypeAny\n): z.ZodTypeAny => {\n if (localeCodes === null) return baseLocaleSchema;\n\n const isLocalized = isContentTypeLocalized(strapi, uid);\n if (isLocalized === false) {\n return z.string().optional().describe('This content type is not localized. Locale is ignored.');\n }\n\n const permissionChecker = getService('permission-checker').create({\n userAbility: context.userAbility,\n model: uid,\n });\n const permitted = getPermittedLocales(permissionChecker, action, localeCodes);\n if (permitted === null) return baseLocaleSchema;\n if (permitted.length === 0) {\n return z.never().optional().describe('No locale access for this action.');\n }\n\n let schema: z.ZodTypeAny = z.enum(permitted).optional();\n\n if (defaultLocale !== null && permitted.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Permitted: ${permitted.join(', ')}. ${localeDefaultDescription(defaultLocale, permitted)}`\n );\n};\n\n/**\n * Recursively resolves leaf field paths for a component, matching the nested\n * path format used by CASL rules (e.g. 'SEO.title', 'SEO.og.image').\n *\n * The admin RBAC system decomposes component attrs into nested paths and removes\n * the parent key — so checking `ability.can(action, uid, 'SEO')` returns false\n * even when the user has full access to the component's sub-fields.\n */\nexport const getComponentLeafPaths = (\n strapi: Core.Strapi,\n componentUid: string,\n prefix: string,\n visited: Set<string> = new Set()\n): string[] => {\n if (visited.has(componentUid) === true) return [prefix];\n\n type ComponentEntry = { attributes: Record<string, { type: string; component?: string }> };\n const component = (strapi.components as unknown as Record<string, ComponentEntry | undefined>)[\n componentUid\n ];\n if (component === undefined) return [prefix];\n\n visited.add(componentUid);\n const paths: string[] = [];\n\n for (const [key, attr] of Object.entries(component.attributes)) {\n if (key === 'id') {\n // skip system id field — it is not a user-facing permission path\n // eslint-disable-next-line no-continue\n continue;\n }\n const fieldPath = `${prefix}.${key}`;\n\n if (attr.type === 'component' && attr.component !== undefined) {\n paths.push(...getComponentLeafPaths(strapi, attr.component, fieldPath, visited));\n } else {\n paths.push(fieldPath);\n }\n }\n\n visited.delete(componentUid);\n\n return paths.length > 0 ? paths : [prefix];\n};\n\n/**\n * Returns the subset of attribute keys the session may access for `action` on `uid`.\n * Returns `null` when all fields are permitted (caller should skip field filtering).\n * Component attributes are resolved to their nested leaf paths before checking CASL rules.\n */\nexport const getPermittedFields = (\n strapi: Core.Strapi,\n userAbility: Modules.MCP.McpHandlerContext['userAbility'],\n action: string,\n uid: string,\n attributes: Struct.SchemaAttributes\n): Set<string> | null => {\n const allKeys = Object.keys(attributes);\n const permitted = allKeys.filter((key) => {\n if (userAbility.can(action, uid, key) === true) return true;\n\n // Component attrs: CASL rules use nested paths (e.g. 'SEO.title').\n // Check if at least one sub-field path is permitted.\n const attr = attributes[key] as { type: string; component?: string };\n if (attr.type === 'component' && attr.component !== undefined) {\n const leafPaths = getComponentLeafPaths(strapi, attr.component, key);\n return leafPaths.some((path) => userAbility.can(action, uid, path) === true);\n }\n\n return false;\n });\n\n if (permitted.length === allKeys.length) {\n return null;\n }\n\n return new Set(permitted);\n};\n\n/**\n * Filters `localeCodes` to only those the session may access for `action`.\n * Returns `null` when all locales are permitted (caller should use the unfiltered base schema).\n * Returns an empty tuple-like array when no locale is permitted.\n */\nexport const getPermittedLocales = (\n permissionChecker: { cannot: (action: string, entity?: unknown) => boolean },\n action: string,\n localeCodes: [string, ...string[]]\n): [string, ...string[]] | null => {\n const permitted = localeCodes.filter(\n (code) => permissionChecker.cannot(action, { locale: code }) === false\n );\n\n if (permitted.length === localeCodes.length) {\n return null;\n }\n\n return permitted.length > 0\n ? (permitted as [string, ...string[]])\n : ([] as unknown as [string, ...string[]]);\n};\n\n// Re-export for use in handler files (avoids needing to import from permissions in each handler)\nexport type { ContentManagerModelForMcp };\n"],"names":["isContentTypeLocalized","strapi","uid","ct","contentTypes","undefined","pluginOptions","i18n","localized","localeDefaultDescription","defaultLocale","allowedLocales","includes","buildLocaleSchema","localeCodes","length","schema","z","enum","optional","default","describe","join","string","resolvePermittedLocaleSchema","context","action","baseLocaleSchema","isLocalized","permissionChecker","getService","create","userAbility","model","permitted","getPermittedLocales","never","getComponentLeafPaths","componentUid","prefix","visited","Set","has","component","components","add","paths","key","attr","Object","entries","attributes","fieldPath","type","push","delete","getPermittedFields","allKeys","keys","filter","can","leafPaths","some","path","code","cannot","locale"],"mappings":"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"}
1
+ {"version":3,"file":"permissions.mjs","sources":["../../../server/src/mcp/permissions.ts"],"sourcesContent":["import { z } from '@strapi/utils';\nimport type { Core, Modules, Struct, UID } from '@strapi/types';\n\nimport { getService } from '../utils';\nimport type { ContentManagerModelForMcp } from './types';\n\n/** Returns true if the registered localization provider reports the content type identified by `uid` as localized. */\nexport const isContentTypeLocalized = (strapi: Core.Strapi, uid: string): boolean => {\n const ct = strapi.contentTypes?.[uid as UID.ContentType];\n if (ct === undefined) return false;\n return strapi.localization.isLocalizedContentType(ct);\n};\n\nconst localeDefaultDescription = (\n defaultLocale: string | null,\n allowedLocales: readonly string[]\n): string => {\n if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {\n return `Defaults to \"${defaultLocale}\".`;\n }\n\n return 'Defaults to the default locale.';\n};\n\n/**\n * Builds the base locale Zod schema for a derived MCP tool input.\n * When `localeCodes` is provided, constrains the field to a `z.enum` of available codes\n * with an optional default; otherwise falls back to a plain optional string.\n */\nexport const buildLocaleSchema = (\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null\n): z.ZodTypeAny => {\n if (localeCodes !== null && localeCodes.length > 0) {\n let schema: z.ZodTypeAny = z.enum(localeCodes).optional();\n\n if (defaultLocale !== null && localeCodes.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Available: ${localeCodes.join(', ')}. ${localeDefaultDescription(defaultLocale, localeCodes)}`\n );\n }\n\n return z\n .string()\n .optional()\n .describe('Locale code (e.g. \"en\", \"fr\"). Defaults to the default locale.');\n};\n\n/**\n * Narrows the base locale schema to only locales the session is permitted to access\n * for the given action + uid combination.\n *\n * Returns the base schema unchanged when:\n * - localeCodes is null (i18n not installed)\n * - the content type is not localized\n * - all installed locales are permitted for this action\n *\n * Returns z.never().optional() when no locales are permitted, keeping the tool\n * registered but signalling no valid locale input.\n */\nexport const resolvePermittedLocaleSchema = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext,\n action: string,\n uid: string,\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null,\n baseLocaleSchema: z.ZodTypeAny\n): z.ZodTypeAny => {\n if (localeCodes === null) return baseLocaleSchema;\n\n const isLocalized = isContentTypeLocalized(strapi, uid);\n if (isLocalized === false) {\n return z.string().optional().describe('This content type is not localized. Locale is ignored.');\n }\n\n const permissionChecker = getService('permission-checker').create({\n userAbility: context.userAbility,\n model: uid,\n });\n const permitted = getPermittedLocales(permissionChecker, action, localeCodes);\n if (permitted === null) return baseLocaleSchema;\n if (permitted.length === 0) {\n return z.never().optional().describe('No locale access for this action.');\n }\n\n let schema: z.ZodTypeAny = z.enum(permitted).optional();\n\n if (defaultLocale !== null && permitted.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Permitted: ${permitted.join(', ')}. ${localeDefaultDescription(defaultLocale, permitted)}`\n );\n};\n\n/**\n * Recursively resolves leaf field paths for a component, matching the nested\n * path format used by CASL rules (e.g. 'SEO.title', 'SEO.og.image').\n *\n * The admin RBAC system decomposes component attrs into nested paths and removes\n * the parent key — so checking `ability.can(action, uid, 'SEO')` returns false\n * even when the user has full access to the component's sub-fields.\n */\nexport const getComponentLeafPaths = (\n strapi: Core.Strapi,\n componentUid: string,\n prefix: string,\n visited: Set<string> = new Set()\n): string[] => {\n if (visited.has(componentUid) === true) return [prefix];\n\n type ComponentEntry = { attributes: Record<string, { type: string; component?: string }> };\n const component = (strapi.components as unknown as Record<string, ComponentEntry | undefined>)[\n componentUid\n ];\n if (component === undefined) return [prefix];\n\n visited.add(componentUid);\n const paths: string[] = [];\n\n for (const [key, attr] of Object.entries(component.attributes)) {\n if (key === 'id') {\n // skip system id field — it is not a user-facing permission path\n // eslint-disable-next-line no-continue\n continue;\n }\n const fieldPath = `${prefix}.${key}`;\n\n if (attr.type === 'component' && attr.component !== undefined) {\n paths.push(...getComponentLeafPaths(strapi, attr.component, fieldPath, visited));\n } else {\n paths.push(fieldPath);\n }\n }\n\n visited.delete(componentUid);\n\n return paths.length > 0 ? paths : [prefix];\n};\n\n/**\n * Returns the subset of attribute keys the session may access for `action` on `uid`.\n * Returns `null` when all fields are permitted (caller should skip field filtering).\n * Component attributes are resolved to their nested leaf paths before checking CASL rules.\n */\nexport const getPermittedFields = (\n strapi: Core.Strapi,\n userAbility: Modules.MCP.McpHandlerContext['userAbility'],\n action: string,\n uid: string,\n attributes: Struct.SchemaAttributes\n): Set<string> | null => {\n const allKeys = Object.keys(attributes);\n const permitted = allKeys.filter((key) => {\n if (userAbility.can(action, uid, key) === true) return true;\n\n // Component attrs: CASL rules use nested paths (e.g. 'SEO.title').\n // Check if at least one sub-field path is permitted.\n const attr = attributes[key] as { type: string; component?: string };\n if (attr.type === 'component' && attr.component !== undefined) {\n const leafPaths = getComponentLeafPaths(strapi, attr.component, key);\n return leafPaths.some((path) => userAbility.can(action, uid, path) === true);\n }\n\n return false;\n });\n\n if (permitted.length === allKeys.length) {\n return null;\n }\n\n return new Set(permitted);\n};\n\n/**\n * Filters `localeCodes` to only those the session may access for `action`.\n * Returns `null` when all locales are permitted (caller should use the unfiltered base schema).\n * Returns an empty tuple-like array when no locale is permitted.\n */\nexport const getPermittedLocales = (\n permissionChecker: { cannot: (action: string, entity?: unknown) => boolean },\n action: string,\n localeCodes: [string, ...string[]]\n): [string, ...string[]] | null => {\n const permitted = localeCodes.filter(\n (code) => permissionChecker.cannot(action, { locale: code }) === false\n );\n\n if (permitted.length === localeCodes.length) {\n return null;\n }\n\n return permitted.length > 0\n ? (permitted as [string, ...string[]])\n : ([] as unknown as [string, ...string[]]);\n};\n\n// Re-export for use in handler files (avoids needing to import from permissions in each handler)\nexport type { ContentManagerModelForMcp };\n"],"names":["isContentTypeLocalized","strapi","uid","ct","contentTypes","undefined","localization","isLocalizedContentType","localeDefaultDescription","defaultLocale","allowedLocales","includes","buildLocaleSchema","localeCodes","length","schema","z","enum","optional","default","describe","join","string","resolvePermittedLocaleSchema","context","action","baseLocaleSchema","isLocalized","permissionChecker","getService","create","userAbility","model","permitted","getPermittedLocales","never","getComponentLeafPaths","componentUid","prefix","visited","Set","has","component","components","add","paths","key","attr","Object","entries","attributes","fieldPath","type","push","delete","getPermittedFields","allKeys","keys","filter","can","leafPaths","some","path","code","cannot","locale"],"mappings":"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"}
@@ -5,19 +5,21 @@ var index = require('../utils/index.js');
5
5
 
6
6
  /**
7
7
  * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().
8
- * Must be called from the plugin register phase, before the MCP HTTP server starts.
8
+ * Must be called from the plugin bootstrap phase: after every plugin's register (so the
9
+ * localization provider is installed and locale rows are readable) and before the MCP HTTP
10
+ * server starts.
9
11
  */ const registerContentManagerMcpTools = async ({ strapi })=>{
10
12
  // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but
11
13
  // never exposed). Skip the expensive derivation below when the MCP server will not start.
12
14
  if (strapi.ai.mcp.isEnabled() !== true) {
13
15
  return;
14
16
  }
15
- const i18nPlugin = strapi.plugin('i18n');
16
17
  let localeCodes = null;
17
18
  let defaultLocale = null;
18
- if (i18nPlugin !== undefined) {
19
- localeCodes = (await i18nPlugin.service('locales').find()).map((locale)=>locale.code);
20
- defaultLocale = await i18nPlugin.service('locales').getDefaultLocale();
19
+ if (strapi.localization.isEnabled() === true) {
20
+ // The tuple type is a lie for zero locales; consumers guard on length > 0 before z.enum.
21
+ localeCodes = (await strapi.localization.getLocales()).map((locale)=>locale.code);
22
+ defaultLocale = await strapi.localization.getDefaultLocale();
21
23
  }
22
24
  const models = index.getService('content-types').findDisplayedContentTypes();
23
25
  const tools = deriveContentTypeMcpTools.deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {
@@ -1 +1 @@
1
- {"version":3,"file":"register-content-manager-mcp-tools.js","sources":["../../../server/src/mcp/register-content-manager-mcp-tools.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { deriveDisplayedContentTypeMcpToolDefinitions } from './derive-content-type-mcp-tools';\nimport { getService } from '../utils';\n\n/**\n * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().\n * Must be called from the plugin register phase, before the MCP HTTP server starts.\n */\nexport const registerContentManagerMcpTools = async ({\n strapi,\n}: {\n strapi: Core.Strapi;\n}): Promise<void> => {\n // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but\n // never exposed). Skip the expensive derivation below when the MCP server will not start.\n if (strapi.ai.mcp.isEnabled() !== true) {\n return;\n }\n\n const i18nPlugin = strapi.plugin('i18n');\n\n let localeCodes = null;\n let defaultLocale: string | null = null;\n if (i18nPlugin !== undefined) {\n localeCodes = (await i18nPlugin.service('locales').find()).map(\n (locale: { code: string }) => locale.code\n ) as [string, ...string[]];\n defaultLocale = await i18nPlugin.service('locales').getDefaultLocale();\n }\n\n const models = getService('content-types').findDisplayedContentTypes();\n const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {\n localeCodes,\n defaultLocale,\n });\n\n for (const tool of tools) {\n strapi.ai.mcp.registerTool(tool);\n }\n};\n"],"names":["registerContentManagerMcpTools","strapi","ai","mcp","isEnabled","i18nPlugin","plugin","localeCodes","defaultLocale","undefined","service","find","map","locale","code","getDefaultLocale","models","getService","findDisplayedContentTypes","tools","deriveDisplayedContentTypeMcpToolDefinitions","tool","registerTool"],"mappings":";;;;;AAKA;;;AAGC,IACM,MAAMA,8BAAAA,GAAiC,OAAO,EACnDC,MAAM,EAGP,GAAA;;;AAGC,IAAA,IAAIA,OAAOC,EAAE,CAACC,GAAG,CAACC,SAAS,OAAO,IAAA,EAAM;AACtC,QAAA;AACF,IAAA;IAEA,MAAMC,UAAAA,GAAaJ,MAAAA,CAAOK,MAAM,CAAC,MAAA,CAAA;AAEjC,IAAA,IAAIC,WAAAA,GAAc,IAAA;AAClB,IAAA,IAAIC,aAAAA,GAA+B,IAAA;AACnC,IAAA,IAAIH,eAAeI,SAAAA,EAAW;AAC5BF,QAAAA,WAAAA,GAAc,CAAC,MAAMF,UAAAA,CAAWK,OAAO,CAAC,SAAA,CAAA,CAAWC,IAAI,EAAC,EAAGC,GAAG,CAC5D,CAACC,MAAAA,GAA6BA,OAAOC,IAAI,CAAA;AAE3CN,QAAAA,aAAAA,GAAgB,MAAMH,UAAAA,CAAWK,OAAO,CAAC,WAAWK,gBAAgB,EAAA;AACtE,IAAA;IAEA,MAAMC,MAAAA,GAASC,gBAAAA,CAAW,eAAA,CAAA,CAAiBC,yBAAyB,EAAA;IACpE,MAAMC,KAAAA,GAAQC,sEAAAA,CAA6CnB,MAAAA,EAAQe,MAAAA,EAAQ;AACzET,QAAAA,WAAAA;AACAC,QAAAA;AACF,KAAA,CAAA;IAEA,KAAK,MAAMa,QAAQF,KAAAA,CAAO;AACxBlB,QAAAA,MAAAA,CAAOC,EAAE,CAACC,GAAG,CAACmB,YAAY,CAACD,IAAAA,CAAAA;AAC7B,IAAA;AACF;;;;"}
1
+ {"version":3,"file":"register-content-manager-mcp-tools.js","sources":["../../../server/src/mcp/register-content-manager-mcp-tools.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { deriveDisplayedContentTypeMcpToolDefinitions } from './derive-content-type-mcp-tools';\nimport { getService } from '../utils';\n\n/**\n * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().\n * Must be called from the plugin bootstrap phase: after every plugin's register (so the\n * localization provider is installed and locale rows are readable) and before the MCP HTTP\n * server starts.\n */\nexport const registerContentManagerMcpTools = async ({\n strapi,\n}: {\n strapi: Core.Strapi;\n}): Promise<void> => {\n // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but\n // never exposed). Skip the expensive derivation below when the MCP server will not start.\n if (strapi.ai.mcp.isEnabled() !== true) {\n return;\n }\n\n let localeCodes: [string, ...string[]] | null = null;\n let defaultLocale: string | null = null;\n if (strapi.localization.isEnabled() === true) {\n // The tuple type is a lie for zero locales; consumers guard on length > 0 before z.enum.\n localeCodes = (await strapi.localization.getLocales()).map((locale) => locale.code) as [\n string,\n ...string[],\n ];\n defaultLocale = await strapi.localization.getDefaultLocale();\n }\n\n const models = getService('content-types').findDisplayedContentTypes();\n const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {\n localeCodes,\n defaultLocale,\n });\n\n for (const tool of tools) {\n strapi.ai.mcp.registerTool(tool);\n }\n};\n"],"names":["registerContentManagerMcpTools","strapi","ai","mcp","isEnabled","localeCodes","defaultLocale","localization","getLocales","map","locale","code","getDefaultLocale","models","getService","findDisplayedContentTypes","tools","deriveDisplayedContentTypeMcpToolDefinitions","tool","registerTool"],"mappings":";;;;;AAKA;;;;;AAKC,IACM,MAAMA,8BAAAA,GAAiC,OAAO,EACnDC,MAAM,EAGP,GAAA;;;AAGC,IAAA,IAAIA,OAAOC,EAAE,CAACC,GAAG,CAACC,SAAS,OAAO,IAAA,EAAM;AACtC,QAAA;AACF,IAAA;AAEA,IAAA,IAAIC,WAAAA,GAA4C,IAAA;AAChD,IAAA,IAAIC,aAAAA,GAA+B,IAAA;AACnC,IAAA,IAAIL,MAAAA,CAAOM,YAAY,CAACH,SAAS,OAAO,IAAA,EAAM;;AAE5CC,QAAAA,WAAAA,GAAc,CAAC,MAAMJ,MAAAA,CAAOM,YAAY,CAACC,UAAU,EAAC,EAAGC,GAAG,CAAC,CAACC,MAAAA,GAAWA,OAAOC,IAAI,CAAA;AAIlFL,QAAAA,aAAAA,GAAgB,MAAML,MAAAA,CAAOM,YAAY,CAACK,gBAAgB,EAAA;AAC5D,IAAA;IAEA,MAAMC,MAAAA,GAASC,gBAAAA,CAAW,eAAA,CAAA,CAAiBC,yBAAyB,EAAA;IACpE,MAAMC,KAAAA,GAAQC,sEAAAA,CAA6ChB,MAAAA,EAAQY,MAAAA,EAAQ;AACzER,QAAAA,WAAAA;AACAC,QAAAA;AACF,KAAA,CAAA;IAEA,KAAK,MAAMY,QAAQF,KAAAA,CAAO;AACxBf,QAAAA,MAAAA,CAAOC,EAAE,CAACC,GAAG,CAACgB,YAAY,CAACD,IAAAA,CAAAA;AAC7B,IAAA;AACF;;;;"}
@@ -3,19 +3,21 @@ import { getService } from '../utils/index.mjs';
3
3
 
4
4
  /**
5
5
  * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().
6
- * Must be called from the plugin register phase, before the MCP HTTP server starts.
6
+ * Must be called from the plugin bootstrap phase: after every plugin's register (so the
7
+ * localization provider is installed and locale rows are readable) and before the MCP HTTP
8
+ * server starts.
7
9
  */ const registerContentManagerMcpTools = async ({ strapi })=>{
8
10
  // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but
9
11
  // never exposed). Skip the expensive derivation below when the MCP server will not start.
10
12
  if (strapi.ai.mcp.isEnabled() !== true) {
11
13
  return;
12
14
  }
13
- const i18nPlugin = strapi.plugin('i18n');
14
15
  let localeCodes = null;
15
16
  let defaultLocale = null;
16
- if (i18nPlugin !== undefined) {
17
- localeCodes = (await i18nPlugin.service('locales').find()).map((locale)=>locale.code);
18
- defaultLocale = await i18nPlugin.service('locales').getDefaultLocale();
17
+ if (strapi.localization.isEnabled() === true) {
18
+ // The tuple type is a lie for zero locales; consumers guard on length > 0 before z.enum.
19
+ localeCodes = (await strapi.localization.getLocales()).map((locale)=>locale.code);
20
+ defaultLocale = await strapi.localization.getDefaultLocale();
19
21
  }
20
22
  const models = getService('content-types').findDisplayedContentTypes();
21
23
  const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {
@@ -1 +1 @@
1
- {"version":3,"file":"register-content-manager-mcp-tools.mjs","sources":["../../../server/src/mcp/register-content-manager-mcp-tools.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { deriveDisplayedContentTypeMcpToolDefinitions } from './derive-content-type-mcp-tools';\nimport { getService } from '../utils';\n\n/**\n * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().\n * Must be called from the plugin register phase, before the MCP HTTP server starts.\n */\nexport const registerContentManagerMcpTools = async ({\n strapi,\n}: {\n strapi: Core.Strapi;\n}): Promise<void> => {\n // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but\n // never exposed). Skip the expensive derivation below when the MCP server will not start.\n if (strapi.ai.mcp.isEnabled() !== true) {\n return;\n }\n\n const i18nPlugin = strapi.plugin('i18n');\n\n let localeCodes = null;\n let defaultLocale: string | null = null;\n if (i18nPlugin !== undefined) {\n localeCodes = (await i18nPlugin.service('locales').find()).map(\n (locale: { code: string }) => locale.code\n ) as [string, ...string[]];\n defaultLocale = await i18nPlugin.service('locales').getDefaultLocale();\n }\n\n const models = getService('content-types').findDisplayedContentTypes();\n const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {\n localeCodes,\n defaultLocale,\n });\n\n for (const tool of tools) {\n strapi.ai.mcp.registerTool(tool);\n }\n};\n"],"names":["registerContentManagerMcpTools","strapi","ai","mcp","isEnabled","i18nPlugin","plugin","localeCodes","defaultLocale","undefined","service","find","map","locale","code","getDefaultLocale","models","getService","findDisplayedContentTypes","tools","deriveDisplayedContentTypeMcpToolDefinitions","tool","registerTool"],"mappings":";;;AAKA;;;AAGC,IACM,MAAMA,8BAAAA,GAAiC,OAAO,EACnDC,MAAM,EAGP,GAAA;;;AAGC,IAAA,IAAIA,OAAOC,EAAE,CAACC,GAAG,CAACC,SAAS,OAAO,IAAA,EAAM;AACtC,QAAA;AACF,IAAA;IAEA,MAAMC,UAAAA,GAAaJ,MAAAA,CAAOK,MAAM,CAAC,MAAA,CAAA;AAEjC,IAAA,IAAIC,WAAAA,GAAc,IAAA;AAClB,IAAA,IAAIC,aAAAA,GAA+B,IAAA;AACnC,IAAA,IAAIH,eAAeI,SAAAA,EAAW;AAC5BF,QAAAA,WAAAA,GAAc,CAAC,MAAMF,UAAAA,CAAWK,OAAO,CAAC,SAAA,CAAA,CAAWC,IAAI,EAAC,EAAGC,GAAG,CAC5D,CAACC,MAAAA,GAA6BA,OAAOC,IAAI,CAAA;AAE3CN,QAAAA,aAAAA,GAAgB,MAAMH,UAAAA,CAAWK,OAAO,CAAC,WAAWK,gBAAgB,EAAA;AACtE,IAAA;IAEA,MAAMC,MAAAA,GAASC,UAAAA,CAAW,eAAA,CAAA,CAAiBC,yBAAyB,EAAA;IACpE,MAAMC,KAAAA,GAAQC,4CAAAA,CAA6CnB,MAAAA,EAAQe,MAAAA,EAAQ;AACzET,QAAAA,WAAAA;AACAC,QAAAA;AACF,KAAA,CAAA;IAEA,KAAK,MAAMa,QAAQF,KAAAA,CAAO;AACxBlB,QAAAA,MAAAA,CAAOC,EAAE,CAACC,GAAG,CAACmB,YAAY,CAACD,IAAAA,CAAAA;AAC7B,IAAA;AACF;;;;"}
1
+ {"version":3,"file":"register-content-manager-mcp-tools.mjs","sources":["../../../server/src/mcp/register-content-manager-mcp-tools.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { deriveDisplayedContentTypeMcpToolDefinitions } from './derive-content-type-mcp-tools';\nimport { getService } from '../utils';\n\n/**\n * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().\n * Must be called from the plugin bootstrap phase: after every plugin's register (so the\n * localization provider is installed and locale rows are readable) and before the MCP HTTP\n * server starts.\n */\nexport const registerContentManagerMcpTools = async ({\n strapi,\n}: {\n strapi: Core.Strapi;\n}): Promise<void> => {\n // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but\n // never exposed). Skip the expensive derivation below when the MCP server will not start.\n if (strapi.ai.mcp.isEnabled() !== true) {\n return;\n }\n\n let localeCodes: [string, ...string[]] | null = null;\n let defaultLocale: string | null = null;\n if (strapi.localization.isEnabled() === true) {\n // The tuple type is a lie for zero locales; consumers guard on length > 0 before z.enum.\n localeCodes = (await strapi.localization.getLocales()).map((locale) => locale.code) as [\n string,\n ...string[],\n ];\n defaultLocale = await strapi.localization.getDefaultLocale();\n }\n\n const models = getService('content-types').findDisplayedContentTypes();\n const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {\n localeCodes,\n defaultLocale,\n });\n\n for (const tool of tools) {\n strapi.ai.mcp.registerTool(tool);\n }\n};\n"],"names":["registerContentManagerMcpTools","strapi","ai","mcp","isEnabled","localeCodes","defaultLocale","localization","getLocales","map","locale","code","getDefaultLocale","models","getService","findDisplayedContentTypes","tools","deriveDisplayedContentTypeMcpToolDefinitions","tool","registerTool"],"mappings":";;;AAKA;;;;;AAKC,IACM,MAAMA,8BAAAA,GAAiC,OAAO,EACnDC,MAAM,EAGP,GAAA;;;AAGC,IAAA,IAAIA,OAAOC,EAAE,CAACC,GAAG,CAACC,SAAS,OAAO,IAAA,EAAM;AACtC,QAAA;AACF,IAAA;AAEA,IAAA,IAAIC,WAAAA,GAA4C,IAAA;AAChD,IAAA,IAAIC,aAAAA,GAA+B,IAAA;AACnC,IAAA,IAAIL,MAAAA,CAAOM,YAAY,CAACH,SAAS,OAAO,IAAA,EAAM;;AAE5CC,QAAAA,WAAAA,GAAc,CAAC,MAAMJ,MAAAA,CAAOM,YAAY,CAACC,UAAU,EAAC,EAAGC,GAAG,CAAC,CAACC,MAAAA,GAAWA,OAAOC,IAAI,CAAA;AAIlFL,QAAAA,aAAAA,GAAgB,MAAML,MAAAA,CAAOM,YAAY,CAACK,gBAAgB,EAAA;AAC5D,IAAA;IAEA,MAAMC,MAAAA,GAASC,UAAAA,CAAW,eAAA,CAAA,CAAiBC,yBAAyB,EAAA;IACpE,MAAMC,KAAAA,GAAQC,4CAAAA,CAA6ChB,MAAAA,EAAQY,MAAAA,EAAQ;AACzER,QAAAA,WAAAA;AACAC,QAAAA;AACF,KAAA,CAAA;IAEA,KAAK,MAAMY,QAAQF,KAAAA,CAAO;AACxBf,QAAAA,MAAAA,CAAOC,EAAE,CAACC,GAAG,CAACgB,YAAY,CAACD,IAAAA,CAAAA;AAC7B,IAAA;AACF;;;;"}
@@ -39,6 +39,7 @@
39
39
  * @property {string} [STRAPI_HIGHLIGHT_ACTIVE_COLOR] Optional override for the highlight active color, injected by the admin.
40
40
  * @property {boolean} [STRAPI_DISABLE_STEGA_DECODING] Optional flag to disable steganography decoding, injected by the admin.
41
41
  * @property {() => void} [__strapi_previewCleanup] Optional cleanup function, injected by the admin.
42
+ * @property {number} [__strapi_previewRunId] Id of the latest run of this script in the page.
42
43
  */
43
44
 
44
45
  /**
@@ -50,6 +51,11 @@ function previewScript(config) {
50
51
  /** @type {typeof window & WindowExtensions} */
51
52
  const win = window;
52
53
 
54
+ // The script can be injected more than once in the same page (e.g. React StrictMode or a
55
+ // remount posting previewReady twice). Only the latest run may set up; see Orchestration.
56
+ const runId = (win.__strapi_previewRunId ?? 0) + 1;
57
+ win.__strapi_previewRunId = runId;
58
+
53
59
  /* -----------------------------------------------------------------------------------------------
54
60
  * Params
55
61
  * ---------------------------------------------------------------------------------------------*/
@@ -514,12 +520,6 @@ function previewScript(config) {
514
520
  };
515
521
 
516
522
  const createHighlightStyles = () => {
517
- const existingStyles = document.getElementById(HIGHLIGHT_STYLES_ID);
518
- // Remove existing styles to avoid duplicates
519
- if (existingStyles) {
520
- existingStyles.remove();
521
- }
522
-
523
523
  const styleElement = document.createElement('style');
524
524
  styleElement.id = HIGHLIGHT_STYLES_ID;
525
525
  styleElement.textContent = `
@@ -548,8 +548,6 @@ function previewScript(config) {
548
548
  };
549
549
 
550
550
  const createOverlaySystem = () => {
551
- // Clean up before creating a new overlay so we can safely call previewScript multiple times
552
- win.__strapi_previewCleanup?.();
553
551
  document.getElementById(OVERLAY_ID)?.remove();
554
552
 
555
553
  const overlay = document.createElement('div');
@@ -1916,6 +1914,7 @@ function previewScript(config) {
1916
1914
  };
1917
1915
 
1918
1916
  /**
1917
+ * @param {HTMLStyleElement} styleElement
1919
1918
  * @param {HTMLElement} overlay
1920
1919
  * @param {ReturnType<typeof setupObservers>} observers
1921
1920
  * @param {ReturnType<typeof setupScrollManagement>} scrollManager
@@ -1923,6 +1922,7 @@ function previewScript(config) {
1923
1922
  * @param {HighlightManager} highlightManager
1924
1923
  */
1925
1924
  const createCleanupSystem = (
1925
+ styleElement,
1926
1926
  overlay,
1927
1927
  observers,
1928
1928
  scrollManager,
@@ -1948,11 +1948,9 @@ function previewScript(config) {
1948
1948
  element.removeEventListener(type, handler);
1949
1949
  });
1950
1950
 
1951
- // Clean up CSS styles
1952
- const existingStyles = document.getElementById(HIGHLIGHT_STYLES_ID);
1953
- if (existingStyles) {
1954
- existingStyles.remove();
1955
- }
1951
+ // Remove this run's own element, not whatever currently holds the id: a newer run may
1952
+ // already have created a stylesheet with the same id.
1953
+ styleElement.remove();
1956
1954
 
1957
1955
  overlay.remove();
1958
1956
  };
@@ -1965,12 +1963,29 @@ function previewScript(config) {
1965
1963
  setupStegaDOMObserver()
1966
1964
  .catch(() => undefined)
1967
1965
  .then((stegaObserver) => {
1968
- createHighlightStyles();
1966
+ // A newer run started while this one was loading the stega decoder: let it win, and
1967
+ // don't leave this run's observer behind.
1968
+ if (runId !== win.__strapi_previewRunId) {
1969
+ stegaObserver?.disconnect();
1970
+ return;
1971
+ }
1972
+
1973
+ // Last run wins: tear the previous run down completely before setting up.
1974
+ win.__strapi_previewCleanup?.();
1975
+
1976
+ const styleElement = createHighlightStyles();
1969
1977
  const overlay = createOverlaySystem();
1970
1978
  const highlightManager = createHighlightManager(overlay);
1971
1979
  const observers = setupObservers(highlightManager, stegaObserver);
1972
1980
  const scrollManager = setupScrollManagement(highlightManager);
1973
1981
  const eventHandlers = setupEventHandlers(highlightManager);
1974
- createCleanupSystem(overlay, observers, scrollManager, eventHandlers, highlightManager);
1982
+ createCleanupSystem(
1983
+ styleElement,
1984
+ overlay,
1985
+ observers,
1986
+ scrollManager,
1987
+ eventHandlers,
1988
+ highlightManager
1989
+ );
1975
1990
  });
1976
1991
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var fp = require('lodash/fp');
3
+ var _ = require('lodash');
4
4
  var strapiUtils = require('@strapi/utils');
5
5
 
6
6
  const getPreviewUrlSchema = strapiUtils.z.object({
@@ -12,11 +12,11 @@ const getPreviewUrlSchema = strapiUtils.z.object({
12
12
  const validatePreviewUrl = async (strapi, uid, params)=>{
13
13
  // Validate the request parameters format
14
14
  strapiUtils.validateZodSchema(getPreviewUrlSchema)(params);
15
- const newParams = fp.pick([
15
+ const newParams = _.pick(params, [
16
16
  'documentId',
17
17
  'locale',
18
18
  'status'
19
- ], params);
19
+ ]);
20
20
  const model = strapi.getModel(uid);
21
21
  // If it's not a collection type or single type
22
22
  if (!model || model.modelType !== 'contentType') {
@@ -1 +1 @@
1
- {"version":3,"file":"preview.js","sources":["../../../../../server/src/preview/controllers/validation/preview.ts"],"sourcesContent":["import { pick } from 'lodash/fp';\n\nimport type { Core, UID } from '@strapi/types';\nimport { z, validateZodSchema, errors } from '@strapi/utils';\n\nimport { Preview } from '../../../../../shared/contracts';\nimport type { HandlerParams } from '../../services/preview-config';\n\nconst getPreviewUrlSchema = z.object({\n // Will be undefined for single types\n documentId: z.string().optional(),\n locale: z.string().nullable().optional(),\n status: z.string().optional(),\n});\n\nexport const validatePreviewUrl = async (\n strapi: Core.Strapi,\n uid: UID.ContentType,\n params: Preview.GetPreviewUrl.Request['query']\n): Promise<HandlerParams> => {\n // Validate the request parameters format\n validateZodSchema(getPreviewUrlSchema)(params);\n\n const newParams = pick(['documentId', 'locale', 'status'], params) as HandlerParams;\n const model = strapi.getModel(uid);\n\n // If it's not a collection type or single type\n if (!model || model.modelType !== 'contentType') {\n throw new errors.ValidationError('Invalid content type');\n }\n\n // Document id is not required for single types\n const isSingleType = model?.kind === 'singleType';\n if (!isSingleType && !params.documentId) {\n throw new errors.ValidationError('documentId is required for Collection Types');\n }\n\n // Fill the documentId if it's a single type\n if (isSingleType) {\n const doc = await strapi.documents(uid).findFirst();\n\n if (!doc) {\n throw new errors.NotFoundError('Document not found');\n }\n\n newParams.documentId = doc?.documentId;\n }\n\n /**\n * If status is not specified, follow the following rules:\n * - D&P disabled: status is considered published\n * - D&P enabled: status is considered draft\n */\n if (!newParams.status) {\n const isDPEnabled = model?.options?.draftAndPublish;\n newParams.status = isDPEnabled ? 'draft' : 'published';\n }\n\n return newParams;\n};\n"],"names":["getPreviewUrlSchema","z","object","documentId","string","optional","locale","nullable","status","validatePreviewUrl","strapi","uid","params","validateZodSchema","newParams","pick","model","getModel","modelType","errors","ValidationError","isSingleType","kind","doc","documents","findFirst","NotFoundError","isDPEnabled","options","draftAndPublish"],"mappings":";;;;;AAQA,MAAMA,mBAAAA,GAAsBC,aAAAA,CAAEC,MAAM,CAAC;;IAEnCC,UAAAA,EAAYF,aAAAA,CAAEG,MAAM,EAAA,CAAGC,QAAQ,EAAA;AAC/BC,IAAAA,MAAAA,EAAQL,aAAAA,CAAEG,MAAM,EAAA,CAAGG,QAAQ,GAAGF,QAAQ,EAAA;IACtCG,MAAAA,EAAQP,aAAAA,CAAEG,MAAM,EAAA,CAAGC,QAAQ;AAC7B,CAAA,CAAA;AAEO,MAAMI,kBAAAA,GAAqB,OAChCC,MAAAA,EACAC,GAAAA,EACAC,MAAAA,GAAAA;;AAGAC,IAAAA,6BAAAA,CAAkBb,mBAAAA,CAAAA,CAAqBY,MAAAA,CAAAA;AAEvC,IAAA,MAAME,YAAYC,OAAAA,CAAK;AAAC,QAAA,YAAA;AAAc,QAAA,QAAA;AAAU,QAAA;KAAS,EAAEH,MAAAA,CAAAA;IAC3D,MAAMI,KAAAA,GAAQN,MAAAA,CAAOO,QAAQ,CAACN,GAAAA,CAAAA;;AAG9B,IAAA,IAAI,CAACK,KAAAA,IAASA,KAAAA,CAAME,SAAS,KAAK,aAAA,EAAe;QAC/C,MAAM,IAAIC,kBAAAA,CAAOC,eAAe,CAAC,sBAAA,CAAA;AACnC,IAAA;;IAGA,MAAMC,YAAAA,GAAeL,OAAOM,IAAAA,KAAS,YAAA;AACrC,IAAA,IAAI,CAACD,YAAAA,IAAgB,CAACT,MAAAA,CAAOT,UAAU,EAAE;QACvC,MAAM,IAAIgB,kBAAAA,CAAOC,eAAe,CAAC,6CAAA,CAAA;AACnC,IAAA;;AAGA,IAAA,IAAIC,YAAAA,EAAc;AAChB,QAAA,MAAME,MAAM,MAAMb,MAAAA,CAAOc,SAAS,CAACb,KAAKc,SAAS,EAAA;AAEjD,QAAA,IAAI,CAACF,GAAAA,EAAK;YACR,MAAM,IAAIJ,kBAAAA,CAAOO,aAAa,CAAC,oBAAA,CAAA;AACjC,QAAA;QAEAZ,SAAAA,CAAUX,UAAU,GAAGoB,GAAAA,EAAKpB,UAAAA;AAC9B,IAAA;AAEA;;;;AAIC,MACD,IAAI,CAACW,SAAAA,CAAUN,MAAM,EAAE;QACrB,MAAMmB,WAAAA,GAAcX,OAAOY,OAAAA,EAASC,eAAAA;QACpCf,SAAAA,CAAUN,MAAM,GAAGmB,WAAAA,GAAc,OAAA,GAAU,WAAA;AAC7C,IAAA;IAEA,OAAOb,SAAAA;AACT;;;;"}
1
+ {"version":3,"file":"preview.js","sources":["../../../../../server/src/preview/controllers/validation/preview.ts"],"sourcesContent":["import { pick } from 'lodash';\n\nimport type { Core, UID } from '@strapi/types';\nimport { z, validateZodSchema, errors } from '@strapi/utils';\n\nimport { Preview } from '../../../../../shared/contracts';\nimport type { HandlerParams } from '../../services/preview-config';\n\nconst getPreviewUrlSchema = z.object({\n // Will be undefined for single types\n documentId: z.string().optional(),\n locale: z.string().nullable().optional(),\n status: z.string().optional(),\n});\n\nexport const validatePreviewUrl = async (\n strapi: Core.Strapi,\n uid: UID.ContentType,\n params: Preview.GetPreviewUrl.Request['query']\n): Promise<HandlerParams> => {\n // Validate the request parameters format\n validateZodSchema(getPreviewUrlSchema)(params);\n\n const newParams = pick(params, ['documentId', 'locale', 'status']) as HandlerParams;\n const model = strapi.getModel(uid);\n\n // If it's not a collection type or single type\n if (!model || model.modelType !== 'contentType') {\n throw new errors.ValidationError('Invalid content type');\n }\n\n // Document id is not required for single types\n const isSingleType = model?.kind === 'singleType';\n if (!isSingleType && !params.documentId) {\n throw new errors.ValidationError('documentId is required for Collection Types');\n }\n\n // Fill the documentId if it's a single type\n if (isSingleType) {\n const doc = await strapi.documents(uid).findFirst();\n\n if (!doc) {\n throw new errors.NotFoundError('Document not found');\n }\n\n newParams.documentId = doc?.documentId;\n }\n\n /**\n * If status is not specified, follow the following rules:\n * - D&P disabled: status is considered published\n * - D&P enabled: status is considered draft\n */\n if (!newParams.status) {\n const isDPEnabled = model?.options?.draftAndPublish;\n newParams.status = isDPEnabled ? 'draft' : 'published';\n }\n\n return newParams;\n};\n"],"names":["getPreviewUrlSchema","z","object","documentId","string","optional","locale","nullable","status","validatePreviewUrl","strapi","uid","params","validateZodSchema","newParams","pick","model","getModel","modelType","errors","ValidationError","isSingleType","kind","doc","documents","findFirst","NotFoundError","isDPEnabled","options","draftAndPublish"],"mappings":";;;;;AAQA,MAAMA,mBAAAA,GAAsBC,aAAAA,CAAEC,MAAM,CAAC;;IAEnCC,UAAAA,EAAYF,aAAAA,CAAEG,MAAM,EAAA,CAAGC,QAAQ,EAAA;AAC/BC,IAAAA,MAAAA,EAAQL,aAAAA,CAAEG,MAAM,EAAA,CAAGG,QAAQ,GAAGF,QAAQ,EAAA;IACtCG,MAAAA,EAAQP,aAAAA,CAAEG,MAAM,EAAA,CAAGC,QAAQ;AAC7B,CAAA,CAAA;AAEO,MAAMI,kBAAAA,GAAqB,OAChCC,MAAAA,EACAC,GAAAA,EACAC,MAAAA,GAAAA;;AAGAC,IAAAA,6BAAAA,CAAkBb,mBAAAA,CAAAA,CAAqBY,MAAAA,CAAAA;IAEvC,MAAME,SAAAA,GAAYC,OAAKH,MAAAA,EAAQ;AAAC,QAAA,YAAA;AAAc,QAAA,QAAA;AAAU,QAAA;AAAS,KAAA,CAAA;IACjE,MAAMI,KAAAA,GAAQN,MAAAA,CAAOO,QAAQ,CAACN,GAAAA,CAAAA;;AAG9B,IAAA,IAAI,CAACK,KAAAA,IAASA,KAAAA,CAAME,SAAS,KAAK,aAAA,EAAe;QAC/C,MAAM,IAAIC,kBAAAA,CAAOC,eAAe,CAAC,sBAAA,CAAA;AACnC,IAAA;;IAGA,MAAMC,YAAAA,GAAeL,OAAOM,IAAAA,KAAS,YAAA;AACrC,IAAA,IAAI,CAACD,YAAAA,IAAgB,CAACT,MAAAA,CAAOT,UAAU,EAAE;QACvC,MAAM,IAAIgB,kBAAAA,CAAOC,eAAe,CAAC,6CAAA,CAAA;AACnC,IAAA;;AAGA,IAAA,IAAIC,YAAAA,EAAc;AAChB,QAAA,MAAME,MAAM,MAAMb,MAAAA,CAAOc,SAAS,CAACb,KAAKc,SAAS,EAAA;AAEjD,QAAA,IAAI,CAACF,GAAAA,EAAK;YACR,MAAM,IAAIJ,kBAAAA,CAAOO,aAAa,CAAC,oBAAA,CAAA;AACjC,QAAA;QAEAZ,SAAAA,CAAUX,UAAU,GAAGoB,GAAAA,EAAKpB,UAAAA;AAC9B,IAAA;AAEA;;;;AAIC,MACD,IAAI,CAACW,SAAAA,CAAUN,MAAM,EAAE;QACrB,MAAMmB,WAAAA,GAAcX,OAAOY,OAAAA,EAASC,eAAAA;QACpCf,SAAAA,CAAUN,MAAM,GAAGmB,WAAAA,GAAc,OAAA,GAAU,WAAA;AAC7C,IAAA;IAEA,OAAOb,SAAAA;AACT;;;;"}
@@ -1,4 +1,4 @@
1
- import { pick } from 'lodash/fp';
1
+ import { pick } from 'lodash';
2
2
  import { z, validateZodSchema, errors } from '@strapi/utils';
3
3
 
4
4
  const getPreviewUrlSchema = z.object({
@@ -10,11 +10,11 @@ const getPreviewUrlSchema = z.object({
10
10
  const validatePreviewUrl = async (strapi, uid, params)=>{
11
11
  // Validate the request parameters format
12
12
  validateZodSchema(getPreviewUrlSchema)(params);
13
- const newParams = pick([
13
+ const newParams = pick(params, [
14
14
  'documentId',
15
15
  'locale',
16
16
  'status'
17
- ], params);
17
+ ]);
18
18
  const model = strapi.getModel(uid);
19
19
  // If it's not a collection type or single type
20
20
  if (!model || model.modelType !== 'contentType') {
@@ -1 +1 @@
1
- {"version":3,"file":"preview.mjs","sources":["../../../../../server/src/preview/controllers/validation/preview.ts"],"sourcesContent":["import { pick } from 'lodash/fp';\n\nimport type { Core, UID } from '@strapi/types';\nimport { z, validateZodSchema, errors } from '@strapi/utils';\n\nimport { Preview } from '../../../../../shared/contracts';\nimport type { HandlerParams } from '../../services/preview-config';\n\nconst getPreviewUrlSchema = z.object({\n // Will be undefined for single types\n documentId: z.string().optional(),\n locale: z.string().nullable().optional(),\n status: z.string().optional(),\n});\n\nexport const validatePreviewUrl = async (\n strapi: Core.Strapi,\n uid: UID.ContentType,\n params: Preview.GetPreviewUrl.Request['query']\n): Promise<HandlerParams> => {\n // Validate the request parameters format\n validateZodSchema(getPreviewUrlSchema)(params);\n\n const newParams = pick(['documentId', 'locale', 'status'], params) as HandlerParams;\n const model = strapi.getModel(uid);\n\n // If it's not a collection type or single type\n if (!model || model.modelType !== 'contentType') {\n throw new errors.ValidationError('Invalid content type');\n }\n\n // Document id is not required for single types\n const isSingleType = model?.kind === 'singleType';\n if (!isSingleType && !params.documentId) {\n throw new errors.ValidationError('documentId is required for Collection Types');\n }\n\n // Fill the documentId if it's a single type\n if (isSingleType) {\n const doc = await strapi.documents(uid).findFirst();\n\n if (!doc) {\n throw new errors.NotFoundError('Document not found');\n }\n\n newParams.documentId = doc?.documentId;\n }\n\n /**\n * If status is not specified, follow the following rules:\n * - D&P disabled: status is considered published\n * - D&P enabled: status is considered draft\n */\n if (!newParams.status) {\n const isDPEnabled = model?.options?.draftAndPublish;\n newParams.status = isDPEnabled ? 'draft' : 'published';\n }\n\n return newParams;\n};\n"],"names":["getPreviewUrlSchema","z","object","documentId","string","optional","locale","nullable","status","validatePreviewUrl","strapi","uid","params","validateZodSchema","newParams","pick","model","getModel","modelType","errors","ValidationError","isSingleType","kind","doc","documents","findFirst","NotFoundError","isDPEnabled","options","draftAndPublish"],"mappings":";;;AAQA,MAAMA,mBAAAA,GAAsBC,CAAAA,CAAEC,MAAM,CAAC;;IAEnCC,UAAAA,EAAYF,CAAAA,CAAEG,MAAM,EAAA,CAAGC,QAAQ,EAAA;AAC/BC,IAAAA,MAAAA,EAAQL,CAAAA,CAAEG,MAAM,EAAA,CAAGG,QAAQ,GAAGF,QAAQ,EAAA;IACtCG,MAAAA,EAAQP,CAAAA,CAAEG,MAAM,EAAA,CAAGC,QAAQ;AAC7B,CAAA,CAAA;AAEO,MAAMI,kBAAAA,GAAqB,OAChCC,MAAAA,EACAC,GAAAA,EACAC,MAAAA,GAAAA;;AAGAC,IAAAA,iBAAAA,CAAkBb,mBAAAA,CAAAA,CAAqBY,MAAAA,CAAAA;AAEvC,IAAA,MAAME,YAAYC,IAAAA,CAAK;AAAC,QAAA,YAAA;AAAc,QAAA,QAAA;AAAU,QAAA;KAAS,EAAEH,MAAAA,CAAAA;IAC3D,MAAMI,KAAAA,GAAQN,MAAAA,CAAOO,QAAQ,CAACN,GAAAA,CAAAA;;AAG9B,IAAA,IAAI,CAACK,KAAAA,IAASA,KAAAA,CAAME,SAAS,KAAK,aAAA,EAAe;QAC/C,MAAM,IAAIC,MAAAA,CAAOC,eAAe,CAAC,sBAAA,CAAA;AACnC,IAAA;;IAGA,MAAMC,YAAAA,GAAeL,OAAOM,IAAAA,KAAS,YAAA;AACrC,IAAA,IAAI,CAACD,YAAAA,IAAgB,CAACT,MAAAA,CAAOT,UAAU,EAAE;QACvC,MAAM,IAAIgB,MAAAA,CAAOC,eAAe,CAAC,6CAAA,CAAA;AACnC,IAAA;;AAGA,IAAA,IAAIC,YAAAA,EAAc;AAChB,QAAA,MAAME,MAAM,MAAMb,MAAAA,CAAOc,SAAS,CAACb,KAAKc,SAAS,EAAA;AAEjD,QAAA,IAAI,CAACF,GAAAA,EAAK;YACR,MAAM,IAAIJ,MAAAA,CAAOO,aAAa,CAAC,oBAAA,CAAA;AACjC,QAAA;QAEAZ,SAAAA,CAAUX,UAAU,GAAGoB,GAAAA,EAAKpB,UAAAA;AAC9B,IAAA;AAEA;;;;AAIC,MACD,IAAI,CAACW,SAAAA,CAAUN,MAAM,EAAE;QACrB,MAAMmB,WAAAA,GAAcX,OAAOY,OAAAA,EAASC,eAAAA;QACpCf,SAAAA,CAAUN,MAAM,GAAGmB,WAAAA,GAAc,OAAA,GAAU,WAAA;AAC7C,IAAA;IAEA,OAAOb,SAAAA;AACT;;;;"}
1
+ {"version":3,"file":"preview.mjs","sources":["../../../../../server/src/preview/controllers/validation/preview.ts"],"sourcesContent":["import { pick } from 'lodash';\n\nimport type { Core, UID } from '@strapi/types';\nimport { z, validateZodSchema, errors } from '@strapi/utils';\n\nimport { Preview } from '../../../../../shared/contracts';\nimport type { HandlerParams } from '../../services/preview-config';\n\nconst getPreviewUrlSchema = z.object({\n // Will be undefined for single types\n documentId: z.string().optional(),\n locale: z.string().nullable().optional(),\n status: z.string().optional(),\n});\n\nexport const validatePreviewUrl = async (\n strapi: Core.Strapi,\n uid: UID.ContentType,\n params: Preview.GetPreviewUrl.Request['query']\n): Promise<HandlerParams> => {\n // Validate the request parameters format\n validateZodSchema(getPreviewUrlSchema)(params);\n\n const newParams = pick(params, ['documentId', 'locale', 'status']) as HandlerParams;\n const model = strapi.getModel(uid);\n\n // If it's not a collection type or single type\n if (!model || model.modelType !== 'contentType') {\n throw new errors.ValidationError('Invalid content type');\n }\n\n // Document id is not required for single types\n const isSingleType = model?.kind === 'singleType';\n if (!isSingleType && !params.documentId) {\n throw new errors.ValidationError('documentId is required for Collection Types');\n }\n\n // Fill the documentId if it's a single type\n if (isSingleType) {\n const doc = await strapi.documents(uid).findFirst();\n\n if (!doc) {\n throw new errors.NotFoundError('Document not found');\n }\n\n newParams.documentId = doc?.documentId;\n }\n\n /**\n * If status is not specified, follow the following rules:\n * - D&P disabled: status is considered published\n * - D&P enabled: status is considered draft\n */\n if (!newParams.status) {\n const isDPEnabled = model?.options?.draftAndPublish;\n newParams.status = isDPEnabled ? 'draft' : 'published';\n }\n\n return newParams;\n};\n"],"names":["getPreviewUrlSchema","z","object","documentId","string","optional","locale","nullable","status","validatePreviewUrl","strapi","uid","params","validateZodSchema","newParams","pick","model","getModel","modelType","errors","ValidationError","isSingleType","kind","doc","documents","findFirst","NotFoundError","isDPEnabled","options","draftAndPublish"],"mappings":";;;AAQA,MAAMA,mBAAAA,GAAsBC,CAAAA,CAAEC,MAAM,CAAC;;IAEnCC,UAAAA,EAAYF,CAAAA,CAAEG,MAAM,EAAA,CAAGC,QAAQ,EAAA;AAC/BC,IAAAA,MAAAA,EAAQL,CAAAA,CAAEG,MAAM,EAAA,CAAGG,QAAQ,GAAGF,QAAQ,EAAA;IACtCG,MAAAA,EAAQP,CAAAA,CAAEG,MAAM,EAAA,CAAGC,QAAQ;AAC7B,CAAA,CAAA;AAEO,MAAMI,kBAAAA,GAAqB,OAChCC,MAAAA,EACAC,GAAAA,EACAC,MAAAA,GAAAA;;AAGAC,IAAAA,iBAAAA,CAAkBb,mBAAAA,CAAAA,CAAqBY,MAAAA,CAAAA;IAEvC,MAAME,SAAAA,GAAYC,KAAKH,MAAAA,EAAQ;AAAC,QAAA,YAAA;AAAc,QAAA,QAAA;AAAU,QAAA;AAAS,KAAA,CAAA;IACjE,MAAMI,KAAAA,GAAQN,MAAAA,CAAOO,QAAQ,CAACN,GAAAA,CAAAA;;AAG9B,IAAA,IAAI,CAACK,KAAAA,IAASA,KAAAA,CAAME,SAAS,KAAK,aAAA,EAAe;QAC/C,MAAM,IAAIC,MAAAA,CAAOC,eAAe,CAAC,sBAAA,CAAA;AACnC,IAAA;;IAGA,MAAMC,YAAAA,GAAeL,OAAOM,IAAAA,KAAS,YAAA;AACrC,IAAA,IAAI,CAACD,YAAAA,IAAgB,CAACT,MAAAA,CAAOT,UAAU,EAAE;QACvC,MAAM,IAAIgB,MAAAA,CAAOC,eAAe,CAAC,6CAAA,CAAA;AACnC,IAAA;;AAGA,IAAA,IAAIC,YAAAA,EAAc;AAChB,QAAA,MAAME,MAAM,MAAMb,MAAAA,CAAOc,SAAS,CAACb,KAAKc,SAAS,EAAA;AAEjD,QAAA,IAAI,CAACF,GAAAA,EAAK;YACR,MAAM,IAAIJ,MAAAA,CAAOO,aAAa,CAAC,oBAAA,CAAA;AACjC,QAAA;QAEAZ,SAAAA,CAAUX,UAAU,GAAGoB,GAAAA,EAAKpB,UAAAA;AAC9B,IAAA;AAEA;;;;AAIC,MACD,IAAI,CAACW,SAAAA,CAAUN,MAAM,EAAE;QACrB,MAAMmB,WAAAA,GAAcX,OAAOY,OAAAA,EAASC,eAAAA;QACpCf,SAAAA,CAAUN,MAAM,GAAGmB,WAAAA,GAAc,OAAA,GAAU,WAAA;AAC7C,IAAA;IAEA,OAAOb,SAAAA;AACT;;;;"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var fp = require('lodash/fp');
3
+ var _ = require('lodash');
4
4
  var index = require('../utils/index.js');
5
5
  var store = require('./utils/store.js');
6
6
  var configuration = require('./configuration.js');
@@ -12,7 +12,7 @@ const configurationService = configuration({
12
12
  prefix: STORE_KEY_PREFIX,
13
13
  getModels () {
14
14
  const { toContentManagerModel } = index.getService('data-mapper');
15
- return fp.mapValues(toContentManagerModel, strapi.components);
15
+ return _.mapValues(strapi.components, (value)=>toContentManagerModel(value));
16
16
  }
17
17
  });
18
18
  var components = (({ strapi: strapi1 })=>({
@@ -23,7 +23,7 @@ var components = (({ strapi: strapi1 })=>({
23
23
  findComponent (uid) {
24
24
  const { toContentManagerModel } = index.getService('data-mapper');
25
25
  const component = strapi1.components[uid];
26
- return fp.isNil(component) ? component : toContentManagerModel(component);
26
+ return component == null ? component : toContentManagerModel(component);
27
27
  },
28
28
  async findConfiguration (component) {
29
29
  const configuration = await configurationService.getConfiguration(component.uid);
@@ -1 +1 @@
1
- {"version":3,"file":"components.js","sources":["../../../server/src/services/components.ts"],"sourcesContent":["import { isNil, mapValues } from 'lodash/fp';\n\nimport type { UID, Struct, Core } from '@strapi/types';\nimport type { Configuration } from '../../../shared/contracts/content-types';\nimport type { ConfigurationUpdate } from './configuration';\n\nimport { getService } from '../utils';\nimport storeUtils from './utils/store';\nimport createConfigurationService from './configuration';\n\nconst STORE_KEY_PREFIX = 'components';\n\nconst configurationService = createConfigurationService({\n storeUtils,\n isComponent: true,\n prefix: STORE_KEY_PREFIX,\n getModels() {\n const { toContentManagerModel } = getService('data-mapper');\n\n return mapValues(toContentManagerModel, strapi.components);\n },\n});\n\nexport default ({ strapi }: { strapi: Core.Strapi }) => ({\n findAllComponents() {\n const { toContentManagerModel } = getService('data-mapper');\n\n return Object.values(strapi.components).map(toContentManagerModel);\n },\n\n findComponent(uid: UID.Component) {\n const { toContentManagerModel } = getService('data-mapper');\n\n const component = strapi.components[uid];\n\n return isNil(component) ? component : toContentManagerModel(component);\n },\n\n async findConfiguration(component: Struct.ComponentSchema) {\n const configuration: Configuration = await configurationService.getConfiguration(component.uid);\n\n return {\n uid: component.uid,\n category: component.category,\n ...configuration,\n };\n },\n\n async updateConfiguration(\n component: Struct.ComponentSchema,\n newConfiguration: ConfigurationUpdate\n ) {\n await configurationService.setConfiguration(component.uid, newConfiguration);\n\n return this.findConfiguration(component);\n },\n\n /**\n * Batch load component configurations.\n *\n * Collects all component UIDs upfront, then loads configurations in a single\n * batch query instead of sequential queries per component.\n */\n async findComponentsConfigurations(model: Struct.ComponentSchema) {\n // Cache on request state so the same request can reuse configs\n const requestState = strapi.requestContext?.get?.()?.state as\n | { __componentsConfigurationsCache?: Map<string, Record<string, Configuration>> }\n | undefined;\n const requestCache = requestState?.__componentsConfigurationsCache;\n\n const componentUids = new Set<UID.Component>();\n\n const collectComponentUids = (schema: Struct.ComponentSchema) => {\n for (const key of Object.keys(schema.attributes)) {\n const attribute = schema.attributes[key];\n\n if (attribute.type === 'component') {\n const uid = attribute.component;\n if (!componentUids.has(uid)) {\n componentUids.add(uid);\n const nestedComponent = this.findComponent(uid);\n if (nestedComponent) {\n collectComponentUids(nestedComponent);\n }\n }\n }\n\n if (attribute.type === 'dynamiczone') {\n for (const uid of attribute.components) {\n if (!componentUids.has(uid)) {\n componentUids.add(uid);\n const nestedComponent = this.findComponent(uid);\n if (nestedComponent) {\n collectComponentUids(nestedComponent);\n }\n }\n }\n }\n }\n };\n\n collectComponentUids(model);\n\n if (componentUids.size === 0) {\n return {};\n }\n\n // Key format must match configuration.ts uidToStoreKey: `${prefix}::${uid}`\n const uidsArray = Array.from(componentUids);\n const prefixedKeys = uidsArray.map((uid) => `components::${uid}`);\n const cacheKey = prefixedKeys.slice().sort().join('|');\n if (requestCache?.has(cacheKey)) {\n return requestCache.get(cacheKey)! as Record<\n string,\n Configuration & { category: string; isComponent: boolean }\n >;\n }\n\n const configs = await storeUtils.getModelConfigurations(prefixedKeys);\n\n const componentsMap: Record<\n string,\n Configuration & { category: string; isComponent: boolean }\n > = {};\n\n for (const uid of uidsArray) {\n const component = this.findComponent(uid);\n const configKey = `components::${uid}`;\n // Fallback must include proper layouts structure for frontend compatibility\n const configuration = configs[configKey] || {\n settings: {},\n metadatas: {},\n layouts: { list: [], edit: [] },\n };\n\n componentsMap[uid] = {\n uid: component.uid,\n category: component.category,\n ...configuration,\n };\n }\n\n if (requestState) {\n if (!requestState.__componentsConfigurationsCache) {\n requestState.__componentsConfigurationsCache = new Map();\n }\n requestState.__componentsConfigurationsCache.set(cacheKey, componentsMap);\n }\n\n return componentsMap;\n },\n\n syncConfigurations() {\n return configurationService.syncConfigurations();\n },\n});\n"],"names":["STORE_KEY_PREFIX","configurationService","createConfigurationService","storeUtils","isComponent","prefix","getModels","toContentManagerModel","getService","mapValues","strapi","components","findAllComponents","Object","values","map","findComponent","uid","component","isNil","findConfiguration","configuration","getConfiguration","category","updateConfiguration","newConfiguration","setConfiguration","findComponentsConfigurations","model","requestState","requestContext","get","state","requestCache","__componentsConfigurationsCache","componentUids","Set","collectComponentUids","schema","key","keys","attributes","attribute","type","has","add","nestedComponent","size","uidsArray","Array","from","prefixedKeys","cacheKey","slice","sort","join","configs","getModelConfigurations","componentsMap","configKey","settings","metadatas","layouts","list","edit","Map","set","syncConfigurations"],"mappings":"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"}
1
+ {"version":3,"file":"components.js","sources":["../../../server/src/services/components.ts"],"sourcesContent":["import { mapValues } from 'lodash';\n\nimport type { UID, Struct, Core } from '@strapi/types';\nimport type { Configuration } from '../../../shared/contracts/content-types';\nimport type { ConfigurationUpdate } from './configuration';\n\nimport { getService } from '../utils';\nimport storeUtils from './utils/store';\nimport createConfigurationService from './configuration';\n\nconst STORE_KEY_PREFIX = 'components';\n\nconst configurationService = createConfigurationService({\n storeUtils,\n isComponent: true,\n prefix: STORE_KEY_PREFIX,\n getModels() {\n const { toContentManagerModel } = getService('data-mapper');\n\n return mapValues(strapi.components, (value) => toContentManagerModel(value));\n },\n});\n\nexport default ({ strapi }: { strapi: Core.Strapi }) => ({\n findAllComponents() {\n const { toContentManagerModel } = getService('data-mapper');\n\n return Object.values(strapi.components).map(toContentManagerModel);\n },\n\n findComponent(uid: UID.Component) {\n const { toContentManagerModel } = getService('data-mapper');\n\n const component = strapi.components[uid];\n\n return component == null ? component : toContentManagerModel(component);\n },\n\n async findConfiguration(component: Struct.ComponentSchema) {\n const configuration: Configuration = await configurationService.getConfiguration(component.uid);\n\n return {\n uid: component.uid,\n category: component.category,\n ...configuration,\n };\n },\n\n async updateConfiguration(\n component: Struct.ComponentSchema,\n newConfiguration: ConfigurationUpdate\n ) {\n await configurationService.setConfiguration(component.uid, newConfiguration);\n\n return this.findConfiguration(component);\n },\n\n /**\n * Batch load component configurations.\n *\n * Collects all component UIDs upfront, then loads configurations in a single\n * batch query instead of sequential queries per component.\n */\n async findComponentsConfigurations(model: Struct.ComponentSchema) {\n // Cache on request state so the same request can reuse configs\n const requestState = strapi.requestContext?.get?.()?.state as\n | { __componentsConfigurationsCache?: Map<string, Record<string, Configuration>> }\n | undefined;\n const requestCache = requestState?.__componentsConfigurationsCache;\n\n const componentUids = new Set<UID.Component>();\n\n const collectComponentUids = (schema: Struct.ComponentSchema) => {\n for (const key of Object.keys(schema.attributes)) {\n const attribute = schema.attributes[key];\n\n if (attribute.type === 'component') {\n const uid = attribute.component;\n if (!componentUids.has(uid)) {\n componentUids.add(uid);\n const nestedComponent = this.findComponent(uid);\n if (nestedComponent) {\n collectComponentUids(nestedComponent);\n }\n }\n }\n\n if (attribute.type === 'dynamiczone') {\n for (const uid of attribute.components) {\n if (!componentUids.has(uid)) {\n componentUids.add(uid);\n const nestedComponent = this.findComponent(uid);\n if (nestedComponent) {\n collectComponentUids(nestedComponent);\n }\n }\n }\n }\n }\n };\n\n collectComponentUids(model);\n\n if (componentUids.size === 0) {\n return {};\n }\n\n // Key format must match configuration.ts uidToStoreKey: `${prefix}::${uid}`\n const uidsArray = Array.from(componentUids);\n const prefixedKeys = uidsArray.map((uid) => `components::${uid}`);\n const cacheKey = prefixedKeys.slice().sort().join('|');\n if (requestCache?.has(cacheKey)) {\n return requestCache.get(cacheKey)! as Record<\n string,\n Configuration & { category: string; isComponent: boolean }\n >;\n }\n\n const configs = await storeUtils.getModelConfigurations(prefixedKeys);\n\n const componentsMap: Record<\n string,\n Configuration & { category: string; isComponent: boolean }\n > = {};\n\n for (const uid of uidsArray) {\n const component = this.findComponent(uid);\n const configKey = `components::${uid}`;\n // Fallback must include proper layouts structure for frontend compatibility\n const configuration = configs[configKey] || {\n settings: {},\n metadatas: {},\n layouts: { list: [], edit: [] },\n };\n\n componentsMap[uid] = {\n uid: component.uid,\n category: component.category,\n ...configuration,\n };\n }\n\n if (requestState) {\n if (!requestState.__componentsConfigurationsCache) {\n requestState.__componentsConfigurationsCache = new Map();\n }\n requestState.__componentsConfigurationsCache.set(cacheKey, componentsMap);\n }\n\n return componentsMap;\n },\n\n syncConfigurations() {\n return configurationService.syncConfigurations();\n },\n});\n"],"names":["STORE_KEY_PREFIX","configurationService","createConfigurationService","storeUtils","isComponent","prefix","getModels","toContentManagerModel","getService","mapValues","strapi","components","value","findAllComponents","Object","values","map","findComponent","uid","component","findConfiguration","configuration","getConfiguration","category","updateConfiguration","newConfiguration","setConfiguration","findComponentsConfigurations","model","requestState","requestContext","get","state","requestCache","__componentsConfigurationsCache","componentUids","Set","collectComponentUids","schema","key","keys","attributes","attribute","type","has","add","nestedComponent","size","uidsArray","Array","from","prefixedKeys","cacheKey","slice","sort","join","configs","getModelConfigurations","componentsMap","configKey","settings","metadatas","layouts","list","edit","Map","set","syncConfigurations"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { isNil, mapValues } from 'lodash/fp';
1
+ import { mapValues } from 'lodash';
2
2
  import { getService } from '../utils/index.mjs';
3
3
  import storeUtils from './utils/store.mjs';
4
4
  import createConfigurationService from './configuration.mjs';
@@ -10,7 +10,7 @@ const configurationService = createConfigurationService({
10
10
  prefix: STORE_KEY_PREFIX,
11
11
  getModels () {
12
12
  const { toContentManagerModel } = getService('data-mapper');
13
- return mapValues(toContentManagerModel, strapi.components);
13
+ return mapValues(strapi.components, (value)=>toContentManagerModel(value));
14
14
  }
15
15
  });
16
16
  var components = (({ strapi: strapi1 })=>({
@@ -21,7 +21,7 @@ var components = (({ strapi: strapi1 })=>({
21
21
  findComponent (uid) {
22
22
  const { toContentManagerModel } = getService('data-mapper');
23
23
  const component = strapi1.components[uid];
24
- return isNil(component) ? component : toContentManagerModel(component);
24
+ return component == null ? component : toContentManagerModel(component);
25
25
  },
26
26
  async findConfiguration (component) {
27
27
  const configuration = await configurationService.getConfiguration(component.uid);
@@ -1 +1 @@
1
- {"version":3,"file":"components.mjs","sources":["../../../server/src/services/components.ts"],"sourcesContent":["import { isNil, mapValues } from 'lodash/fp';\n\nimport type { UID, Struct, Core } from '@strapi/types';\nimport type { Configuration } from '../../../shared/contracts/content-types';\nimport type { ConfigurationUpdate } from './configuration';\n\nimport { getService } from '../utils';\nimport storeUtils from './utils/store';\nimport createConfigurationService from './configuration';\n\nconst STORE_KEY_PREFIX = 'components';\n\nconst configurationService = createConfigurationService({\n storeUtils,\n isComponent: true,\n prefix: STORE_KEY_PREFIX,\n getModels() {\n const { toContentManagerModel } = getService('data-mapper');\n\n return mapValues(toContentManagerModel, strapi.components);\n },\n});\n\nexport default ({ strapi }: { strapi: Core.Strapi }) => ({\n findAllComponents() {\n const { toContentManagerModel } = getService('data-mapper');\n\n return Object.values(strapi.components).map(toContentManagerModel);\n },\n\n findComponent(uid: UID.Component) {\n const { toContentManagerModel } = getService('data-mapper');\n\n const component = strapi.components[uid];\n\n return isNil(component) ? component : toContentManagerModel(component);\n },\n\n async findConfiguration(component: Struct.ComponentSchema) {\n const configuration: Configuration = await configurationService.getConfiguration(component.uid);\n\n return {\n uid: component.uid,\n category: component.category,\n ...configuration,\n };\n },\n\n async updateConfiguration(\n component: Struct.ComponentSchema,\n newConfiguration: ConfigurationUpdate\n ) {\n await configurationService.setConfiguration(component.uid, newConfiguration);\n\n return this.findConfiguration(component);\n },\n\n /**\n * Batch load component configurations.\n *\n * Collects all component UIDs upfront, then loads configurations in a single\n * batch query instead of sequential queries per component.\n */\n async findComponentsConfigurations(model: Struct.ComponentSchema) {\n // Cache on request state so the same request can reuse configs\n const requestState = strapi.requestContext?.get?.()?.state as\n | { __componentsConfigurationsCache?: Map<string, Record<string, Configuration>> }\n | undefined;\n const requestCache = requestState?.__componentsConfigurationsCache;\n\n const componentUids = new Set<UID.Component>();\n\n const collectComponentUids = (schema: Struct.ComponentSchema) => {\n for (const key of Object.keys(schema.attributes)) {\n const attribute = schema.attributes[key];\n\n if (attribute.type === 'component') {\n const uid = attribute.component;\n if (!componentUids.has(uid)) {\n componentUids.add(uid);\n const nestedComponent = this.findComponent(uid);\n if (nestedComponent) {\n collectComponentUids(nestedComponent);\n }\n }\n }\n\n if (attribute.type === 'dynamiczone') {\n for (const uid of attribute.components) {\n if (!componentUids.has(uid)) {\n componentUids.add(uid);\n const nestedComponent = this.findComponent(uid);\n if (nestedComponent) {\n collectComponentUids(nestedComponent);\n }\n }\n }\n }\n }\n };\n\n collectComponentUids(model);\n\n if (componentUids.size === 0) {\n return {};\n }\n\n // Key format must match configuration.ts uidToStoreKey: `${prefix}::${uid}`\n const uidsArray = Array.from(componentUids);\n const prefixedKeys = uidsArray.map((uid) => `components::${uid}`);\n const cacheKey = prefixedKeys.slice().sort().join('|');\n if (requestCache?.has(cacheKey)) {\n return requestCache.get(cacheKey)! as Record<\n string,\n Configuration & { category: string; isComponent: boolean }\n >;\n }\n\n const configs = await storeUtils.getModelConfigurations(prefixedKeys);\n\n const componentsMap: Record<\n string,\n Configuration & { category: string; isComponent: boolean }\n > = {};\n\n for (const uid of uidsArray) {\n const component = this.findComponent(uid);\n const configKey = `components::${uid}`;\n // Fallback must include proper layouts structure for frontend compatibility\n const configuration = configs[configKey] || {\n settings: {},\n metadatas: {},\n layouts: { list: [], edit: [] },\n };\n\n componentsMap[uid] = {\n uid: component.uid,\n category: component.category,\n ...configuration,\n };\n }\n\n if (requestState) {\n if (!requestState.__componentsConfigurationsCache) {\n requestState.__componentsConfigurationsCache = new Map();\n }\n requestState.__componentsConfigurationsCache.set(cacheKey, componentsMap);\n }\n\n return componentsMap;\n },\n\n syncConfigurations() {\n return configurationService.syncConfigurations();\n },\n});\n"],"names":["STORE_KEY_PREFIX","configurationService","createConfigurationService","storeUtils","isComponent","prefix","getModels","toContentManagerModel","getService","mapValues","strapi","components","findAllComponents","Object","values","map","findComponent","uid","component","isNil","findConfiguration","configuration","getConfiguration","category","updateConfiguration","newConfiguration","setConfiguration","findComponentsConfigurations","model","requestState","requestContext","get","state","requestCache","__componentsConfigurationsCache","componentUids","Set","collectComponentUids","schema","key","keys","attributes","attribute","type","has","add","nestedComponent","size","uidsArray","Array","from","prefixedKeys","cacheKey","slice","sort","join","configs","getModelConfigurations","componentsMap","configKey","settings","metadatas","layouts","list","edit","Map","set","syncConfigurations"],"mappings":"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"}
1
+ {"version":3,"file":"components.mjs","sources":["../../../server/src/services/components.ts"],"sourcesContent":["import { mapValues } from 'lodash';\n\nimport type { UID, Struct, Core } from '@strapi/types';\nimport type { Configuration } from '../../../shared/contracts/content-types';\nimport type { ConfigurationUpdate } from './configuration';\n\nimport { getService } from '../utils';\nimport storeUtils from './utils/store';\nimport createConfigurationService from './configuration';\n\nconst STORE_KEY_PREFIX = 'components';\n\nconst configurationService = createConfigurationService({\n storeUtils,\n isComponent: true,\n prefix: STORE_KEY_PREFIX,\n getModels() {\n const { toContentManagerModel } = getService('data-mapper');\n\n return mapValues(strapi.components, (value) => toContentManagerModel(value));\n },\n});\n\nexport default ({ strapi }: { strapi: Core.Strapi }) => ({\n findAllComponents() {\n const { toContentManagerModel } = getService('data-mapper');\n\n return Object.values(strapi.components).map(toContentManagerModel);\n },\n\n findComponent(uid: UID.Component) {\n const { toContentManagerModel } = getService('data-mapper');\n\n const component = strapi.components[uid];\n\n return component == null ? component : toContentManagerModel(component);\n },\n\n async findConfiguration(component: Struct.ComponentSchema) {\n const configuration: Configuration = await configurationService.getConfiguration(component.uid);\n\n return {\n uid: component.uid,\n category: component.category,\n ...configuration,\n };\n },\n\n async updateConfiguration(\n component: Struct.ComponentSchema,\n newConfiguration: ConfigurationUpdate\n ) {\n await configurationService.setConfiguration(component.uid, newConfiguration);\n\n return this.findConfiguration(component);\n },\n\n /**\n * Batch load component configurations.\n *\n * Collects all component UIDs upfront, then loads configurations in a single\n * batch query instead of sequential queries per component.\n */\n async findComponentsConfigurations(model: Struct.ComponentSchema) {\n // Cache on request state so the same request can reuse configs\n const requestState = strapi.requestContext?.get?.()?.state as\n | { __componentsConfigurationsCache?: Map<string, Record<string, Configuration>> }\n | undefined;\n const requestCache = requestState?.__componentsConfigurationsCache;\n\n const componentUids = new Set<UID.Component>();\n\n const collectComponentUids = (schema: Struct.ComponentSchema) => {\n for (const key of Object.keys(schema.attributes)) {\n const attribute = schema.attributes[key];\n\n if (attribute.type === 'component') {\n const uid = attribute.component;\n if (!componentUids.has(uid)) {\n componentUids.add(uid);\n const nestedComponent = this.findComponent(uid);\n if (nestedComponent) {\n collectComponentUids(nestedComponent);\n }\n }\n }\n\n if (attribute.type === 'dynamiczone') {\n for (const uid of attribute.components) {\n if (!componentUids.has(uid)) {\n componentUids.add(uid);\n const nestedComponent = this.findComponent(uid);\n if (nestedComponent) {\n collectComponentUids(nestedComponent);\n }\n }\n }\n }\n }\n };\n\n collectComponentUids(model);\n\n if (componentUids.size === 0) {\n return {};\n }\n\n // Key format must match configuration.ts uidToStoreKey: `${prefix}::${uid}`\n const uidsArray = Array.from(componentUids);\n const prefixedKeys = uidsArray.map((uid) => `components::${uid}`);\n const cacheKey = prefixedKeys.slice().sort().join('|');\n if (requestCache?.has(cacheKey)) {\n return requestCache.get(cacheKey)! as Record<\n string,\n Configuration & { category: string; isComponent: boolean }\n >;\n }\n\n const configs = await storeUtils.getModelConfigurations(prefixedKeys);\n\n const componentsMap: Record<\n string,\n Configuration & { category: string; isComponent: boolean }\n > = {};\n\n for (const uid of uidsArray) {\n const component = this.findComponent(uid);\n const configKey = `components::${uid}`;\n // Fallback must include proper layouts structure for frontend compatibility\n const configuration = configs[configKey] || {\n settings: {},\n metadatas: {},\n layouts: { list: [], edit: [] },\n };\n\n componentsMap[uid] = {\n uid: component.uid,\n category: component.category,\n ...configuration,\n };\n }\n\n if (requestState) {\n if (!requestState.__componentsConfigurationsCache) {\n requestState.__componentsConfigurationsCache = new Map();\n }\n requestState.__componentsConfigurationsCache.set(cacheKey, componentsMap);\n }\n\n return componentsMap;\n },\n\n syncConfigurations() {\n return configurationService.syncConfigurations();\n },\n});\n"],"names":["STORE_KEY_PREFIX","configurationService","createConfigurationService","storeUtils","isComponent","prefix","getModels","toContentManagerModel","getService","mapValues","strapi","components","value","findAllComponents","Object","values","map","findComponent","uid","component","findConfiguration","configuration","getConfiguration","category","updateConfiguration","newConfiguration","setConfiguration","findComponentsConfigurations","model","requestState","requestContext","get","state","requestCache","__componentsConfigurationsCache","componentUids","Set","collectComponentUids","schema","key","keys","attributes","attribute","type","has","add","nestedComponent","size","uidsArray","Array","from","prefixedKeys","cacheKey","slice","sort","join","configs","getModelConfigurations","componentsMap","configKey","settings","metadatas","layouts","list","edit","Map","set","syncConfigurations"],"mappings":"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"}