templates: pin all payload packages, improve gen-templates script (#11841)

This PR comes with a bunch of improvements to our template generation
script that makes it safer and more reliable

- bumps all our templates
- Using `latest` as payload version in our templates has proven to be
unreliable. This updates the gen-templates script to pin all payload
packages to the latest version
- adds the missing `website` entry for our template variations, thus
ensuring its lockfile gets updated
- adds importmap generation to the gen-templates script
- adds new `script:gen-templates:build` script to verify that all
templates still build correctly
This commit is contained in:
Alessio Gravili
2025-03-26 14:52:53 -06:00
committed by GitHub
parent 1578cd2425
commit 59c9feeb45
26 changed files with 8383 additions and 2982 deletions

View File

@@ -87,6 +87,7 @@
"runts": "cross-env NODE_OPTIONS=--no-deprecation node --no-deprecation --import @swc-node/register/esm-register", "runts": "cross-env NODE_OPTIONS=--no-deprecation node --no-deprecation --import @swc-node/register/esm-register",
"script:build-template-with-local-pkgs": "pnpm --filter scripts build-template-with-local-pkgs", "script:build-template-with-local-pkgs": "pnpm --filter scripts build-template-with-local-pkgs",
"script:gen-templates": "pnpm --filter scripts gen-templates", "script:gen-templates": "pnpm --filter scripts gen-templates",
"script:gen-templates:build": "pnpm --filter scripts gen-templates --build",
"script:license-check": "pnpm --filter scripts license-check", "script:license-check": "pnpm --filter scripts license-check",
"script:list-published": "pnpm --filter releaser list-published", "script:list-published": "pnpm --filter releaser list-published",
"script:pack": "pnpm --filter scripts pack-all-to-dest", "script:pack": "pnpm --filter scripts pack-all-to-dest",

View File

@@ -15,14 +15,14 @@
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-mongodb": "latest", "@payloadcms/db-mongodb": "3.31.0",
"@payloadcms/next": "latest", "@payloadcms/next": "3.31.0",
"@payloadcms/payload-cloud": "latest", "@payloadcms/payload-cloud": "3.31.0",
"@payloadcms/richtext-lexical": "latest", "@payloadcms/richtext-lexical": "3.31.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"next": "15.2.3", "next": "15.2.3",
"payload": "latest", "payload": "3.31.0",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"sharp": "0.32.6" "sharp": "0.32.6"

File diff suppressed because it is too large Load Diff

View File

@@ -19,18 +19,18 @@
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start"
}, },
"dependencies": { "dependencies": {
"@payloadcms/admin-bar": "latest", "@payloadcms/admin-bar": "3.31.0",
"@payloadcms/db-mongodb": "latest", "@payloadcms/db-mongodb": "3.31.0",
"@payloadcms/live-preview-react": "latest", "@payloadcms/live-preview-react": "3.31.0",
"@payloadcms/next": "latest", "@payloadcms/next": "3.31.0",
"@payloadcms/payload-cloud": "latest", "@payloadcms/payload-cloud": "3.31.0",
"@payloadcms/plugin-form-builder": "latest", "@payloadcms/plugin-form-builder": "3.31.0",
"@payloadcms/plugin-nested-docs": "latest", "@payloadcms/plugin-nested-docs": "3.31.0",
"@payloadcms/plugin-redirects": "latest", "@payloadcms/plugin-redirects": "3.31.0",
"@payloadcms/plugin-search": "latest", "@payloadcms/plugin-search": "3.31.0",
"@payloadcms/plugin-seo": "latest", "@payloadcms/plugin-seo": "3.31.0",
"@payloadcms/richtext-lexical": "latest", "@payloadcms/richtext-lexical": "3.31.0",
"@payloadcms/ui": "latest", "@payloadcms/ui": "3.31.0",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-select": "^2.0.0",
@@ -43,7 +43,7 @@
"lucide-react": "^0.378.0", "lucide-react": "^0.378.0",
"next": "15.2.3", "next": "15.2.3",
"next-sitemap": "^4.2.3", "next-sitemap": "^4.2.3",
"payload": "latest", "payload": "3.31.0",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",

File diff suppressed because it is too large Load Diff

View File

@@ -16,17 +16,17 @@
"ci": "payload migrate && pnpm build" "ci": "payload migrate && pnpm build"
}, },
"dependencies": { "dependencies": {
"@payloadcms/next": "latest", "@payloadcms/next": "3.31.0",
"@payloadcms/payload-cloud": "latest", "@payloadcms/payload-cloud": "3.31.0",
"@payloadcms/richtext-lexical": "latest", "@payloadcms/richtext-lexical": "3.31.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"next": "15.2.3", "next": "15.2.3",
"payload": "latest", "payload": "3.31.0",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"sharp": "0.32.6", "sharp": "0.32.6",
"@payloadcms/db-postgres": "latest" "@payloadcms/db-postgres": "3.31.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",

View File

@@ -1,5 +1,5 @@
{ {
"id": "9cce5027-9a03-4012-908a-037491622267", "id": "aa5feeb6-613d-43e6-88eb-07c5d853a81c",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",

View File

@@ -1,9 +1,9 @@
import * as migration_20250311_214602_initial from './20250311_214602_initial' import * as migration_20250326_181533_initial from './20250326_181533_initial'
export const migrations = [ export const migrations = [
{ {
up: migration_20250311_214602_initial.up, up: migration_20250326_181533_initial.up,
down: migration_20250311_214602_initial.down, down: migration_20250326_181533_initial.down,
name: '20250311_214602_initial', name: '20250326_181533_initial',
}, },
] ]

View File

@@ -54,6 +54,7 @@ export type SupportedTimezones =
| 'Asia/Singapore' | 'Asia/Singapore'
| 'Asia/Tokyo' | 'Asia/Tokyo'
| 'Asia/Seoul' | 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney' | 'Australia/Sydney'
| 'Pacific/Guam' | 'Pacific/Guam'
| 'Pacific/Noumea' | 'Pacific/Noumea'

View File

@@ -15,17 +15,17 @@
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-mongodb": "latest", "@payloadcms/db-mongodb": "3.31.0",
"@payloadcms/next": "latest", "@payloadcms/next": "3.31.0",
"@payloadcms/payload-cloud": "latest", "@payloadcms/payload-cloud": "3.31.0",
"@payloadcms/richtext-lexical": "latest", "@payloadcms/richtext-lexical": "3.31.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"next": "15.2.3", "next": "15.2.3",
"payload": "latest", "payload": "3.31.0",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"@payloadcms/storage-vercel-blob": "latest" "@payloadcms/storage-vercel-blob": "3.31.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",

View File

@@ -1 +1,6 @@
export const importMap = {} import { VercelBlobClientUploadHandler as VercelBlobClientUploadHandler_16c82c5e25f430251a3e3ba57219ff4e } from '@payloadcms/storage-vercel-blob/client'
export const importMap = {
'@payloadcms/storage-vercel-blob/client#VercelBlobClientUploadHandler':
VercelBlobClientUploadHandler_16c82c5e25f430251a3e3ba57219ff4e,
}

View File

@@ -16,17 +16,17 @@
"ci": "payload migrate && pnpm build" "ci": "payload migrate && pnpm build"
}, },
"dependencies": { "dependencies": {
"@payloadcms/next": "latest", "@payloadcms/next": "3.31.0",
"@payloadcms/payload-cloud": "latest", "@payloadcms/payload-cloud": "3.31.0",
"@payloadcms/richtext-lexical": "latest", "@payloadcms/richtext-lexical": "3.31.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"next": "15.2.3", "next": "15.2.3",
"payload": "latest", "payload": "3.31.0",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"@payloadcms/db-vercel-postgres": "latest", "@payloadcms/db-vercel-postgres": "3.31.0",
"@payloadcms/storage-vercel-blob": "latest" "@payloadcms/storage-vercel-blob": "3.31.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",

View File

@@ -1 +1,6 @@
export const importMap = {} import { VercelBlobClientUploadHandler as VercelBlobClientUploadHandler_16c82c5e25f430251a3e3ba57219ff4e } from '@payloadcms/storage-vercel-blob/client'
export const importMap = {
'@payloadcms/storage-vercel-blob/client#VercelBlobClientUploadHandler':
VercelBlobClientUploadHandler_16c82c5e25f430251a3e3ba57219ff4e,
}

View File

@@ -1,5 +1,5 @@
{ {
"id": "de2cf2f0-6fb4-4af1-8a4c-fdeecf2904e8", "id": "43e7c0ef-81d3-45a7-9d08-0744951f1942",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",

View File

@@ -1,9 +1,9 @@
import * as migration_20250311_214549_initial from './20250311_214549_initial' import * as migration_20250326_181520_initial from './20250326_181520_initial'
export const migrations = [ export const migrations = [
{ {
up: migration_20250311_214549_initial.up, up: migration_20250326_181520_initial.up,
down: migration_20250311_214549_initial.down, down: migration_20250326_181520_initial.down,
name: '20250311_214549_initial', name: '20250326_181520_initial',
}, },
] ]

View File

@@ -54,6 +54,7 @@ export type SupportedTimezones =
| 'Asia/Singapore' | 'Asia/Singapore'
| 'Asia/Tokyo' | 'Asia/Tokyo'
| 'Asia/Seoul' | 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney' | 'Australia/Sydney'
| 'Pacific/Guam' | 'Pacific/Guam'
| 'Pacific/Noumea' | 'Pacific/Noumea'

View File

@@ -20,17 +20,17 @@
"ci": "payload migrate && pnpm build" "ci": "payload migrate && pnpm build"
}, },
"dependencies": { "dependencies": {
"@payloadcms/admin-bar": "latest", "@payloadcms/admin-bar": "3.31.0",
"@payloadcms/live-preview-react": "latest", "@payloadcms/live-preview-react": "3.31.0",
"@payloadcms/next": "latest", "@payloadcms/next": "3.31.0",
"@payloadcms/payload-cloud": "latest", "@payloadcms/payload-cloud": "3.31.0",
"@payloadcms/plugin-form-builder": "latest", "@payloadcms/plugin-form-builder": "3.31.0",
"@payloadcms/plugin-nested-docs": "latest", "@payloadcms/plugin-nested-docs": "3.31.0",
"@payloadcms/plugin-redirects": "latest", "@payloadcms/plugin-redirects": "3.31.0",
"@payloadcms/plugin-search": "latest", "@payloadcms/plugin-search": "3.31.0",
"@payloadcms/plugin-seo": "latest", "@payloadcms/plugin-seo": "3.31.0",
"@payloadcms/richtext-lexical": "latest", "@payloadcms/richtext-lexical": "3.31.0",
"@payloadcms/ui": "latest", "@payloadcms/ui": "3.31.0",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-select": "^2.0.0",
@@ -43,7 +43,7 @@
"lucide-react": "^0.378.0", "lucide-react": "^0.378.0",
"next": "15.2.3", "next": "15.2.3",
"next-sitemap": "^4.2.3", "next-sitemap": "^4.2.3",
"payload": "latest", "payload": "3.31.0",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
@@ -51,8 +51,8 @@
"sharp": "0.32.6", "sharp": "0.32.6",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"@payloadcms/db-vercel-postgres": "latest", "@payloadcms/db-vercel-postgres": "3.31.0",
"@payloadcms/storage-vercel-blob": "latest" "@payloadcms/storage-vercel-blob": "3.31.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",

View File

@@ -21,6 +21,7 @@ export const ImageMedia: React.FC<MediaProps> = (props) => {
const { const {
alt: altFromProps, alt: altFromProps,
fill, fill,
pictureClassName,
imgClassName, imgClassName,
priority, priority,
resource, resource,
@@ -56,7 +57,7 @@ export const ImageMedia: React.FC<MediaProps> = (props) => {
.join(', ') .join(', ')
return ( return (
<picture> <picture className={cn(pictureClassName)}>
<NextImage <NextImage
alt={alt || ''} alt={alt || ''}
className={cn(imgClassName)} className={cn(imgClassName)}

View File

@@ -8,6 +8,7 @@ export interface Props {
className?: string className?: string
fill?: boolean // for NextImage only fill?: boolean // for NextImage only
htmlElement?: ElementType | null htmlElement?: ElementType | null
pictureClassName?: string
imgClassName?: string imgClassName?: string
onClick?: () => void onClick?: () => void
onLoad?: () => void onLoad?: () => void

View File

@@ -1,5 +1,5 @@
{ {
"id": "2dadfab5-c4b0-4ae2-ad54-0b01dbc9a2a9", "id": "301748ca-6ea5-4be4-a44b-1771a3e1130f",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
@@ -5764,6 +5764,12 @@
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
}, },
"placeholder": {
"name": "placeholder",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"required": { "required": {
"name": "required", "name": "required",
"type": "boolean", "type": "boolean",

View File

@@ -528,6 +528,7 @@ export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
"label" varchar, "label" varchar,
"width" numeric, "width" numeric,
"default_value" varchar, "default_value" varchar,
"placeholder" varchar,
"required" boolean, "required" boolean,
"block_name" varchar "block_name" varchar
); );

View File

@@ -1,9 +1,9 @@
import * as migration_20250311_214555_initial from './20250311_214555_initial' import * as migration_20250326_181527_initial from './20250326_181527_initial'
export const migrations = [ export const migrations = [
{ {
up: migration_20250311_214555_initial.up, up: migration_20250326_181527_initial.up,
down: migration_20250311_214555_initial.down, down: migration_20250326_181527_initial.down,
name: '20250311_214555_initial', name: '20250326_181527_initial',
}, },
] ]

View File

@@ -54,6 +54,7 @@ export type SupportedTimezones =
| 'Asia/Singapore' | 'Asia/Singapore'
| 'Asia/Tokyo' | 'Asia/Tokyo'
| 'Asia/Seoul' | 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney' | 'Australia/Sydney'
| 'Pacific/Guam' | 'Pacific/Guam'
| 'Pacific/Noumea' | 'Pacific/Noumea'
@@ -624,6 +625,7 @@ export interface Form {
label?: string | null; label?: string | null;
width?: number | null; width?: number | null;
defaultValue?: string | null; defaultValue?: string | null;
placeholder?: string | null;
options?: options?:
| { | {
label: string; label: string;
@@ -1355,6 +1357,7 @@ export interface FormsSelect<T extends boolean = true> {
label?: T; label?: T;
width?: T; width?: T;
defaultValue?: T; defaultValue?: T;
placeholder?: T;
options?: options?:
| T | T
| { | {

View File

@@ -20,9 +20,9 @@ import minimist from 'minimist'
import * as fs from 'node:fs/promises' import * as fs from 'node:fs/promises'
import path from 'path' import path from 'path'
type TemplateVariations = { type TemplateVariation = {
/** Base template to copy from */ /** Base template to copy from */
base?: string base?: 'none' | ({} & string)
configureConfig?: boolean configureConfig?: boolean
db: DbType db: DbType
/** Directory in templates dir */ /** Directory in templates dir */
@@ -56,9 +56,11 @@ async function main() {
const args = minimist(process.argv.slice(2)) const args = minimist(process.argv.slice(2))
const template = args['template'] // template directory name const template = args['template'] // template directory name
const shouldBuild = args['build']
const templateRepoUrlBase = `https://github.com/payloadcms/payload/tree/main/templates` const templateRepoUrlBase = `https://github.com/payloadcms/payload/tree/main/templates`
let variations: TemplateVariations[] = [ let variations: TemplateVariation[] = [
{ {
name: 'payload-vercel-postgres-template', name: 'payload-vercel-postgres-template',
db: 'vercel-postgres', db: 'vercel-postgres',
@@ -143,6 +145,21 @@ async function main() {
// so we do not configure the payload.config.ts file, which leaves the placeholder comments. // so we do not configure the payload.config.ts file, which leaves the placeholder comments.
configureConfig: false, configureConfig: false,
}, },
{
name: 'website',
db: 'mongodb',
dirname: 'website',
generateLockfile: true,
sharp: true,
skipConfig: true, // Do not copy the payload.config.ts file from the base template
storage: 'localDisk',
// The blank template is used as a base for create-payload-app functionality,
// so we do not configure the payload.config.ts file, which leaves the placeholder comments.
configureConfig: false,
base: 'none',
skipDockerCompose: true,
skipReadme: true,
},
] ]
// If template is set, only generate that template // If template is set, only generate that template
@@ -154,7 +171,8 @@ async function main() {
variations = [variation] variations = [variation]
} }
for (const { for (const variation of variations) {
const {
name, name,
base, base,
configureConfig, configureConfig,
@@ -168,9 +186,11 @@ async function main() {
skipReadme = false, skipReadme = false,
storage, storage,
vercelDeployButtonLink, vercelDeployButtonLink,
} of variations) { } = variation
header(`Generating ${name}...`) header(`Generating ${name}...`)
const destDir = path.join(TEMPLATES_DIR, dirname) const destDir = path.join(TEMPLATES_DIR, dirname)
if (base !== 'none') {
copyRecursiveSync(path.join(TEMPLATES_DIR, base || '_template'), destDir, [ copyRecursiveSync(path.join(TEMPLATES_DIR, base || '_template'), destDir, [
'node_modules', 'node_modules',
'\\*\\.tgz', '\\*\\.tgz',
@@ -181,6 +201,7 @@ async function main() {
...(skipDockerCompose ? ['docker-compose.yml'] : []), ...(skipDockerCompose ? ['docker-compose.yml'] : []),
...(skipConfig ? ['payload.config.ts'] : []), ...(skipConfig ? ['payload.config.ts'] : []),
]) ])
}
log(`Copied to ${destDir}`) log(`Copied to ${destDir}`)
@@ -217,6 +238,15 @@ async function main() {
}) })
} }
// Fetch latest npm version of payload package:
const version = await getLatestPayloadVersion()
// Bump package.json versions
await bumpPackageJson({
templateDir: destDir,
latestVersion: version,
})
if (generateLockfile) { if (generateLockfile) {
log('Generating pnpm-lock.yaml') log('Generating pnpm-lock.yaml')
execSyncSafe(`pnpm install --ignore-workspace`, { cwd: destDir }) execSyncSafe(`pnpm install --ignore-workspace`, { cwd: destDir })
@@ -254,6 +284,15 @@ async function main() {
}) })
} }
// Generate importmap
log('Generating import map')
execSyncSafe(`pnpm --ignore-workspace generate:importmap`, { cwd: destDir })
if (shouldBuild) {
log('Building...')
execSyncSafe(`pnpm --ignore-workspace build`, { cwd: destDir })
}
// TODO: Email? // TODO: Email?
// TODO: Sharp? // TODO: Sharp?
@@ -271,7 +310,7 @@ async function generateReadme({
destDir, destDir,
}: { }: {
data: { data: {
attributes: Pick<TemplateVariations, 'db' | 'storage'> attributes: Pick<TemplateVariation, 'db' | 'storage'>
description: string description: string
name: string name: string
vercelDeployButtonLink?: string vercelDeployButtonLink?: string
@@ -305,7 +344,7 @@ async function writeEnvExample({
}: { }: {
dbType: DbType dbType: DbType
destDir: string destDir: string
envNames?: TemplateVariations['envNames'] envNames?: TemplateVariation['envNames']
}) { }) {
const envExamplePath = path.join(destDir, '.env.example') const envExamplePath = path.join(destDir, '.env.example')
const envFileContents = await fs.readFile(envExamplePath, 'utf8') const envFileContents = await fs.readFile(envExamplePath, 'utf8')
@@ -378,3 +417,48 @@ function execSyncSafe(command: string, options?: Parameters<typeof execSync>[1])
throw error throw error
} }
} }
const DO_NOT_BUMP = ['@payloadcms/eslint-config', '@payloadcms/eslint-plugin']
async function bumpPackageJson({
templateDir,
latestVersion,
}: {
templateDir: string
latestVersion: string
}) {
const packageJsonString = await fs.readFile(path.resolve(templateDir, 'package.json'), 'utf8')
const packageJson = JSON.parse(packageJsonString)
for (const key of ['dependencies', 'devDependencies']) {
const dependencies = packageJson[key]
if (dependencies) {
for (const [packageName, _packageVersion] of Object.entries(dependencies)) {
if (
(packageName === 'payload' || packageName.startsWith('@payloadcms')) &&
!DO_NOT_BUMP.includes(packageName)
) {
dependencies[packageName] = latestVersion
}
}
}
}
// write it out
await fs.writeFile(
path.resolve(templateDir, 'package.json'),
JSON.stringify(packageJson, null, 2),
)
}
async function getLatestPayloadVersion() {
try {
const response = await fetch('https://registry.npmjs.org/payload')
const data = await response.json()
const latestVersion = data['dist-tags'].latest
return latestVersion
} catch (error) {
console.error('Error fetching Payload version:', error)
throw error
}
}