Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1f808d079 | ||
|
|
a8a632e1b0 | ||
|
|
a695ec7319 | ||
|
|
c6730335ef | ||
|
|
247b849759 | ||
|
|
0dee178ded | ||
|
|
9359f26788 | ||
|
|
5630765215 | ||
|
|
ed4a78b109 | ||
|
|
9acb22e2f9 | ||
|
|
6a8361d710 | ||
|
|
502ffee31f | ||
|
|
5a322230ae | ||
|
|
f919c288b2 | ||
|
|
21b948a4f8 | ||
|
|
d04271f942 |
Vendored
+1
@@ -7,3 +7,4 @@
|
|||||||
*/
|
*/
|
||||||
export { RenderBlocks } from '../modules/blocks/index.js';
|
export { RenderBlocks } from '../modules/blocks/index.js';
|
||||||
export type { BlockComponentMap, BlockData, EnhanceProps, RenderBlocksProps, } from '../modules/blocks/index.js';
|
export type { BlockComponentMap, BlockData, EnhanceProps, RenderBlocksProps, } from '../modules/blocks/index.js';
|
||||||
|
export { MediaPreconnect } from '../modules/storage/MediaPreconnect.js';
|
||||||
|
|||||||
Vendored
+1
@@ -5,5 +5,6 @@
|
|||||||
* lives here rather than in the main package entry to keep React out of the
|
* lives here rather than in the main package entry to keep React out of the
|
||||||
* server-config bundle.
|
* server-config bundle.
|
||||||
*/ export { RenderBlocks } from '../modules/blocks/index.js';
|
*/ export { RenderBlocks } from '../modules/blocks/index.js';
|
||||||
|
export { MediaPreconnect } from '../modules/storage/MediaPreconnect.js';
|
||||||
|
|
||||||
//# sourceMappingURL=rsc.js.map
|
//# sourceMappingURL=rsc.js.map
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"sources":["../../src/exports/rsc.ts"],"sourcesContent":["/**\n * Entry point: ipal-kit/rsc\n *\n * Server-component exports. RenderBlocks is a React Server Component, so it\n * lives here rather than in the main package entry to keep React out of the\n * server-config bundle.\n */\nexport { RenderBlocks } from '../modules/blocks/index.js'\nexport type {\n BlockComponentMap,\n BlockData,\n EnhanceProps,\n RenderBlocksProps,\n} from '../modules/blocks/index.js'\n"],"names":["RenderBlocks"],"mappings":"AAAA;;;;;;CAMC,GACD,SAASA,YAAY,QAAQ,6BAA4B"}
|
{"version":3,"sources":["../../src/exports/rsc.ts"],"sourcesContent":["/**\n * Entry point: ipal-kit/rsc\n *\n * Server-component exports. RenderBlocks is a React Server Component, so it\n * lives here rather than in the main package entry to keep React out of the\n * server-config bundle.\n */\nexport { RenderBlocks } from '../modules/blocks/index.js'\nexport type {\n BlockComponentMap,\n BlockData,\n EnhanceProps,\n RenderBlocksProps,\n} from '../modules/blocks/index.js'\nexport { MediaPreconnect } from '../modules/storage/MediaPreconnect.js'\n"],"names":["RenderBlocks","MediaPreconnect"],"mappings":"AAAA;;;;;;CAMC,GACD,SAASA,YAAY,QAAQ,6BAA4B;AAOzD,SAASC,eAAe,QAAQ,wCAAuC"}
|
||||||
+4
@@ -14,6 +14,10 @@ type BuildSiteIntegrationsArgs = {
|
|||||||
* impossible to enter.)
|
* impossible to enter.)
|
||||||
*
|
*
|
||||||
* Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).
|
* Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).
|
||||||
|
*
|
||||||
|
* Note: R2 storage credentials are NOT here — storage is infrastructure and
|
||||||
|
* binds at boot, so its config lives in .env (R2_BUCKET, R2_ENDPOINT, ...),
|
||||||
|
* consumed by buildR2Storage. See docs/storage.md.
|
||||||
*/
|
*/
|
||||||
export declare function buildSiteIntegrations({ additionalFields, }?: BuildSiteIntegrationsArgs): GlobalConfig;
|
export declare function buildSiteIntegrations({ additionalFields, }?: BuildSiteIntegrationsArgs): GlobalConfig;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
+4
-5
@@ -1,7 +1,6 @@
|
|||||||
import { isAdmin } from '../../modules/access/index.js';
|
import { isAdmin } from '../../modules/access/index.js';
|
||||||
import { analyticsFields } from './fields/analytics.js';
|
import { analyticsFields } from './fields/analytics.js';
|
||||||
import { smtpFields } from './fields/smtp.js';
|
import { smtpFields } from './fields/smtp.js';
|
||||||
import { storageFields } from './fields/storage.js';
|
|
||||||
import { turnstileFields } from './fields/turnstile.js';
|
import { turnstileFields } from './fields/turnstile.js';
|
||||||
/**
|
/**
|
||||||
* Builds the SiteIntegrations global.
|
* Builds the SiteIntegrations global.
|
||||||
@@ -14,6 +13,10 @@ import { turnstileFields } from './fields/turnstile.js';
|
|||||||
* impossible to enter.)
|
* impossible to enter.)
|
||||||
*
|
*
|
||||||
* Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).
|
* Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).
|
||||||
|
*
|
||||||
|
* Note: R2 storage credentials are NOT here — storage is infrastructure and
|
||||||
|
* binds at boot, so its config lives in .env (R2_BUCKET, R2_ENDPOINT, ...),
|
||||||
|
* consumed by buildR2Storage. See docs/storage.md.
|
||||||
*/ export function buildSiteIntegrations({ additionalFields } = {}) {
|
*/ export function buildSiteIntegrations({ additionalFields } = {}) {
|
||||||
return {
|
return {
|
||||||
slug: 'site-integrations',
|
slug: 'site-integrations',
|
||||||
@@ -42,10 +45,6 @@ import { turnstileFields } from './fields/turnstile.js';
|
|||||||
fields: smtpFields,
|
fields: smtpFields,
|
||||||
label: 'SMTP'
|
label: 'SMTP'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
fields: storageFields,
|
|
||||||
label: 'Storage'
|
|
||||||
},
|
|
||||||
...additionalFields?.length ? [
|
...additionalFields?.length ? [
|
||||||
{
|
{
|
||||||
fields: additionalFields,
|
fields: additionalFields,
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"sources":["../../../src/globals/SiteIntegrations/index.ts"],"sourcesContent":["import type { Field, GlobalConfig } from 'payload'\n\nimport { isAdmin } from '../../modules/access/index.js'\nimport { analyticsFields } from './fields/analytics.js'\nimport { smtpFields } from './fields/smtp.js'\nimport { storageFields } from './fields/storage.js'\nimport { turnstileFields } from './fields/turnstile.js'\n\ntype BuildSiteIntegrationsArgs = {\n /** Extra fields injected by the client project */\n additionalFields?: Field[]\n}\n\n/**\n * Builds the SiteIntegrations global.\n *\n * Holds third-party service credentials. Access is enforced at the global\n * level — the whole global requires an authenticated user — so secrets stay\n * out of anonymous API responses while remaining editable in the admin panel\n * and readable via the server-side Local API. (Field-level read:false was\n * avoided because it also hides fields from the admin UI, making them\n * impossible to enter.)\n *\n * Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).\n */\nexport function buildSiteIntegrations({\n additionalFields,\n}: BuildSiteIntegrationsArgs = {}): GlobalConfig {\n return {\n slug: 'site-integrations',\n access: {\n // Admin-only — secrets live here. Anonymous and non-admin users get\n // nothing through the API; admins read/edit in the panel and via Local API.\n read: ({ req: { user } }) => isAdmin(user),\n update: ({ req: { user } }) => isAdmin(user),\n },\n admin: {\n group: 'Settings',\n },\n fields: [\n {\n type: 'tabs',\n tabs: [\n { fields: analyticsFields, label: 'Analytics' },\n { fields: turnstileFields, label: 'Turnstile' },\n { fields: smtpFields, label: 'SMTP' },\n { fields: storageFields, label: 'Storage' },\n ...(additionalFields?.length ? [{ fields: additionalFields, label: 'Custom' }] : []),\n ],\n },\n ],\n label: 'Site Integrations',\n }\n}\n"],"names":["isAdmin","analyticsFields","smtpFields","storageFields","turnstileFields","buildSiteIntegrations","additionalFields","slug","access","read","req","user","update","admin","group","fields","type","tabs","label","length"],"mappings":"AAEA,SAASA,OAAO,QAAQ,gCAA+B;AACvD,SAASC,eAAe,QAAQ,wBAAuB;AACvD,SAASC,UAAU,QAAQ,mBAAkB;AAC7C,SAASC,aAAa,QAAQ,sBAAqB;AACnD,SAASC,eAAe,QAAQ,wBAAuB;AAOvD;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,sBAAsB,EACpCC,gBAAgB,EACU,GAAG,CAAC,CAAC;IAC/B,OAAO;QACLC,MAAM;QACNC,QAAQ;YACN,oEAAoE;YACpE,4EAA4E;YAC5EC,MAAM,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKX,QAAQW;YACrCC,QAAQ,CAAC,EAAEF,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKX,QAAQW;QACzC;QACAE,OAAO;YACLC,OAAO;QACT;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;oBACJ;wBAAEF,QAAQd;wBAAiBiB,OAAO;oBAAY;oBAC9C;wBAAEH,QAAQX;wBAAiBc,OAAO;oBAAY;oBAC9C;wBAAEH,QAAQb;wBAAYgB,OAAO;oBAAO;oBACpC;wBAAEH,QAAQZ;wBAAee,OAAO;oBAAU;uBACtCZ,kBAAkBa,SAAS;wBAAC;4BAAEJ,QAAQT;4BAAkBY,OAAO;wBAAS;qBAAE,GAAG,EAAE;iBACpF;YACH;SACD;QACDA,OAAO;IACT;AACF"}
|
{"version":3,"sources":["../../../src/globals/SiteIntegrations/index.ts"],"sourcesContent":["import type { Field, GlobalConfig } from 'payload'\n\nimport { isAdmin } from '../../modules/access/index.js'\nimport { analyticsFields } from './fields/analytics.js'\nimport { smtpFields } from './fields/smtp.js'\nimport { turnstileFields } from './fields/turnstile.js'\n\ntype BuildSiteIntegrationsArgs = {\n /** Extra fields injected by the client project */\n additionalFields?: Field[]\n}\n\n/**\n * Builds the SiteIntegrations global.\n *\n * Holds third-party service credentials. Access is enforced at the global\n * level — the whole global requires an authenticated user — so secrets stay\n * out of anonymous API responses while remaining editable in the admin panel\n * and readable via the server-side Local API. (Field-level read:false was\n * avoided because it also hides fields from the admin UI, making them\n * impossible to enter.)\n *\n * Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).\n *\n * Note: R2 storage credentials are NOT here — storage is infrastructure and\n * binds at boot, so its config lives in .env (R2_BUCKET, R2_ENDPOINT, ...),\n * consumed by buildR2Storage. See docs/storage.md.\n */\nexport function buildSiteIntegrations({\n additionalFields,\n}: BuildSiteIntegrationsArgs = {}): GlobalConfig {\n return {\n slug: 'site-integrations',\n access: {\n // Admin-only — secrets live here. Anonymous and non-admin users get\n // nothing through the API; admins read/edit in the panel and via Local API.\n read: ({ req: { user } }) => isAdmin(user),\n update: ({ req: { user } }) => isAdmin(user),\n },\n admin: {\n group: 'Settings',\n },\n fields: [\n {\n type: 'tabs',\n tabs: [\n { fields: analyticsFields, label: 'Analytics' },\n { fields: turnstileFields, label: 'Turnstile' },\n { fields: smtpFields, label: 'SMTP' },\n ...(additionalFields?.length ? [{ fields: additionalFields, label: 'Custom' }] : []),\n ],\n },\n ],\n label: 'Site Integrations',\n }\n}\n"],"names":["isAdmin","analyticsFields","smtpFields","turnstileFields","buildSiteIntegrations","additionalFields","slug","access","read","req","user","update","admin","group","fields","type","tabs","label","length"],"mappings":"AAEA,SAASA,OAAO,QAAQ,gCAA+B;AACvD,SAASC,eAAe,QAAQ,wBAAuB;AACvD,SAASC,UAAU,QAAQ,mBAAkB;AAC7C,SAASC,eAAe,QAAQ,wBAAuB;AAOvD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,sBAAsB,EACpCC,gBAAgB,EACU,GAAG,CAAC,CAAC;IAC/B,OAAO;QACLC,MAAM;QACNC,QAAQ;YACN,oEAAoE;YACpE,4EAA4E;YAC5EC,MAAM,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKV,QAAQU;YACrCC,QAAQ,CAAC,EAAEF,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKV,QAAQU;QACzC;QACAE,OAAO;YACLC,OAAO;QACT;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;oBACJ;wBAAEF,QAAQb;wBAAiBgB,OAAO;oBAAY;oBAC9C;wBAAEH,QAAQX;wBAAiBc,OAAO;oBAAY;oBAC9C;wBAAEH,QAAQZ;wBAAYe,OAAO;oBAAO;uBAChCZ,kBAAkBa,SAAS;wBAAC;4BAAEJ,QAAQT;4BAAkBY,OAAO;wBAAS;qBAAE,GAAG,EAAE;iBACpF;YACH;SACD;QACDA,OAAO;IACT;AACF"}
|
||||||
Vendored
+4
-2
@@ -20,6 +20,9 @@ export type { I18nConfig, LocaleDefinition, LocalizedSlugs } from './modules/i18
|
|||||||
export { buildLocalizedPath, getDefaultLocale, getLocaleCodes, getLocaleDefinition, getLocalizedSlugs, isValidLocale, LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale, switchLocalePath, } from './modules/i18n/index.js';
|
export { buildLocalizedPath, getDefaultLocale, getLocaleCodes, getLocaleDefinition, getLocalizedSlugs, isValidLocale, LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale, switchLocalePath, } from './modules/i18n/index.js';
|
||||||
export type { LocaleMiddlewareResult } from './modules/i18n/index.js';
|
export type { LocaleMiddlewareResult } from './modules/i18n/index.js';
|
||||||
export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './modules/i18n/index.js';
|
export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './modules/i18n/index.js';
|
||||||
|
export { normalizeFilename, normalizeFilenameHook } from './modules/media/index.js';
|
||||||
|
export { getNotificationTexts, NOTIFICATION_FALLBACK, resolveFormMessage, } from './modules/notifications/index.js';
|
||||||
|
export type { FormNotificationTexts, NotificationsData, NotificationTexts, } from './modules/notifications/index.js';
|
||||||
export type { PagesOption, SystemPageRole } from './modules/pages/index.js';
|
export type { PagesOption, SystemPageRole } from './modules/pages/index.js';
|
||||||
export { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js';
|
export { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js';
|
||||||
export type { GlobalQueryOptions } from './modules/payload/index.js';
|
export type { GlobalQueryOptions } from './modules/payload/index.js';
|
||||||
@@ -31,7 +34,6 @@ export { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/
|
|||||||
export type { AutoFillMapping, RobotsRules, SitemapEntry } from './modules/seo/index.js';
|
export type { AutoFillMapping, RobotsRules, SitemapEntry } from './modules/seo/index.js';
|
||||||
export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta, } from './modules/seo/index.js';
|
export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta, } from './modules/seo/index.js';
|
||||||
export { buildSlugField, toSlug } from './modules/slug/index.js';
|
export { buildSlugField, toSlug } from './modules/slug/index.js';
|
||||||
export { NOTIFICATION_FALLBACK, getNotificationTexts, resolveFormMessage, } from './modules/notifications/index.js';
|
export { buildR2Storage } from './modules/storage/index.js';
|
||||||
export type { FormNotificationTexts, NotificationsData, NotificationTexts, } from './modules/notifications/index.js';
|
|
||||||
export { ipalKit } from './plugin.js';
|
export { ipalKit } from './plugin.js';
|
||||||
export type { IpalOptions } from './types.js';
|
export type { IpalOptions } from './types.js';
|
||||||
|
|||||||
Vendored
+5
-1
@@ -12,13 +12,17 @@ export { buildFormsPlugin } from './modules/forms/formsPluginConfig.js';
|
|||||||
export { createContentHelpers } from './modules/frontend/index.js';
|
export { createContentHelpers } from './modules/frontend/index.js';
|
||||||
export { buildLocalizedPath, getDefaultLocale, getLocaleCodes, getLocaleDefinition, getLocalizedSlugs, isValidLocale, LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale, switchLocalePath } from './modules/i18n/index.js';
|
export { buildLocalizedPath, getDefaultLocale, getLocaleCodes, getLocaleDefinition, getLocalizedSlugs, isValidLocale, LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale, switchLocalePath } from './modules/i18n/index.js';
|
||||||
export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './modules/i18n/index.js';
|
export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './modules/i18n/index.js';
|
||||||
|
// Media — filename normalization hook for upload collections (Media).
|
||||||
|
export { normalizeFilename, normalizeFilenameHook } from './modules/media/index.js';
|
||||||
|
export { getNotificationTexts, NOTIFICATION_FALLBACK, resolveFormMessage } from './modules/notifications/index.js';
|
||||||
export { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js';
|
export { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js';
|
||||||
export { getGlobal, getSiteIntegrations, getSiteSettings, SITE_INTEGRATIONS_SLUG, SITE_SETTINGS_SLUG } from './modules/payload/index.js';
|
export { getGlobal, getSiteIntegrations, getSiteSettings, SITE_INTEGRATIONS_SLUG, SITE_SETTINGS_SLUG } from './modules/payload/index.js';
|
||||||
export { buildSecurityHeaders } from './modules/security/index.js';
|
export { buildSecurityHeaders } from './modules/security/index.js';
|
||||||
export { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/seo/index.js';
|
export { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/seo/index.js';
|
||||||
export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta } from './modules/seo/index.js';
|
export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta } from './modules/seo/index.js';
|
||||||
export { buildSlugField, toSlug } from './modules/slug/index.js';
|
export { buildSlugField, toSlug } from './modules/slug/index.js';
|
||||||
export { NOTIFICATION_FALLBACK, getNotificationTexts, resolveFormMessage } from './modules/notifications/index.js';
|
// Storage — Cloudflare R2 media offload, configured from .env.
|
||||||
|
export { buildR2Storage } from './modules/storage/index.js';
|
||||||
export { ipalKit } from './plugin.js';
|
export { ipalKit } from './plugin.js';
|
||||||
|
|
||||||
//# sourceMappingURL=index.js.map
|
//# sourceMappingURL=index.js.map
|
||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
import type { I18nConfig } from './types.js';
|
import type { I18nConfig } from './types.js';
|
||||||
/** Cookie name the template uses to persist a visitor's locale choice. */
|
/** Cookie name the template uses to persist a visitor's locale choice. */
|
||||||
export declare const LOCALE_COOKIE_NAME = "ipal-locale";
|
/**
|
||||||
|
* Cookie name for the persisted locale choice. Uses NEXT_LOCALE — the convention
|
||||||
|
* Next.js and its i18n ecosystem expect — so the cookie is interoperable with
|
||||||
|
* other libraries that read the active locale (instead of a plugin-specific
|
||||||
|
* name). Written only under functional consent; cleared when that consent is
|
||||||
|
* withdrawn (see consent cookieMap).
|
||||||
|
*/
|
||||||
|
export declare const LOCALE_COOKIE_NAME = "NEXT_LOCALE";
|
||||||
type NegotiateLocaleArgs = {
|
type NegotiateLocaleArgs = {
|
||||||
/** Raw Accept-Language header value */
|
/** Raw Accept-Language header value */
|
||||||
acceptLanguage?: null | string;
|
acceptLanguage?: null | string;
|
||||||
|
|||||||
Vendored
+7
-1
@@ -1,5 +1,11 @@
|
|||||||
import { getLocaleCodes, isValidLocale } from './helpers.js';
|
import { getLocaleCodes, isValidLocale } from './helpers.js';
|
||||||
/** Cookie name the template uses to persist a visitor's locale choice. */ export const LOCALE_COOKIE_NAME = 'ipal-locale';
|
/** Cookie name the template uses to persist a visitor's locale choice. */ /**
|
||||||
|
* Cookie name for the persisted locale choice. Uses NEXT_LOCALE — the convention
|
||||||
|
* Next.js and its i18n ecosystem expect — so the cookie is interoperable with
|
||||||
|
* other libraries that read the active locale (instead of a plugin-specific
|
||||||
|
* name). Written only under functional consent; cleared when that consent is
|
||||||
|
* withdrawn (see consent cookieMap).
|
||||||
|
*/ export const LOCALE_COOKIE_NAME = 'NEXT_LOCALE';
|
||||||
/**
|
/**
|
||||||
* Resolves which locale to serve, in priority order:
|
* Resolves which locale to serve, in priority order:
|
||||||
* 1. Cookie (explicit prior choice)
|
* 1. Cookie (explicit prior choice)
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
export { normalizeFilename, normalizeFilenameHook } from './normalizeFilename.js';
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export { normalizeFilename, normalizeFilenameHook } from './normalizeFilename.js';
|
||||||
|
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["../../../src/modules/media/index.ts"],"sourcesContent":["export { normalizeFilename, normalizeFilenameHook } from './normalizeFilename.js'\n"],"names":["normalizeFilename","normalizeFilenameHook"],"mappings":"AAAA,SAASA,iBAAiB,EAAEC,qBAAqB,QAAQ,yBAAwB"}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import type { CollectionBeforeOperationHook } from 'payload';
|
||||||
|
/**
|
||||||
|
* Normalizes a filename: slugifies the NAME part (diacritics, spaces, case)
|
||||||
|
* while preserving the extension. Keeps uploaded media URLs clean and portable.
|
||||||
|
*
|
||||||
|
* "Zdjęcie jeden nad morzem.jpg" → "zdjecie-jeden-nad-morzem.jpg"
|
||||||
|
* "Faktura #12 (2024).PDF" → "faktura-12-2024.pdf"
|
||||||
|
* "already-clean.webp" → "already-clean.webp"
|
||||||
|
*
|
||||||
|
* Why not toSlug(): toSlug uses strict:true, which would strip the dot and
|
||||||
|
* merge name+extension. Here we split on the LAST dot, slug the stem, lowercase
|
||||||
|
* the extension, and rejoin.
|
||||||
|
*/
|
||||||
|
export declare function normalizeFilename(filename: string): string;
|
||||||
|
/**
|
||||||
|
* beforeOperation hook for an upload collection (e.g. Media). Rewrites the
|
||||||
|
* incoming file's name to its normalized form before Payload stores it, so both
|
||||||
|
* the stored file and its DB filename are clean. Works with local disk and with
|
||||||
|
* cloud storage adapters (R2/S3) — it runs before the storage layer.
|
||||||
|
*
|
||||||
|
* Wire into your Media collection:
|
||||||
|
* import { normalizeFilenameHook } from '@intecion/ipal-kit'
|
||||||
|
* hooks: { beforeOperation: [normalizeFilenameHook] }
|
||||||
|
*/
|
||||||
|
export declare const normalizeFilenameHook: CollectionBeforeOperationHook;
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
import slugify from 'slugify';
|
||||||
|
/**
|
||||||
|
* Normalizes a filename: slugifies the NAME part (diacritics, spaces, case)
|
||||||
|
* while preserving the extension. Keeps uploaded media URLs clean and portable.
|
||||||
|
*
|
||||||
|
* "Zdjęcie jeden nad morzem.jpg" → "zdjecie-jeden-nad-morzem.jpg"
|
||||||
|
* "Faktura #12 (2024).PDF" → "faktura-12-2024.pdf"
|
||||||
|
* "already-clean.webp" → "already-clean.webp"
|
||||||
|
*
|
||||||
|
* Why not toSlug(): toSlug uses strict:true, which would strip the dot and
|
||||||
|
* merge name+extension. Here we split on the LAST dot, slug the stem, lowercase
|
||||||
|
* the extension, and rejoin.
|
||||||
|
*/ export function normalizeFilename(filename) {
|
||||||
|
const lastDot = filename.lastIndexOf('.');
|
||||||
|
// No extension (or leading-dot dotfile) → slug the whole thing.
|
||||||
|
if (lastDot <= 0) {
|
||||||
|
return slugify(filename, {
|
||||||
|
lower: true,
|
||||||
|
strict: true,
|
||||||
|
trim: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const stem = filename.slice(0, lastDot);
|
||||||
|
const ext = filename.slice(lastDot + 1).toLowerCase();
|
||||||
|
const cleanStem = slugify(stem, {
|
||||||
|
lower: true,
|
||||||
|
strict: true,
|
||||||
|
trim: true
|
||||||
|
});
|
||||||
|
const cleanExt = slugify(ext, {
|
||||||
|
lower: true,
|
||||||
|
strict: true,
|
||||||
|
trim: true
|
||||||
|
});
|
||||||
|
// Stem could slug to empty (e.g. filename was all symbols) — fall back so we
|
||||||
|
// never produce a nameless file.
|
||||||
|
const safeStem = cleanStem || 'plik';
|
||||||
|
return cleanExt ? `${safeStem}.${cleanExt}` : safeStem;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* beforeOperation hook for an upload collection (e.g. Media). Rewrites the
|
||||||
|
* incoming file's name to its normalized form before Payload stores it, so both
|
||||||
|
* the stored file and its DB filename are clean. Works with local disk and with
|
||||||
|
* cloud storage adapters (R2/S3) — it runs before the storage layer.
|
||||||
|
*
|
||||||
|
* Wire into your Media collection:
|
||||||
|
* import { normalizeFilenameHook } from '@intecion/ipal-kit'
|
||||||
|
* hooks: { beforeOperation: [normalizeFilenameHook] }
|
||||||
|
*/ export const normalizeFilenameHook = ({ req, operation })=>{
|
||||||
|
if (operation !== 'create' && operation !== 'update') return;
|
||||||
|
const file = req.file;
|
||||||
|
if (file?.name) {
|
||||||
|
file.name = normalizeFilename(file.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//# sourceMappingURL=normalizeFilename.js.map
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["../../../src/modules/media/normalizeFilename.ts"],"sourcesContent":["import type { CollectionBeforeOperationHook } from 'payload'\nimport slugify from 'slugify'\n\n/**\n * Normalizes a filename: slugifies the NAME part (diacritics, spaces, case)\n * while preserving the extension. Keeps uploaded media URLs clean and portable.\n *\n * \"Zdjęcie jeden nad morzem.jpg\" → \"zdjecie-jeden-nad-morzem.jpg\"\n * \"Faktura #12 (2024).PDF\" → \"faktura-12-2024.pdf\"\n * \"already-clean.webp\" → \"already-clean.webp\"\n *\n * Why not toSlug(): toSlug uses strict:true, which would strip the dot and\n * merge name+extension. Here we split on the LAST dot, slug the stem, lowercase\n * the extension, and rejoin.\n */\nexport function normalizeFilename(filename: string): string {\n const lastDot = filename.lastIndexOf('.')\n\n // No extension (or leading-dot dotfile) → slug the whole thing.\n if (lastDot <= 0) {\n return slugify(filename, { lower: true, strict: true, trim: true })\n }\n\n const stem = filename.slice(0, lastDot)\n const ext = filename.slice(lastDot + 1).toLowerCase()\n\n const cleanStem = slugify(stem, { lower: true, strict: true, trim: true })\n const cleanExt = slugify(ext, { lower: true, strict: true, trim: true })\n\n // Stem could slug to empty (e.g. filename was all symbols) — fall back so we\n // never produce a nameless file.\n const safeStem = cleanStem || 'plik'\n\n return cleanExt ? `${safeStem}.${cleanExt}` : safeStem\n}\n\n/**\n * beforeOperation hook for an upload collection (e.g. Media). Rewrites the\n * incoming file's name to its normalized form before Payload stores it, so both\n * the stored file and its DB filename are clean. Works with local disk and with\n * cloud storage adapters (R2/S3) — it runs before the storage layer.\n *\n * Wire into your Media collection:\n * import { normalizeFilenameHook } from '@intecion/ipal-kit'\n * hooks: { beforeOperation: [normalizeFilenameHook] }\n */\nexport const normalizeFilenameHook: CollectionBeforeOperationHook = ({ req, operation }) => {\n if (operation !== 'create' && operation !== 'update') return\n const file = req.file\n if (file?.name) {\n file.name = normalizeFilename(file.name)\n }\n}\n"],"names":["slugify","normalizeFilename","filename","lastDot","lastIndexOf","lower","strict","trim","stem","slice","ext","toLowerCase","cleanStem","cleanExt","safeStem","normalizeFilenameHook","req","operation","file","name"],"mappings":"AACA,OAAOA,aAAa,UAAS;AAE7B;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,kBAAkBC,QAAgB;IAChD,MAAMC,UAAUD,SAASE,WAAW,CAAC;IAErC,gEAAgE;IAChE,IAAID,WAAW,GAAG;QAChB,OAAOH,QAAQE,UAAU;YAAEG,OAAO;YAAMC,QAAQ;YAAMC,MAAM;QAAK;IACnE;IAEA,MAAMC,OAAON,SAASO,KAAK,CAAC,GAAGN;IAC/B,MAAMO,MAAMR,SAASO,KAAK,CAACN,UAAU,GAAGQ,WAAW;IAEnD,MAAMC,YAAYZ,QAAQQ,MAAM;QAAEH,OAAO;QAAMC,QAAQ;QAAMC,MAAM;IAAK;IACxE,MAAMM,WAAWb,QAAQU,KAAK;QAAEL,OAAO;QAAMC,QAAQ;QAAMC,MAAM;IAAK;IAEtE,6EAA6E;IAC7E,iCAAiC;IACjC,MAAMO,WAAWF,aAAa;IAE9B,OAAOC,WAAW,GAAGC,SAAS,CAAC,EAAED,UAAU,GAAGC;AAChD;AAEA;;;;;;;;;CASC,GACD,OAAO,MAAMC,wBAAuD,CAAC,EAAEC,GAAG,EAAEC,SAAS,EAAE;IACrF,IAAIA,cAAc,YAAYA,cAAc,UAAU;IACtD,MAAMC,OAAOF,IAAIE,IAAI;IACrB,IAAIA,MAAMC,MAAM;QACdD,KAAKC,IAAI,GAAGlB,kBAAkBiB,KAAKC,IAAI;IACzC;AACF,EAAC"}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Emits <link rel="preconnect"> + <link rel="dns-prefetch"> for the media CDN
|
||||||
|
* domain (R2_PUBLIC_URL), so the browser opens the TLS/DNS connection to the
|
||||||
|
* media host early — before it hits the first <img>. Saves ~150–300ms on the
|
||||||
|
* first image load.
|
||||||
|
*
|
||||||
|
* Reads the domain from R2_PUBLIC_URL (the same env var buildR2Storage uses),
|
||||||
|
* so there's ONE source of truth — no per-project hardcoded domain. Renders
|
||||||
|
* nothing when R2_PUBLIC_URL isn't set (local disk / no CDN → nothing to
|
||||||
|
* preconnect).
|
||||||
|
*
|
||||||
|
* Server Component — drop it in the <head> of your locale layout:
|
||||||
|
*
|
||||||
|
* import { MediaPreconnect } from '@intecion/ipal-kit/rsc'
|
||||||
|
* // in <head> (or top of <body> — Next hoists link tags):
|
||||||
|
* <MediaPreconnect />
|
||||||
|
*/
|
||||||
|
export declare function MediaPreconnect(): import("react/jsx-runtime").JSX.Element | null;
|
||||||
|
export default MediaPreconnect;
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
||||||
|
/**
|
||||||
|
* Emits <link rel="preconnect"> + <link rel="dns-prefetch"> for the media CDN
|
||||||
|
* domain (R2_PUBLIC_URL), so the browser opens the TLS/DNS connection to the
|
||||||
|
* media host early — before it hits the first <img>. Saves ~150–300ms on the
|
||||||
|
* first image load.
|
||||||
|
*
|
||||||
|
* Reads the domain from R2_PUBLIC_URL (the same env var buildR2Storage uses),
|
||||||
|
* so there's ONE source of truth — no per-project hardcoded domain. Renders
|
||||||
|
* nothing when R2_PUBLIC_URL isn't set (local disk / no CDN → nothing to
|
||||||
|
* preconnect).
|
||||||
|
*
|
||||||
|
* Server Component — drop it in the <head> of your locale layout:
|
||||||
|
*
|
||||||
|
* import { MediaPreconnect } from '@intecion/ipal-kit/rsc'
|
||||||
|
* // in <head> (or top of <body> — Next hoists link tags):
|
||||||
|
* <MediaPreconnect />
|
||||||
|
*/ export function MediaPreconnect() {
|
||||||
|
const publicUrl = process.env.R2_PUBLIC_URL?.replace(/\/$/, '');
|
||||||
|
if (!publicUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Origin only (scheme + host) — preconnect targets an origin, not a path.
|
||||||
|
let origin;
|
||||||
|
try {
|
||||||
|
origin = new URL(publicUrl).origin;
|
||||||
|
} catch {
|
||||||
|
return null // malformed URL → skip rather than emit a broken tag
|
||||||
|
;
|
||||||
|
}
|
||||||
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
||||||
|
children: [
|
||||||
|
/*#__PURE__*/ _jsx("link", {
|
||||||
|
crossOrigin: "anonymous",
|
||||||
|
href: origin,
|
||||||
|
rel: "preconnect"
|
||||||
|
}),
|
||||||
|
/*#__PURE__*/ _jsx("link", {
|
||||||
|
href: origin,
|
||||||
|
rel: "dns-prefetch"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default MediaPreconnect;
|
||||||
|
|
||||||
|
//# sourceMappingURL=MediaPreconnect.js.map
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["../../../src/modules/storage/MediaPreconnect.tsx"],"sourcesContent":["/**\n * Emits <link rel=\"preconnect\"> + <link rel=\"dns-prefetch\"> for the media CDN\n * domain (R2_PUBLIC_URL), so the browser opens the TLS/DNS connection to the\n * media host early — before it hits the first <img>. Saves ~150–300ms on the\n * first image load.\n *\n * Reads the domain from R2_PUBLIC_URL (the same env var buildR2Storage uses),\n * so there's ONE source of truth — no per-project hardcoded domain. Renders\n * nothing when R2_PUBLIC_URL isn't set (local disk / no CDN → nothing to\n * preconnect).\n *\n * Server Component — drop it in the <head> of your locale layout:\n *\n * import { MediaPreconnect } from '@intecion/ipal-kit/rsc'\n * // in <head> (or top of <body> — Next hoists link tags):\n * <MediaPreconnect />\n */\nexport function MediaPreconnect() {\n const publicUrl = process.env.R2_PUBLIC_URL?.replace(/\\/$/, '')\n if (!publicUrl) {return null}\n\n // Origin only (scheme + host) — preconnect targets an origin, not a path.\n let origin: string\n try {\n origin = new URL(publicUrl).origin\n } catch {\n return null // malformed URL → skip rather than emit a broken tag\n }\n\n return (\n <>\n <link crossOrigin=\"anonymous\" href={origin} rel=\"preconnect\" />\n <link href={origin} rel=\"dns-prefetch\" />\n </>\n )\n}\n\nexport default MediaPreconnect\n"],"names":["MediaPreconnect","publicUrl","process","env","R2_PUBLIC_URL","replace","origin","URL","link","crossOrigin","href","rel"],"mappings":";AAAA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASA;IACd,MAAMC,YAAYC,QAAQC,GAAG,CAACC,aAAa,EAAEC,QAAQ,OAAO;IAC5D,IAAI,CAACJ,WAAW;QAAC,OAAO;IAAI;IAE5B,0EAA0E;IAC1E,IAAIK;IACJ,IAAI;QACFA,SAAS,IAAIC,IAAIN,WAAWK,MAAM;IACpC,EAAE,OAAM;QACN,OAAO,KAAK,qDAAqD;;IACnE;IAEA,qBACE;;0BACE,KAACE;gBAAKC,aAAY;gBAAYC,MAAMJ;gBAAQK,KAAI;;0BAChD,KAACH;gBAAKE,MAAMJ;gBAAQK,KAAI;;;;AAG9B;AAEA,eAAeX,gBAAe"}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
import type { Plugin } from 'payload';
|
||||||
|
/**
|
||||||
|
* Cloudflare R2 media storage — configured from environment variables (agency
|
||||||
|
* infrastructure, not per-project panel data). R2 is S3-compatible, so we use
|
||||||
|
* @payloadcms/storage-s3 pointed at the R2 endpoint.
|
||||||
|
*
|
||||||
|
* Storage is infrastructure (like the database or PAYLOAD_SECRET): it binds at
|
||||||
|
* boot, and its credentials are agency-owned — so it lives in .env, not the
|
||||||
|
* panel. See docs/storage.md for the required variables.
|
||||||
|
*
|
||||||
|
* Returns the storage plugin when all R2 vars are present; otherwise returns a
|
||||||
|
* no-op passthrough so the project falls back to Payload's default local disk
|
||||||
|
* storage (useful in dev without R2). This mirrors how mailAdapter degrades
|
||||||
|
* gracefully when a transport isn't configured.
|
||||||
|
*
|
||||||
|
* @param collections - slugs of upload collections to offload to R2 (e.g. ['media'])
|
||||||
|
*/
|
||||||
|
export declare const buildR2Storage: (collections?: string[]) => Plugin;
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
import { s3Storage } from '@payloadcms/storage-s3';
|
||||||
|
/**
|
||||||
|
* Cloudflare R2 media storage — configured from environment variables (agency
|
||||||
|
* infrastructure, not per-project panel data). R2 is S3-compatible, so we use
|
||||||
|
* @payloadcms/storage-s3 pointed at the R2 endpoint.
|
||||||
|
*
|
||||||
|
* Storage is infrastructure (like the database or PAYLOAD_SECRET): it binds at
|
||||||
|
* boot, and its credentials are agency-owned — so it lives in .env, not the
|
||||||
|
* panel. See docs/storage.md for the required variables.
|
||||||
|
*
|
||||||
|
* Returns the storage plugin when all R2 vars are present; otherwise returns a
|
||||||
|
* no-op passthrough so the project falls back to Payload's default local disk
|
||||||
|
* storage (useful in dev without R2). This mirrors how mailAdapter degrades
|
||||||
|
* gracefully when a transport isn't configured.
|
||||||
|
*
|
||||||
|
* @param collections - slugs of upload collections to offload to R2 (e.g. ['media'])
|
||||||
|
*/ export const buildR2Storage = (collections = [
|
||||||
|
'media'
|
||||||
|
])=>{
|
||||||
|
const bucket = process.env.R2_BUCKET;
|
||||||
|
const endpoint = process.env.R2_ENDPOINT;
|
||||||
|
const accessKeyId = process.env.R2_ACCESS_KEY_ID;
|
||||||
|
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
|
||||||
|
// Any missing → skip R2, fall back to local disk. Warn so it's not silent.
|
||||||
|
if (!bucket || !endpoint || !accessKeyId || !secretAccessKey) {
|
||||||
|
return (config)=>{
|
||||||
|
// Only warn when SOME vars are set (partial config = likely a mistake).
|
||||||
|
if (bucket || endpoint || accessKeyId || secretAccessKey) {
|
||||||
|
console.warn('[ipal] R2 storage: incomplete env (need R2_BUCKET, R2_ENDPOINT, ' + 'R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY). Falling back to local disk.');
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Public URL for served media. R2 is private by default; its S3 endpoint only
|
||||||
|
// accepts uploads and won't serve files (403). With a custom domain
|
||||||
|
// (media.klient.pl → bucket) set R2_PUBLIC_URL so Payload generates URLs
|
||||||
|
// pointing there. Without it, uploads work but images don't display publicly.
|
||||||
|
// See docs/storage.md.
|
||||||
|
const publicUrl = process.env.R2_PUBLIC_URL?.replace(/\/$/, '') // strip trailing slash
|
||||||
|
;
|
||||||
|
// generateFileURL is a PER-COLLECTION option in @payloadcms/storage-s3 (not a
|
||||||
|
// top-level one) — R2 needs it to point served URLs at the custom domain
|
||||||
|
// instead of the private S3 endpoint. Each collection gets either `true`
|
||||||
|
// (plain offload) or an object carrying generateFileURL when a public URL is set.
|
||||||
|
const generateFileURL = publicUrl ? ({ filename, prefix })=>[
|
||||||
|
publicUrl,
|
||||||
|
prefix,
|
||||||
|
filename
|
||||||
|
].filter(Boolean).join('/') : undefined;
|
||||||
|
const collectionsConfig = {};
|
||||||
|
for (const slug of collections){
|
||||||
|
collectionsConfig[slug] = generateFileURL ? {
|
||||||
|
generateFileURL
|
||||||
|
} : true;
|
||||||
|
}
|
||||||
|
return s3Storage({
|
||||||
|
bucket,
|
||||||
|
collections: collectionsConfig,
|
||||||
|
config: {
|
||||||
|
credentials: {
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey
|
||||||
|
},
|
||||||
|
endpoint,
|
||||||
|
region: 'auto',
|
||||||
|
// R2 requires path-style addressing for S3 compatibility.
|
||||||
|
forcePathStyle: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//# sourceMappingURL=buildR2Storage.js.map
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["../../../src/modules/storage/buildR2Storage.ts"],"sourcesContent":["import type { Plugin } from 'payload'\n\nimport { s3Storage } from '@payloadcms/storage-s3'\n\n/**\n * Cloudflare R2 media storage — configured from environment variables (agency\n * infrastructure, not per-project panel data). R2 is S3-compatible, so we use\n * @payloadcms/storage-s3 pointed at the R2 endpoint.\n *\n * Storage is infrastructure (like the database or PAYLOAD_SECRET): it binds at\n * boot, and its credentials are agency-owned — so it lives in .env, not the\n * panel. See docs/storage.md for the required variables.\n *\n * Returns the storage plugin when all R2 vars are present; otherwise returns a\n * no-op passthrough so the project falls back to Payload's default local disk\n * storage (useful in dev without R2). This mirrors how mailAdapter degrades\n * gracefully when a transport isn't configured.\n *\n * @param collections - slugs of upload collections to offload to R2 (e.g. ['media'])\n */\nexport const buildR2Storage = (collections: string[] = ['media']): Plugin => {\n const bucket = process.env.R2_BUCKET\n const endpoint = process.env.R2_ENDPOINT\n const accessKeyId = process.env.R2_ACCESS_KEY_ID\n const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY\n\n // Any missing → skip R2, fall back to local disk. Warn so it's not silent.\n if (!bucket || !endpoint || !accessKeyId || !secretAccessKey) {\n return (config) => {\n // Only warn when SOME vars are set (partial config = likely a mistake).\n if (bucket || endpoint || accessKeyId || secretAccessKey) {\n console.warn(\n '[ipal] R2 storage: incomplete env (need R2_BUCKET, R2_ENDPOINT, ' +\n 'R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY). Falling back to local disk.',\n )\n }\n return config\n }\n }\n\n // Public URL for served media. R2 is private by default; its S3 endpoint only\n // accepts uploads and won't serve files (403). With a custom domain\n // (media.klient.pl → bucket) set R2_PUBLIC_URL so Payload generates URLs\n // pointing there. Without it, uploads work but images don't display publicly.\n // See docs/storage.md.\n const publicUrl = process.env.R2_PUBLIC_URL?.replace(/\\/$/, '') // strip trailing slash\n\n // generateFileURL is a PER-COLLECTION option in @payloadcms/storage-s3 (not a\n // top-level one) — R2 needs it to point served URLs at the custom domain\n // instead of the private S3 endpoint. Each collection gets either `true`\n // (plain offload) or an object carrying generateFileURL when a public URL is set.\n const generateFileURL = publicUrl\n ? ({ filename, prefix }: { filename: string; prefix?: string }) =>\n [publicUrl, prefix, filename].filter(Boolean).join('/')\n : undefined\n\n const collectionsConfig: Record<string, { generateFileURL: typeof generateFileURL } | true> = {}\n for (const slug of collections) {\n collectionsConfig[slug] = generateFileURL ? { generateFileURL } : true\n }\n\n return s3Storage({\n bucket,\n collections: collectionsConfig,\n config: {\n credentials: { accessKeyId, secretAccessKey },\n endpoint,\n region: 'auto', // R2 uses 'auto'\n // R2 requires path-style addressing for S3 compatibility.\n forcePathStyle: true,\n },\n })\n}\n"],"names":["s3Storage","buildR2Storage","collections","bucket","process","env","R2_BUCKET","endpoint","R2_ENDPOINT","accessKeyId","R2_ACCESS_KEY_ID","secretAccessKey","R2_SECRET_ACCESS_KEY","config","console","warn","publicUrl","R2_PUBLIC_URL","replace","generateFileURL","filename","prefix","filter","Boolean","join","undefined","collectionsConfig","slug","credentials","region","forcePathStyle"],"mappings":"AAEA,SAASA,SAAS,QAAQ,yBAAwB;AAElD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,iBAAiB,CAACC,cAAwB;IAAC;CAAQ;IAC9D,MAAMC,SAASC,QAAQC,GAAG,CAACC,SAAS;IACpC,MAAMC,WAAWH,QAAQC,GAAG,CAACG,WAAW;IACxC,MAAMC,cAAcL,QAAQC,GAAG,CAACK,gBAAgB;IAChD,MAAMC,kBAAkBP,QAAQC,GAAG,CAACO,oBAAoB;IAExD,2EAA2E;IAC3E,IAAI,CAACT,UAAU,CAACI,YAAY,CAACE,eAAe,CAACE,iBAAiB;QAC5D,OAAO,CAACE;YACN,wEAAwE;YACxE,IAAIV,UAAUI,YAAYE,eAAeE,iBAAiB;gBACxDG,QAAQC,IAAI,CACV,qEACE;YAEN;YACA,OAAOF;QACT;IACF;IAEA,8EAA8E;IAC9E,oEAAoE;IACpE,yEAAyE;IACzE,8EAA8E;IAC9E,uBAAuB;IACvB,MAAMG,YAAYZ,QAAQC,GAAG,CAACY,aAAa,EAAEC,QAAQ,OAAO,IAAI,uBAAuB;;IAEvF,8EAA8E;IAC9E,yEAAyE;IACzE,yEAAyE;IACzE,kFAAkF;IAClF,MAAMC,kBAAkBH,YACpB,CAAC,EAAEI,QAAQ,EAAEC,MAAM,EAAyC,GAC1D;YAACL;YAAWK;YAAQD;SAAS,CAACE,MAAM,CAACC,SAASC,IAAI,CAAC,OACrDC;IAEJ,MAAMC,oBAAwF,CAAC;IAC/F,KAAK,MAAMC,QAAQzB,YAAa;QAC9BwB,iBAAiB,CAACC,KAAK,GAAGR,kBAAkB;YAAEA;QAAgB,IAAI;IACpE;IAEA,OAAOnB,UAAU;QACfG;QACAD,aAAawB;QACbb,QAAQ;YACNe,aAAa;gBAAEnB;gBAAaE;YAAgB;YAC5CJ;YACAsB,QAAQ;YACR,0DAA0D;YAC1DC,gBAAgB;QAClB;IACF;AACF,EAAC"}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
export { buildR2Storage } from './buildR2Storage.js';
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export { buildR2Storage } from './buildR2Storage.js';
|
||||||
|
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["../../../src/modules/storage/index.ts"],"sourcesContent":["export { buildR2Storage } from './buildR2Storage.js'\n"],"names":["buildR2Storage"],"mappings":"AAAA,SAASA,cAAc,QAAQ,sBAAqB"}
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
# Playbook wdrożenia — ipal-kit
|
||||||
|
|
||||||
|
Sztywna procedura dla pracownika albo AI (Antigravity). Mówi CO robić, W JAKIEJ
|
||||||
|
KOLEJNOŚCI, i CZYM SIĘ KIEROWAĆ. Zasady są twarde, przykłady realne — wzięte z
|
||||||
|
faktycznych błędów, które się zdarzyły. Odstępstwa tylko za świadomą decyzją.
|
||||||
|
|
||||||
|
Powiązane: [standardy-kodu.md](./standardy-kodu.md) (dobre praktyki senior),
|
||||||
|
[publishing.md](./publishing.md) (cykl publikacji), [getting-started.md](./getting-started.md)
|
||||||
|
(nowy projekt), ../ANTIGRAVITY-ZASADY-AGENT.md (zasady dla AI).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ZŁOTE ZASADY (łam tylko świadomie)
|
||||||
|
|
||||||
|
1. **Nic na sztywno.** Tekst, obraz, link, dane firmy → panel/baza, nie kod.
|
||||||
|
2. **Logika w pluginie, projekt podłącza.** Jeśli piszesz w projekcie coś, co
|
||||||
|
robi już plugin — zatrzymaj się, użyj pluginu.
|
||||||
|
3. **Next 16 = proxy.ts.** NIGDY middleware.ts. Jeśli istnieje — usuń.
|
||||||
|
4. **Weryfikuj każdy etap grepem.** Nie zakładaj, że zadziałało. Sprawdź.
|
||||||
|
5. **Napraw u źródła, nie łataj.** Bez `as any`, `@ts-ignore`, kopii logiki.
|
||||||
|
6. **Zmiana w pluginie nie działa, dopóki nie: build → publish → wciągnięcie.**
|
||||||
|
7. **Zmieniłeś API → zaktualizuj docs w tym samym commicie.** Docs jadą w
|
||||||
|
pakiecie; rozjazd kod↔docs = agent dostaje złą mapę.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CZĘŚĆ A — ŁAŃCUCH ZMIANY W PLUGINIE (najważniejsze)
|
||||||
|
|
||||||
|
Najczęstsze źródło frustracji tej sesji: „zmieniłem kod, a nie działa". Prawie
|
||||||
|
zawsze przyczyna: **przerwany łańcuch**. Zmiana w pluginie przechodzi przez
|
||||||
|
PIĘĆ etapów. Pominięcie któregokolwiek = stara wersja w projekcie.
|
||||||
|
|
||||||
|
```
|
||||||
|
źródła (src) → build (dist) → publish (rejestr) → wciągnięcie (node_modules) → restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sztywna procedura zmiany w pluginie
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/payload-cms/ipal-kit
|
||||||
|
|
||||||
|
# 1. ŹRÓDŁA — nanieś zmianę, ZWERYFIKUJ że jest
|
||||||
|
grep -c "<symbol-zmiany>" src/<ścieżka> # MUSI być >0
|
||||||
|
|
||||||
|
# 1b. DOCS — jeśli zmiana dotyka API/zachowania, ZAKTUALIZUJ docs/
|
||||||
|
# (nowa funkcja, zmiana sygnatury, nowe pole panelu, nowy adapter...).
|
||||||
|
# Docs jadą w pakiecie (files: dist, docs) — nieaktualne docs = agent
|
||||||
|
# dostaje złą mapę. Kod i docs publikuj RAZEM.
|
||||||
|
|
||||||
|
# 2. BUILD — zbuduj, ZWERYFIKUJ że dist ma zmianę
|
||||||
|
pnpm build
|
||||||
|
grep -c "<symbol-zmiany>" dist/<ścieżka> # MUSI być >0
|
||||||
|
|
||||||
|
# 3. COMMIT (PRZED version — inaczej "working directory not clean")
|
||||||
|
git add -A && git commit -m "opis"
|
||||||
|
|
||||||
|
# 4. VERSION + PUBLISH
|
||||||
|
npm version patch # czyste repo wymagane
|
||||||
|
npm publish
|
||||||
|
|
||||||
|
# 5. PUSH
|
||||||
|
git push && git push --tags
|
||||||
|
|
||||||
|
# 6. PROJEKT — wciągnij, ZWERYFIKUJ że node_modules ma zmianę
|
||||||
|
cd ~/<projekt>
|
||||||
|
pnpm add @intecion/ipal-kit@<nowa-wersja>
|
||||||
|
grep -c "<symbol-zmiany>" node_modules/@intecion/ipal-kit/dist/<ścieżka> # MUSI być >0
|
||||||
|
|
||||||
|
# 7. RESTART dev (Payload buduje adaptery/config przy starcie!)
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### TRZY punkty kontrolne grep (nie pomijaj żadnego)
|
||||||
|
|
||||||
|
| Etap | Grep | Jeśli 0 |
|
||||||
|
|---|---|---|
|
||||||
|
| po edycji | `src/...` | zmiana nie zapisana / zły plik |
|
||||||
|
| po build | `dist/...` | build nie złapał / błąd typów |
|
||||||
|
| po pnpm add | `node_modules/...` | projekt ma starą wersję |
|
||||||
|
|
||||||
|
**Realny przykład (z tej sesji):** `buildSecurityHeaders is not a function`.
|
||||||
|
Przyczyna: moduł istniał w `src`, ale NIE był wyeksportowany w `src/index.ts`
|
||||||
|
→ `dist` go nie miał → import w projekcie = undefined. Grep `dist/index.js`
|
||||||
|
pokazał 0. Naprawa: dodać eksport, przejść łańcuch od nowa.
|
||||||
|
|
||||||
|
### Pułapki kolejności (realne błędy sesji)
|
||||||
|
|
||||||
|
- **`npm version` przed commitem** → "Git working directory not clean". ZAWSZE
|
||||||
|
commit przed version.
|
||||||
|
- **`npm publish` bez `pnpm build`** → publikujesz STARY dist. ZAWSZE build przed
|
||||||
|
publish, grep dist po buildzie.
|
||||||
|
- **`pnpm add` przy działającym dev** → proces ma stary adapter w pamięci.
|
||||||
|
Payload czyta email/config przy starcie. ZAWSZE restart po wciągnięciu.
|
||||||
|
- **Publikacja bez aktualizacji docs** → agent (Antigravity) po `pnpm add`
|
||||||
|
czyta `node_modules/@intecion/ipal-kit/docs/` z NIEAKTUALNĄ mapą. Jeśli
|
||||||
|
zmieniłeś API — docs w tym samym commicie.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CZĘŚĆ B — GREP JAKO NARZĘDZIE (jak weryfikować dobrze)
|
||||||
|
|
||||||
|
Grep był w tej sesji głównym narzędziem diagnozy. Ale trzeba go używać mądrze.
|
||||||
|
|
||||||
|
### Reguła: grepuj TOKENY, nie całe frazy z kolejnością
|
||||||
|
|
||||||
|
**Realny błąd:** grep `"env.sender, name: senderName"` dał 0, choć kod był OK —
|
||||||
|
bo plik miał odwróconą kolejność kluczy (`name: senderName, address: env.sender`).
|
||||||
|
Obiekt JS ignoruje kolejność, ale grep nie.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ŹLE — zależny od kolejności/formatowania:
|
||||||
|
grep -c "env.sender, name: senderName" plik.ts # 0 mimo poprawnego kodu
|
||||||
|
|
||||||
|
# DOBRZE — pojedynczy token, odporny:
|
||||||
|
grep -c "senderName" plik.ts # 3 ✓
|
||||||
|
```
|
||||||
|
|
||||||
|
Grepuj **nazwę symbolu** (funkcja, zmienna, eksport), nie całą linię z interpunkcją.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CZĘŚĆ C — DIAGNOSTYKA „KOD DOBRY, ZACHOWANIE ZŁE"
|
||||||
|
|
||||||
|
Gdy grep potwierdza kod, wersja nowa, a zachowanie stare — przejdź listę:
|
||||||
|
|
||||||
|
1. **Dev nie zrestartowany?** Payload buduje adaptery/config przy starcie.
|
||||||
|
Ctrl+C + `pnpm dev`. (Najczęstsza przyczyna.)
|
||||||
|
2. **Zmiana zapisana w panelu?** Endpointy czytają z BAZY, nie z pola na ekranie.
|
||||||
|
Kliknij Save.
|
||||||
|
3. **Zdublowana zależność?** `@payloadcms/ui` w node_modules pluginu = dwie
|
||||||
|
instancje = hooki bez kontekstu. Sprawdź:
|
||||||
|
`ls node_modules/@intecion/ipal-kit/node_modules/@payloadcms/ui`
|
||||||
|
Jest? → peerDependency problem (patrz Część D).
|
||||||
|
4. **Cache klienta?** Np. klient pocztowy pokazuje zapamiętaną nazwę nadawcy
|
||||||
|
mimo poprawnych nagłówków. Sprawdź surowe źródło (View Source), wyślij na
|
||||||
|
inny adres.
|
||||||
|
5. **Import map nieaktualny?** Custom komponenty Payload:
|
||||||
|
`npx payload generate:importmap`.
|
||||||
|
|
||||||
|
**Realny przykład:** MaskedField rzucał "Cannot destructure property 'config'".
|
||||||
|
Kod OK. Przyczyna: dublet `@payloadcms/ui` (plugin miał własną kopię) →
|
||||||
|
`useField` z jednej instancji nie widział kontekstu z drugiej. Naprawa w Część D.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CZĘŚĆ D — peerDependencies (dublety zależności)
|
||||||
|
|
||||||
|
**Zasada:** wszystko, co dostarcza PROJEKT, jest `peerDependency` w pluginie,
|
||||||
|
NIE `dependency`. Inaczej menedżer instaluje własną kopię dla pluginu → dublet
|
||||||
|
→ React/Payload context się rozjeżdża (dwie instancje nie widzą się nawzajem).
|
||||||
|
|
||||||
|
Peer (projekt dostarcza): `payload`, `@payloadcms/ui`, `@payloadcms/next`,
|
||||||
|
`@payloadcms/plugin-*`, `react`, `react-dom`, `next`.
|
||||||
|
|
||||||
|
**Realny błąd:** `@payloadcms/ui` był tylko w devDependencies (brak w peer) →
|
||||||
|
pnpm dołożył kopię pluginowi → MaskedField/TestEmailButton/CookieBanner
|
||||||
|
wszystkie się psuły (hooki bez kontekstu). Naprawa: dodać do peerDependencies,
|
||||||
|
opublikować, w projekcie `rm -rf node_modules/@intecion/ipal-kit && pnpm add`.
|
||||||
|
|
||||||
|
Weryfikacja braku dubletu:
|
||||||
|
```bash
|
||||||
|
ls node_modules/@intecion/ipal-kit/node_modules/@payloadcms/ui 2>/dev/null \
|
||||||
|
&& echo "DUBLET ✗" || echo "OK ✓"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CZĘŚĆ E — NOWY PROJEKT KLIENCKI (kolejność)
|
||||||
|
|
||||||
|
Pełne szczegóły: [getting-started.md](./getting-started.md). Tu skrót kolejności.
|
||||||
|
|
||||||
|
1. **Szkielet** Payload 3 + Next 16, pnpm, Node 22
|
||||||
|
2. **`.npmrc`** — `legacy-peer-deps=true` + rejestr `@intecion`
|
||||||
|
3. **`pnpm add @intecion/ipal-kit`** + zależności peer
|
||||||
|
4. **build script z `--webpack`** (Next 16 + Payload; Turbopack konfliktuje)
|
||||||
|
5. **i18n.config.ts** — jedno źródło locale
|
||||||
|
6. **payload.config.ts** — ipalKit({...}), `email: mailAdapter()`
|
||||||
|
7. **Kolekcje/globale** — wszystko localized/upload (nic na sztywno)
|
||||||
|
8. **lib/content.ts + lib/payload.ts** — helpery, jedno źródło getCachedPayload
|
||||||
|
9. **proxy.ts** (NIE middleware.ts) — routing locale, obsługa roota
|
||||||
|
10. **Bloki** — dane przez enhanceProps, nie import lib (cykl)
|
||||||
|
11. **buildSlugField** zamiast ręcznego slug
|
||||||
|
12. **getLocalizedSlugs** zamiast zaszytej mapy ścieżek
|
||||||
|
13. **buildSecurityHeaders** w next.config
|
||||||
|
14. **Test:** root `/` przekierowuje, formularz wysyła, panel działa
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CZĘŚĆ F — EMAIL (SMTP vs Graph)
|
||||||
|
|
||||||
|
Pełne szczegóły: [email.md](./email.md). Decyzja transportu:
|
||||||
|
|
||||||
|
- **Klient na M365/Exchange** → Graph (SMTP AUTH na M365 często wyłączony)
|
||||||
|
- **Klient z własnym SMTP / Gmail** → SMTP
|
||||||
|
- **Przełącznik:** panel → Site Integrations → SMTP → Email Transport
|
||||||
|
- **Dyspozytor:** `email: mailAdapter()` czyta wybór przy każdej wysyłce
|
||||||
|
|
||||||
|
### Graph — checklist wdrożenia (Wasza strona, jednorazowo)
|
||||||
|
|
||||||
|
1. Azure: App registration → tenantId, clientId, clientSecret
|
||||||
|
2. Azure: Mail.Send APPLICATION permission + **Grant admin consent**
|
||||||
|
3. `.env` projektu: GRAPH_TENANT_ID, GRAPH_CLIENT_ID, GRAPH_CLIENT_SECRET, GRAPH_SENDER
|
||||||
|
4. Panel: From Name (nazwa nadawcy), From Address (→ reply-to)
|
||||||
|
|
||||||
|
### Realne pułapki Graph (wszystkie zdarzyły się w sesji)
|
||||||
|
|
||||||
|
| Błąd | Przyczyna | Naprawa |
|
||||||
|
|---|---|---|
|
||||||
|
| `ErrorSendAsDenied` | `from` ≠ sender | from.address = GRAPH_SENDER, klient w replyTo |
|
||||||
|
| nazwa „Noreply" mimo panelu | Exchange nadpisuje / cache klienta | display name skrzynki / sprawdź nagłówki |
|
||||||
|
| `Insufficient privileges` | brak admin consent | Grant admin consent w Azure |
|
||||||
|
| `AADSTS1002012` | zły scope | scope = `.../.default`, nie Mail.Send |
|
||||||
|
|
||||||
|
**Zasada from/replyTo:** `from.address` ZAWSZE = GRAPH_SENDER (wspólna skrzynka,
|
||||||
|
zero Send-As). Nazwa (`from.name`) z panelu — różna per projekt. Adres klienta
|
||||||
|
→ replyTo (odpowiedzi trafiają do klienta).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CZĘŚĆ G — CO NALEŻY DO PLUGINU, A CO DO PROJEKTU
|
||||||
|
|
||||||
|
Powtarzalne pytanie. Reguła: **jeśli zależy od danych/domen konkretnego projektu
|
||||||
|
→ projekt. Jeśli identyczne wszędzie → plugin.**
|
||||||
|
|
||||||
|
| Rzecz | Gdzie | Dlaczego |
|
||||||
|
|---|---|---|
|
||||||
|
| i18n, SEO meta, forms, consent, blog | plugin | uniwersalne |
|
||||||
|
| Powiadomienia (teksty wyników) | plugin | uniwersalne, per język z panelu |
|
||||||
|
| Zgoda RODO (enforcement) | plugin | uniwersalne, server-side |
|
||||||
|
| Nagłówki bezpieczeństwa (HSTS...) | plugin | identyczne wszędzie |
|
||||||
|
| Email (SMTP + Graph) | plugin | uniwersalne, konfiguracja z panelu/env |
|
||||||
|
| **CSP** | **projekt** | zależy od domen projektu |
|
||||||
|
| **schema.org / JSON-LD** | **projekt** | zależy od danych firmy |
|
||||||
|
| **Breadcrumbs** | **projekt** | render z danych routingu projektu |
|
||||||
|
| **Dane rejestrowe firmy** | **projekt** | różne per typ firmy |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CZĘŚĆ H — CHECKLIST PRZED „GOTOWE"
|
||||||
|
|
||||||
|
Nie mów „działa", dopóki:
|
||||||
|
|
||||||
|
- [ ] `pnpm build --webpack` przechodzi lokalnie (nie tylko dev)
|
||||||
|
- [ ] root `/` przekierowuje na locale (bez middleware.ts)
|
||||||
|
- [ ] formularz wysyła (test przez panel: Send test)
|
||||||
|
- [ ] panel: wszystkie teksty/obrazy edytowalne (nic na sztywno)
|
||||||
|
- [ ] brak dubletu @payloadcms/ui (Część D)
|
||||||
|
- [ ] grep potwierdza wersję pluginu w node_modules
|
||||||
|
- [ ] sekrety w .env (nie w repo), maskowane w panelu
|
||||||
|
- [ ] brak plików middleware.ts, brak zaszytej mapy slugów
|
||||||
|
- [ ] strona 404 (not-found.tsx) — edytowalna, per język, link powrotu
|
||||||
|
- [ ] formularze z buildera w panelu (NIE własne hardkodowane)
|
||||||
|
- [ ] compliance: polityki, baner cookies, zgoda RODO w formularzach
|
||||||
|
(patrz [wymagania-prawne.md](./wymagania-prawne.md))
|
||||||
+4
-1
@@ -1,5 +1,7 @@
|
|||||||
# IPAL — Dokumentacja modułów
|
# IPAL — Dokumentacja modułów
|
||||||
|
|
||||||
|
> **Zaczynasz wdrożenie?** Przeczytaj najpierw [WDROZENIE-PLAYBOOK.md](./WDROZENIE-PLAYBOOK.md) — sztywna procedura, kolejność, realne przykłady błędów.
|
||||||
|
|
||||||
**Instalacja pakietu** (token Gitea, rejestr vs repozytorium) → główny
|
**Instalacja pakietu** (token Gitea, rejestr vs repozytorium) → główny
|
||||||
[README](../README.md).
|
[README](../README.md).
|
||||||
**Nowy projekt krok po kroku** → [getting-started.md](./getting-started.md).
|
**Nowy projekt krok po kroku** → [getting-started.md](./getting-started.md).
|
||||||
@@ -106,6 +108,7 @@ export default buildConfig({
|
|||||||
| access | Role admin > editor > user, kontrola dostępu | [access.md](./access.md) |
|
| access | Role admin > editor > user, kontrola dostępu | [access.md](./access.md) |
|
||||||
| payload-helpers | getSiteSettings / getSiteIntegrations | [payload-helpers.md](./payload-helpers.md) |
|
| payload-helpers | getSiteSettings / getSiteIntegrations | [payload-helpers.md](./payload-helpers.md) |
|
||||||
| seo | Metadata, hreflang, auto-fill, plugin-seo | [seo.md](./seo.md) |
|
| seo | Metadata, hreflang, auto-fill, plugin-seo | [seo.md](./seo.md) |
|
||||||
|
| architektura-tresci | **Jak budować, żeby klient wszystko edytował** (filozofia CMS) | [architektura-tresci.md](./architektura-tresci.md) |
|
||||||
| blocks | RenderBlocks — silnik renderowania bloków | [blocks.md](./blocks.md) |
|
| blocks | RenderBlocks — silnik renderowania bloków | [blocks.md](./blocks.md) |
|
||||||
| consent | Banner cookies GDPR, Google Consent Mode | [consent.md](./consent.md) |
|
| consent | Banner cookies GDPR, Google Consent Mode | [consent.md](./consent.md) |
|
||||||
| turnstile | Cloudflare Turnstile (widget + verify) | [turnstile.md](./turnstile.md) |
|
| turnstile | Cloudflare Turnstile (widget + verify) | [turnstile.md](./turnstile.md) |
|
||||||
@@ -118,7 +121,7 @@ export default buildConfig({
|
|||||||
| content | Blog/archiwa: kolekcje pod stroną-archiwum, listing, paginacja | [content.md](./content.md) |
|
| content | Blog/archiwa: kolekcje pod stroną-archiwum, listing, paginacja | [content.md](./content.md) |
|
||||||
|
|
||||||
Nowy projekt krok po kroku: [getting-started.md](./getting-started.md)
|
Nowy projekt krok po kroku: [getting-started.md](./getting-started.md)
|
||||||
Referencja wdrożenia frontu: [frontend-setup.md](./frontend-setup.md)
|
Referencja wdrożenia frontu: [getting-started.md](./getting-started.md)
|
||||||
Wydawanie nowych wersji wtyczki: [publishing.md](./publishing.md)
|
Wydawanie nowych wersji wtyczki: [publishing.md](./publishing.md)
|
||||||
Jak komendy łączą się z Gitea (dla instalujących): [gitea-commands.md](./gitea-commands.md)
|
Jak komendy łączą się z Gitea (dla instalujących): [gitea-commands.md](./gitea-commands.md)
|
||||||
Working with a project repo on Gitea (clone/pull/push): [gitea-workflow.md](./gitea-workflow.md) · [🇵🇱 PL](./gitea-workflow.pl.md)
|
Working with a project repo on Gitea (clone/pull/push): [gitea-workflow.md](./gitea-workflow.md) · [🇵🇱 PL](./gitea-workflow.pl.md)
|
||||||
|
|||||||
+36
-1
@@ -109,7 +109,7 @@ Dostępne tokeny (każdy ma odpowiednik `-dark` używany pod `dark:`):
|
|||||||
| `--ipal-hover` | `#f5f5f5` | hover przycisków drugorzędnych |
|
| `--ipal-hover` | `#f5f5f5` | hover przycisków drugorzędnych |
|
||||||
| `--ipal-radius` | `0.375rem` | zaokrąglenie przycisków |
|
| `--ipal-radius` | `0.375rem` | zaokrąglenie przycisków |
|
||||||
|
|
||||||
Wymaga `@source` skanującego pakiet (patrz frontend-setup.md) — inaczej Tailwind
|
Wymaga `@source` skanującego pakiet (patrz getting-started.md) — inaczej Tailwind
|
||||||
nie wygeneruje tych klas.
|
nie wygeneruje tych klas.
|
||||||
|
|
||||||
### Gdy tokeny nie wystarczą
|
### Gdy tokeny nie wystarczą
|
||||||
@@ -132,3 +132,38 @@ domyślny (nie dokleja się).
|
|||||||
|
|
||||||
Elementy mają też `data-ipal="banner"` i `data-ipal="cookie-button"` — stabilne
|
Elementy mają też `data-ipal="banner"` i `data-ipal="cookie-button"` — stabilne
|
||||||
uchwyty do CSS albo testów e2e.
|
uchwyty do CSS albo testów e2e.
|
||||||
|
|
||||||
|
## Locale jako cookie functional (wbudowane)
|
||||||
|
|
||||||
|
Plugin sam zarządza jedną cookie functional: **`NEXT_LOCALE`** (wybór języka).
|
||||||
|
Nie musisz nic konfigurować — działa out of the box:
|
||||||
|
|
||||||
|
- **Zapis za zgodą.** Middleware zapisuje `NEXT_LOCALE` tylko, gdy użytkownik
|
||||||
|
zaakceptował kategorię **functional**. Bez zgody język działa (negocjacja per
|
||||||
|
żądanie), ale nie jest utrwalany w cookie.
|
||||||
|
- **Sprzątanie po cofnięciu.** Gdy użytkownik cofnie zgodę na functional, hook
|
||||||
|
consent usuwa `NEXT_LOCALE` automatycznie. Odpowiada za to `DEFAULT_COOKIE_MAP`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const DEFAULT_COOKIE_MAP = {
|
||||||
|
functional: [LOCALE_COOKIE_NAME], // 'NEXT_LOCALE' — plugin zna własną cookie
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Twoje własne cookie functional/analytics
|
||||||
|
|
||||||
|
Jeśli ustawiasz własne cookie podlegające zgodzie, rozszerz mapę — hook wtedy
|
||||||
|
sprzątnie też Twoje po cofnięciu zgody:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
useConsent({
|
||||||
|
functional: ['NEXT_LOCALE', 'moje-ustawienie'],
|
||||||
|
analytics: ['_ga', '_gid'],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Przekazana mapa zastępuje domyślną — pamiętaj dołączyć `NEXT_LOCALE`, jeśli
|
||||||
|
chcesz zachować sprzątanie locale (albo zaimportuj `LOCALE_COOKIE_NAME` i dodaj).
|
||||||
|
|
||||||
|
> Mechanizm zgody dla locale jest opisany też od strony i18n:
|
||||||
|
> [i18n.md](./i18n.md#cookie-locale-a-zgoda-rodo).
|
||||||
@@ -150,3 +150,30 @@ Jeśli `from` w panelu = cudza domena (np. `[email protected]`), a sender =
|
|||||||
domenę. Najbezpieczniej: `from` = `GRAPH_SENDER` (Wasza skrzynka), a adres
|
domenę. Najbezpieczniej: `from` = `GRAPH_SENDER` (Wasza skrzynka), a adres
|
||||||
klienta w `replyTo` (odpowiedzi trafią do klienta). Wtedy Send-As na cudze
|
klienta w `replyTo` (odpowiedzi trafią do klienta). Wtedy Send-As na cudze
|
||||||
domeny nie jest potrzebny.
|
domeny nie jest potrzebny.
|
||||||
|
|
||||||
|
## Przełącznik transportu — mailAdapter
|
||||||
|
|
||||||
|
`mailAdapter()` to dyspozytor: jeden adapter wpięty w config, wybiera transport
|
||||||
|
(SMTP/Graph) przy KAŻDEJ wysyłce, czytając ustawienie z panelu. Dzięki temu
|
||||||
|
przełącznik działa w panelu (Payload buduje adapter raz przy starcie, więc nie
|
||||||
|
da się podmieniać osobnych adapterów w runtime — dyspozytor deleguje wewnątrz).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// payload.config.ts — JEDEN adapter, wybór wewnątrz
|
||||||
|
import { mailAdapter } from '@intecion/ipal-kit'
|
||||||
|
email: mailAdapter()
|
||||||
|
```
|
||||||
|
|
||||||
|
Panel → Site Integrations → SMTP → **Email Transport** (SMTP / Microsoft Graph).
|
||||||
|
Dyspozytor czyta ten wybór per wysyłka. Guard: jeśli wybrano Graph, ale brak
|
||||||
|
sekretów w .env → log + fallback na SMTP (nie cicha awaria).
|
||||||
|
|
||||||
|
## Test wysyłki — przycisk w panelu
|
||||||
|
|
||||||
|
W tabie SMTP jest przycisk **Send test**: podaj adres, kliknij, wyślij testowy
|
||||||
|
mail przez AKTUALNY transport. Pokazuje wynik (✓/✗ z błędem). Endpoint
|
||||||
|
`POST /api/ipal/test-email` (admin-only). Zapisz zmiany przed testem — endpoint
|
||||||
|
czyta z bazy, nie z pola na ekranie.
|
||||||
|
|
||||||
|
> Bezcenne przy diagnozie Graph — od razu widzisz `ErrorSendAsDenied`,
|
||||||
|
> `Insufficient privileges` itp. zamiast zgadywać.
|
||||||
@@ -4,6 +4,31 @@ Wpina `@payloadcms/plugin-form-builder` (kolekcje forms + form-submissions) i
|
|||||||
dostarcza `submitForm` — wywoływalną z frontu funkcję, która spina: weryfikację
|
dostarcza `submitForm` — wywoływalną z frontu funkcję, która spina: weryfikację
|
||||||
Turnstile → zapis zgłoszenia → wysyłkę maili (naszym senderem).
|
Turnstile → zapis zgłoszenia → wysyłkę maili (naszym senderem).
|
||||||
|
|
||||||
|
## ⚠️ ZASADA: formularz POCHODZI z buildera w panelu (obowiązkowe)
|
||||||
|
|
||||||
|
**Formularze buduje redaktor w panelu** (kolekcja Forms), NIE deweloper w kodzie.
|
||||||
|
To jest CMS — klient sam definiuje pola, etykiety, komunikaty, odbiorcę. Front
|
||||||
|
tylko RENDERUJE formularz z panelu i wysyła przez `submitForm`.
|
||||||
|
|
||||||
|
**NIGDY nie twórz własnego, hardkodowanego formularza** — z ręcznie wpisanymi
|
||||||
|
polami, etykietami w JSX, własną walidacją. To łamie „nic na sztywno" (klient nie
|
||||||
|
zmieni pól ani tekstów) i omija cały mechanizm pluginu (Turnstile, rate-limit,
|
||||||
|
consent RODO, powiadomienia).
|
||||||
|
|
||||||
|
| ŹLE (własny formularz) | DOBRZE (builder pluginu) |
|
||||||
|
|---|---|
|
||||||
|
| `<input name="email" placeholder="Email" />` w JSX | pola z kolekcji Forms (panel) |
|
||||||
|
| etykiety/komunikaty w kodzie | etykiety per język w panelu |
|
||||||
|
| własna walidacja/wysyłka | `submitForm` (Turnstile+consent+mail) |
|
||||||
|
| klient nie zmieni formularza | klient edytuje pola w panelu |
|
||||||
|
|
||||||
|
**Jak poprawnie:** redaktor tworzy formularz w kolekcji Forms → front pobiera
|
||||||
|
jego definicję → renderuje pola dynamicznie → wysyła przez `submitForm`. Pola,
|
||||||
|
etykiety, komunikaty, odbiorca — wszystko z panelu.
|
||||||
|
|
||||||
|
Jeśli formularz wymaga pola, którego builder nie ma — dodaj je przez konfigurację
|
||||||
|
`fields` (patrz niżej) albo rozbuduj plugin. NIE hardkoduj własnego formularza.
|
||||||
|
|
||||||
## Zależność
|
## Zależność
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
+213
-227
@@ -1,14 +1,18 @@
|
|||||||
# Nowy projekt — krok po kroku
|
# Setup projektu — od zera do wdrożenia
|
||||||
|
|
||||||
> **Instalacja pluginu** (token Gitea, rejestr vs git) jest opisana w głównym
|
Pełny przewodnik: od pustego katalogu do działającej, wielojęzycznej strony z
|
||||||
|
blokami, consentem, formularzem i SEO. Łączy szkielet projektu (kolejność
|
||||||
|
kroków) z wymaganiami frontendu (Tailwind, trasy, bloki, metadata).
|
||||||
|
|
||||||
|
> **Instalacja pluginu** (token Gitea, rejestr vs git) jest w głównym
|
||||||
> [README](../README.md). Ten przewodnik zakłada, że `@intecion/ipal-kit` jest
|
> [README](../README.md). Ten przewodnik zakłada, że `@intecion/ipal-kit` jest
|
||||||
> już zainstalowany, i przeprowadza przez **konfigurację** projektu.
|
> zainstalowany, i przeprowadza przez konfigurację.
|
||||||
|
>
|
||||||
|
> **Zaczynasz wdrożenie produkcyjne?** Najpierw [WDROZENIE-PLAYBOOK.md](./WDROZENIE-PLAYBOOK.md)
|
||||||
|
> — zasady, procedura, pułapki.
|
||||||
|
|
||||||
Od pustego katalogu do działającej, wielojęzycznej strony z blokami, consentem i
|
Kolejność jest istotna — kilka kroków zależy od poprzednich (schemat bazy,
|
||||||
formularzem. Kolejność jest istotna: kilka kroków zależy od poprzednich (schemat
|
importMap, kolejność wpięcia). Zakłada: pnpm, Node 22, Next 16.
|
||||||
bazy, importMap, kolejność wpięcia).
|
|
||||||
|
|
||||||
Zakłada: pnpm, Node 20+, SQLite (dla Postgres zmienia się tylko adapter).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,21 +20,22 @@ Zakłada: pnpm, Node 20+, SQLite (dla Postgres zmienia się tylko adapter).
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx create-payload-app@latest moj-projekt
|
npx create-payload-app@latest moj-projekt
|
||||||
# → Blank, SQLite
|
# → Blank, SQLite (dev) / Postgres (prod)
|
||||||
cd moj-projekt
|
cd moj-projekt
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Plugin i zależności
|
## 2. Plugin i zależności
|
||||||
|
|
||||||
Zainstaluj `@intecion/ipal-kit` zgodnie z [README](../README.md) (rejestr Gitea
|
Zainstaluj `@intecion/ipal-kit` zgodnie z [README](../README.md). Dodaj
|
||||||
albo bezpośrednio z repozytorium — wymaga tokenu). Następnie dodaj zależności
|
zależności współdzielone z Payloadem, których plugin nie zaciąga sam:
|
||||||
współdzielone z Payloadem, których plugin nie zaciąga sam:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm add @payloadcms/plugin-seo@3.84.1 @payloadcms/plugin-form-builder@3.84.1 \
|
pnpm add @payloadcms/plugin-seo @payloadcms/plugin-form-builder \
|
||||||
nodemailer lucide-react slugify server-only
|
nodemailer lucide-react slugify server-only
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Spójność wersji @payloadcms/* (KRYTYCZNE)
|
||||||
|
|
||||||
Wersje `@payloadcms/*` **muszą** zgadzać się z wersją `payload` — inaczej
|
Wersje `@payloadcms/*` **muszą** zgadzać się z wersją `payload` — inaczej
|
||||||
zagnieżdżone pluginy się nie wpinają (pusty tab SEO, brak kolekcji Forms) albo
|
zagnieżdżone pluginy się nie wpinają (pusty tab SEO, brak kolekcji Forms) albo
|
||||||
projekt się wywala. Wymuś w `package.json`:
|
projekt się wywala. Wymuś w `package.json`:
|
||||||
@@ -38,13 +43,13 @@ projekt się wywala. Wymuś w `package.json`:
|
|||||||
```json
|
```json
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"payload": "3.84.1",
|
"payload": "3.88.0",
|
||||||
"@payloadcms/ui": "3.84.1",
|
"@payloadcms/ui": "3.88.0",
|
||||||
"@payloadcms/next": "3.84.1",
|
"@payloadcms/next": "3.88.0",
|
||||||
"@payloadcms/db-sqlite": "3.84.1",
|
"@payloadcms/db-postgres": "3.88.0",
|
||||||
"@payloadcms/richtext-lexical": "3.84.1",
|
"@payloadcms/richtext-lexical": "3.88.0",
|
||||||
"@payloadcms/plugin-seo": "3.84.1",
|
"@payloadcms/plugin-seo": "3.88.0",
|
||||||
"@payloadcms/plugin-form-builder": "3.84.1"
|
"@payloadcms/plugin-form-builder": "3.88.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -53,11 +58,17 @@ projekt się wywala. Wymuś w `package.json`:
|
|||||||
rm -rf node_modules pnpm-lock.yaml && pnpm install
|
rm -rf node_modules pnpm-lock.yaml && pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Build script z --webpack (Next 16)
|
||||||
|
|
||||||
|
Next 16 domyślnie Turbopack, który konfliktuje z withPayload. W `package.json`:
|
||||||
|
```json
|
||||||
|
"build": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" next build --webpack"
|
||||||
|
```
|
||||||
|
|
||||||
## 3. Konfiguracja locale — jedno źródło
|
## 3. Konfiguracja locale — jedno źródło
|
||||||
|
|
||||||
Middleware działa przed Payloadem i potrzebuje listy locale synchronicznie, więc
|
Proxy działa przed Payloadem i potrzebuje listy locale synchronicznie, więc nie
|
||||||
nie może jej czytać z gotowego configu. Wydziel osobny plik i importuj w obu
|
może jej czytać z gotowego configu. Wydziel osobny plik, importuj wszędzie:
|
||||||
miejscach:
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/i18n.config.ts
|
// src/i18n.config.ts
|
||||||
@@ -67,25 +78,24 @@ export const i18nConfig = {
|
|||||||
{ code: 'pl', label: 'Polski' },
|
{ code: 'pl', label: 'Polski' },
|
||||||
{ code: 'en', label: 'English' },
|
{ code: 'en', label: 'English' },
|
||||||
],
|
],
|
||||||
} as const
|
} as const // as const — inaczej TS nie uzna locales za niepustą tuple
|
||||||
```
|
```
|
||||||
|
|
||||||
`as const` jest konieczne — bez niego TS nie uzna `locales` za niepustą listę.
|
Importuj w: `payload.config` (ipalKit({ i18n: i18nConfig })) i `proxy.ts`.
|
||||||
|
|
||||||
## 4. payload.config.ts
|
## 4. payload.config.ts
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit'
|
import { ipalKit, mailAdapter } from '@intecion/ipal-kit'
|
||||||
import { i18nConfig } from '@/i18n.config'
|
import { i18nConfig } from '@/i18n.config'
|
||||||
import { Pages } from '@/collections/Pages'
|
import { Pages } from '@/collections/Pages'
|
||||||
|
|
||||||
export default buildConfig({
|
export default buildConfig({
|
||||||
// …reszta z template'u
|
|
||||||
collections: [Users, Media, Pages],
|
collections: [Users, Media, Pages],
|
||||||
|
|
||||||
// SMTP z panelu zamiast env — czyta Site Integrations przy każdym wysłaniu.
|
// Dyspozytor email: czyta transport (SMTP/Graph) z panelu przy każdej wysyłce.
|
||||||
// Bez tego maile form-buildera nie wyjdą (Payload podstawia mocka).
|
// Bez tego maile form-buildera nie wyjdą (Payload podstawia mocka).
|
||||||
email: panelSmtpAdapter(),
|
email: mailAdapter(),
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
ipalKit({
|
ipalKit({
|
||||||
@@ -113,11 +123,11 @@ export const Pages: CollectionConfig = {
|
|||||||
access: { read: () => true },
|
access: { read: () => true },
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'title', type: 'text', required: true, localized: true },
|
{ name: 'title', type: 'text', required: true, localized: true },
|
||||||
buildSlugField({ from: 'title' }),
|
buildSlugField({ from: 'title' }), // NIGDY ręczny slug — plugin to ma
|
||||||
{
|
{
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
type: 'blocks',
|
type: 'blocks',
|
||||||
blocks: [ContentBlock], // NIGDY pusta lista — Payload się wywala
|
blocks: [ContentBlock], // NIGDY pusta lista — Payload crashuje
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -158,15 +168,31 @@ import type { BlockComponentMap } from '@intecion/ipal-kit/rsc'
|
|||||||
import { ContentBlockComponent } from '@/blocks/Content/Component'
|
import { ContentBlockComponent } from '@/blocks/Content/Component'
|
||||||
|
|
||||||
export const blockRegistry: BlockComponentMap = {
|
export const blockRegistry: BlockComponentMap = {
|
||||||
content: ContentBlockComponent,
|
content: ContentBlockComponent, // klucz = slug bloku
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Klucz w rejestrze = `slug` bloku.
|
> **Jak budować treść, żeby klient mógł wszystko edytować** (filozofia
|
||||||
|
> CMS, kolejność komponent→blok→strona): [architektura-tresci.md](./architektura-tresci.md).
|
||||||
|
|
||||||
## 7. Tailwind
|
**Puste `blocks: []` crashuje** (traverseFields) — zawsze co najmniej jeden blok.
|
||||||
|
|
||||||
Blank template go nie ma, a komponenty pluginu (banner cookies) są w Tailwindzie.
|
### enhanceProps — wstrzykiwanie danych server-side do bloków
|
||||||
|
|
||||||
|
Bloki NIE importują `lib/*` (cykl importów). Wartości server-side (turnstileSiteKey,
|
||||||
|
odbiorca formularza) wstrzykuje się przez enhanceProps — bez wiedzy pluginu:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const enhanceProps = ({ block }) => {
|
||||||
|
if (block.blockType === 'formBlock') return { turnstileSiteKey, notificationTo }
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Tailwind (WYMÓG)
|
||||||
|
|
||||||
|
Blank template go nie ma, a komponenty pluginu (banner cookies, Turnstile) są w
|
||||||
|
czystym Tailwindzie.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm add tailwindcss @tailwindcss/postcss
|
pnpm add tailwindcss @tailwindcss/postcss
|
||||||
@@ -183,16 +209,30 @@ export default { plugins: { '@tailwindcss/postcss': {} } }
|
|||||||
@source "../../../node_modules/@intecion/ipal-kit/dist/**/*.js";
|
@source "../../../node_modules/@intecion/ipal-kit/dist/**/*.js";
|
||||||
```
|
```
|
||||||
|
|
||||||
`@source` jest **konieczny** — Tailwind nie skanuje `node_modules`, więc bez
|
**`@source` jest KONIECZNY** — Tailwind nie skanuje `node_modules`, więc bez
|
||||||
niego klasy komponentów pluginu nie powstaną i banner wyrenderuje się goły.
|
niego klasy komponentów pluginu nie powstaną (banner wyrenderuje się goły).
|
||||||
Ścieżka jest relatywna do pliku CSS.
|
Ścieżka relatywna do pliku CSS.
|
||||||
|
|
||||||
## 8. Proxy (dawniej middleware)
|
### Przestylowanie pod klienta
|
||||||
|
|
||||||
> **Next 16:** konwencja `middleware.ts` jest przestarzała — nazwa pliku to teraz
|
Komponenty pluginu mają domyślny wygląd. Kolory/zaokrąglenia przez CSS custom
|
||||||
> `proxy.ts`, a funkcja `proxy` zamiast `middleware`. Logika pluginu bez zmian:
|
properties (fallbacki wbudowane):
|
||||||
> `createLocaleMiddleware` działa tak samo. Migracja jednej komendy:
|
```css
|
||||||
> `npx @next/codemod@canary middleware-to-proxy .`
|
:root {
|
||||||
|
--ipal-primary: #16a34a;
|
||||||
|
--ipal-radius: 1rem;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Pełna lista tokenów + opcja classNames: [consent.md](./consent.md).
|
||||||
|
|
||||||
|
## 8. Proxy (routing locale) — NIGDY middleware.ts
|
||||||
|
|
||||||
|
> **Next 16 używa `proxy.ts`, NIE `middleware.ts`.** Plik `proxy.ts`, funkcja
|
||||||
|
> `proxy`. `middleware.ts` jest przestarzały — jeśli istnieje, USUŃ go. Nigdy
|
||||||
|
> obu naraz. Migracja starego: `npx @next/codemod@canary middleware-to-proxy .`
|
||||||
|
>
|
||||||
|
> Import z pluginu zostaje `@intecion/ipal-kit/next/middleware` — to nazwa
|
||||||
|
> subpath eksportu, NIE nazwa pliku. Nie myl ich.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/proxy.ts
|
// src/proxy.ts
|
||||||
@@ -205,104 +245,89 @@ const localeMiddleware = createLocaleMiddleware({ config: i18nConfig })
|
|||||||
|
|
||||||
export function proxy(request: NextRequest) {
|
export function proxy(request: NextRequest) {
|
||||||
const result = localeMiddleware(request)
|
const result = localeMiddleware(request)
|
||||||
if (result.type === 'next') return NextResponse.next()
|
|
||||||
|
|
||||||
const response = NextResponse.redirect(result.location)
|
// Cookie zapisywany w OBU wynikach (redirect na '/' i next przy zmianie
|
||||||
// cookie tylko gdy jest zgoda na kategorię functional — inaczej undefined
|
// języka), TYLKO gdy jest zgoda na functional.
|
||||||
if (result.cookie) response.cookies.set(result.cookie.name, result.cookie.value)
|
const response =
|
||||||
|
result.type === 'next'
|
||||||
|
? NextResponse.next()
|
||||||
|
: NextResponse.redirect(result.location)
|
||||||
|
|
||||||
|
if (result.cookie) {
|
||||||
|
response.cookies.set(result.cookie.name, result.cookie.value)
|
||||||
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
// INLINE, nie import — Next analizuje ten obiekt statycznie i nie wykonuje
|
// Matcher INLINE (nie import) — Next analizuje statycznie, nie wykonuje importów.
|
||||||
// importów. Importowana stała zostanie zignorowana, proxy złapie /admin
|
// Import stałej byłby zignorowany → proxy złapałby /admin /_next /api → 500.
|
||||||
// i /_next, i wszystko zwróci 500.
|
// Ten wzorzec łapie root '/' (negocjacja locale), pomija api/admin/_next/pliki.
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ['/((?!api|admin|_next|.*\\..*).*)'],
|
matcher: ['/((?!api|admin|_next|.*\\..*).*)'],
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> Import z pluginu zostaje `@intecion/ipal-kit/next/middleware` — to nazwa
|
### Zlokalizowane ścieżki — getLocalizedSlugs (NIGDY zaszyta mapa)
|
||||||
> subpath eksportu w pakiecie, niezależna od tego, czy plik projektu nazywa się
|
|
||||||
> `middleware.ts` czy `proxy.ts`.
|
|
||||||
|
|
||||||
## 9. Warstwa dostępu do danych
|
Do przełącznika języka / budowania ścieżek NIE twórz zaszytej mapy slugów.
|
||||||
|
Slugi są w bazie (pole `slug` localized):
|
||||||
Next uruchamia `generateMetadata` i komponent strony niezależnie — `cache()`
|
|
||||||
sprawia, że nie pytają bazy dwa razy o to samo.
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/lib/payload.ts
|
import { getLocalizedSlugs, switchLocalePath } from '@intecion/ipal-kit'
|
||||||
import { cache } from 'react'
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@/payload.config'
|
|
||||||
|
|
||||||
export const getCachedPayload = cache(async () => getPayload({ config: await config }))
|
const doc = await payload.findByID({ collection: 'pages', id, locale: 'all' })
|
||||||
|
const slugs = getLocalizedSlugs({ slugField: doc.slug, config: i18nConfig })
|
||||||
|
switchLocalePath({ slugs, targetLocale: 'en', config: i18nConfig }) // → '/en/about'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Warstwa dostępu do danych — lib/ (jedno źródło)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/lib/content.ts — JEDYNE źródło helperów pluginu
|
||||||
|
import { createContentHelpers } from '@intecion/ipal-kit'
|
||||||
|
import payloadConfig from '@/payload.config'
|
||||||
|
import { i18nConfig } from '@/i18n.config'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
getCachedPayload, getSettings, getConfiguredLocales, resolveRoute, getEntries, robots,
|
||||||
|
} = createContentHelpers({
|
||||||
|
config: payloadConfig, // PAYLOAD config (nie i18n!)
|
||||||
|
content: { collections: [] },
|
||||||
|
i18n: i18nConfig, // i18n OSOBNO
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/lib/payload.ts — funkcje projektu, typowane
|
||||||
|
import { cache } from 'react'
|
||||||
|
import { getSiteSettings } from '@intecion/ipal-kit'
|
||||||
|
import { getCachedPayload } from './content' // z content, nie osobny getPayload
|
||||||
|
import type { SiteSetting } from '@/payload-types'
|
||||||
|
|
||||||
export const getSettings = cache(async (locale: string) =>
|
export const getSettings = cache(async (locale: string) =>
|
||||||
(await getCachedPayload()).findGlobal({
|
getSiteSettings<SiteSetting>(await getCachedPayload(), { locale: locale as never, depth: 2 }),
|
||||||
slug: 'site-settings',
|
|
||||||
locale: locale as 'pl' | 'en',
|
|
||||||
depth: 2,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
> NIE twórz `lib/pages.ts` (resolvePage) ani `lib/locales.ts` — plugin ma
|
||||||
// src/lib/locales.ts
|
> `resolveRoute` i `getConfiguredLocales`. Duplikaty = rozjazd.
|
||||||
import { cache } from 'react'
|
|
||||||
import config from '@/payload.config'
|
|
||||||
|
|
||||||
export const getConfiguredLocales = cache(async (): Promise<string[]> => {
|
|
||||||
const payloadConfig = await config
|
|
||||||
return payloadConfig.localization ? payloadConfig.localization.locales.map((l) => l.code) : []
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// src/lib/pages.ts
|
|
||||||
import { cache } from 'react'
|
|
||||||
import type { Page, SiteSetting } from '@/payload-types'
|
|
||||||
import { getCachedPayload, getSettings } from './payload'
|
|
||||||
|
|
||||||
export const resolvePage = cache(
|
|
||||||
async (locale: string, slugPath: string | null): Promise<Page | null> => {
|
|
||||||
if (!slugPath) {
|
|
||||||
// Strona główna z System Pages — edytor może ją zmienić bez zmiany kodu.
|
|
||||||
const settings = (await getSettings(locale)) as SiteSetting
|
|
||||||
const homepage = settings.homepage
|
|
||||||
return homepage && typeof homepage === 'object' ? homepage : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = await getCachedPayload()
|
|
||||||
const result = await payload.find({
|
|
||||||
collection: 'pages',
|
|
||||||
where: { slug: { equals: slugPath } },
|
|
||||||
locale: locale as 'pl' | 'en',
|
|
||||||
depth: 2,
|
|
||||||
limit: 1,
|
|
||||||
})
|
|
||||||
return result.docs[0] ?? null
|
|
||||||
},
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 10. Trasy
|
## 10. Trasy
|
||||||
|
|
||||||
Usuń starter — `(frontend)/layout.tsx` i `(frontend)/page.tsx`. Rootem zostaje
|
Usuń starter — `(frontend)/layout.tsx` i `(frontend)/page.tsx`. Rootem zostaje
|
||||||
layout locale, bo `<html lang>` musi znać język, a `(frontend)` jest ponad
|
layout locale (bo `<html lang>` musi znać język).
|
||||||
segmentem `[locale]`. Każdy trafia na ścieżkę z locale — middleware przekierowuje.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
src/app/(frontend)/
|
src/app/(frontend)/
|
||||||
styles.css
|
styles.css
|
||||||
[locale]/
|
[locale]/
|
||||||
layout.tsx
|
layout.tsx # walidacja locale + ConsentProvider + Analytics
|
||||||
[[...slug]]/
|
[[...slug]]/
|
||||||
page.tsx
|
page.tsx # render bloków
|
||||||
```
|
```
|
||||||
|
|
||||||
`[[...slug]]` — **podwójne** nawiasy. Pojedyncze `[slug]` dają string zamiast
|
**`[[...slug]]` — PODWÓJNE nawiasy** (opcjonalny catch-all). Pojedyncze `[slug]`
|
||||||
tablicy (`slug.join is not a function`) i nie łapią samego `/pl`.
|
dają string (`slug.join is not a function`) i nie łapią samego `/pl`.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/app/(frontend)/[locale]/layout.tsx
|
// src/app/(frontend)/[locale]/layout.tsx
|
||||||
@@ -310,8 +335,8 @@ import { notFound } from 'next/navigation'
|
|||||||
import { getConsentTexts, getAnalyticsConfig } from '@intecion/ipal-kit'
|
import { getConsentTexts, getAnalyticsConfig } from '@intecion/ipal-kit'
|
||||||
import { ConsentProvider, CookieBanner, CookieButton, Analytics } from '@intecion/ipal-kit/client'
|
import { ConsentProvider, CookieBanner, CookieButton, Analytics } from '@intecion/ipal-kit/client'
|
||||||
import { i18nConfig } from '@/i18n.config'
|
import { i18nConfig } from '@/i18n.config'
|
||||||
import { getCachedPayload, getSettings } from '@/lib/payload'
|
import { getCachedPayload, getConfiguredLocales } from '@/lib/content'
|
||||||
import { getConfiguredLocales } from '@/lib/locales'
|
import { getSettings } from '@/lib/payload'
|
||||||
import '../styles.css'
|
import '../styles.css'
|
||||||
|
|
||||||
export default async function LocaleLayout({ children, params }) {
|
export default async function LocaleLayout({ children, params }) {
|
||||||
@@ -325,13 +350,9 @@ export default async function LocaleLayout({ children, params }) {
|
|||||||
|
|
||||||
const [texts, analytics] = await Promise.all([
|
const [texts, analytics] = await Promise.all([
|
||||||
getConsentTexts({
|
getConsentTexts({
|
||||||
config: i18nConfig,
|
config: i18nConfig, locale, payload,
|
||||||
locale,
|
privacyPolicy: privacyPage && typeof privacyPage === 'object'
|
||||||
payload,
|
? { page: privacyPage, label: 'Polityka prywatności' } : undefined,
|
||||||
privacyPolicy:
|
|
||||||
privacyPage && typeof privacyPage === 'object'
|
|
||||||
? { page: privacyPage, label: 'Polityka prywatności' }
|
|
||||||
: undefined,
|
|
||||||
}),
|
}),
|
||||||
getAnalyticsConfig(payload),
|
getAnalyticsConfig(payload),
|
||||||
])
|
])
|
||||||
@@ -343,7 +364,7 @@ export default async function LocaleLayout({ children, params }) {
|
|||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
<CookieBanner />
|
<CookieBanner />
|
||||||
<CookieButton />
|
<CookieButton />
|
||||||
<Analytics {...analytics} />
|
<Analytics {...analytics} /> {/* WEWNĄTRZ ConsentProvider */}
|
||||||
</ConsentProvider>
|
</ConsentProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -364,8 +385,7 @@ import { RenderBlocks } from '@intecion/ipal-kit/rsc'
|
|||||||
import { createPageMetadata } from '@intecion/ipal-kit'
|
import { createPageMetadata } from '@intecion/ipal-kit'
|
||||||
import { i18nConfig } from '@/i18n.config'
|
import { i18nConfig } from '@/i18n.config'
|
||||||
import { blockRegistry } from '@/blocks/registry'
|
import { blockRegistry } from '@/blocks/registry'
|
||||||
import { getCachedPayload } from '@/lib/payload'
|
import { getCachedPayload, resolveRoute } from '@/lib/content'
|
||||||
import { resolvePage } from '@/lib/pages'
|
|
||||||
|
|
||||||
const pageMetadata = createPageMetadata({
|
const pageMetadata = createPageMetadata({
|
||||||
config: i18nConfig,
|
config: i18nConfig,
|
||||||
@@ -377,104 +397,92 @@ export async function generateMetadata({ params }): Promise<Metadata> {
|
|||||||
return pageMetadata({ payload: await getCachedPayload(), locale, slug })
|
return pageMetadata({ payload: await getCachedPayload(), locale, slug })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ params }) {
|
export default async function Page({ params, searchParams }) {
|
||||||
const { locale, slug } = await params
|
const { locale, slug } = await params
|
||||||
const page = await resolvePage(locale, slug?.length ? slug.join('/') : null)
|
const { page } = await searchParams
|
||||||
if (!page) notFound()
|
const route = await resolveRoute(locale, slug ?? [], page) // 3 args
|
||||||
|
if (!route) notFound()
|
||||||
return <RenderBlocks blocks={page.layout as never} components={blockRegistry} />
|
return <RenderBlocks blocks={route.doc.layout as never} components={blockRegistry} />
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 11. Środowisko
|
- brak slug (`/pl`) → home przez System Pages (nie hardkod slug)
|
||||||
|
- slug (`/pl/o-nas`) → resolveRoute po slug w danym locale
|
||||||
|
- `depth: 2` → relacje w blokach (form) się populują
|
||||||
|
|
||||||
```bash
|
## 11. Metadata / SEO (szczegóły)
|
||||||
# .env
|
|
||||||
DATABASE_URL=file:./moj-projekt.db
|
`createPageMetadata` obsługuje hreflang. Kluczowe: resolveDocument pobiera
|
||||||
PAYLOAD_SECRET=<losowy-ciąg>
|
dokument z **`locale: 'all'`** — wtedy `slug` jest mapą locale→wartość, z której
|
||||||
NEXT_PUBLIC_SERVER_URL=http://localhost:3000
|
budują się hreflang alternates. Zwykły fetch (jeden locale) → tylko string,
|
||||||
```
|
hreflang nie powstanie.
|
||||||
|
|
||||||
Bez `NEXT_PUBLIC_SERVER_URL` canonical i hreflang wyjdą względne.
|
Bez `NEXT_PUBLIC_SERVER_URL` canonical i hreflang wyjdą względne.
|
||||||
|
|
||||||
## 12. Generowanie i start
|
## 12. Nagłówki bezpieczeństwa
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// next.config.ts
|
||||||
|
import { buildSecurityHeaders } from '@intecion/ipal-kit'
|
||||||
|
const securityHeaders = buildSecurityHeaders({
|
||||||
|
hsts: process.env.NODE_ENV === 'production', // off w dev (http)
|
||||||
|
additional: [ /* CSP projektu — zna swoje domeny */ ],
|
||||||
|
})
|
||||||
|
// async headers() { return [{ source: '/:path*', headers: securityHeaders }] }
|
||||||
|
```
|
||||||
|
Szczegóły: [security.md](./security.md).
|
||||||
|
|
||||||
|
## 13. Środowisko
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
DATABASE_URI=<postgres albo file:./dev.db>
|
||||||
|
PAYLOAD_SECRET=<losowy-ciąg>
|
||||||
|
NEXT_PUBLIC_SERVER_URL=http://localhost:3000
|
||||||
|
# Email przez Graph (opcjonalnie — sekrety agencyjne):
|
||||||
|
# GRAPH_TENANT_ID=... GRAPH_CLIENT_ID=... GRAPH_CLIENT_SECRET=... GRAPH_SENDER=...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 14. Generowanie i start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm generate:types
|
pnpm generate:types
|
||||||
pnpm payload generate:importmap # pola SEO to komponenty admina
|
pnpm payload generate:importmap # pola SEO + custom komponenty (MaskedField...)
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
`generate:importmap` powtarzaj po każdej zmianie, która dokłada komponenty
|
`generate:importmap` powtarzaj po każdej zmianie dokładającej komponenty admina.
|
||||||
admina.
|
|
||||||
|
|
||||||
## 13. Konfiguracja w panelu
|
## 15. Konfiguracja w panelu
|
||||||
|
|
||||||
`http://localhost:3000/admin`
|
`http://localhost:3000/admin`
|
||||||
|
|
||||||
1. **Utwórz pierwszego użytkownika** (dostanie rolę admin).
|
1. **Utwórz pierwszego użytkownika** (rola admin).
|
||||||
2. **Site Settings → General** — nazwa witryny, kolejność i separator tytułu.
|
2. **Site Settings → General** — nazwa witryny, tytuł.
|
||||||
3. **Pages** — utwórz stronę główną. Wypełnij tytuł **w każdym locale**
|
3. **Pages** — strona główna. Tytuł **w każdym locale** (slug per język; pusty
|
||||||
(przełącznik u góry) — slug generuje się per język, a pusty slug w EN oznacza
|
slug EN = 404 na `/en/…`).
|
||||||
404 na `/en/…`.
|
4. **Site Settings → System Pages** — wskaż Homepage (bez tego `/pl` → 404).
|
||||||
4. **Site Settings → System Pages** — wskaż Homepage. Bez tego `/pl` da 404.
|
5. **Cookie Settings** — treść bannera per język.
|
||||||
5. **Cookie Settings** — treść bannera (bez tego lecą angielskie domyślne).
|
6. **Notifications** — teksty wyników formularza per język (opcjonalne, ma fallback).
|
||||||
|
7. **Site Integrations → SMTP** — transport (SMTP/Graph), From Name, From Address.
|
||||||
|
|
||||||
Wejdź na `/` — powinno przekierować na `/pl` i pokazać stronę.
|
Wejdź na `/` — powinno przekierować na `/pl`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Rzeczy opcjonalne
|
## Opcjonalne
|
||||||
|
|
||||||
### Formularz z Turnstile
|
### Formularz z Turnstile
|
||||||
|
Wymaga bloku formularza (patrz [forms.md](./forms.md)) + Site Integrations →
|
||||||
|
Turnstile (klucze testowe Cloudflare: site `1x00000000000000000000AA`, secret
|
||||||
|
`1x0000000000000000000000000000000AA`). Maile wysyła form-builder przez
|
||||||
|
mailAdapter — nie pisze się ich w kodzie. Zgoda RODO: checkbox o nazwie `consent`.
|
||||||
|
|
||||||
Wymaga bloku formularza w projekcie (patrz forms.md) oraz:
|
### Blog / archiwum
|
||||||
|
Pełny opis: [content.md](./content.md). Kolekcja + content.config.ts +
|
||||||
- **Site Integrations → Turnstile** — site key i secret. Klucze testowe
|
przypisanie strony-archiwum w System Pages.
|
||||||
Cloudflare (zawsze przechodzą): site `1x00000000000000000000AA`, secret
|
|
||||||
`1x0000000000000000000000000000000AA`.
|
|
||||||
- **Site Integrations → SMTP** — host, port, user, hasło, adres nadawcy.
|
|
||||||
- **Forms → dany formularz → Emails** — odbiorca, temat, treść (`{{*:table}}`
|
|
||||||
wypisze wszystkie pola tabelką). Maile wysyła form-builder przez
|
|
||||||
`panelSmtpAdapter` — nie pisze się ich w kodzie.
|
|
||||||
|
|
||||||
|
|
||||||
### Blog / archiwum (kolekcja pod stroną-archiwum)
|
|
||||||
|
|
||||||
Pełny opis: content.md. W skrócie:
|
|
||||||
|
|
||||||
1. **Kolekcja** `src/collections/Posts.ts` — tytuł (localized), `buildSlugField`,
|
|
||||||
pola, bloki. Dodaj ją do `collections` w payload.config.
|
|
||||||
|
|
||||||
2. **content.config.ts** obok i18n.config.ts:
|
|
||||||
```ts
|
|
||||||
import type { ContentOption } from '@intecion/ipal-kit'
|
|
||||||
export const contentConfig: ContentOption = {
|
|
||||||
collections: [{ slug: 'posts', label: 'Artykuły', perPage: 10 }],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **payload.config** — `content: contentConfig`, plus `posts` w `seo.collections`.
|
|
||||||
|
|
||||||
4. **Front** — `createContentHelpers` w `src/lib/content.ts`, `resolveRoute`
|
|
||||||
w page.tsx (obsługa typów page/archive/entry), blok listy (EntriesList).
|
|
||||||
|
|
||||||
5. **Baza + typy** — nowa kolekcja to nowy schemat:
|
|
||||||
```bash
|
|
||||||
rm -f *.db *.db-shm *.db-wal && pnpm generate:types && pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **W panelu** — utwórz stronę „Artykuły" (w każdym locale!), dodaj do niej blok
|
|
||||||
listy, w System Pages przypisz ją jako archiwum kolekcji posts. Dodaj wpisy.
|
|
||||||
|
|
||||||
Adres wpisów = slug strony-archiwum. Zmiana tytułu strony przenosi sekcję. Kolejny
|
|
||||||
typ treści (realizacje) = kolejna kolekcja + kolejna pozycja w content.config.
|
|
||||||
|
|
||||||
### Sitemapa i robots.txt
|
|
||||||
|
|
||||||
`createContentHelpers` oddaje gotowe handlery — dodaj `i18n` i `baseUrl` do jego
|
|
||||||
argumentów (patrz seo.md), potem dwa pliki po jednej linii:
|
|
||||||
|
|
||||||
|
### Sitemapa i robots
|
||||||
```ts
|
```ts
|
||||||
// app/sitemap.ts
|
// app/sitemap.ts
|
||||||
export { sitemap as default } from '@/lib/content'
|
export { sitemap as default } from '@/lib/content'
|
||||||
@@ -482,44 +490,22 @@ export { sitemap as default } from '@/lib/content'
|
|||||||
export { robots as default } from '@/lib/content'
|
export { robots as default } from '@/lib/content'
|
||||||
```
|
```
|
||||||
|
|
||||||
Sitemapa z hreflangiem per URL, lastmod, wpisami bloga; pomija drafty i noindex.
|
|
||||||
|
|
||||||
### Analytics
|
|
||||||
|
|
||||||
**Site Integrations** → GA4 Measurement ID albo GTM Container ID. Tagi ładują
|
|
||||||
się z Consent Mode: nic nie zapisze ciasteczek, dopóki odwiedzający nie
|
|
||||||
zaakceptuje kategorii Analytics.
|
|
||||||
|
|
||||||
### Przestylowanie pod klienta
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* styles.css */
|
|
||||||
:root {
|
|
||||||
--ipal-primary: #16a34a;
|
|
||||||
--ipal-radius: 1rem;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Pełna lista tokenów: consent.md.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Kiedy coś nie działa
|
## Kiedy coś nie działa
|
||||||
|
|
||||||
| Objaw | Przyczyna |
|
| Objaw | Przyczyna |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Pusty tab SEO / brak kolekcji Forms | rozjazd wersji `@payloadcms/*` — sprawdź `pnpm.overrides` |
|
| Pusty tab SEO / brak Forms | rozjazd wersji `@payloadcms/*` — sprawdź `pnpm.overrides` |
|
||||||
| `PayloadComponent not found in importMap` | `pnpm payload generate:importmap` |
|
| `PayloadComponent not found in importMap` | `pnpm payload generate:importmap` |
|
||||||
| Banner bez stylów | brak `@source` na `node_modules/@intecion/ipal-kit` albo brak Tailwinda |
|
| `Cannot destructure property 'config'` (custom pole) | dublet `@payloadcms/ui` — peerDependency (playbook D) |
|
||||||
| `/admin` i `/_next` zwracają 500 | matcher w middleware nie jest inline |
|
| Banner bez stylów | brak `@source` na node_modules albo brak Tailwinda |
|
||||||
| `slug.join is not a function` | katalog `[slug]` zamiast `[[...slug]]` |
|
| `/admin` i `/_next` → 500 | matcher w proxy nie jest inline |
|
||||||
|
| `slug.join is not a function` | `[slug]` zamiast `[[...slug]]` |
|
||||||
| `/pl` → 404 | Homepage nieustawiony w System Pages |
|
| `/pl` → 404 | Homepage nieustawiony w System Pages |
|
||||||
| `/en/cokolwiek` → 404, `/pl/cokolwiek` działa | pusty tytuł (a więc i slug) w locale EN |
|
| `/en/*` → 404, `/pl/*` działa | pusty tytuł/slug w locale EN |
|
||||||
| `Missing <html> and <body>` | root layout usunięty, a `[locale]/layout.tsx` ich nie ma |
|
| `Missing <html> and <body>` | root layout usunięty, a `[locale]/layout.tsx` ich nie ma |
|
||||||
| `SQLITE_ERROR: index … already exists` | zmiana schematu — usuń `*.db *.db-shm *.db-wal` |
|
| Zmiany w pluginie nie widać | `rm -rf .next`; sprawdź czy wciągnięto wersję (grep node_modules) |
|
||||||
| Zmiany w pluginie nie widać | Turbopack cache — `rm -rf .next` |
|
| Maile nie wychodzą | brak `email: mailAdapter()` albo pusty SMTP/Graph |
|
||||||
| Maile nie wychodzą | brak `email: panelSmtpAdapter()` w configu albo pusty SMTP w panelu |
|
| istnieje `middleware.ts` | USUŃ — Next 16 to `proxy.ts` |
|
||||||
| GTM ładuje się, brak `_ga` | pusty kontener — GTM sam nie ustawia ciasteczek, potrzebny opublikowany tag GA4 |
|
| zaszyta mapa `localizedRoutes` | antywzorzec — `getLocalizedSlugs` z bazy |
|
||||||
| `/pl/artykuly` → 404 | strona nieprzypisana jako archiwum w System Pages |
|
|
||||||
| brak pola „archive page" w panelu | brak `content` w configu albo `generate:importmap` po dodaniu |
|
|
||||||
| wpis 404 mimo że istnieje | slug pusty w tym locale — wypełnij tytuł w danym języku |
|
|
||||||
@@ -108,3 +108,35 @@ Zachowanie:
|
|||||||
- wybrany locale zapisany w cookie (`LOCALE_COOKIE_NAME`)
|
- wybrany locale zapisany w cookie (`LOCALE_COOKIE_NAME`)
|
||||||
|
|
||||||
`DEFAULT_MIDDLEWARE_MATCHER` wyklucza `api`, `admin`, `_next`, pliki statyczne.
|
`DEFAULT_MIDDLEWARE_MATCHER` wyklucza `api`, `admin`, `_next`, pliki statyczne.
|
||||||
|
|
||||||
|
## Cookie locale a zgoda (RODO)
|
||||||
|
|
||||||
|
Wybór języka zapisywany jest w cookie **`NEXT_LOCALE`** (konwencja Next.js —
|
||||||
|
kompatybilna z innymi bibliotekami i18n, które czytają aktywny locale). Ale
|
||||||
|
zapis podlega zgodzie: to cookie kategorii **functional**, więc:
|
||||||
|
|
||||||
|
- **Zapis TYLKO za zgodą.** Middleware zapisuje `NEXT_LOCALE` jedynie, gdy
|
||||||
|
użytkownik zgodził się na kategorię functional (`mayPersistLocale` sprawdza
|
||||||
|
zgodę). Bez zgody język działa per żądanie (negocjacja z Accept-Language),
|
||||||
|
ale nie jest utrwalany.
|
||||||
|
- **Sprzątanie po cofnięciu zgody.** Gdy użytkownik cofnie zgodę na functional,
|
||||||
|
cookie `NEXT_LOCALE` jest usuwane automatycznie (consent zna tę cookie przez
|
||||||
|
`DEFAULT_COOKIE_MAP` — patrz [consent.md](./consent.md)).
|
||||||
|
|
||||||
|
Nazwa cookie to jedna stała `LOCALE_COOKIE_NAME` (`modules/i18n/negotiateLocale`),
|
||||||
|
propagująca do middleware i sprzątania consent. Można nadpisać w
|
||||||
|
`createLocaleMiddleware({ cookieName })`, ale domyślnie `NEXT_LOCALE` jest
|
||||||
|
zalecane (interop).
|
||||||
|
|
||||||
|
### Kolejność negocjacji locale
|
||||||
|
|
||||||
|
1. Cookie `NEXT_LOCALE` (jeśli jest — czyli był wybór za zgodą)
|
||||||
|
2. Nagłówek `Accept-Language` (preferencje przeglądarki)
|
||||||
|
3. `defaultLocale` z konfiguracji
|
||||||
|
|
||||||
|
Wejście na `/` → negocjacja → redirect na `/pl` (albo wynik negocjacji).
|
||||||
|
Zmiana języka (URL `/en` różny od cookie) → zapis nowego wyboru (za zgodą).
|
||||||
|
|
||||||
|
> **Migracja ze starej nazwy:** wcześniej cookie nazywało się `ipal-locale`.
|
||||||
|
> Po zmianie na `NEXT_LOCALE` użytkownicy ze starą cookie przejdą raz ponowną
|
||||||
|
> negocjację (stara cookie ignorowana). Jednorazowe, bez wpływu na nowe projekty.
|
||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
# storage — media na Cloudflare R2
|
||||||
|
|
||||||
|
Offload mediów (obrazy, pliki) do Cloudflare R2 zamiast lokalnego dysku. R2 jest
|
||||||
|
S3-kompatybilny; plugin dostarcza `buildR2Storage`, który czyta dane z `.env`
|
||||||
|
i konfiguruje adapter.
|
||||||
|
|
||||||
|
> **Storage to infrastruktura, nie treść.** Dane R2 (klucze, bucket) idą do
|
||||||
|
> `.env` — jak DATABASE_URI, PAYLOAD_SECRET, GRAPH_*. NIE do panelu (to sekrety
|
||||||
|
> agencyjne, wiążą się przy starcie, nie zmienia ich redaktor).
|
||||||
|
|
||||||
|
## Zależność
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add @payloadcms/storage-s3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zmienne .env
|
||||||
|
|
||||||
|
Patrz [R2-ENV-przyklad](../R2-ENV-przyklad.md) po pełną instrukcję skąd wziąć wartości.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
R2_BUCKET=nazwa-bucketa
|
||||||
|
R2_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
|
||||||
|
R2_ACCESS_KEY_ID=<access-key-id>
|
||||||
|
R2_SECRET_ACCESS_KEY=<secret-access-key>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wpięcie (payload.config.ts)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { buildR2Storage } from '@intecion/ipal-kit'
|
||||||
|
|
||||||
|
export default buildConfig({
|
||||||
|
// ...
|
||||||
|
plugins: [
|
||||||
|
ipalKit({ /* ... */ }),
|
||||||
|
buildR2Storage(['media']), // slugi kolekcji upload do offloadu
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`buildR2Storage` przyjmuje listę kolekcji upload (domyślnie `['media']`). Jeśli
|
||||||
|
masz więcej kolekcji plików: `buildR2Storage(['media', 'documents'])`.
|
||||||
|
|
||||||
|
## Zachowanie (fallback)
|
||||||
|
|
||||||
|
- **Wszystkie 4 zmienne** → media w R2.
|
||||||
|
- **Brak zmiennych** → fallback na lokalny dysk (dev działa bez R2, zero konfiguracji).
|
||||||
|
- **Część zmiennych** → ostrzeżenie w logu + fallback (częściowa konfiguracja =
|
||||||
|
pewnie pomyłka).
|
||||||
|
|
||||||
|
To wzorzec „degrade gracefully" — jak mailAdapter, który wraca do SMTP, gdy brak
|
||||||
|
Graph. Projekt działa niezależnie od tego, czy R2 jest skonfigurowany.
|
||||||
|
|
||||||
|
## Publiczny dostęp + custom domena (WAŻNE — krok po kroku)
|
||||||
|
|
||||||
|
R2 domyślnie prywatny. Upload zadziała, ale obrazy się NIE wyświetlą (403),
|
||||||
|
dopóki nie skonfigurujesz publicznego odczytu przez custom domenę. To proces
|
||||||
|
w Cloudflare (nie w kodzie), wieloetapowy — poniżej dokładnie.
|
||||||
|
|
||||||
|
### Dlaczego custom domena, nie „r2.dev"
|
||||||
|
|
||||||
|
R2 oferuje szybki publiczny URL `*.r2.dev`, ALE:
|
||||||
|
- jest rate-limitowany (nie do produkcji)
|
||||||
|
- nie przechodzi przez cache Cloudflare (brak CDN, wolniej, drożej)
|
||||||
|
- brzydki URL (nie Twoja domena)
|
||||||
|
|
||||||
|
Dla produkcji ZAWSZE custom domena (np. `media.klient.pl`) — daje CDN, cache,
|
||||||
|
własny URL. r2.dev tylko do szybkiego testu.
|
||||||
|
|
||||||
|
### Warunek wstępny: domena w Cloudflare
|
||||||
|
|
||||||
|
Custom domena dla R2 wymaga, żeby domena (albo subdomena) była zarządzana przez
|
||||||
|
Cloudflare (nameservery klienta wskazują na Cloudflare). Jeśli domena klienta
|
||||||
|
jest u innego rejestratora — trzeba ją najpierw dodać do Cloudflare (Add Site)
|
||||||
|
i przełączyć nameservery. Sama subdomena `media.klient.pl` wystarczy, jeśli
|
||||||
|
główna domena jest już w Cloudflare.
|
||||||
|
|
||||||
|
### Krok po kroku — podpięcie custom domeny
|
||||||
|
|
||||||
|
1. **Cloudflare Dashboard → R2 → wybierz bucket**
|
||||||
|
2. Zakładka **Settings** → sekcja **Public access** → **Custom Domains**
|
||||||
|
3. **Connect Domain** → wpisz subdomenę, np. `media.klient.pl`
|
||||||
|
4. Cloudflare automatycznie doda rekord CNAME (bo domena jest w Cloudflare) i
|
||||||
|
wystawi certyfikat SSL. Poczekaj, aż status = **Active** (kilka minut).
|
||||||
|
5. Od tej chwili pliki są publiczne pod `https://media.klient.pl/<klucz-pliku>`.
|
||||||
|
|
||||||
|
### Krok: ustaw publiczny URL w projekcie
|
||||||
|
|
||||||
|
Payload musi generować URL-e mediów wskazujące na custom domenę, nie na endpoint
|
||||||
|
S3. Dodaj zmienną i przekaż ją do adaptera:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
R2_PUBLIC_URL=https://media.klient.pl
|
||||||
|
```
|
||||||
|
|
||||||
|
Adapter `buildR2Storage` czyta ją i ustawia jako bazowy URL mediów (jeśli
|
||||||
|
ustawiona). Bez niej Payload zwróci URL wskazujący na prywatny endpoint S3 →
|
||||||
|
403 na froncie. (Patrz aktualizacja buildR2Storage niżej.)
|
||||||
|
|
||||||
|
### Weryfikacja
|
||||||
|
|
||||||
|
1. Wgraj obraz w panelu (Media).
|
||||||
|
2. Sprawdź URL obrazu w panelu — powinien być `https://media.klient.pl/...`,
|
||||||
|
NIE `https://<account>.r2.cloudflarestorage.com/...`.
|
||||||
|
3. Otwórz URL w przeglądarce — obraz się pokazuje (nie 403).
|
||||||
|
4. Na froncie `<img src>` działa.
|
||||||
|
|
||||||
|
### Częsty błąd: 403 mimo custom domeny
|
||||||
|
|
||||||
|
- **URL wskazuje na endpoint S3, nie custom domenę** → brakuje `R2_PUBLIC_URL`
|
||||||
|
albo adapter jej nie używa. Sprawdź URL w panelu.
|
||||||
|
- **Custom domena nie Active** → poczekaj na SSL/CNAME w Cloudflare.
|
||||||
|
- **Public access wyłączony** → w bucket Settings sprawdź, czy custom domena jest
|
||||||
|
podpięta (nie tylko utworzona).
|
||||||
|
|
||||||
|
Bez tego media wgrają się do R2, ale front pokaże 403. Konfiguracja domeny jest
|
||||||
|
po stronie Cloudflare, publiczny URL po stronie projektu (.env).
|
||||||
|
|
||||||
|
## Migracja istniejących mediów
|
||||||
|
|
||||||
|
Jeśli projekt miał media lokalnie i przełączasz na R2 — nowe uploady idą do R2,
|
||||||
|
ale STARE zostają na dysku (i znikną przy redeployu bez wolumenu). Przed
|
||||||
|
przełączeniem na produkcji przenieś istniejące pliki do bucketa (np. `rclone`
|
||||||
|
albo ręcznie przez R2 dashboard), inaczej stare obrazy znikną.
|
||||||
|
|
||||||
|
## Weryfikacja
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# po wpięciu i ustawieniu .env:
|
||||||
|
pnpm dev
|
||||||
|
# wgraj obraz w panelu (Media) → sprawdź w Cloudflare R2, czy plik się pojawił
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dev na lokalnym I na R2 (seedowanie podczas developmentu)
|
||||||
|
|
||||||
|
Fallback (brak zmiennych → lokalny dysk) oznacza, że **dev działa w obu trybach**:
|
||||||
|
|
||||||
|
- **Dev bez R2 w .env** → media na lokalnym dysku. Szybki start, zero konfiguracji.
|
||||||
|
- **Dev z R2 w .env** → media w R2 już podczas developmentu. Przydatne, gdy
|
||||||
|
seedujesz treść w devie i chcesz, żeby od razu lądowała w buckecie (np. wspólny
|
||||||
|
bucket dev, albo test realnego flow przed produkcją).
|
||||||
|
|
||||||
|
Przełączasz trybem po prostu obecnością zmiennych R2 w `.env`. Ten sam kod,
|
||||||
|
`buildR2Storage` sam wykrywa. Nie musisz nic zmieniać w configu między trybami.
|
||||||
|
|
||||||
|
> Jeśli seedujesz w devie do R2 — pamiętaj, że to realny bucket. Używaj osobnego
|
||||||
|
> bucketa dev (nie produkcyjnego), żeby nie mieszać danych testowych z realnymi.
|
||||||
|
|
||||||
|
## Normalizacja nazw plików (automatyczna)
|
||||||
|
|
||||||
|
Plik `normalizeFilenameHook` czyści nazwy wgrywanych plików — slugifikuje nazwę,
|
||||||
|
zachowuje rozszerzenie:
|
||||||
|
|
||||||
|
```
|
||||||
|
"Zdjęcie jeden nad morzem.jpg" → "zdjecie-jeden-nad-morzem.jpg"
|
||||||
|
"Faktura #12 (2024).PDF" → "faktura-12-2024.pdf"
|
||||||
|
```
|
||||||
|
|
||||||
|
Wpięcie w kolekcję Media (projekt):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { normalizeFilenameHook } from '@intecion/ipal-kit'
|
||||||
|
|
||||||
|
export const Media: CollectionConfig = {
|
||||||
|
slug: 'media',
|
||||||
|
upload: { staticDir: 'media' /* ... */ },
|
||||||
|
hooks: {
|
||||||
|
beforeOperation: [normalizeFilenameHook], // czyści nazwę przed zapisem
|
||||||
|
},
|
||||||
|
fields: [ /* alt itd. */ ],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Działa z lokalnym dyskiem i z R2/S3 (hook biegnie PRZED warstwą storage, więc
|
||||||
|
czysta nazwa trafia i do bazy, i do bucketa). Dlaczego to ważne:
|
||||||
|
|
||||||
|
- **URL-e mediów są czyste** — `/media/zdjecie-nad-morzem.jpg`, nie
|
||||||
|
`/media/Zdjęcie%20jeden%20nad%20morzem.jpg` (spacje/diakrytyki w URL = problemy).
|
||||||
|
- **Przenośność** — nazwa bez polskich znaków/spacji działa wszędzie (CDN, S3, systemy plików).
|
||||||
|
- **Bez kolizji kodowania** — spacje i `#`, `()` w nazwach plików potrafią psuć
|
||||||
|
ścieżki i cache.
|
||||||
|
|
||||||
|
Sama funkcja `normalizeFilename(name)` też jest wyeksportowana, gdybyś potrzebował
|
||||||
|
jej poza hookiem.
|
||||||
|
|
||||||
|
## Preconnect do domeny mediów (wydajność)
|
||||||
|
|
||||||
|
Komponent `MediaPreconnect` generuje `<link rel="preconnect">` + `dns-prefetch`
|
||||||
|
dla domeny mediów (R2_PUBLIC_URL) — przeglądarka nawiązuje połączenie TLS/DNS
|
||||||
|
z CDN zawczasu, zanim napotka pierwszy `<img>`. Zysk ~150–300 ms na pierwszym
|
||||||
|
obrazie.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// layout.tsx — w <head> (albo górze <body>, Next hoistuje link tagi)
|
||||||
|
import { MediaPreconnect } from '@intecion/ipal-kit/rsc'
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<MediaPreconnect />
|
||||||
|
</head>
|
||||||
|
```
|
||||||
|
|
||||||
|
Czyta domenę z **R2_PUBLIC_URL** (to samo źródło co buildR2Storage) — zero
|
||||||
|
hardkodu, jedno źródło prawdy. Gdy R2_PUBLIC_URL nie ustawione (lokalny dysk,
|
||||||
|
brak CDN) → nie renderuje nic. Zmiana domeny mediów = zmiana jednej zmiennej
|
||||||
|
env, komponent podąża automatycznie.
|
||||||
|
|
||||||
|
> NIE hardkoduj `<link rel="preconnect" href="https://media.klient.pl">` ręcznie
|
||||||
|
> w layoutcie — to zaszywa domenę klienta w kodzie. Użyj MediaPreconnect, który
|
||||||
|
> bierze ją z env.
|
||||||
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@intecion/ipal-kit",
|
"name": "@intecion/ipal-kit",
|
||||||
"version": "1.0.15",
|
"version": "1.1.0",
|
||||||
"description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.",
|
"description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -38,7 +38,8 @@
|
|||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist",
|
||||||
|
"docs"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
"test:int": "vitest"
|
"test:int": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@payloadcms/storage-s3": "^3.88.0",
|
||||||
"lucide-react": "^0.400.0",
|
"lucide-react": "^0.400.0",
|
||||||
"nodemailer": "^8.0.1",
|
"nodemailer": "^8.0.1",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
|
|||||||
Generated
+509
@@ -8,6 +8,9 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@payloadcms/storage-s3':
|
||||||
|
specifier: ^3.88.0
|
||||||
|
version: 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.400.0
|
specifier: ^0.400.0
|
||||||
version: 0.400.0([email protected])
|
version: 0.400.0([email protected])
|
||||||
@@ -133,6 +136,88 @@ packages:
|
|||||||
resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==}
|
resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-Dtu0gr4dnATZAPwEYbpCsG+MpLM7OAliy2gTepEFQwl1vZ6DL3QMH2FveMa3HLvPsOdhJsPRB3KtxVhph9T75A==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-8+UH1uBWwjbRu0ugTBhJ/cu3rL/vzf1tm9/3+xZGf1uybIO3HTF2xLJJaTKkRPvzxz8/n/w+YrwoHGZ2Q/a2Zg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-Rml+WitoFvXmv6JZ18U/xGdGDGGvB/mOin0ya0lTnTrdC0Z1lrVxTYh7iNklZBcvcRMrs4DoEf6xy1KWyrLQQw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-4dxJX2pueKHtFyxdtT/IH385NpAWjbNjpbbIN6L9YK58dbae6ldyiLSom1EV5nPoOxKtWLXiqch+yo3NjXfleQ==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@aws-sdk/client-s3': ^3.1119.0
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-wMIsNumRVKaNMKhvU/s9VrdEwE8S6gSzXp4RygFG5BEMnGkkXf8cjh8zf7cKJBpUDpqTWqwbz5isEgp9rH6Lng==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-yEqb0ispm9uXZ7nk5stErMB7yn43j3XCAaHM15MsdWaCk7RvEmGjYdbyHZcH86JpUg7mk1v1m2xwzLj2o+3uCg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
resolution: {integrity: sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws/[email protected]':
|
||||||
|
resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@babel/[email protected]':
|
'@babel/[email protected]':
|
||||||
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
|
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -1646,6 +1731,13 @@ packages:
|
|||||||
next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0'
|
next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0'
|
||||||
payload: 3.88.0
|
payload: 3.88.0
|
||||||
|
|
||||||
|
'@payloadcms/[email protected]':
|
||||||
|
resolution: {integrity: sha512-wIcRVedtBZIQMu7dpSE1TOJKJd0HuVSUNPrV2KxIbtqQFm+ly34NCJYY0m1+K0YS6my8BKdcB4jj2exg8454Ig==}
|
||||||
|
peerDependencies:
|
||||||
|
payload: 3.88.0
|
||||||
|
react: ^19.0.1 || ^19.1.2 || ^19.2.1
|
||||||
|
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
|
||||||
|
|
||||||
'@payloadcms/[email protected]':
|
'@payloadcms/[email protected]':
|
||||||
resolution: {integrity: sha512-C915jEHADVN9xW7fSq/bYIZig1zjfQo+t4wrgiqjOCmB81oMgyqLRB3RlL2e6y4PzBUtgss2VS40vn/tVD/61Q==}
|
resolution: {integrity: sha512-C915jEHADVN9xW7fSq/bYIZig1zjfQo+t4wrgiqjOCmB81oMgyqLRB3RlL2e6y4PzBUtgss2VS40vn/tVD/61Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1671,6 +1763,12 @@ packages:
|
|||||||
react: ^19.0.1 || ^19.1.2 || ^19.2.1
|
react: ^19.0.1 || ^19.1.2 || ^19.2.1
|
||||||
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
|
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
|
||||||
|
|
||||||
|
'@payloadcms/[email protected]':
|
||||||
|
resolution: {integrity: sha512-+35JRD4y7ImnIJuPSBhECHGGml0A+xju9BT/zW+lRUycDvvA0cqZNP+t95OVoZrxIB6V75Vg8GKPvAaRAmgOEw==}
|
||||||
|
engines: {node: ^18.20.2 || >=20.9.0}
|
||||||
|
peerDependencies:
|
||||||
|
payload: 3.88.0
|
||||||
|
|
||||||
'@payloadcms/[email protected]':
|
'@payloadcms/[email protected]':
|
||||||
resolution: {integrity: sha512-AZVQjjW5pKxO+K9ODeh6yw6/EfXK1hxGPw55KzzDC5srO3hjNskgyWtkauKvbz+S0+xFspANbRc4OD/Rg/74WA==}
|
resolution: {integrity: sha512-AZVQjjW5pKxO+K9ODeh6yw6/EfXK1hxGPw55KzzDC5srO3hjNskgyWtkauKvbz+S0+xFspANbRc4OD/Rg/74WA==}
|
||||||
|
|
||||||
@@ -1839,6 +1937,30 @@ packages:
|
|||||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
resolution: {integrity: sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
resolution: {integrity: sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
resolution: {integrity: sha512-2jY1tSpERfPfWqyBV2pH+iGFaghVsIJszJNsT7hxtQYhVJpWDyc0LqOWI+nXOxOAHaEfZ4PXXtp1wW1TGpHhkA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
resolution: {integrity: sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
resolution: {integrity: sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@standard-schema/[email protected]':
|
'@standard-schema/[email protected]':
|
||||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||||
|
|
||||||
@@ -2563,6 +2685,9 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==}
|
resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==}
|
resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==}
|
||||||
|
|
||||||
@@ -2596,6 +2721,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==}
|
resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||||
|
|
||||||
@@ -2866,6 +2994,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==}
|
resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==}
|
||||||
engines: {node: '>=12.20'}
|
engines: {node: '>=12.20'}
|
||||||
@@ -3363,10 +3495,18 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||||
|
engines: {node: '>=0.8.x'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
|
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@@ -3455,6 +3595,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
|
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
|
||||||
|
|
||||||
@@ -3470,6 +3613,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==}
|
resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@@ -3577,6 +3724,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -3672,6 +3827,10 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
|
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
|
||||||
|
|
||||||
@@ -3725,6 +3884,9 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==}
|
resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==}
|
||||||
|
|
||||||
@@ -3894,6 +4056,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
|
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
|
resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@@ -4098,6 +4264,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
|
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
|
||||||
|
|
||||||
@@ -4430,6 +4599,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4682,6 +4855,10 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=8.10.0'}
|
||||||
@@ -4721,6 +4898,10 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -4966,6 +5147,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
@@ -5393,6 +5577,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
|
resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -5490,6 +5678,190 @@ snapshots:
|
|||||||
'@types/json-schema': 7.0.15
|
'@types/json-schema': 7.0.15
|
||||||
js-yaml: 4.3.0
|
js-yaml: 4.3.0
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/checksums': 3.1000.29
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/credential-provider-node': 3.972.81
|
||||||
|
'@aws-sdk/middleware-sdk-s3': 3.972.75
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.996.46
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/fetch-http-handler': 5.7.2
|
||||||
|
'@smithy/node-http-handler': 4.11.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@aws-sdk/xml-builder': 3.972.40
|
||||||
|
'@aws/lambda-invoke-store': 0.3.0
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/signature-v4': 5.7.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
bowser: 2.14.1
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/fetch-http-handler': 5.7.2
|
||||||
|
'@smithy/node-http-handler': 4.11.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/credential-provider-env': 3.972.70
|
||||||
|
'@aws-sdk/credential-provider-http': 3.972.72
|
||||||
|
'@aws-sdk/credential-provider-login': 3.972.77
|
||||||
|
'@aws-sdk/credential-provider-process': 3.972.70
|
||||||
|
'@aws-sdk/credential-provider-sso': 3.973.14
|
||||||
|
'@aws-sdk/credential-provider-web-identity': 3.972.76
|
||||||
|
'@aws-sdk/nested-clients': 3.997.44
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/credential-provider-imds': 4.5.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/nested-clients': 3.997.44
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/credential-provider-env': 3.972.70
|
||||||
|
'@aws-sdk/credential-provider-http': 3.972.72
|
||||||
|
'@aws-sdk/credential-provider-ini': 3.973.15
|
||||||
|
'@aws-sdk/credential-provider-process': 3.972.70
|
||||||
|
'@aws-sdk/credential-provider-sso': 3.973.14
|
||||||
|
'@aws-sdk/credential-provider-web-identity': 3.972.76
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/credential-provider-imds': 4.5.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/nested-clients': 3.997.44
|
||||||
|
'@aws-sdk/token-providers': 3.1116.0
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/nested-clients': 3.997.44
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/client-s3': 3.1119.0
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
buffer: 5.6.0
|
||||||
|
events: 3.3.0
|
||||||
|
stream-browserify: 3.0.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.996.46
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.996.46
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/fetch-http-handler': 5.7.2
|
||||||
|
'@smithy/node-http-handler': 4.11.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.996.46
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/signature-v4': 5.7.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.9
|
||||||
|
'@aws-sdk/nested-clients': 3.997.44
|
||||||
|
'@aws-sdk/types': 3.974.5
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws/[email protected]': {}
|
||||||
|
|
||||||
'@babel/[email protected]':
|
'@babel/[email protected]':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-validator-identifier': 7.29.7
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
@@ -7020,6 +7392,21 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
|
'@payloadcms/[email protected](@types/[email protected])([email protected])([email protected](@babel/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])':
|
||||||
|
dependencies:
|
||||||
|
'@payloadcms/ui': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
|
||||||
|
find-node-modules: 2.1.3
|
||||||
|
payload: 3.88.0([email protected])([email protected])
|
||||||
|
range-parser: 1.2.1
|
||||||
|
react: 19.2.6
|
||||||
|
react-dom: 19.2.6([email protected])
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- monaco-editor
|
||||||
|
- next
|
||||||
|
- supports-color
|
||||||
|
- typescript
|
||||||
|
|
||||||
'@payloadcms/[email protected](@types/[email protected])([email protected])([email protected](@babel/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])':
|
'@payloadcms/[email protected](@types/[email protected])([email protected])([email protected](@babel/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@payloadcms/translations': 3.88.0
|
'@payloadcms/translations': 3.88.0
|
||||||
@@ -7094,6 +7481,22 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- yjs
|
- yjs
|
||||||
|
|
||||||
|
'@payloadcms/[email protected](@types/[email protected])([email protected])([email protected](@babel/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/client-s3': 3.1119.0
|
||||||
|
'@aws-sdk/lib-storage': 3.1119.0(@aws-sdk/[email protected])
|
||||||
|
'@aws-sdk/s3-request-presigner': 3.1119.0
|
||||||
|
'@payloadcms/plugin-cloud-storage': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
|
||||||
|
payload: 3.88.0([email protected])([email protected])
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- monaco-editor
|
||||||
|
- next
|
||||||
|
- react
|
||||||
|
- react-dom
|
||||||
|
- supports-color
|
||||||
|
- typescript
|
||||||
|
|
||||||
'@payloadcms/[email protected]':
|
'@payloadcms/[email protected]':
|
||||||
dependencies:
|
dependencies:
|
||||||
date-fns: 4.1.0
|
date-fns: 4.1.0
|
||||||
@@ -7220,6 +7623,39 @@ snapshots:
|
|||||||
|
|
||||||
'@sindresorhus/[email protected]': {}
|
'@sindresorhus/[email protected]': {}
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.33.3
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@standard-schema/[email protected]': {}
|
'@standard-schema/[email protected]': {}
|
||||||
|
|
||||||
'@swc-node/[email protected](@swc/[email protected])(@swc/[email protected])':
|
'@swc-node/[email protected](@swc/[email protected])(@swc/[email protected])':
|
||||||
@@ -8043,6 +8479,8 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
@@ -8078,6 +8516,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.19.9
|
'@types/node': 22.19.9
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
@@ -8330,6 +8773,8 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
@@ -9098,6 +9543,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bare-abort-controller
|
- bare-abort-controller
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
@@ -9110,6 +9557,10 @@ snapshots:
|
|||||||
signal-exit: 3.0.7
|
signal-exit: 3.0.7
|
||||||
strip-final-newline: 2.0.0
|
strip-final-newline: 2.0.0
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
homedir-polyfill: 1.0.3
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
@@ -9204,6 +9655,11 @@ snapshots:
|
|||||||
make-dir: 3.1.0
|
make-dir: 3.1.0
|
||||||
pkg-dir: 4.2.0
|
pkg-dir: 4.2.0
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
findup-sync: 4.0.0
|
||||||
|
merge: 2.1.1
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
@@ -9220,6 +9676,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver-regex: 4.0.5
|
semver-regex: 4.0.5
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
detect-file: 1.0.0
|
||||||
|
is-glob: 4.0.3
|
||||||
|
micromatch: 4.0.8
|
||||||
|
resolve-dir: 1.0.1
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
flatted: 3.4.2
|
flatted: 3.4.2
|
||||||
@@ -9330,6 +9793,20 @@ snapshots:
|
|||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
path-is-absolute: 1.0.1
|
path-is-absolute: 1.0.1
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
global-prefix: 1.0.2
|
||||||
|
is-windows: 1.0.2
|
||||||
|
resolve-dir: 1.0.1
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
expand-tilde: 2.0.2
|
||||||
|
homedir-polyfill: 1.0.3
|
||||||
|
ini: 1.3.8
|
||||||
|
is-windows: 1.0.2
|
||||||
|
which: 1.3.1
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
@@ -9425,6 +9902,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
parse-passwd: 1.0.0
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
@@ -9467,6 +9948,8 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
kind-of: 6.0.3
|
kind-of: 6.0.3
|
||||||
@@ -9630,6 +10113,8 @@ snapshots:
|
|||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-inside-container: 1.0.0
|
is-inside-container: 1.0.0
|
||||||
@@ -9855,6 +10340,8 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
decode-named-character-reference: 1.3.0
|
decode-named-character-reference: 1.3.0
|
||||||
@@ -10320,6 +10807,8 @@ snapshots:
|
|||||||
json-parse-even-better-errors: 2.3.1
|
json-parse-even-better-errors: 2.3.1
|
||||||
lines-and-columns: 1.2.4
|
lines-and-columns: 1.2.4
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
@@ -10610,6 +11099,12 @@ snapshots:
|
|||||||
string_decoder: 1.1.1
|
string_decoder: 1.1.1
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
string_decoder: 1.1.1
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch: 2.3.2
|
picomatch: 2.3.2
|
||||||
@@ -10653,6 +11148,11 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
expand-tilde: 2.0.2
|
||||||
|
global-modules: 1.0.0
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
@@ -10979,6 +11479,11 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
internal-slot: 1.1.0
|
internal-slot: 1.1.0
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
@@ -11479,6 +11984,10 @@ snapshots:
|
|||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
isexe: 2.0.0
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ export type {
|
|||||||
EnhanceProps,
|
EnhanceProps,
|
||||||
RenderBlocksProps,
|
RenderBlocksProps,
|
||||||
} from '../modules/blocks/index.js'
|
} from '../modules/blocks/index.js'
|
||||||
|
export { MediaPreconnect } from '../modules/storage/MediaPreconnect.js'
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import type { Field } from 'payload'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cloudflare R2 storage credentials.
|
|
||||||
* Reserved for future use — media offloading to R2.
|
|
||||||
*
|
|
||||||
* Protected at the global level (SiteIntegrations requires an authenticated
|
|
||||||
* user), so the access keys stay editable in the admin panel while remaining
|
|
||||||
* inaccessible to anonymous API requests.
|
|
||||||
*/
|
|
||||||
export const storageFields: Field[] = [
|
|
||||||
{
|
|
||||||
name: 'r2Bucket',
|
|
||||||
type: 'text',
|
|
||||||
admin: {
|
|
||||||
description: 'R2 bucket name.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'r2Endpoint',
|
|
||||||
type: 'text',
|
|
||||||
admin: {
|
|
||||||
description: 'R2 S3-compatible endpoint URL.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'r2AccessKeyId',
|
|
||||||
type: 'text',
|
|
||||||
admin: {
|
|
||||||
description: 'R2 access key ID.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'r2SecretAccessKey',
|
|
||||||
type: 'text',
|
|
||||||
admin: {
|
|
||||||
description: 'R2 secret access key.',
|
|
||||||
// Masked in the UI (••••) — stored plaintext, readable for R2 auth.
|
|
||||||
components: {
|
|
||||||
Field: '@intecion/ipal-kit/client#MaskedField',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -3,7 +3,6 @@ import type { Field, GlobalConfig } from 'payload'
|
|||||||
import { isAdmin } from '../../modules/access/index.js'
|
import { isAdmin } from '../../modules/access/index.js'
|
||||||
import { analyticsFields } from './fields/analytics.js'
|
import { analyticsFields } from './fields/analytics.js'
|
||||||
import { smtpFields } from './fields/smtp.js'
|
import { smtpFields } from './fields/smtp.js'
|
||||||
import { storageFields } from './fields/storage.js'
|
|
||||||
import { turnstileFields } from './fields/turnstile.js'
|
import { turnstileFields } from './fields/turnstile.js'
|
||||||
|
|
||||||
type BuildSiteIntegrationsArgs = {
|
type BuildSiteIntegrationsArgs = {
|
||||||
@@ -22,6 +21,10 @@ type BuildSiteIntegrationsArgs = {
|
|||||||
* impossible to enter.)
|
* impossible to enter.)
|
||||||
*
|
*
|
||||||
* Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).
|
* Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).
|
||||||
|
*
|
||||||
|
* Note: R2 storage credentials are NOT here — storage is infrastructure and
|
||||||
|
* binds at boot, so its config lives in .env (R2_BUCKET, R2_ENDPOINT, ...),
|
||||||
|
* consumed by buildR2Storage. See docs/storage.md.
|
||||||
*/
|
*/
|
||||||
export function buildSiteIntegrations({
|
export function buildSiteIntegrations({
|
||||||
additionalFields,
|
additionalFields,
|
||||||
@@ -44,7 +47,6 @@ export function buildSiteIntegrations({
|
|||||||
{ fields: analyticsFields, label: 'Analytics' },
|
{ fields: analyticsFields, label: 'Analytics' },
|
||||||
{ fields: turnstileFields, label: 'Turnstile' },
|
{ fields: turnstileFields, label: 'Turnstile' },
|
||||||
{ fields: smtpFields, label: 'SMTP' },
|
{ fields: smtpFields, label: 'SMTP' },
|
||||||
{ fields: storageFields, label: 'Storage' },
|
|
||||||
...(additionalFields?.length ? [{ fields: additionalFields, label: 'Custom' }] : []),
|
...(additionalFields?.length ? [{ fields: additionalFields, label: 'Custom' }] : []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
+14
-12
@@ -75,6 +75,18 @@ export {
|
|||||||
} from './modules/i18n/index.js'
|
} from './modules/i18n/index.js'
|
||||||
export type { LocaleMiddlewareResult } from './modules/i18n/index.js'
|
export type { LocaleMiddlewareResult } from './modules/i18n/index.js'
|
||||||
export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './modules/i18n/index.js'
|
export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './modules/i18n/index.js'
|
||||||
|
// Media — filename normalization hook for upload collections (Media).
|
||||||
|
export { normalizeFilename, normalizeFilenameHook } from './modules/media/index.js'
|
||||||
|
export {
|
||||||
|
getNotificationTexts,
|
||||||
|
NOTIFICATION_FALLBACK,
|
||||||
|
resolveFormMessage,
|
||||||
|
} from './modules/notifications/index.js'
|
||||||
|
export type {
|
||||||
|
FormNotificationTexts,
|
||||||
|
NotificationsData,
|
||||||
|
NotificationTexts,
|
||||||
|
} from './modules/notifications/index.js'
|
||||||
export type { PagesOption, SystemPageRole } from './modules/pages/index.js'
|
export type { PagesOption, SystemPageRole } from './modules/pages/index.js'
|
||||||
export { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js'
|
export { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js'
|
||||||
export type { GlobalQueryOptions } from './modules/payload/index.js'
|
export type { GlobalQueryOptions } from './modules/payload/index.js'
|
||||||
@@ -99,18 +111,8 @@ export {
|
|||||||
injectAutoFillMeta,
|
injectAutoFillMeta,
|
||||||
} from './modules/seo/index.js'
|
} from './modules/seo/index.js'
|
||||||
export { buildSlugField, toSlug } from './modules/slug/index.js'
|
export { buildSlugField, toSlug } from './modules/slug/index.js'
|
||||||
export {
|
// Storage — Cloudflare R2 media offload, configured from .env.
|
||||||
NOTIFICATION_FALLBACK,
|
export { buildR2Storage } from './modules/storage/index.js'
|
||||||
getNotificationTexts,
|
|
||||||
resolveFormMessage,
|
|
||||||
} from './modules/notifications/index.js'
|
|
||||||
export type {
|
|
||||||
FormNotificationTexts,
|
|
||||||
NotificationsData,
|
|
||||||
NotificationTexts,
|
|
||||||
} from './modules/notifications/index.js'
|
|
||||||
|
|
||||||
export { ipalKit } from './plugin.js'
|
export { ipalKit } from './plugin.js'
|
||||||
export type { IpalOptions } from './types.js'
|
export type { IpalOptions } from './types.js'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,14 @@ import type { I18nConfig } from './types.js'
|
|||||||
import { getLocaleCodes, isValidLocale } from './helpers.js'
|
import { getLocaleCodes, isValidLocale } from './helpers.js'
|
||||||
|
|
||||||
/** Cookie name the template uses to persist a visitor's locale choice. */
|
/** Cookie name the template uses to persist a visitor's locale choice. */
|
||||||
export const LOCALE_COOKIE_NAME = 'ipal-locale'
|
/**
|
||||||
|
* Cookie name for the persisted locale choice. Uses NEXT_LOCALE — the convention
|
||||||
|
* Next.js and its i18n ecosystem expect — so the cookie is interoperable with
|
||||||
|
* other libraries that read the active locale (instead of a plugin-specific
|
||||||
|
* name). Written only under functional consent; cleared when that consent is
|
||||||
|
* withdrawn (see consent cookieMap).
|
||||||
|
*/
|
||||||
|
export const LOCALE_COOKIE_NAME = 'NEXT_LOCALE'
|
||||||
|
|
||||||
type NegotiateLocaleArgs = {
|
type NegotiateLocaleArgs = {
|
||||||
/** Raw Accept-Language header value */
|
/** Raw Accept-Language header value */
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { normalizeFilename, normalizeFilenameHook } from './normalizeFilename.js'
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import type { CollectionBeforeOperationHook } from 'payload'
|
||||||
|
import slugify from 'slugify'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes a filename: slugifies the NAME part (diacritics, spaces, case)
|
||||||
|
* while preserving the extension. Keeps uploaded media URLs clean and portable.
|
||||||
|
*
|
||||||
|
* "Zdjęcie jeden nad morzem.jpg" → "zdjecie-jeden-nad-morzem.jpg"
|
||||||
|
* "Faktura #12 (2024).PDF" → "faktura-12-2024.pdf"
|
||||||
|
* "already-clean.webp" → "already-clean.webp"
|
||||||
|
*
|
||||||
|
* Why not toSlug(): toSlug uses strict:true, which would strip the dot and
|
||||||
|
* merge name+extension. Here we split on the LAST dot, slug the stem, lowercase
|
||||||
|
* the extension, and rejoin.
|
||||||
|
*/
|
||||||
|
export function normalizeFilename(filename: string): string {
|
||||||
|
const lastDot = filename.lastIndexOf('.')
|
||||||
|
|
||||||
|
// No extension (or leading-dot dotfile) → slug the whole thing.
|
||||||
|
if (lastDot <= 0) {
|
||||||
|
return slugify(filename, { lower: true, strict: true, trim: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const stem = filename.slice(0, lastDot)
|
||||||
|
const ext = filename.slice(lastDot + 1).toLowerCase()
|
||||||
|
|
||||||
|
const cleanStem = slugify(stem, { lower: true, strict: true, trim: true })
|
||||||
|
const cleanExt = slugify(ext, { lower: true, strict: true, trim: true })
|
||||||
|
|
||||||
|
// Stem could slug to empty (e.g. filename was all symbols) — fall back so we
|
||||||
|
// never produce a nameless file.
|
||||||
|
const safeStem = cleanStem || 'plik'
|
||||||
|
|
||||||
|
return cleanExt ? `${safeStem}.${cleanExt}` : safeStem
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* beforeOperation hook for an upload collection (e.g. Media). Rewrites the
|
||||||
|
* incoming file's name to its normalized form before Payload stores it, so both
|
||||||
|
* the stored file and its DB filename are clean. Works with local disk and with
|
||||||
|
* cloud storage adapters (R2/S3) — it runs before the storage layer.
|
||||||
|
*
|
||||||
|
* Wire into your Media collection:
|
||||||
|
* import { normalizeFilenameHook } from '@intecion/ipal-kit'
|
||||||
|
* hooks: { beforeOperation: [normalizeFilenameHook] }
|
||||||
|
*/
|
||||||
|
export const normalizeFilenameHook: CollectionBeforeOperationHook = ({ req, operation }) => {
|
||||||
|
if (operation !== 'create' && operation !== 'update') return
|
||||||
|
const file = req.file
|
||||||
|
if (file?.name) {
|
||||||
|
file.name = normalizeFilename(file.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Emits <link rel="preconnect"> + <link rel="dns-prefetch"> for the media CDN
|
||||||
|
* domain (R2_PUBLIC_URL), so the browser opens the TLS/DNS connection to the
|
||||||
|
* media host early — before it hits the first <img>. Saves ~150–300ms on the
|
||||||
|
* first image load.
|
||||||
|
*
|
||||||
|
* Reads the domain from R2_PUBLIC_URL (the same env var buildR2Storage uses),
|
||||||
|
* so there's ONE source of truth — no per-project hardcoded domain. Renders
|
||||||
|
* nothing when R2_PUBLIC_URL isn't set (local disk / no CDN → nothing to
|
||||||
|
* preconnect).
|
||||||
|
*
|
||||||
|
* Server Component — drop it in the <head> of your locale layout:
|
||||||
|
*
|
||||||
|
* import { MediaPreconnect } from '@intecion/ipal-kit/rsc'
|
||||||
|
* // in <head> (or top of <body> — Next hoists link tags):
|
||||||
|
* <MediaPreconnect />
|
||||||
|
*/
|
||||||
|
export function MediaPreconnect() {
|
||||||
|
const publicUrl = process.env.R2_PUBLIC_URL?.replace(/\/$/, '')
|
||||||
|
if (!publicUrl) {return null}
|
||||||
|
|
||||||
|
// Origin only (scheme + host) — preconnect targets an origin, not a path.
|
||||||
|
let origin: string
|
||||||
|
try {
|
||||||
|
origin = new URL(publicUrl).origin
|
||||||
|
} catch {
|
||||||
|
return null // malformed URL → skip rather than emit a broken tag
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<link crossOrigin="anonymous" href={origin} rel="preconnect" />
|
||||||
|
<link href={origin} rel="dns-prefetch" />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MediaPreconnect
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import type { Plugin } from 'payload'
|
||||||
|
|
||||||
|
import { s3Storage } from '@payloadcms/storage-s3'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cloudflare R2 media storage — configured from environment variables (agency
|
||||||
|
* infrastructure, not per-project panel data). R2 is S3-compatible, so we use
|
||||||
|
* @payloadcms/storage-s3 pointed at the R2 endpoint.
|
||||||
|
*
|
||||||
|
* Storage is infrastructure (like the database or PAYLOAD_SECRET): it binds at
|
||||||
|
* boot, and its credentials are agency-owned — so it lives in .env, not the
|
||||||
|
* panel. See docs/storage.md for the required variables.
|
||||||
|
*
|
||||||
|
* Returns the storage plugin when all R2 vars are present; otherwise returns a
|
||||||
|
* no-op passthrough so the project falls back to Payload's default local disk
|
||||||
|
* storage (useful in dev without R2). This mirrors how mailAdapter degrades
|
||||||
|
* gracefully when a transport isn't configured.
|
||||||
|
*
|
||||||
|
* @param collections - slugs of upload collections to offload to R2 (e.g. ['media'])
|
||||||
|
*/
|
||||||
|
export const buildR2Storage = (collections: string[] = ['media']): Plugin => {
|
||||||
|
const bucket = process.env.R2_BUCKET
|
||||||
|
const endpoint = process.env.R2_ENDPOINT
|
||||||
|
const accessKeyId = process.env.R2_ACCESS_KEY_ID
|
||||||
|
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY
|
||||||
|
|
||||||
|
// Any missing → skip R2, fall back to local disk. Warn so it's not silent.
|
||||||
|
if (!bucket || !endpoint || !accessKeyId || !secretAccessKey) {
|
||||||
|
return (config) => {
|
||||||
|
// Only warn when SOME vars are set (partial config = likely a mistake).
|
||||||
|
if (bucket || endpoint || accessKeyId || secretAccessKey) {
|
||||||
|
console.warn(
|
||||||
|
'[ipal] R2 storage: incomplete env (need R2_BUCKET, R2_ENDPOINT, ' +
|
||||||
|
'R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY). Falling back to local disk.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public URL for served media. R2 is private by default; its S3 endpoint only
|
||||||
|
// accepts uploads and won't serve files (403). With a custom domain
|
||||||
|
// (media.klient.pl → bucket) set R2_PUBLIC_URL so Payload generates URLs
|
||||||
|
// pointing there. Without it, uploads work but images don't display publicly.
|
||||||
|
// See docs/storage.md.
|
||||||
|
const publicUrl = process.env.R2_PUBLIC_URL?.replace(/\/$/, '') // strip trailing slash
|
||||||
|
|
||||||
|
// generateFileURL is a PER-COLLECTION option in @payloadcms/storage-s3 (not a
|
||||||
|
// top-level one) — R2 needs it to point served URLs at the custom domain
|
||||||
|
// instead of the private S3 endpoint. Each collection gets either `true`
|
||||||
|
// (plain offload) or an object carrying generateFileURL when a public URL is set.
|
||||||
|
const generateFileURL = publicUrl
|
||||||
|
? ({ filename, prefix }: { filename: string; prefix?: string }) =>
|
||||||
|
[publicUrl, prefix, filename].filter(Boolean).join('/')
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const collectionsConfig: Record<string, { generateFileURL: typeof generateFileURL } | true> = {}
|
||||||
|
for (const slug of collections) {
|
||||||
|
collectionsConfig[slug] = generateFileURL ? { generateFileURL } : true
|
||||||
|
}
|
||||||
|
|
||||||
|
return s3Storage({
|
||||||
|
bucket,
|
||||||
|
collections: collectionsConfig,
|
||||||
|
config: {
|
||||||
|
credentials: { accessKeyId, secretAccessKey },
|
||||||
|
endpoint,
|
||||||
|
region: 'auto', // R2 uses 'auto'
|
||||||
|
// R2 requires path-style addressing for S3 compatibility.
|
||||||
|
forcePathStyle: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { buildR2Storage } from './buildR2Storage.js'
|
||||||
Reference in New Issue
Block a user