Compare commits
11 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cda7d2363 | ||
|
|
ea48cfbfe9 | ||
|
|
1aeb912762 | ||
|
|
ce2cb35d71 | ||
|
|
d3ec68ac2f | ||
|
|
05bf52aac3 | ||
|
|
fed7f2fa5b | ||
|
|
686b0865b2 | ||
|
|
dfb4c8eb4c | ||
|
|
ad7a387e19 | ||
|
|
d05be016ce |
@@ -23,7 +23,7 @@ export default withBundleAnalyzer(
|
||||
env: {
|
||||
PAYLOAD_CORE_DEV: 'true',
|
||||
ROOT_DIR: path.resolve(dirname),
|
||||
PAYLOAD_DISABLE_DEPENDENCY_CHECKER: 'true',
|
||||
PAYLOAD_CI_DEPENDENCY_CHECKER: 'true',
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -37,6 +37,8 @@ export const DeviceContainer: React.FC<{
|
||||
x = '-50%'
|
||||
|
||||
if (
|
||||
desiredSize &&
|
||||
measuredDeviceSize &&
|
||||
typeof zoom === 'number' &&
|
||||
typeof desiredSize.width === 'number' &&
|
||||
typeof desiredSize.height === 'number' &&
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { BinScript } from '../config/types.js'
|
||||
import { findConfig } from '../config/find.js'
|
||||
import { generateImportMap } from './generateImportMap/index.js'
|
||||
import { generateTypes } from './generateTypes.js'
|
||||
import { info } from './info.js'
|
||||
import { loadEnv } from './loadEnv.js'
|
||||
import { migrate } from './migrate.js'
|
||||
|
||||
@@ -16,6 +17,11 @@ export const bin = async () => {
|
||||
const args = minimist(process.argv.slice(2))
|
||||
const script = (typeof args._[0] === 'string' ? args._[0] : '').toLowerCase()
|
||||
|
||||
if (script === 'info') {
|
||||
await info()
|
||||
return
|
||||
}
|
||||
|
||||
if (script === 'run') {
|
||||
const scriptPath = args._[1]
|
||||
if (!scriptPath) {
|
||||
|
||||
63
packages/payload/src/bin/info.ts
Normal file
63
packages/payload/src/bin/info.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { execFileSync } from 'child_process'
|
||||
import os from 'os'
|
||||
|
||||
import { getDependencies } from '../index.js'
|
||||
import { PAYLOAD_PACKAGE_LIST } from '../versions/payloadPackageList.js'
|
||||
|
||||
export const info = async () => {
|
||||
const deps = await getDependencies(process.cwd(), [
|
||||
...PAYLOAD_PACKAGE_LIST,
|
||||
'next',
|
||||
'react',
|
||||
'react-dom',
|
||||
])
|
||||
|
||||
const formattedDeps = Array.from(deps.resolved.entries()).map(([name, { version }]) => ({
|
||||
name,
|
||||
version,
|
||||
}))
|
||||
|
||||
console.log(generateOutput(formattedDeps))
|
||||
}
|
||||
|
||||
function generateOutput(packages: Array<{ name: string; version: string }>) {
|
||||
const cpuCores = os.cpus().length
|
||||
|
||||
const primaryDeps = packages.filter(({ name }) => name === 'payload' || name === 'next')
|
||||
const otherDeps = packages
|
||||
.filter(({ name }) => name !== 'payload' && name !== 'next')
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
const formattedDeps = [...primaryDeps, ...otherDeps]
|
||||
.map(({ name, version }) => ` ${name}: ${version}`)
|
||||
.join('\n')
|
||||
|
||||
return `
|
||||
Binaries:
|
||||
Node: ${process.versions.node}
|
||||
npm: ${getBinaryVersion('npm')}
|
||||
Yarn: ${getBinaryVersion('yarn')}
|
||||
pnpm: ${getBinaryVersion('pnpm')}
|
||||
Relevant Packages:
|
||||
${formattedDeps}
|
||||
Operating System:
|
||||
Platform: ${os.platform()}
|
||||
Arch: ${os.arch()}
|
||||
Version: ${os.version()}
|
||||
Available memory (MB): ${Math.ceil(os.totalmem() / 1024 / 1024)}
|
||||
Available CPU cores: ${cpuCores > 0 ? cpuCores : 'N/A'}
|
||||
`
|
||||
}
|
||||
|
||||
function getBinaryVersion(binaryName: string) {
|
||||
try {
|
||||
return execFileSync(binaryName, ['--version']).toString().trim()
|
||||
} catch {
|
||||
return 'N/A'
|
||||
}
|
||||
}
|
||||
|
||||
// Direct execution
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
void info()
|
||||
}
|
||||
59
packages/payload/src/checkPayloadDependencies.ts
Normal file
59
packages/payload/src/checkPayloadDependencies.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { CustomVersionParser } from './utilities/dependencies/dependencyChecker.js'
|
||||
|
||||
import { checkDependencies } from './utilities/dependencies/dependencyChecker.js'
|
||||
import { PAYLOAD_PACKAGE_LIST } from './versions/payloadPackageList.js'
|
||||
|
||||
const customReactVersionParser: CustomVersionParser = (version) => {
|
||||
const [mainVersion, ...preReleases] = version.split('-')
|
||||
|
||||
if (preReleases?.length === 3) {
|
||||
// Needs different handling, as it's in a format like 19.0.0-rc-06d0b89e-20240801 format
|
||||
const date = preReleases[2]
|
||||
|
||||
const parts = mainVersion.split('.').map(Number)
|
||||
return { parts, preReleases: [date] }
|
||||
}
|
||||
|
||||
const parts = mainVersion.split('.').map(Number)
|
||||
return { parts, preReleases }
|
||||
}
|
||||
|
||||
export async function checkPayloadDependencies() {
|
||||
const dependencies = [...PAYLOAD_PACKAGE_LIST]
|
||||
|
||||
if (process.env.PAYLOAD_CI_DEPENDENCY_CHECKER !== 'true') {
|
||||
dependencies.push('@payloadcms/plugin-sentry')
|
||||
}
|
||||
|
||||
// First load. First check if there are mismatching dependency versions of payload packages
|
||||
await checkDependencies({
|
||||
dependencyGroups: [
|
||||
{
|
||||
name: 'payload',
|
||||
dependencies,
|
||||
targetVersionDependency: 'payload',
|
||||
},
|
||||
{
|
||||
name: 'react',
|
||||
dependencies: ['react', 'react-dom'],
|
||||
targetVersionDependency: 'react',
|
||||
},
|
||||
],
|
||||
dependencyVersions: {
|
||||
next: {
|
||||
required: false,
|
||||
version: '>=15.0.0-canary.104',
|
||||
},
|
||||
react: {
|
||||
customVersionParser: customReactVersionParser,
|
||||
required: false,
|
||||
version: '>=19.0.0-rc-06d0b89e-20240801',
|
||||
},
|
||||
'react-dom': {
|
||||
customVersionParser: customReactVersionParser,
|
||||
required: false,
|
||||
version: '>=19.0.0-rc-06d0b89e-20240801',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -14,6 +14,15 @@ export type ServerOnlyCollectionAdminProperties = keyof Pick<
|
||||
'hidden' | 'preview'
|
||||
>
|
||||
|
||||
export type ServerOnlyUploadProperties = keyof Pick<
|
||||
SanitizedCollectionConfig['upload'],
|
||||
| 'adminThumbnail'
|
||||
| 'externalFileHeaderFilter'
|
||||
| 'handlers'
|
||||
| 'modifyResponseHeaders'
|
||||
| 'withMetadata'
|
||||
>
|
||||
|
||||
export type ClientCollectionConfig = {
|
||||
_isPreviewEnabled?: true
|
||||
admin: {
|
||||
|
||||
@@ -57,11 +57,12 @@ import type { TypeWithVersion } from './versions/types.js'
|
||||
import { decrypt, encrypt } from './auth/crypto.js'
|
||||
import { APIKeyAuthentication } from './auth/strategies/apiKey.js'
|
||||
import { JWTAuthentication } from './auth/strategies/jwt.js'
|
||||
import { checkPayloadDependencies } from './checkPayloadDependencies.js'
|
||||
import localOperations from './collections/operations/local/index.js'
|
||||
import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'
|
||||
import { fieldAffectsData } from './fields/config/types.js'
|
||||
import localGlobalOperations from './globals/operations/local/index.js'
|
||||
import { getDependencies } from './utilities/dependencies/getDependencies.js'
|
||||
import { checkDependencies } from './utilities/dependencies/dependencyChecker.js'
|
||||
import flattenFields from './utilities/flattenTopLevelFields.js'
|
||||
import { getLogger } from './utilities/logger.js'
|
||||
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'
|
||||
@@ -430,58 +431,7 @@ export class BasePayload {
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true'
|
||||
) {
|
||||
// First load. First check if there are mismatching dependency versions of payload packages
|
||||
const resolvedDependencies = await getDependencies(dirname, [
|
||||
'@payloadcms/ui/shared',
|
||||
'payload',
|
||||
'@payloadcms/next/utilities',
|
||||
'@payloadcms/richtext-lexical',
|
||||
'@payloadcms/richtext-slate',
|
||||
'@payloadcms/graphql',
|
||||
'@payloadcms/plugin-cloud',
|
||||
'@payloadcms/db-mongodb',
|
||||
'@payloadcms/db-postgres',
|
||||
'@payloadcms/plugin-form-builder',
|
||||
'@payloadcms/plugin-nested-docs',
|
||||
'@payloadcms/plugin-seo',
|
||||
'@payloadcms/plugin-search',
|
||||
'@payloadcms/plugin-cloud-storage',
|
||||
'@payloadcms/plugin-stripe',
|
||||
'@payloadcms/plugin-zapier',
|
||||
'@payloadcms/plugin-redirects',
|
||||
'@payloadcms/plugin-sentry',
|
||||
'@payloadcms/bundler-webpack',
|
||||
'@payloadcms/bundler-vite',
|
||||
'@payloadcms/live-preview',
|
||||
'@payloadcms/live-preview-react',
|
||||
'@payloadcms/translations',
|
||||
'@payloadcms/email-nodemailer',
|
||||
'@payloadcms/email-resend',
|
||||
'@payloadcms/storage-azure',
|
||||
'@payloadcms/storage-s3',
|
||||
'@payloadcms/storage-gcs',
|
||||
'@payloadcms/storage-vercel-blob',
|
||||
'@payloadcms/storage-uploadthing',
|
||||
])
|
||||
|
||||
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
|
||||
const foundVersions: {
|
||||
[version: string]: string
|
||||
} = {}
|
||||
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
|
||||
if (!Object.keys(foundVersions).includes(version)) {
|
||||
foundVersions[version] = _pkg
|
||||
}
|
||||
}
|
||||
if (Object.keys(foundVersions).length > 1) {
|
||||
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
|
||||
.map(([version, pkg]) => `${pkg}@${version}`)
|
||||
.join(', ')
|
||||
|
||||
throw new Error(
|
||||
`Mismatching payload dependency versions found: ${formattedVersionsWithPackageNameString}. All payload and @payloadcms/* packages must have the same version. This is an error with your set-up, caused by you, not a bug in payload. Please go to your package.json and ensure all payload and @payloadcms/* packages have the same version.`,
|
||||
)
|
||||
}
|
||||
await checkPayloadDependencies()
|
||||
}
|
||||
|
||||
this.importMap = options.importMap
|
||||
@@ -713,6 +663,7 @@ export type { ClientCollectionConfig } from './collections/config/client.js'
|
||||
export type {
|
||||
ServerOnlyCollectionAdminProperties,
|
||||
ServerOnlyCollectionProperties,
|
||||
ServerOnlyUploadProperties,
|
||||
} from './collections/config/client.js'
|
||||
export type {
|
||||
AfterChangeHook as CollectionAfterChangeHook,
|
||||
@@ -1047,6 +998,7 @@ export {
|
||||
deepMergeWithReactComponents,
|
||||
deepMergeWithSourceArrays,
|
||||
} from './utilities/deepMerge.js'
|
||||
export { getDependencies } from './utilities/dependencies/getDependencies.js'
|
||||
export { default as flattenTopLevelFields } from './utilities/flattenTopLevelFields.js'
|
||||
export { formatLabels, formatNames, toWords } from './utilities/formatLabels.js'
|
||||
export { getCollectionIDFieldTypes } from './utilities/getCollectionIDFieldTypes.js'
|
||||
@@ -1061,7 +1013,7 @@ export { mapAsync } from './utilities/mapAsync.js'
|
||||
export { mergeListSearchAndWhere } from './utilities/mergeListSearchAndWhere.js'
|
||||
export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'
|
||||
export { buildVersionGlobalFields } from './versions/buildGlobalFields.js'
|
||||
export { getDependencies }
|
||||
export { checkDependencies }
|
||||
export { versionDefaults } from './versions/defaults.js'
|
||||
export { deleteCollectionVersions } from './versions/deleteCollectionVersions.js'
|
||||
export { enforceMaxVersions } from './versions/enforceMaxVersions.js'
|
||||
|
||||
113
packages/payload/src/utilities/dependencies/dependencyChecker.ts
Normal file
113
packages/payload/src/utilities/dependencies/dependencyChecker.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { getDependencies } from '../../index.js'
|
||||
import { compareVersions } from './versionUtils.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export type CustomVersionParser = (version: string) => { parts: number[]; preReleases: string[] }
|
||||
|
||||
export type DependencyCheckerArgs = {
|
||||
/**
|
||||
* Define dependency groups to ensure that all dependencies within that group are on the same version, and that no dependencies in that group with different versions are found
|
||||
*/
|
||||
dependencyGroups?: {
|
||||
dependencies: string[]
|
||||
/**
|
||||
* Name of the dependency group to be displayed in the error message
|
||||
*/
|
||||
name: string
|
||||
targetVersionDependency: string
|
||||
}[]
|
||||
/**
|
||||
* Dependency package names keyed to their required versions. Supports >= (greater or equal than version) as a prefix, or no prefix for the exact version
|
||||
*/
|
||||
dependencyVersions?: {
|
||||
[dependency: string]: {
|
||||
customVersionParser?: CustomVersionParser
|
||||
required?: boolean
|
||||
version?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkDependencies({
|
||||
dependencyGroups,
|
||||
dependencyVersions,
|
||||
}: DependencyCheckerArgs): Promise<void> {
|
||||
if (dependencyGroups?.length) {
|
||||
for (const dependencyGroup of dependencyGroups) {
|
||||
const resolvedDependencies = await getDependencies(dirname, dependencyGroup.dependencies)
|
||||
|
||||
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
|
||||
const foundVersions: {
|
||||
[version: string]: string
|
||||
} = {}
|
||||
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
|
||||
if (!Object.keys(foundVersions).includes(version)) {
|
||||
foundVersions[version] = _pkg
|
||||
}
|
||||
}
|
||||
if (Object.keys(foundVersions).length > 1) {
|
||||
const targetVersion = resolvedDependencies.resolved.get(
|
||||
dependencyGroup.targetVersionDependency,
|
||||
)?.version
|
||||
if (targetVersion) {
|
||||
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
|
||||
.filter(([version]) => version !== targetVersion)
|
||||
.map(([version, pkg]) => `${pkg}@${version} (Please change this to ${targetVersion})`)
|
||||
.join(', ')
|
||||
throw new Error(
|
||||
`Mismatching "${dependencyGroup.name}" dependency versions found: ${formattedVersionsWithPackageNameString}. All "${dependencyGroup.name}" packages must have the same version. This is an error with your set-up, not a bug in Payload. Please go to your package.json and ensure all "${dependencyGroup.name}" packages have the same version.`,
|
||||
)
|
||||
} else {
|
||||
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
|
||||
.map(([version, pkg]) => `${pkg}@${version}`)
|
||||
.join(', ')
|
||||
throw new Error(
|
||||
`Mismatching "${dependencyGroup.name}" dependency versions found: ${formattedVersionsWithPackageNameString}. All "${dependencyGroup.name}" packages must have the same version. This is an error with your set-up, not a bug in Payload. Please go to your package.json and ensure all "${dependencyGroup.name}" packages have the same version.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dependencyVersions && Object.keys(dependencyVersions).length) {
|
||||
const resolvedDependencies = await getDependencies(dirname, Object.keys(dependencyVersions))
|
||||
for (const [dependency, settings] of Object.entries(dependencyVersions)) {
|
||||
const resolvedDependency = resolvedDependencies.resolved.get(dependency)
|
||||
if (!resolvedDependency) {
|
||||
if (!settings.required) {
|
||||
continue
|
||||
}
|
||||
throw new Error(`Dependency ${dependency} not found. Please ensure it is installed.`)
|
||||
}
|
||||
|
||||
if (settings.version) {
|
||||
const settingsVersionToCheck = settings.version.startsWith('>=')
|
||||
? settings.version.slice(2)
|
||||
: settings.version
|
||||
|
||||
const versionCompareResult = compareVersions(
|
||||
resolvedDependency.version,
|
||||
settingsVersionToCheck,
|
||||
settings.customVersionParser,
|
||||
)
|
||||
|
||||
if (settings.version.startsWith('>=')) {
|
||||
if (versionCompareResult === 'lower') {
|
||||
throw new Error(
|
||||
`Dependency ${dependency} is on version ${resolvedDependency.version}, but ${settings.version} or greater is required. Please update this dependency.`,
|
||||
)
|
||||
}
|
||||
} else if (versionCompareResult === 'lower' || versionCompareResult === 'greater') {
|
||||
throw new Error(
|
||||
`Dependency ${dependency} is on version ${resolvedDependency.version}, but ${settings.version} is required. Please update this dependency.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
packages/payload/src/utilities/dependencies/versionUtils.ts
Normal file
75
packages/payload/src/utilities/dependencies/versionUtils.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { CustomVersionParser } from './dependencyChecker.js'
|
||||
|
||||
export function parseVersion(version: string): { parts: number[]; preReleases: string[] } {
|
||||
const [mainVersion, ...preReleases] = version.split('-')
|
||||
const parts = mainVersion.split('.').map(Number)
|
||||
return { parts, preReleases }
|
||||
}
|
||||
|
||||
function extractNumbers(str: string): number[] {
|
||||
const matches = str.match(/\d+/g) || []
|
||||
return matches.map(Number)
|
||||
}
|
||||
|
||||
function comparePreRelease(v1: string, v2: string): number {
|
||||
const num1 = extractNumbers(v1)
|
||||
const num2 = extractNumbers(v2)
|
||||
|
||||
for (let i = 0; i < Math.max(num1.length, num2.length); i++) {
|
||||
if ((num1[i] || 0) < (num2[i] || 0)) return -1
|
||||
if ((num1[i] || 0) > (num2[i] || 0)) return 1
|
||||
}
|
||||
|
||||
// If numeric parts are equal, compare the whole string
|
||||
if (v1 < v2) return -1
|
||||
if (v1 > v2) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two semantic version strings, including handling pre-release identifiers.
|
||||
*
|
||||
* This function first compares the major, minor, and patch components as integers.
|
||||
* If these components are equal, it then moves on to compare pre-release versions.
|
||||
* Pre-release versions are compared first by extracting and comparing any numerical values.
|
||||
* If numerical values are equal, it compares the whole pre-release string lexicographically.
|
||||
*
|
||||
* @param {string} compare - The first version string to compare.
|
||||
* @param {string} to - The second version string to compare.
|
||||
* @param {function} [customVersionParser] - An optional function to parse version strings into parts and pre-releases.
|
||||
* @returns {string} - Returns greater if compare is greater than to, lower if compare is less than to, and equal if they are equal.
|
||||
*/
|
||||
export function compareVersions(
|
||||
compare: string,
|
||||
to: string,
|
||||
customVersionParser?: CustomVersionParser,
|
||||
): 'equal' | 'greater' | 'lower' {
|
||||
const { parts: parts1, preReleases: preReleases1 } = customVersionParser
|
||||
? customVersionParser(compare)
|
||||
: parseVersion(compare)
|
||||
const { parts: parts2, preReleases: preReleases2 } = customVersionParser
|
||||
? customVersionParser(to)
|
||||
: parseVersion(to)
|
||||
|
||||
// Compare main version parts
|
||||
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||
if ((parts1[i] || 0) > (parts2[i] || 0)) return 'greater'
|
||||
if ((parts1[i] || 0) < (parts2[i] || 0)) return 'lower'
|
||||
}
|
||||
|
||||
// Compare pre-release parts if main versions are equal
|
||||
if (preReleases1?.length || preReleases2?.length) {
|
||||
for (let i = 0; i < Math.max(preReleases1.length, preReleases2.length); i++) {
|
||||
if (!preReleases1[i]) return 'greater'
|
||||
if (!preReleases2[i]) return 'lower'
|
||||
|
||||
const result = comparePreRelease(preReleases1[i], preReleases2[i])
|
||||
if (result !== 0) {
|
||||
return result === 1 ? 'greater' : 'lower'
|
||||
}
|
||||
// Equal => continue for loop to check for next pre-release part
|
||||
}
|
||||
}
|
||||
|
||||
return 'equal'
|
||||
}
|
||||
31
packages/payload/src/versions/payloadPackageList.ts
Normal file
31
packages/payload/src/versions/payloadPackageList.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const PAYLOAD_PACKAGE_LIST = [
|
||||
'payload',
|
||||
'@payloadcms/bundler-vite',
|
||||
'@payloadcms/bundler-webpack',
|
||||
'@payloadcms/db-mongodb',
|
||||
'@payloadcms/db-postgres',
|
||||
'@payloadcms/email-nodemailer',
|
||||
'@payloadcms/email-resend',
|
||||
'@payloadcms/graphql',
|
||||
'@payloadcms/live-preview-react',
|
||||
'@payloadcms/live-preview',
|
||||
'@payloadcms/next/utilities',
|
||||
'@payloadcms/plugin-cloud-storage',
|
||||
'@payloadcms/plugin-cloud',
|
||||
'@payloadcms/plugin-form-builder',
|
||||
'@payloadcms/plugin-nested-docs',
|
||||
'@payloadcms/plugin-redirects',
|
||||
'@payloadcms/plugin-search',
|
||||
'@payloadcms/plugin-seo',
|
||||
'@payloadcms/plugin-stripe',
|
||||
'@payloadcms/plugin-zapier',
|
||||
'@payloadcms/richtext-lexical',
|
||||
'@payloadcms/richtext-slate',
|
||||
'@payloadcms/storage-azure',
|
||||
'@payloadcms/storage-gcs',
|
||||
'@payloadcms/storage-s3',
|
||||
'@payloadcms/storage-uploadthing',
|
||||
'@payloadcms/storage-vercel-blob',
|
||||
'@payloadcms/translations',
|
||||
'@payloadcms/ui/shared',
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-relationship-object-ids",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "A Payload plugin to store all relationship IDs as ObjectIDs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Config, GroupField, TabsField, TextField } from 'payload'
|
||||
|
||||
import { addDataAndFileToRequest } from '@payloadcms/next/utilities'
|
||||
import { deepMergeSimple } from 'payload/shared'
|
||||
|
||||
import type {
|
||||
@@ -133,7 +132,11 @@ export const seoPlugin =
|
||||
...(config.endpoints ?? []),
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
const data = await req.json()
|
||||
|
||||
if (data) {
|
||||
req.data = data
|
||||
}
|
||||
|
||||
const result = pluginConfig.generateTitle
|
||||
? await pluginConfig.generateTitle({
|
||||
@@ -148,7 +151,11 @@ export const seoPlugin =
|
||||
},
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
const data = await req.json()
|
||||
|
||||
if (data) {
|
||||
req.data = data
|
||||
}
|
||||
|
||||
const result = pluginConfig.generateDescription
|
||||
? await pluginConfig.generateDescription({
|
||||
@@ -163,7 +170,11 @@ export const seoPlugin =
|
||||
},
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
const data = await req.json()
|
||||
|
||||
if (data) {
|
||||
req.data = data
|
||||
}
|
||||
|
||||
const result = pluginConfig.generateURL
|
||||
? await pluginConfig.generateURL({
|
||||
@@ -178,7 +189,11 @@ export const seoPlugin =
|
||||
},
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
const data = await req.json()
|
||||
|
||||
if (data) {
|
||||
req.data = data
|
||||
}
|
||||
|
||||
const result = pluginConfig.generateImage
|
||||
? await pluginConfig.generateImage({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-stripe",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Stripe plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
User,
|
||||
} from 'payload'
|
||||
|
||||
import { validateUrl } from '../../../lexical/utils/url.js'
|
||||
import { validateUrl, validateUrlMinimal } from '../../../lexical/utils/url.js'
|
||||
|
||||
export const getBaseFields = (
|
||||
config: SanitizedConfig,
|
||||
@@ -64,10 +64,20 @@ export const getBaseFields = (
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ value }) => {
|
||||
if (!validateUrl(value)) {
|
||||
return encodeURIComponent(value)
|
||||
}
|
||||
return value
|
||||
},
|
||||
],
|
||||
},
|
||||
label: ({ t }) => t('fields:enterURL'),
|
||||
required: true,
|
||||
validate: (value: string) => {
|
||||
if (!validateUrl(value)) {
|
||||
if (!validateUrlMinimal(value)) {
|
||||
return 'Invalid URL'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,16 +5,14 @@ import type {
|
||||
SerializedLexicalNode,
|
||||
} from 'lexical'
|
||||
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
import {
|
||||
afterChangeTraverseFields,
|
||||
afterReadTraverseFields,
|
||||
beforeChangeTraverseFields,
|
||||
beforeValidateTraverseFields,
|
||||
checkDependencies,
|
||||
deepCopyObject,
|
||||
deepCopyObjectSimple,
|
||||
getDependencies,
|
||||
withNullableJSONSchemaType,
|
||||
} from 'payload'
|
||||
|
||||
@@ -42,46 +40,32 @@ import { richTextValidateHOC } from './validate/index.js'
|
||||
|
||||
let defaultSanitizedServerEditorConfig: SanitizedServerEditorConfig = null
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
||||
return async ({ config, isRoot }) => {
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true'
|
||||
) {
|
||||
const resolvedDependencies = await getDependencies(dirname, [
|
||||
'lexical',
|
||||
'@lexical/headless',
|
||||
'@lexical/link',
|
||||
'@lexical/list',
|
||||
'@lexical/mark',
|
||||
'@lexical/markdown',
|
||||
'@lexical/react',
|
||||
'@lexical/rich-text',
|
||||
'@lexical/selection',
|
||||
'@lexical/utils',
|
||||
])
|
||||
|
||||
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
|
||||
const foundVersions: {
|
||||
[version: string]: string
|
||||
} = {}
|
||||
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
|
||||
if (!Object.keys(foundVersions).includes(version)) {
|
||||
foundVersions[version] = _pkg
|
||||
}
|
||||
}
|
||||
if (Object.keys(foundVersions).length > 1) {
|
||||
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
|
||||
.map(([version, pkg]) => `${pkg}@${version}`)
|
||||
.join(', ')
|
||||
|
||||
throw new Error(
|
||||
`Mismatching lexical dependency versions found: ${formattedVersionsWithPackageNameString}. All lexical and @lexical/* packages must have the same version. This is an error with your set-up, caused by you, not a bug in payload. Please go to your package.json and ensure all lexical and @lexical/* packages have the same version.`,
|
||||
)
|
||||
}
|
||||
await checkDependencies({
|
||||
dependencyGroups: [
|
||||
{
|
||||
name: 'lexical',
|
||||
dependencies: [
|
||||
'lexical',
|
||||
'@lexical/headless',
|
||||
'@lexical/link',
|
||||
'@lexical/list',
|
||||
'@lexical/mark',
|
||||
'@lexical/markdown',
|
||||
'@lexical/react',
|
||||
'@lexical/rich-text',
|
||||
'@lexical/selection',
|
||||
'@lexical/utils',
|
||||
],
|
||||
targetVersionDependency: 'lexical',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
let features: FeatureProviderServer<any, any, any>[] = []
|
||||
|
||||
@@ -25,6 +25,14 @@ const absoluteRegExp =
|
||||
* */
|
||||
const relativeOrAnchorRegExp = /^[\w\-./]*(?:#\w[\w-]*)?$/
|
||||
|
||||
/**
|
||||
* Prevents unreasonable URLs from being inserted into the editor.
|
||||
* @param url
|
||||
*/
|
||||
export function validateUrlMinimal(url: string): boolean {
|
||||
return !url.includes(' ')
|
||||
}
|
||||
|
||||
// Do not keep validateUrl function too loose. This is run when pasting in text, to determine if links are in that text and if it should create AutoLinkNodes.
|
||||
// This is why we do not allow stuff like anchors here, as we don't want copied anchors to be turned into AutoLinkNodes.
|
||||
export function validateUrl(url: string): boolean {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-slate",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "The officially supported Slate richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -63,3 +63,4 @@ export { UnderlineLeafButton } from '../../field/leaves/underline/LeafButton.js'
|
||||
export { UnderlineLeaf } from '../../field/leaves/underline/Underline/index.js'
|
||||
|
||||
export { useLeaf } from '../../field/providers/LeafProvider.js'
|
||||
export { useSlatePlugin } from '../../utilities/useSlatePlugin.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-azure",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Payload storage adapter for Azure Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-gcs",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Payload storage adapter for Google Cloud Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-s3",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Payload storage adapter for Amazon S3",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-uploadthing",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Payload storage adapter for uploadthing",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-vercel-blob",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"description": "Payload storage adapter for Vercel Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/translations",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/ui",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.92",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
SanitizedCollectionConfig,
|
||||
ServerOnlyCollectionAdminProperties,
|
||||
ServerOnlyCollectionProperties,
|
||||
ServerOnlyUploadProperties,
|
||||
} from 'payload'
|
||||
import type React from 'react'
|
||||
|
||||
@@ -18,6 +19,30 @@ import { deepCopyObjectSimple } from 'payload'
|
||||
|
||||
import { createClientFields } from './fields.js'
|
||||
|
||||
const serverOnlyCollectionProperties: Partial<ServerOnlyCollectionProperties>[] = [
|
||||
'hooks',
|
||||
'access',
|
||||
'endpoints',
|
||||
'custom',
|
||||
// `upload`
|
||||
// `admin`
|
||||
// are all handled separately
|
||||
]
|
||||
|
||||
const serverOnlyUploadProperties: Partial<ServerOnlyUploadProperties>[] = [
|
||||
'adminThumbnail',
|
||||
'externalFileHeaderFilter',
|
||||
'handlers',
|
||||
'modifyResponseHeaders',
|
||||
'withMetadata',
|
||||
]
|
||||
|
||||
const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
|
||||
'hidden',
|
||||
'preview',
|
||||
// `livePreview` is handled separately
|
||||
]
|
||||
|
||||
export const createClientCollectionConfig = ({
|
||||
DefaultEditView,
|
||||
DefaultListView,
|
||||
@@ -46,16 +71,6 @@ export const createClientCollectionConfig = ({
|
||||
payload,
|
||||
})
|
||||
|
||||
const serverOnlyCollectionProperties: Partial<ServerOnlyCollectionProperties>[] = [
|
||||
'hooks',
|
||||
'access',
|
||||
'endpoints',
|
||||
'custom',
|
||||
// `upload`
|
||||
// `admin`
|
||||
// are all handled separately
|
||||
]
|
||||
|
||||
serverOnlyCollectionProperties.forEach((key) => {
|
||||
if (key in clientCollection) {
|
||||
delete clientCollection[key]
|
||||
@@ -63,10 +78,11 @@ export const createClientCollectionConfig = ({
|
||||
})
|
||||
|
||||
if ('upload' in clientCollection && typeof clientCollection.upload === 'object') {
|
||||
delete clientCollection.upload.handlers
|
||||
delete clientCollection.upload.adminThumbnail
|
||||
delete clientCollection.upload.externalFileHeaderFilter
|
||||
delete clientCollection.upload.withMetadata
|
||||
serverOnlyUploadProperties.forEach((key) => {
|
||||
if (key in clientCollection.upload) {
|
||||
delete clientCollection.upload[key]
|
||||
}
|
||||
})
|
||||
|
||||
if ('imageSizes' in clientCollection.upload && clientCollection.upload.imageSizes.length) {
|
||||
clientCollection.upload.imageSizes = clientCollection.upload.imageSizes.map((size) => {
|
||||
@@ -91,12 +107,6 @@ export const createClientCollectionConfig = ({
|
||||
})
|
||||
}
|
||||
|
||||
const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
|
||||
'hidden',
|
||||
'preview',
|
||||
// `livePreview` is handled separately
|
||||
]
|
||||
|
||||
serverOnlyCollectionAdminProperties.forEach((key) => {
|
||||
if (key in clientCollection.admin) {
|
||||
delete clientCollection.admin[key]
|
||||
|
||||
@@ -87,13 +87,13 @@ export const updateChangelog = async (args: Args = {}): Promise<ChangelogResult>
|
||||
// Might need to swap out HEAD for the new proposed version
|
||||
let changelog = `## [${proposedReleaseVersion}](https://github.com/payloadcms/payload/compare/${fromVersion}...${proposedReleaseVersion}) (${yyyyMMdd})\n\n\n`
|
||||
if (sections.feat.length) {
|
||||
changelog += `### Features\n\n${sections.feat.join('\n')}\n\n`
|
||||
changelog += `### 🚀 Features\n\n${sections.feat.join('\n')}\n\n`
|
||||
}
|
||||
if (sections.fix.length) {
|
||||
changelog += `### Bug Fixes\n\n${sections.fix.join('\n')}\n\n`
|
||||
changelog += `### 🐛 Bug Fixes\n\n${sections.fix.join('\n')}\n\n`
|
||||
}
|
||||
if (sections.breaking.length) {
|
||||
changelog += `### BREAKING CHANGES\n\n${sections.breaking.join('\n')}\n\n`
|
||||
changelog += `### ⚠️ BREAKING CHANGES\n\n${sections.breaking.join('\n')}\n\n`
|
||||
}
|
||||
|
||||
if (writeChangelog) {
|
||||
|
||||
@@ -18,16 +18,16 @@
|
||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/db-mongodb": "3.0.0-beta.84",
|
||||
"@payloadcms/live-preview-react": "3.0.0-beta.84",
|
||||
"@payloadcms/next": "3.0.0-beta.84",
|
||||
"@payloadcms/plugin-cloud": "3.0.0-beta.84",
|
||||
"@payloadcms/plugin-form-builder": "3.0.0-beta.84",
|
||||
"@payloadcms/plugin-nested-docs": "3.0.0-beta.84",
|
||||
"@payloadcms/plugin-redirects": "3.0.0-beta.84",
|
||||
"@payloadcms/plugin-seo": "3.0.0-beta.84",
|
||||
"@payloadcms/richtext-lexical": "3.0.0-beta.84",
|
||||
"@payloadcms/ui": "3.0.0-beta.84",
|
||||
"@payloadcms/db-mongodb": "3.0.0-beta.91",
|
||||
"@payloadcms/live-preview-react": "3.0.0-beta.91",
|
||||
"@payloadcms/next": "3.0.0-beta.91",
|
||||
"@payloadcms/plugin-cloud": "3.0.0-beta.91",
|
||||
"@payloadcms/plugin-form-builder": "3.0.0-beta.91",
|
||||
"@payloadcms/plugin-nested-docs": "3.0.0-beta.91",
|
||||
"@payloadcms/plugin-redirects": "3.0.0-beta.91",
|
||||
"@payloadcms/plugin-seo": "3.0.0-beta.91",
|
||||
"@payloadcms/richtext-lexical": "3.0.0-beta.91",
|
||||
"@payloadcms/ui": "3.0.0-beta.91",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
@@ -41,7 +41,7 @@
|
||||
"lexical": "0.17.0",
|
||||
"lucide-react": "^0.378.0",
|
||||
"next": "15.0.0-canary.104",
|
||||
"payload": "3.0.0-beta.84",
|
||||
"payload": "3.0.0-beta.91",
|
||||
"payload-admin-bar": "^1.0.6",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react": "19.0.0-rc-06d0b89e-20240801",
|
||||
@@ -56,6 +56,7 @@
|
||||
"@payloadcms/eslint-config": "^1.1.1",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@types/escape-html": "^1.0.2",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/node": "18.11.3",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.0",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
|
||||
|
||||
10230
templates/website/pnpm-lock.yaml
generated
Normal file
10230
templates/website/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -41,8 +41,8 @@ export default async function Post({ params: { slug = '' } }) {
|
||||
|
||||
<PostHero post={post} />
|
||||
|
||||
<div className="flex flex-col gap-4 pt-8">
|
||||
<div className="container lg:grid lg:grid-cols-[1fr_48rem_1fr] grid-rows-[1fr]">
|
||||
<div className="flex flex-col items-center gap-4 pt-8">
|
||||
<div className="container lg:mx-0 lg:grid lg:grid-cols-[1fr_48rem_1fr] grid-rows-[1fr]">
|
||||
<RichText
|
||||
className="lg:grid lg:grid-cols-subgrid col-start-1 col-span-3 grid-rows-[1fr]"
|
||||
content={post.content}
|
||||
@@ -50,16 +50,22 @@ export default async function Post({ params: { slug = '' } }) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<RelatedPosts
|
||||
className="mt-12"
|
||||
docs={post.relatedPosts.filter((post) => typeof post === 'object')}
|
||||
/>
|
||||
{post.relatedPosts && post.relatedPosts.length > 0 && (
|
||||
<RelatedPosts
|
||||
className="mt-12"
|
||||
docs={post.relatedPosts.filter((post) => typeof post === 'object')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params: { slug } }): Promise<Metadata> {
|
||||
export async function generateMetadata({
|
||||
params: { slug },
|
||||
}: {
|
||||
params: { slug: string }
|
||||
}): Promise<Metadata> {
|
||||
const post = await queryPostBySlug({ slug })
|
||||
|
||||
return generateMeta({ doc: post })
|
||||
|
||||
@@ -39,7 +39,9 @@ export default async function Page() {
|
||||
<CollectionArchive posts={posts.docs} />
|
||||
|
||||
<div className="container">
|
||||
{posts.totalPages > 1 && <Pagination page={posts.page} totalPages={posts.totalPages} />}
|
||||
{posts.totalPages > 1 && posts.page && (
|
||||
<Pagination page={posts.page} totalPages={posts.totalPages} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -40,7 +40,9 @@ export default async function Page({ params: { pageNumber = 2 } }) {
|
||||
<CollectionArchive posts={posts.docs} />
|
||||
|
||||
<div className="container">
|
||||
{posts.totalPages > 1 && <Pagination page={posts.page} totalPages={posts.totalPages} />}
|
||||
{posts.totalPages > 1 && posts.page && (
|
||||
<Pagination page={posts.page} totalPages={posts.totalPages} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -60,7 +62,7 @@ export async function generateStaticParams() {
|
||||
limit: 10,
|
||||
})
|
||||
|
||||
const pages = []
|
||||
const pages: number[] = []
|
||||
|
||||
for (let i = 1; i <= posts.totalPages; i++) {
|
||||
pages.push(i)
|
||||
|
||||
@@ -19,24 +19,23 @@ import { default as default_17 } from 'src/payload/components/BeforeDashboard'
|
||||
import { default as default_18 } from 'src/payload/components/BeforeLogin'
|
||||
|
||||
export const importMap = {
|
||||
'@payloadcms/richtext-lexical/client#RichTextCell': RichTextCell_0,
|
||||
'@payloadcms/richtext-lexical/client#RichTextField': RichTextField_1,
|
||||
'@payloadcms/richtext-lexical/generateComponentMap#getGenerateComponentMap':
|
||||
getGenerateComponentMap_2,
|
||||
'@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient': InlineToolbarFeatureClient_3,
|
||||
'@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient': FixedToolbarFeatureClient_4,
|
||||
'@payloadcms/richtext-lexical/client#HeadingFeatureClient': HeadingFeatureClient_5,
|
||||
'@payloadcms/richtext-lexical/client#UnderlineFeatureClient': UnderlineFeatureClient_6,
|
||||
'@payloadcms/richtext-lexical/client#BoldFeatureClient': BoldFeatureClient_7,
|
||||
'@payloadcms/richtext-lexical/client#ItalicFeatureClient': ItalicFeatureClient_8,
|
||||
'@payloadcms/richtext-lexical/client#LinkFeatureClient': LinkFeatureClient_9,
|
||||
'@payloadcms/plugin-seo/client#OverviewComponent': OverviewComponent_10,
|
||||
'@payloadcms/plugin-seo/client#MetaTitleComponent': MetaTitleComponent_11,
|
||||
'@payloadcms/plugin-seo/client#MetaImageComponent': MetaImageComponent_12,
|
||||
'@payloadcms/plugin-seo/client#MetaDescriptionComponent': MetaDescriptionComponent_13,
|
||||
'@payloadcms/plugin-seo/client#PreviewComponent': PreviewComponent_14,
|
||||
'@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient': HorizontalRuleFeatureClient_15,
|
||||
'@payloadcms/richtext-lexical/client#BlocksFeatureClient': BlocksFeatureClient_16,
|
||||
'/payload/components/BeforeDashboard#default': default_17,
|
||||
'/payload/components/BeforeLogin#default': default_18,
|
||||
"@payloadcms/richtext-lexical/client#RichTextCell": RichTextCell_0,
|
||||
"@payloadcms/richtext-lexical/client#RichTextField": RichTextField_1,
|
||||
"@payloadcms/richtext-lexical/generateComponentMap#getGenerateComponentMap": getGenerateComponentMap_2,
|
||||
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_3,
|
||||
"@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_4,
|
||||
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_5,
|
||||
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_6,
|
||||
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_7,
|
||||
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_8,
|
||||
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_9,
|
||||
"@payloadcms/plugin-seo/client#OverviewComponent": OverviewComponent_10,
|
||||
"@payloadcms/plugin-seo/client#MetaTitleComponent": MetaTitleComponent_11,
|
||||
"@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_12,
|
||||
"@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_13,
|
||||
"@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_14,
|
||||
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_15,
|
||||
"@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_16,
|
||||
"/payload/components/BeforeDashboard#default": default_17,
|
||||
"/payload/components/BeforeLogin#default": default_18
|
||||
}
|
||||
|
||||
@@ -14,14 +14,16 @@ export const ArchiveBlock: React.FC<
|
||||
id?: string
|
||||
}
|
||||
> = async (props) => {
|
||||
const { id, categories, introContent, limit = 3, populateBy, selectedDocs } = props
|
||||
const { id, categories, introContent, limit: limitFromProps, populateBy, selectedDocs } = props
|
||||
|
||||
const limit = limitFromProps || 3
|
||||
|
||||
let posts: Post[] = []
|
||||
|
||||
if (populateBy === 'collection') {
|
||||
const payload = await getPayloadHMR({ config: configPromise })
|
||||
|
||||
const flattenedCategories = categories.map((category) => {
|
||||
const flattenedCategories = categories?.map((category) => {
|
||||
if (typeof category === 'object') return category.id
|
||||
else return category
|
||||
})
|
||||
@@ -43,9 +45,13 @@ export const ArchiveBlock: React.FC<
|
||||
|
||||
posts = fetchedPosts.docs
|
||||
} else {
|
||||
posts = selectedDocs.map((post) => {
|
||||
if (typeof post.value === 'object') return post.value
|
||||
})
|
||||
if (selectedDocs?.length) {
|
||||
const filteredSelectedPosts = selectedDocs.map((post) => {
|
||||
if (typeof post.value === 'object') return post.value
|
||||
}) as Post[]
|
||||
|
||||
posts = filteredSelectedPosts
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,7 +16,7 @@ export const CallToActionBlock: React.FC<
|
||||
<div className="container">
|
||||
<div className="bg-card rounded border-border border p-4 flex flex-col gap-8 md:flex-row md:justify-between md:items-center">
|
||||
<div className="max-w-[48rem] flex items-center">
|
||||
<RichText className="" content={richText} enableGutter={false} />
|
||||
{richText && <RichText className="mb-0" content={richText} enableGutter={false} />}
|
||||
</div>
|
||||
<div className="flex flex-col gap-8">
|
||||
{(links || []).map(({ link }, i) => {
|
||||
|
||||
@@ -7,7 +7,9 @@ type Props = {
|
||||
language?: string
|
||||
}
|
||||
|
||||
export const Code: React.FC<Props> = ({ code, language }) => {
|
||||
export const Code: React.FC<Props> = ({ code, language = '' }) => {
|
||||
if (!code) return null
|
||||
|
||||
return (
|
||||
<Highlight code={code} language={language} theme={themes.vsDark}>
|
||||
{({ getLineProps, getTokenProps, tokens }) => (
|
||||
|
||||
@@ -32,12 +32,13 @@ export const ContentBlock: React.FC<
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(`col-span-4 lg:col-span-${colsSpanClasses[size]}`, {
|
||||
className={cn(`col-span-4 lg:col-span-${colsSpanClasses[size!]}`, {
|
||||
'md:col-span-2': size !== 'full',
|
||||
})}
|
||||
key={index}
|
||||
>
|
||||
<RichText content={richText} enableGutter={false} />
|
||||
{richText && <RichText content={richText} enableGutter={false} />}
|
||||
|
||||
{enableLink && <CMSLink {...link} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { CheckboxField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
|
||||
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { Checkbox as CheckboxUi } from '@/components/ui/checkbox'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import React from 'react'
|
||||
@@ -20,13 +22,19 @@ export const Checkbox: React.FC<
|
||||
setValue: any
|
||||
}
|
||||
> = ({ name, defaultValue, errors, label, register, required: requiredFromProps, width }) => {
|
||||
const props = register(name, { required: requiredFromProps })
|
||||
const { setValue } = useFormContext()
|
||||
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckboxUi
|
||||
defaultChecked={defaultValue}
|
||||
id={name}
|
||||
{...register(name, { required: requiredFromProps })}
|
||||
{...props}
|
||||
onCheckedChange={(checked) => {
|
||||
setValue(props.name, checked)
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={name}>{label}</Label>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import type { MessageField } from '@payloadcms/plugin-form-builder/types'
|
||||
|
||||
import RichText from '@/components/RichText'
|
||||
import React from 'react'
|
||||
|
||||
import { Width } from '../Width'
|
||||
|
||||
export const Message: React.FC<MessageField> = ({ message }) => {
|
||||
export const Message: React.FC = ({ message }: { message: Record<string, any> }) => {
|
||||
return (
|
||||
<Width className="my-12" width="100">
|
||||
<RichText content={message} />
|
||||
{message && <RichText content={message} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export const buildInitialFormState = (fields: FormFieldBlock[]) => {
|
||||
if (field.blockType === 'checkbox') {
|
||||
return {
|
||||
...initialSchema,
|
||||
[field.name]: false,
|
||||
[field.name]: field.defaultValue,
|
||||
}
|
||||
}
|
||||
if (field.blockType === 'country') {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Form as FormType } from '@payloadcms/plugin-form-builder/types'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import RichText from 'src/app/components/RichText'
|
||||
import { Button } from 'src/app/components/ui/button'
|
||||
|
||||
@@ -48,10 +48,8 @@ export const FormBlock: React.FC<
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
getValues,
|
||||
handleSubmit,
|
||||
register,
|
||||
setValue,
|
||||
} = formMethods
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@@ -128,44 +126,46 @@ export const FormBlock: React.FC<
|
||||
|
||||
return (
|
||||
<div className="container max-w-[48rem] pb-20">
|
||||
{enableIntro && introContent && !hasSubmitted && (
|
||||
<RichText className="mb-8" content={introContent} enableGutter={false} />
|
||||
)}
|
||||
{!isLoading && hasSubmitted && confirmationType === 'message' && (
|
||||
<RichText content={confirmationMessage} />
|
||||
)}
|
||||
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
|
||||
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
|
||||
{!hasSubmitted && (
|
||||
<form id={formID} onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-4 last:mb-0">
|
||||
{formFromProps &&
|
||||
formFromProps.fields &&
|
||||
formFromProps.fields?.map((field, index) => {
|
||||
const Field: React.FC<any> = fields?.[field.blockType]
|
||||
if (Field) {
|
||||
return (
|
||||
<div className="mb-6 last:mb-0" key={index}>
|
||||
<Field
|
||||
form={formFromProps}
|
||||
{...field}
|
||||
{...formMethods}
|
||||
control={control}
|
||||
errors={errors}
|
||||
register={register}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</div>
|
||||
<FormProvider {...formMethods}>
|
||||
{enableIntro && introContent && !hasSubmitted && (
|
||||
<RichText className="mb-8" content={introContent} enableGutter={false} />
|
||||
)}
|
||||
{!isLoading && hasSubmitted && confirmationType === 'message' && (
|
||||
<RichText content={confirmationMessage} />
|
||||
)}
|
||||
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
|
||||
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
|
||||
{!hasSubmitted && (
|
||||
<form id={formID} onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-4 last:mb-0">
|
||||
{formFromProps &&
|
||||
formFromProps.fields &&
|
||||
formFromProps.fields?.map((field, index) => {
|
||||
const Field: React.FC<any> = fields?.[field.blockType]
|
||||
if (Field) {
|
||||
return (
|
||||
<div className="mb-6 last:mb-0" key={index}>
|
||||
<Field
|
||||
form={formFromProps}
|
||||
{...field}
|
||||
{...formMethods}
|
||||
control={control}
|
||||
errors={errors}
|
||||
register={register}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Button form={formID} type="submit" variant="default">
|
||||
{submitButtonLabel}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
<Button form={formID} type="submit" variant="default">
|
||||
{submitButtonLabel}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</FormProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ type Props = Extract<Page['layout'][0], { blockType: 'mediaBlock' }> & {
|
||||
id?: string
|
||||
imgClassName?: string
|
||||
staticImage?: StaticImageData
|
||||
disableInnerContainer?: boolean
|
||||
}
|
||||
|
||||
export const MediaBlock: React.FC<Props> = (props) => {
|
||||
@@ -27,6 +28,7 @@ export const MediaBlock: React.FC<Props> = (props) => {
|
||||
media,
|
||||
position = 'default',
|
||||
staticImage,
|
||||
disableInnerContainer,
|
||||
} = props
|
||||
|
||||
let caption
|
||||
@@ -55,7 +57,7 @@ export const MediaBlock: React.FC<Props> = (props) => {
|
||||
className={cn(
|
||||
'mt-6',
|
||||
{
|
||||
container: position === 'fullscreen',
|
||||
container: position === 'fullscreen' && !disableInnerContainer,
|
||||
},
|
||||
captionClassName,
|
||||
)}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const Blocks: React.FC<{
|
||||
return (
|
||||
<Fragment>
|
||||
{blocks.map((block, index) => {
|
||||
const { blockName, blockType } = block
|
||||
const { blockType } = block
|
||||
|
||||
if (blockType && blockType in blockComponents) {
|
||||
const Block = blockComponents[blockType]
|
||||
@@ -38,7 +38,7 @@ export const Blocks: React.FC<{
|
||||
return (
|
||||
<div className="my-16" key={index}>
|
||||
{/* @ts-expect-error */}
|
||||
<Block id={toKebabCase(blockName)} {...block} />
|
||||
<Block {...block} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ interface HeaderClientProps {
|
||||
|
||||
export const HeaderClient: React.FC<HeaderClientProps> = ({ header }) => {
|
||||
/* Storing the value in a useState to avoid hydration errors */
|
||||
const [theme, setTheme] = useState(null)
|
||||
const [theme, setTheme] = useState<string | null>(null)
|
||||
const { headerTheme, setHeaderTheme } = useHeaderTheme()
|
||||
const pathname = usePathname()
|
||||
|
||||
@@ -25,7 +25,7 @@ export const HeaderClient: React.FC<HeaderClientProps> = ({ header }) => {
|
||||
}, [pathname])
|
||||
|
||||
useEffect(() => {
|
||||
if (headerTheme !== theme) setTheme(headerTheme)
|
||||
if (headerTheme && headerTheme !== theme) setTheme(headerTheme)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [headerTheme])
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ type CMSLinkType = {
|
||||
appearance?: 'inline' | ButtonProps['variant']
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
label?: string
|
||||
newTab?: boolean
|
||||
label?: string | null
|
||||
newTab?: boolean | null
|
||||
reference?: {
|
||||
relationTo: 'pages' | 'posts'
|
||||
value: Page | Post | string | number
|
||||
}
|
||||
size?: ButtonProps['size']
|
||||
type?: 'custom' | 'reference'
|
||||
url?: string
|
||||
} | null
|
||||
size?: ButtonProps['size'] | null
|
||||
type?: 'custom' | 'reference' | null
|
||||
url?: string | null
|
||||
}
|
||||
|
||||
export const CMSLink: React.FC<CMSLinkType> = (props) => {
|
||||
@@ -48,7 +48,7 @@ export const CMSLink: React.FC<CMSLinkType> = (props) => {
|
||||
/* Ensure we don't break any styles set by richText */
|
||||
if (appearance === 'inline') {
|
||||
return (
|
||||
<Link className={cn(className)} href={href || url} {...newTabProps}>
|
||||
<Link className={cn(className)} href={href || url || ''} {...newTabProps}>
|
||||
{label && label}
|
||||
{children && children}
|
||||
</Link>
|
||||
@@ -57,7 +57,7 @@ export const CMSLink: React.FC<CMSLinkType> = (props) => {
|
||||
|
||||
return (
|
||||
<Button asChild className={className} size={size} variant={appearance}>
|
||||
<Link className={cn(className)} href={href || url} {...newTabProps}>
|
||||
<Link className={cn(className)} href={href || url || ''} {...newTabProps}>
|
||||
{label && label}
|
||||
{children && children}
|
||||
</Link>
|
||||
|
||||
@@ -6,6 +6,6 @@ import React from 'react'
|
||||
export const LivePreviewListener: React.FC = () => {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<PayloadLivePreview refresh={router.refresh} serverURL={process.env.NEXT_PUBLIC_SERVER_URL} />
|
||||
<PayloadLivePreview refresh={router.refresh} serverURL={process.env.NEXT_PUBLIC_SERVER_URL!} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ export const ImageMedia: React.FC<MediaProps> = (props) => {
|
||||
width: fullWidth,
|
||||
} = resource
|
||||
|
||||
width = fullWidth
|
||||
height = fullHeight
|
||||
width = fullWidth!
|
||||
height = fullHeight!
|
||||
alt = altFromResource
|
||||
|
||||
src = `${process.env.NEXT_PUBLIC_SERVER_URL}${url}`
|
||||
|
||||
@@ -118,6 +118,7 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
|
||||
{...block}
|
||||
captionClassName="mx-auto max-w-[48rem]"
|
||||
enableGutter={false}
|
||||
disableInnerContainer={true}
|
||||
/>
|
||||
)
|
||||
case 'banner':
|
||||
|
||||
@@ -19,7 +19,7 @@ export const HighImpactHero: React.FC<Page['hero']> = ({ links, media, richText
|
||||
<div className="relative -mt-[10.4rem] flex items-end text-white" data-theme="dark">
|
||||
<div className="container mb-8 z-10 relative">
|
||||
<div className="max-w-[34rem]">
|
||||
<RichText className="mb-6" content={richText} enableGutter={false} />
|
||||
{richText && <RichText className="mb-6" content={richText} enableGutter={false} />}
|
||||
{Array.isArray(links) && links.length > 0 && (
|
||||
<ul className="flex gap-4">
|
||||
{links.map(({ link }, i) => {
|
||||
@@ -34,7 +34,7 @@ export const HighImpactHero: React.FC<Page['hero']> = ({ links, media, richText
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-[80vh] select-none">
|
||||
{typeof media === 'object' && (
|
||||
{media && typeof media === 'object' && (
|
||||
<React.Fragment>
|
||||
<Media fill imgClassName="-z-10 object-cover" priority resource={media} />
|
||||
<div className="absolute pointer-events-none left-0 bottom-0 w-full h-1/2 bg-gradient-to-t from-black to-transparent" />
|
||||
|
||||
@@ -18,7 +18,7 @@ export const LowImpactHero: React.FC<LowImpactHeroType> = ({ children, richText
|
||||
return (
|
||||
<div className="container mt-16">
|
||||
<div className="max-w-[48rem]">
|
||||
{children || <RichText content={richText} enableGutter={false} />}
|
||||
{children || (richText && <RichText content={richText} enableGutter={false} />)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,8 @@ export const MediumImpactHero: React.FC<Page['hero']> = ({ links, media, richTex
|
||||
return (
|
||||
<div className="">
|
||||
<div className="container mb-8">
|
||||
<RichText className="mb-6" content={richText} enableGutter={false} />
|
||||
{richText && <RichText className="mb-6" content={richText} enableGutter={false} />}
|
||||
|
||||
{Array.isArray(links) && links.length > 0 && (
|
||||
<ul className="flex gap-4">
|
||||
{links.map(({ link }, i) => {
|
||||
@@ -24,7 +25,7 @@ export const MediumImpactHero: React.FC<Page['hero']> = ({ links, media, richTex
|
||||
)}
|
||||
</div>
|
||||
<div className="container ">
|
||||
{typeof media === 'object' && (
|
||||
{media && typeof media === 'object' && (
|
||||
<div>
|
||||
<Media
|
||||
className="-mx-4 md:-mx-8 2xl:-mx-16"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { draftMode } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import configPromise from '@payload-config'
|
||||
|
||||
const payloadToken = 'payload-token'
|
||||
|
||||
@@ -13,6 +15,7 @@ export async function GET(
|
||||
}
|
||||
},
|
||||
): Promise<Response> {
|
||||
const payload = await getPayloadHMR({ config: configPromise })
|
||||
const token = req.cookies.get(payloadToken)?.value
|
||||
const { searchParams } = new URL(req.url)
|
||||
const path = searchParams.get('path')
|
||||
@@ -25,8 +28,15 @@ export async function GET(
|
||||
new Response('You are not allowed to preview this page', { status: 403 })
|
||||
}
|
||||
|
||||
const user = jwt.decode(token, process.env.PAYLOAD_SECRET)
|
||||
let user
|
||||
|
||||
try {
|
||||
user = jwt.verify(token, payload.secret)
|
||||
} catch (error) {
|
||||
payload.logger.error('Error verifying token for live preview:', error)
|
||||
}
|
||||
|
||||
// You can add additional checks here to see if the user is allowed to preview this page
|
||||
if (!user) {
|
||||
draftMode().disable()
|
||||
return new Response('You are not allowed to preview this page', { status: 403 })
|
||||
|
||||
@@ -19,7 +19,7 @@ const initialContext: ContextType = {
|
||||
const HeaderThemeContext = createContext(initialContext)
|
||||
|
||||
export const HeaderThemeProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [headerTheme, setThemeState] = useState<Theme | undefined>(
|
||||
const [headerTheme, setThemeState] = useState<Theme | undefined | null>(
|
||||
canUseDOM ? (document.documentElement.getAttribute('data-theme') as Theme) : undefined,
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export const generateMeta = async (args: { doc: Page | Post }): Promise<Metadata
|
||||
return {
|
||||
description: doc?.meta?.description,
|
||||
openGraph: mergeOpenGraph({
|
||||
description: doc?.meta?.description,
|
||||
description: doc?.meta?.description || '',
|
||||
images: ogImage
|
||||
? [
|
||||
{
|
||||
|
||||
@@ -34,8 +34,9 @@ export const getMeUser = async (args?: {
|
||||
redirect(nullUserRedirect)
|
||||
}
|
||||
|
||||
// Token will exist here because if it doesn't the user will be redirected
|
||||
return {
|
||||
token,
|
||||
token: token!,
|
||||
user,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import { useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
type UseClickableCardType<T extends HTMLElement> = {
|
||||
card: {
|
||||
ref: RefObject<T>
|
||||
ref: RefObject<T | null>
|
||||
}
|
||||
link: {
|
||||
ref: RefObject<HTMLAnchorElement>
|
||||
ref: RefObject<HTMLAnchorElement | null>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ export interface Page {
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
media?: string | Media | null;
|
||||
media?: (string | null) | Media;
|
||||
};
|
||||
layout: (
|
||||
| {
|
||||
@@ -231,7 +231,7 @@ export interface Page {
|
||||
)[];
|
||||
meta?: {
|
||||
title?: string | null;
|
||||
image?: string | Media | null;
|
||||
image?: (string | null) | Media;
|
||||
description?: string | null;
|
||||
};
|
||||
publishedAt?: string | null;
|
||||
@@ -319,7 +319,7 @@ export interface Post {
|
||||
categories?: (string | Category)[] | null;
|
||||
meta?: {
|
||||
title?: string | null;
|
||||
image?: string | Media | null;
|
||||
image?: (string | null) | Media;
|
||||
description?: string | null;
|
||||
};
|
||||
publishedAt?: string | null;
|
||||
|
||||
@@ -41,8 +41,8 @@ const generateTitle: GenerateTitle<Post | Page> = ({ doc }) => {
|
||||
|
||||
const generateURL: GenerateURL<Post | Page> = ({ doc }) => {
|
||||
return doc?.slug
|
||||
? `${process.env.NEXT_PUBLIC_SERVER_URL}/${doc.slug}`
|
||||
: process.env.NEXT_PUBLIC_SERVER_URL
|
||||
? `${process.env.NEXT_PUBLIC_SERVER_URL!}/${doc.slug}`
|
||||
: process.env.NEXT_PUBLIC_SERVER_URL!
|
||||
}
|
||||
|
||||
export default buildConfig({
|
||||
@@ -116,7 +116,7 @@ export default buildConfig({
|
||||
}),
|
||||
// database-adapter-config-start
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI,
|
||||
url: process.env.DATABASE_URI!,
|
||||
}),
|
||||
// database-adapter-config-end
|
||||
collections: [Pages, Posts, Media, Categories, Users],
|
||||
@@ -190,7 +190,7 @@ export default buildConfig({
|
||||
}),
|
||||
payloadCloudPlugin(), // storage-adapter-placeholder
|
||||
],
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
secret: process.env.PAYLOAD_SECRET!,
|
||||
sharp,
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
|
||||
@@ -5,7 +5,5 @@ import type { User } from '../../payload-types'
|
||||
type isAuthenticated = (args: AccessArgs<User>) => boolean
|
||||
|
||||
export const authenticated: isAuthenticated = ({ req: { user } }) => {
|
||||
if (user) {
|
||||
return true
|
||||
}
|
||||
return Boolean(user)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CollectionAfterReadHook } from 'payload'
|
||||
import { User } from 'src/payload-types'
|
||||
|
||||
// The `user` collection has access control locked so that users are not publicly accessible
|
||||
// This means that we need to populate the authors manually here to protect user privacy
|
||||
@@ -6,7 +7,7 @@ import type { CollectionAfterReadHook } from 'payload'
|
||||
// So we use an alternative `populatedAuthors` field to populate the user data, hidden from the admin UI
|
||||
export const populateAuthors: CollectionAfterReadHook = async ({ doc, req, req: { payload } }) => {
|
||||
if (doc?.authors) {
|
||||
const authorDocs = []
|
||||
const authorDocs: User[] = []
|
||||
|
||||
for (const author of doc.authors) {
|
||||
const authorDoc = await payload.findByID({
|
||||
|
||||
@@ -13,8 +13,8 @@ const formatSlug =
|
||||
return format(value)
|
||||
}
|
||||
|
||||
if (operation === 'create') {
|
||||
const fallbackData = data?.[fallback] || originalDoc?.[fallback]
|
||||
if (operation === 'create' || !data?.slug) {
|
||||
const fallbackData = data?.[fallback] || data?.[fallback]
|
||||
|
||||
if (fallbackData && typeof fallbackData === 'string') {
|
||||
return format(fallbackData)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function initPayloadE2E({ dirname }: Args): Promise<Result> {
|
||||
|
||||
const port = 3000
|
||||
process.env.PORT = String(port)
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER = 'true'
|
||||
process.env.PAYLOAD_CI_DEPENDENCY_CHECKER = 'true'
|
||||
|
||||
const serverURL = `http://localhost:${port}`
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export async function initPayloadE2ENoConfig<T extends GeneratedTypes<T>>({
|
||||
|
||||
const port = 3000
|
||||
process.env.PORT = String(port)
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER = 'true'
|
||||
process.env.PAYLOAD_CI_DEPENDENCY_CHECKER = 'true'
|
||||
|
||||
const serverURL = `http://localhost:${port}`
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ process.env.PAYLOAD_DROP_DATABASE = 'true'
|
||||
process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER = 's3'
|
||||
|
||||
process.env.NODE_OPTIONS = '--no-deprecation'
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER = 'true'
|
||||
process.env.PAYLOAD_CI_DEPENDENCY_CHECKER = 'true'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
Reference in New Issue
Block a user