Files
ipal-kit/src/plugin.ts
T

109 lines
3.7 KiB
TypeScript

import type { Config, Plugin } from 'payload'
import type { IpalOptions } from './types.js'
import { buildCookieSettings } from './globals/CookieSettings/index.js'
import { buildSiteIntegrations } from './globals/SiteIntegrations/index.js'
import { buildSiteSettings } from './globals/SiteSettings/index.js'
import { injectRoles } from './modules/access/index.js'
import { buildArchiveFields } from './modules/content/index.js'
import { buildFormsPlugin } from './modules/forms/formsPluginConfig.js'
import { buildLocalizationConfig, validateI18nConfig } from './modules/i18n/index.js'
import { buildSystemPagesFields } from './modules/pages/index.js'
import { buildSeoPlugin, injectAutoFillMeta, injectSeoTabs } from './modules/seo/index.js'
/**
* IPAL (Intecion Payload Advanced Library) plugin for Payload CMS 3.
*
* @example
* ```ts
* import { ipalKit } from 'ipal-kit'
*
* export default buildConfig({
* plugins: [
* ipalKit({
* i18n: {
* locales: [
* { code: 'pl', label: 'Polski' },
* { code: 'en', label: 'English' },
* ],
* defaultLocale: 'pl',
* },
* access: { authCollection: 'users' },
* }),
* ],
* })
* ```
*/
export const ipalKit = (options: IpalOptions): Plugin => {
// Validate eagerly — fail fast before Payload boots
validateI18nConfig(options.i18n)
return async (incomingConfig: Config): Promise<Config> => {
// Early return when disabled — schema stays, behavior off
if (options.enabled === false) {
return incomingConfig
}
let config = { ...incomingConfig }
// --- i18n ---
config.localization = buildLocalizationConfig(options.i18n)
// --- access: inject roles into the client's auth collection ---
if (options.access) {
config = injectRoles(config, options.access)
}
// --- seo: apply @payloadcms/plugin-seo directly ---
// NOTE: apply the plugin function to the config immediately rather than
// pushing it onto config.plugins. Payload has already iterated the plugins
// array by the time IPAL runs, so nested plugins added to that list are
// never executed. Calling the plugin as (config) => config applies its
// transform now.
if (options.seo) {
config = await buildSeoPlugin({ seo: options.seo })(config)
// Auto-fill empty meta from document content on save
config = injectAutoFillMeta(config, options.seo)
// Wrap fields into Content + SEO tabs (replaces plugin-seo's tabbedUI,
// which breaks when other fields already exist in the collection)
config = injectSeoTabs(config, options.seo)
}
// --- forms: apply @payloadcms/plugin-form-builder directly ---
if (options.forms) {
config = await buildFormsPlugin(options.forms)(config)
}
// --- globals ---
// The System Pages tab collects every "which page plays this role"
// assignment. Composing it here keeps SiteSettings unaware of which modules
// are enabled — it just renders the fields it's given.
const systemPageFields = [
...(options.pages ? buildSystemPagesFields(options.pages) : []),
...(options.content && options.pages
? buildArchiveFields(options.content, options.pages.slug)
: []),
]
config.globals = [
...(config.globals ?? []),
buildSiteSettings({
additionalFields: options.siteSettingsFields,
systemPageFields,
}),
buildSiteIntegrations({ additionalFields: options.integrationsFields }),
buildCookieSettings(),
]
// --- hooks: onInit ---
const incomingOnInit = config.onInit
config.onInit = async (payload) => {
if (incomingOnInit) {await incomingOnInit(payload)}
payload.logger.info('[ipal] Plugin initialized.')
}
return config
}
}