feat(templates): added int and e2e tests to blank and website templates (#12866)

This PR adds int tests with vitest and e2e tests with playwright
directly into our templates.

The following are also updated:
- bumps core turbo to 2.5.4 in monorepo
- blank and website templates moved up to be part of the monorepo
workspace
- this means we now have thes templates filtered out in pnpm commands in
package.json
- they will now by default use workspace packages which we can use for
manual testing and int and e2e tests
  - note that turbo doesnt work with these for dev in monorepo context
- CPA script will fetch latest version and then replace `workspace:*` or
the pinned version in the package.json before installation
- blank template no longer uses _template as a base, this is to simplify
management for workspace
- updated the generate template variations script
This commit is contained in:
Paul
2025-06-26 10:55:28 -07:00
committed by GitHub
parent 141133a27f
commit 87c7952558
85 changed files with 7805 additions and 326 deletions

View File

@@ -523,24 +523,30 @@ jobs:
# report-tag: ${{ matrix.suite }} # report-tag: ${{ matrix.suite }}
# job-summary: true # job-summary: true
# Build listed templates with packed local packages # Build listed templates with packed local packages and then runs their int and e2e tests
build-templates: build-and-test-templates:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: build needs: build
strategy: strategy:
fail-fast: false
matrix: matrix:
include: include:
- template: blank - template: blank
database: mongodb database: mongodb
- template: website - template: website
database: mongodb database: mongodb
- template: with-payload-cloud - template: with-payload-cloud
database: mongodb database: mongodb
- template: with-vercel-mongodb - template: with-vercel-mongodb
database: mongodb database: mongodb
# Postgres # Postgres
- template: with-postgres - template: with-postgres
database: postgres database: postgres
- template: with-vercel-postgres - template: with-vercel-postgres
database: postgres database: postgres
@@ -615,6 +621,45 @@ jobs:
env: env:
NODE_OPTIONS: --max-old-space-size=8096 NODE_OPTIONS: --max-old-space-size=8096
- name: Store Playwright's Version
run: |
# Extract the version number using a more targeted regex pattern with awk
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --depth=0 | awk '/@playwright\/test/ {print $2}')
echo "Playwright's Version: $PLAYWRIGHT_VERSION"
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
- name: Cache Playwright Browsers for Playwright's Version
id: cache-playwright-browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
- name: Setup Playwright - Browsers and Dependencies
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Setup Playwright - Dependencies-only
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: Runs Template Int Tests
run: pnpm --filter ${{ matrix.template }} run test:int
env:
NODE_OPTIONS: --max-old-space-size=8096
PAYLOAD_DATABASE: ${{ matrix.database }}
POSTGRES_URL: ${{ env.POSTGRES_URL }}
MONGODB_URL: mongodb://localhost:27017/payloadtests
- name: Runs Template E2E Tests
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.template }}.json pnpm --filter ${{ matrix.template }} test:e2e
env:
NODE_OPTIONS: --max-old-space-size=8096
PAYLOAD_DATABASE: ${{ matrix.database }}
POSTGRES_URL: ${{ env.POSTGRES_URL }}
MONGODB_URL: mongodb://localhost:27017/payloadtests
NEXT_TELEMETRY_DISABLED: 1
tests-type-generation: tests-type-generation:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: [changes, build] needs: [changes, build]
@@ -650,7 +695,7 @@ jobs:
needs: needs:
- lint - lint
- build - build
- build-templates - build-and-test-templates
- tests-unit - tests-unit
- tests-int - tests-int
- tests-e2e - tests-e2e

View File

@@ -75,9 +75,7 @@ import * as Sentry from '@sentry/nextjs'
const config = buildConfig({ const config = buildConfig({
collections: [Pages, Media], collections: [Pages, Media],
plugins: [ plugins: [
sentryPlugin({ sentryPlugin({ Sentry })
Sentry,
}),
], ],
}) })
@@ -101,7 +99,7 @@ export default buildConfig({
pg, // Inject the patched pg driver for Sentry instrumentation pg, // Inject the patched pg driver for Sentry instrumentation
}), }),
plugins: [ plugins: [
sentryPlugin({ Sentry }), sentryPlugin({ Sentry })
], ],
}) })
``` ```

View File

@@ -1,5 +1,5 @@
{ {
"name": "website", "name": "astro-website",
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -11,11 +11,11 @@
"bf": "pnpm run build:force", "bf": "pnpm run build:force",
"build": "pnpm run build:core", "build": "pnpm run build:core",
"build:admin-bar": "turbo build --filter \"@payloadcms/admin-bar\"", "build:admin-bar": "turbo build --filter \"@payloadcms/admin-bar\"",
"build:all": "turbo build", "build:all": "turbo build --filter \"!blank\" --filter \"!website\"",
"build:app": "next build", "build:app": "next build",
"build:app:analyze": "cross-env ANALYZE=true next build", "build:app:analyze": "cross-env ANALYZE=true next build",
"build:clean": "pnpm clean:build", "build:clean": "pnpm clean:build",
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\" --filter \"!@payloadcms/storage-*\"", "build:core": "turbo build --filter \"!@payloadcms/plugin-*\" --filter \"!@payloadcms/storage-*\" --filter \"!blank\" --filter \"!website\"",
"build:core:force": "pnpm clean:build && pnpm build:core --no-cache --force", "build:core:force": "pnpm clean:build && pnpm build:core --no-cache --force",
"build:create-payload-app": "turbo build --filter create-payload-app", "build:create-payload-app": "turbo build --filter create-payload-app",
"build:db-mongodb": "turbo build --filter \"@payloadcms/db-mongodb\"", "build:db-mongodb": "turbo build --filter \"@payloadcms/db-mongodb\"",
@@ -79,9 +79,9 @@
"docker:start": "docker compose -f test/docker-compose.yml up -d", "docker:start": "docker compose -f test/docker-compose.yml up -d",
"docker:stop": "docker compose -f test/docker-compose.yml down", "docker:stop": "docker compose -f test/docker-compose.yml down",
"force:build": "pnpm run build:core:force", "force:build": "pnpm run build:core:force",
"lint": "turbo run lint --log-order=grouped --continue", "lint": "turbo run lint --log-order=grouped --continue --filter \"!blank\" --filter \"!website\"",
"lint-staged": "lint-staged", "lint-staged": "lint-staged",
"lint:fix": "turbo run lint:fix --log-order=grouped --continue", "lint:fix": "turbo run lint:fix --log-order=grouped --continue --filter \"!blank\" --filter \"!website\"",
"obliterate-playwright-cache-macos": "rm -rf ~/Library/Caches/ms-playwright && find /System/Volumes/Data/private/var/folders -type d -name 'playwright*' -exec rm -rf {} +", "obliterate-playwright-cache-macos": "rm -rf ~/Library/Caches/ms-playwright && find /System/Volumes/Data/private/var/folders -type d -name 'playwright*' -exec rm -rf {} +",
"prepare": "husky", "prepare": "husky",
"prepare-run-test-against-prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..", "prepare-run-test-against-prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..",
@@ -181,7 +181,7 @@
"tempy": "1.0.1", "tempy": "1.0.1",
"tstyche": "^3.1.1", "tstyche": "^3.1.1",
"tsx": "4.19.2", "tsx": "4.19.2",
"turbo": "^2.3.3", "turbo": "^2.5.4",
"typescript": "5.7.3" "typescript": "5.7.3"
}, },
"packageManager": "pnpm@9.7.1", "packageManager": "pnpm@9.7.1",

View File

@@ -7,7 +7,7 @@ import path from 'path'
import type { CliArgs, DbType, ProjectExample, ProjectTemplate } from '../types.js' import type { CliArgs, DbType, ProjectExample, ProjectTemplate } from '../types.js'
import { createProject } from './create-project.js' import { createProject, updatePackageJSONDependencies } from './create-project.js'
import { dbReplacements } from './replacements.js' import { dbReplacements } from './replacements.js'
import { getValidTemplates } from './templates.js' import { getValidTemplates } from './templates.js'
@@ -179,5 +179,37 @@ describe('createProject', () => {
expect(content).toContain(dbReplacement.configReplacement().join('\n')) expect(content).toContain(dbReplacement.configReplacement().join('\n'))
}) })
}) })
describe('updates package.json', () => {
it('updates package name and bumps workspace versions', async () => {
const latestVersion = '3.0.0'
const initialJSON = {
name: 'test-project',
version: '1.0.0',
dependencies: {
'@payloadcms/db-mongodb': 'workspace:*',
payload: 'workspace:*',
'@payloadcms/ui': 'workspace:*',
},
}
const correctlyModifiedJSON = {
name: 'test-project',
version: '1.0.0',
dependencies: {
'@payloadcms/db-mongodb': `${latestVersion}`,
payload: `${latestVersion}`,
'@payloadcms/ui': `${latestVersion}`,
},
}
updatePackageJSONDependencies({
latestVersion,
packageJson: initialJSON,
})
expect(initialJSON).toEqual(correctlyModifiedJSON)
})
})
}) })
}) })

View File

@@ -129,7 +129,11 @@ export async function createProject(
const spinner = p.spinner() const spinner = p.spinner()
spinner.start('Checking latest Payload version...') spinner.start('Checking latest Payload version...')
await updatePackageJSON({ projectDir, projectName }) const payloadVersion = await getLatestPackageVersion({ packageName: 'payload' })
spinner.stop(`Found latest version of Payload ${payloadVersion}`)
await updatePackageJSON({ latestVersion: payloadVersion, projectDir, projectName })
if ('template' in args) { if ('template' in args) {
if (args.template.type === 'plugin') { if (args.template.type === 'plugin') {
@@ -177,17 +181,105 @@ export async function createProject(
} }
} }
/**
* Reads the package.json file into an object and then does the following:
* - Sets the `name` property to the provided `projectName`.
* - Bumps the payload packages from workspace:* to the latest version.
* - Writes the updated object back to the package.json file.
*/
export async function updatePackageJSON(args: { export async function updatePackageJSON(args: {
/**
* The latest version of Payload to use in the package.json.
*/
latestVersion: string
projectDir: string projectDir: string
/**
* The name of the project to set in package.json.
*/
projectName: string projectName: string
}): Promise<void> { }): Promise<void> {
const { projectDir, projectName } = args const { latestVersion, projectDir, projectName } = args
const packageJsonPath = path.resolve(projectDir, 'package.json') const packageJsonPath = path.resolve(projectDir, 'package.json')
try { try {
const packageObj = await fse.readJson(packageJsonPath) const packageObj = await fse.readJson(packageJsonPath)
packageObj.name = projectName packageObj.name = projectName
updatePackageJSONDependencies({
latestVersion,
packageJson: packageObj,
})
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 }) await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
} catch (err: unknown) { } catch (err: unknown) {
warning(`Unable to update name in package.json. ${err instanceof Error ? err.message : ''}`) warning(`Unable to update name in package.json. ${err instanceof Error ? err.message : ''}`)
} }
} }
/**
* Recursively updates a JSON object to replace all instances of `workspace:` with the latest version pinned.
*
* Does not return and instead modifies the `packageJson` object in place.
*/
export function updatePackageJSONDependencies(args: {
latestVersion: string
packageJson: Record<string, unknown>
}): void {
const { latestVersion, packageJson } = args
const updatedDependencies = Object.entries(packageJson.dependencies || {}).reduce(
(acc, [key, value]) => {
if (typeof value === 'string' && value.startsWith('workspace:')) {
acc[key] = `${latestVersion}`
} else if (key === 'payload' || key.startsWith('@payloadcms')) {
acc[key] = `${latestVersion}`
} else {
acc[key] = value
}
return acc
},
{} as Record<string, string>,
)
packageJson.dependencies = updatedDependencies
}
/**
* Fetches the latest version of a package from the NPM registry.
*
* Used in determining the latest version of Payload to use in the generated templates.
*/
async function getLatestPackageVersion({
packageName = 'payload',
}: {
/**
* Package name to fetch the latest version for based on the NPM registry URL
*
* Eg. for `'payload'`, it will fetch the version from `https://registry.npmjs.org/payload`
*
* @default 'payload'
*/
packageName?: string
}): Promise<string> {
try {
const response = await fetch(`https://registry.npmjs.org/-/package/${packageName}/dist-tags`)
const data = await response.json()
// Monster chaining for type safety just checking for data.latest
const latestVersion =
data &&
typeof data === 'object' &&
'latest' in data &&
data.latest &&
typeof data.latest === 'string'
? data.latest
: null
if (!latestVersion) {
throw new Error(`No latest version found for package: ${packageName}`)
}
return latestVersion
} catch (error) {
console.error('Error fetching Payload version:', error)
throw error
}
}

View File

@@ -17,7 +17,7 @@ export async function downloadTemplate({
}) { }) {
const branchOrTag = template.url.split('#')?.[1] || 'latest' const branchOrTag = template.url.split('#')?.[1] || 'latest'
const url = `https://codeload.github.com/payloadcms/payload/tar.gz/${branchOrTag}` const url = `https://codeload.github.com/payloadcms/payload/tar.gz/${branchOrTag}`
const filter = `payload-${branchOrTag.replace(/^v/, '')}/templates/${template.name}/` const filter = `payload-${branchOrTag.replace(/^v/, '').replaceAll('/', '-')}/templates/${template.name}/`
if (debug) { if (debug) {
debugLog(`Using template url: ${template.url}`) debugLog(`Using template url: ${template.url}`)

View File

@@ -22,6 +22,10 @@
], ],
"type": "module", "type": "module",
"exports": { "exports": {
"./css": {
"import": "./src/dummy.css",
"default": "./src/dummy.css"
},
".": { ".": {
"import": "./src/index.js", "import": "./src/index.js",
"types": "./src/index.js", "types": "./src/index.js",

View File

View File

@@ -36,6 +36,16 @@
"import": "./src/scss/styles.scss", "import": "./src/scss/styles.scss",
"default": "./src/scss/styles.scss" "default": "./src/scss/styles.scss"
}, },
"./icons/*": {
"import": "./src/icons/*/index.tsx",
"types": "./src/icons/*/index.tsx",
"default": "./src/icons/*/index.tsx"
},
"./elements/*": {
"import": "./src/elements/*/index.tsx",
"types": "./src/elements/*/index.tsx",
"default": "./src/elements/*/index.tsx"
},
"./elements/RenderServerComponent": { "./elements/RenderServerComponent": {
"import": "./src/elements/RenderServerComponent/index.tsx", "import": "./src/elements/RenderServerComponent/index.tsx",
"types": "./src/elements/RenderServerComponent/index.tsx", "types": "./src/elements/RenderServerComponent/index.tsx",

4286
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,5 +3,7 @@ packages:
- 'packages/*' - 'packages/*'
- 'tools/*' - 'tools/*'
- 'test' - 'test'
- 'templates/blank'
- 'templates/website'
# exclude packages that are inside test directories # exclude packages that are inside test directories
# - '!**/test/**' # - '!**/test/**'

View File

@@ -41,3 +41,10 @@ next-env.d.ts
.env .env
/media /media
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -3,6 +3,15 @@ import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
// Your Next.js config here // Your Next.js config here
webpack: (webpackConfig) => {
webpackConfig.resolve.extensionAlias = {
'.cjs': ['.cts', '.cjs'],
'.js': ['.ts', '.tsx', '.js', '.jsx'],
'.mjs': ['.mts', '.mjs'],
}
return webpackConfig
},
} }
export default withPayload(nextConfig, { devBundleServerPackages: false }) export default withPayload(nextConfig, { devBundleServerPackages: false })

View File

@@ -12,7 +12,10 @@
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types", "generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"test": "pnpm run test:int && pnpm run test:e2e",
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test",
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-mongodb": "latest", "@payloadcms/db-mongodb": "latest",
@@ -30,13 +33,21 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@playwright/test": "1.50.0",
"@testing-library/react": "16.3.0",
"@types/node": "^22.5.4", "@types/node": "^22.5.4",
"@types/react": "19.1.0", "@types/react": "19.1.0",
"@types/react-dom": "19.1.2", "@types/react-dom": "19.1.2",
"@vitejs/plugin-react": "4.5.2",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-config-next": "15.3.0", "eslint-config-next": "15.3.0",
"jsdom": "26.1.0",
"playwright": "1.50.0",
"playwright-core": "1.50.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"typescript": "5.7.3" "typescript": "5.7.3",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.3"
}, },
"engines": { "engines": {
"node": "^18.20.2 || >=20.9.0", "node": "^18.20.2 || >=20.9.0",

View File

@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import 'dotenv/config'
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm dev',
reuseExistingServer: true,
url: 'http://localhost:3000',
},
})

View File

@@ -0,0 +1 @@
NODE_OPTIONS="--no-deprecation --no-experimental-strip-types"

View File

@@ -0,0 +1,20 @@
import { test, expect, Page } from '@playwright/test'
test.describe('Frontend', () => {
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
const context = await browser.newContext()
page = await context.newPage()
})
test('can go on homepage', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page).toHaveTitle(/Payload Blank Template/)
const headging = page.locator('h1').first()
await expect(headging).toHaveText('Welcome to your new project.')
})
})

View File

@@ -0,0 +1,20 @@
import { getPayload, Payload } from 'payload'
import config from '@/payload.config'
import { describe, it, beforeAll, expect } from 'vitest'
let payload: Payload
describe('API', () => {
beforeAll(async () => {
const payloadConfig = await config
payload = await getPayload({ config: payloadConfig })
})
it('fetches users', async () => {
const users = await payload.find({
collection: 'users',
})
expect(users).toBeDefined()
})
})

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
include: ['tests/int/**/*.int.spec.ts'],
},
})

View File

@@ -0,0 +1,4 @@
// Any setup scripts you might need go here
// Load .env files
import 'dotenv/config'

View File

@@ -41,3 +41,10 @@ next-env.d.ts
.env .env
/media /media
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -3,6 +3,15 @@ import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
// Your Next.js config here // Your Next.js config here
webpack: (webpackConfig) => {
webpackConfig.resolve.extensionAlias = {
'.cjs': ['.cts', '.cjs'],
'.js': ['.ts', '.tsx', '.js', '.jsx'],
'.mjs': ['.mts', '.mjs'],
}
return webpackConfig
},
} }
export default withPayload(nextConfig, { devBundleServerPackages: false }) export default withPayload(nextConfig, { devBundleServerPackages: false })

View File

@@ -1,42 +1,52 @@
{ {
"name": "template-blank-3.0", "name": "blank",
"version": "1.0.0", "version": "1.0.0",
"description": "A blank template to get started with Payload 3.0", "description": "A blank template to get started with Payload 3.0",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "cross-env NODE_OPTIONS=--no-deprecation next build", "build": "cross-env NODE_OPTIONS=\"--no-deprecation --max-old-space-size=8000\" next build",
"dev": "cross-env NODE_OPTIONS=--no-deprecation next dev", "dev": "cross-env NODE_OPTIONS=--no-deprecation next dev",
"devsafe": "rm -rf .next && cross-env NODE_OPTIONS=--no-deprecation next dev", "devsafe": "rm -rf .next && cross-env NODE_OPTIONS=--no-deprecation next dev",
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap", "generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types", "generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"test": "pnpm run test:int && pnpm run test:e2e",
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test",
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-mongodb": "3.43.0", "@payloadcms/db-mongodb": "workspace:*",
"@payloadcms/next": "3.43.0", "@payloadcms/next": "workspace:*",
"@payloadcms/payload-cloud": "3.43.0", "@payloadcms/payload-cloud": "workspace:*",
"@payloadcms/richtext-lexical": "3.43.0", "@payloadcms/richtext-lexical": "workspace:*",
"@payloadcms/ui": "3.43.0", "@payloadcms/ui": "workspace:*",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "16.4.7",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"next": "15.3.0", "next": "15.3.2",
"payload": "3.43.0", "payload": "workspace:*",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0",
"sharp": "0.32.6" "sharp": "0.32.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@playwright/test": "1.50.0",
"@testing-library/react": "16.3.0",
"@types/node": "^22.5.4", "@types/node": "^22.5.4",
"@types/react": "19.1.0", "@types/react": "19.1.0",
"@types/react-dom": "19.1.2", "@types/react-dom": "19.1.2",
"@vitejs/plugin-react": "4.5.2",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-config-next": "15.3.0", "eslint-config-next": "15.3.0",
"jsdom": "26.1.0",
"playwright": "1.50.0",
"playwright-core": "1.50.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"typescript": "5.7.3" "typescript": "5.7.3",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.3"
}, },
"engines": { "engines": {
"node": "^18.20.2 || >=20.9.0", "node": "^18.20.2 || >=20.9.0",

View File

@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import 'dotenv/config'
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm dev',
reuseExistingServer: true,
url: 'http://localhost:3000',
},
})

File diff suppressed because it is too large Load Diff

View File

@@ -6,24 +6,94 @@
* and re-run `payload generate:types` to regenerate this file. * and re-run `payload generate:types` to regenerate this file.
*/ */
/**
* Supported timezones in IANA format.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "supportedTimezones".
*/
export type SupportedTimezones =
| 'Pacific/Midway'
| 'Pacific/Niue'
| 'Pacific/Honolulu'
| 'Pacific/Rarotonga'
| 'America/Anchorage'
| 'Pacific/Gambier'
| 'America/Los_Angeles'
| 'America/Tijuana'
| 'America/Denver'
| 'America/Phoenix'
| 'America/Chicago'
| 'America/Guatemala'
| 'America/New_York'
| 'America/Bogota'
| 'America/Caracas'
| 'America/Santiago'
| 'America/Buenos_Aires'
| 'America/Sao_Paulo'
| 'Atlantic/South_Georgia'
| 'Atlantic/Azores'
| 'Atlantic/Cape_Verde'
| 'Europe/London'
| 'Europe/Berlin'
| 'Africa/Lagos'
| 'Europe/Athens'
| 'Africa/Cairo'
| 'Europe/Moscow'
| 'Asia/Riyadh'
| 'Asia/Dubai'
| 'Asia/Baku'
| 'Asia/Karachi'
| 'Asia/Tashkent'
| 'Asia/Calcutta'
| 'Asia/Dhaka'
| 'Asia/Almaty'
| 'Asia/Jakarta'
| 'Asia/Bangkok'
| 'Asia/Shanghai'
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
| 'Pacific/Auckland'
| 'Pacific/Fiji';
export interface Config { export interface Config {
auth: { auth: {
users: UserAuthOperations; users: UserAuthOperations;
}; };
blocks: {};
collections: { collections: {
users: User; users: User;
media: Media; media: Media;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference; 'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration; 'payload-migrations': PayloadMigration;
}; };
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
media: MediaSelect<false> | MediaSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: { db: {
defaultIDType: string; defaultIDType: string;
}; };
globals: {}; globals: {};
globalsSelect: {};
locale: null; locale: null;
user: User & { user: User & {
collection: 'users'; collection: 'users';
}; };
jobs: {
tasks: unknown;
workflows: unknown;
};
} }
export interface UserAuthOperations { export interface UserAuthOperations {
forgotPassword: { forgotPassword: {
@@ -79,6 +149,29 @@ export interface Media {
focalX?: number | null; focalX?: number | null;
focalY?: number | null; focalY?: number | null;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?:
| ({
relationTo: 'users';
value: string | User;
} | null)
| ({
relationTo: 'media';
value: string | Media;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences". * via the `definition` "payload-preferences".
@@ -113,6 +206,71 @@ export interface PayloadMigration {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "media_select".
*/
export interface MediaSelect<T extends boolean = true> {
alt?: T;
updatedAt?: T;
createdAt?: T;
url?: T;
thumbnailURL?: T;
filename?: T;
mimeType?: T;
filesize?: T;
width?: T;
height?: T;
focalX?: T;
focalY?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth". * via the `definition` "auth".

1
templates/blank/test.env Normal file
View File

@@ -0,0 +1 @@
NODE_OPTIONS="--no-deprecation --no-experimental-strip-types"

View File

@@ -0,0 +1,20 @@
import { test, expect, Page } from '@playwright/test'
test.describe('Frontend', () => {
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
const context = await browser.newContext()
page = await context.newPage()
})
test('can go on homepage', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page).toHaveTitle(/Payload Blank Template/)
const headging = page.locator('h1').first()
await expect(headging).toHaveText('Welcome to your new project.')
})
})

View File

@@ -0,0 +1,20 @@
import { getPayload, Payload } from 'payload'
import config from '@/payload.config'
import { describe, it, beforeAll, expect } from 'vitest'
let payload: Payload
describe('API', () => {
beforeAll(async () => {
const payloadConfig = await config
payload = await getPayload({ config: payloadConfig })
})
it('fetches users', async () => {
const users = await payload.find({
collection: 'users',
})
expect(users).toBeDefined()
})
})

View File

@@ -40,5 +40,5 @@
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"
] ],
} }

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
include: ['tests/int/**/*.int.spec.ts'],
},
})

View File

@@ -0,0 +1,4 @@
// Any setup scripts you might need go here
// Load .env files
import 'dotenv/config'

View File

@@ -1,5 +1,5 @@
{ {
"name": "plugin-package-name-placeholder", "name": "plugin",
"version": "1.0.0", "version": "1.0.0",
"description": "A blank template to get started with Payload 3.0", "description": "A blank template to get started with Payload 3.0",
"license": "MIT", "license": "MIT",

View File

@@ -11,3 +11,11 @@ public/media/
public/robots.txt public/robots.txt
public/sitemap*.xml public/sitemap*.xml
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -20,6 +20,15 @@ const nextConfig = {
}), }),
], ],
}, },
webpack: (webpackConfig) => {
webpackConfig.resolve.extensionAlias = {
'.cjs': ['.cts', '.cjs'],
'.js': ['.ts', '.tsx', '.js', '.jsx'],
'.mjs': ['.mts', '.mjs'],
}
return webpackConfig
},
reactStrictMode: true, reactStrictMode: true,
redirects, redirects,
} }

View File

@@ -1,5 +1,5 @@
{ {
"name": "@payloadcms/template-website", "name": "website",
"version": "1.0.0", "version": "1.0.0",
"description": "Website template for Payload", "description": "Website template for Payload",
"license": "MIT", "license": "MIT",
@@ -16,21 +16,24 @@
"lint:fix": "cross-env NODE_OPTIONS=--no-deprecation next lint --fix", "lint:fix": "cross-env NODE_OPTIONS=--no-deprecation next lint --fix",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"reinstall": "cross-env NODE_OPTIONS=--no-deprecation rm -rf node_modules && rm pnpm-lock.yaml && pnpm --ignore-workspace install", "reinstall": "cross-env NODE_OPTIONS=--no-deprecation rm -rf node_modules && rm pnpm-lock.yaml && pnpm --ignore-workspace install",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"test": "pnpm run test:int && pnpm run test:e2e",
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test --config=playwright.config.ts",
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/admin-bar": "3.43.0", "@payloadcms/admin-bar": "workspace:*",
"@payloadcms/db-mongodb": "3.43.0", "@payloadcms/db-mongodb": "workspace:*",
"@payloadcms/live-preview-react": "3.43.0", "@payloadcms/live-preview-react": "workspace:*",
"@payloadcms/next": "3.43.0", "@payloadcms/next": "workspace:*",
"@payloadcms/payload-cloud": "3.43.0", "@payloadcms/payload-cloud": "workspace:*",
"@payloadcms/plugin-form-builder": "3.43.0", "@payloadcms/plugin-form-builder": "workspace:*",
"@payloadcms/plugin-nested-docs": "3.43.0", "@payloadcms/plugin-nested-docs": "workspace:*",
"@payloadcms/plugin-redirects": "3.43.0", "@payloadcms/plugin-redirects": "workspace:*",
"@payloadcms/plugin-search": "3.43.0", "@payloadcms/plugin-search": "workspace:*",
"@payloadcms/plugin-seo": "3.43.0", "@payloadcms/plugin-seo": "workspace:*",
"@payloadcms/richtext-lexical": "3.43.0", "@payloadcms/richtext-lexical": "workspace:*",
"@payloadcms/ui": "3.43.0", "@payloadcms/ui": "workspace:*",
"@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",
@@ -38,12 +41,13 @@
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "16.4.7",
"geist": "^1.3.0", "geist": "^1.3.0",
"graphql": "^16.8.2", "graphql": "^16.8.2",
"lucide-react": "^0.378.0", "lucide-react": "^0.378.0",
"next": "15.3.0", "next": "15.3.3",
"next-sitemap": "^4.2.3", "next-sitemap": "^4.2.3",
"payload": "3.43.0", "payload": "workspace:*",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
@@ -66,6 +70,14 @@
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"@playwright/test": "1.50.0",
"jsdom": "26.1.0",
"@testing-library/react": "16.3.0",
"@vitejs/plugin-react": "4.5.2",
"playwright": "1.50.0",
"playwright-core": "1.50.0",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.3",
"typescript": "5.7.3" "typescript": "5.7.3"
}, },
"engines": { "engines": {

View File

@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import 'dotenv/config'
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm dev',
reuseExistingServer: true,
url: 'http://localhost:3000',
},
})

View File

@@ -25,47 +25,29 @@ import { default as default_1a7510af427896d367a49dbf838d2de6 } from '@/component
import { default as default_8a7ab0eb7ab5c511aba12e68480bfe5e } from '@/components/BeforeLogin' import { default as default_8a7ab0eb7ab5c511aba12e68480bfe5e } from '@/components/BeforeLogin'
export const importMap = { export const importMap = {
'@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell': "@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e, "@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
'@payloadcms/richtext-lexical/rsc#RscEntryLexicalField': "@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e, "@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
'@payloadcms/richtext-lexical/rsc#LexicalDiffComponent': "@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e, "@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
'@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient': "@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
'@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient': "@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
'@payloadcms/richtext-lexical/client#HeadingFeatureClient': "@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/plugin-seo/client#OverviewComponent": OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
'@payloadcms/richtext-lexical/client#ParagraphFeatureClient': "@payloadcms/plugin-seo/client#MetaTitleComponent": MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860,
ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860,
'@payloadcms/richtext-lexical/client#UnderlineFeatureClient': "@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860,
UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
'@payloadcms/richtext-lexical/client#BoldFeatureClient': "@/fields/slug/SlugComponent#SlugComponent": SlugComponent_92cc057d0a2abb4f6cf0307edf59f986,
BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
'@payloadcms/richtext-lexical/client#ItalicFeatureClient': "@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/plugin-search/client#LinkToDoc": LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634,
'@payloadcms/richtext-lexical/client#LinkFeatureClient': "@payloadcms/plugin-search/client#ReindexButton": ReindexButton_aead06e4cbf6b2620c5c51c9ab283634,
LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@/Header/RowLabel#RowLabel": RowLabel_ec255a65fa6fa8d1faeb09cf35284224,
'@payloadcms/plugin-seo/client#OverviewComponent': "@/Footer/RowLabel#RowLabel": RowLabel_1f6ff6ff633e3695d348f4f3c58f1466,
OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860, "@/components/BeforeDashboard#default": default_1a7510af427896d367a49dbf838d2de6,
'@payloadcms/plugin-seo/client#MetaTitleComponent': "@/components/BeforeLogin#default": default_8a7ab0eb7ab5c511aba12e68480bfe5e
MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860,
'@payloadcms/plugin-seo/client#MetaImageComponent':
MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860,
'@payloadcms/plugin-seo/client#MetaDescriptionComponent':
MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860,
'@payloadcms/plugin-seo/client#PreviewComponent':
PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
'@/fields/slug/SlugComponent#SlugComponent': SlugComponent_92cc057d0a2abb4f6cf0307edf59f986,
'@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient':
HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
'@payloadcms/richtext-lexical/client#BlocksFeatureClient':
BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
'@payloadcms/plugin-search/client#LinkToDoc': LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634,
'@payloadcms/plugin-search/client#ReindexButton': ReindexButton_aead06e4cbf6b2620c5c51c9ab283634,
'@/Header/RowLabel#RowLabel': RowLabel_ec255a65fa6fa8d1faeb09cf35284224,
'@/Footer/RowLabel#RowLabel': RowLabel_1f6ff6ff633e3695d348f4f3c58f1466,
'@/components/BeforeDashboard#default': default_1a7510af427896d367a49dbf838d2de6,
'@/components/BeforeLogin#default': default_8a7ab0eb7ab5c511aba12e68480bfe5e,
} }

View File

@@ -0,0 +1 @@
NODE_OPTIONS="--no-deprecation --no-experimental-strip-types"

View File

@@ -0,0 +1,20 @@
import { test, expect, Page } from '@playwright/test'
test.describe('Frontend', () => {
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
const context = await browser.newContext()
page = await context.newPage()
})
test('can go on homepage', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page).toHaveTitle(/Payload Website Template/)
const headging = page.locator('h1').first()
await expect(headging).toHaveText('Payload Website Template')
})
})

View File

@@ -0,0 +1,20 @@
import { getPayload, Payload } from 'payload'
import config from '@/payload.config'
import { describe, it, beforeAll, expect } from 'vitest'
let payload: Payload
describe('API', () => {
beforeAll(async () => {
const payloadConfig = await config
payload = await getPayload({ config: payloadConfig })
})
it('fetches users', async () => {
const users = await payload.find({
collection: 'users',
})
expect(users).toBeDefined()
})
})

View File

@@ -1,10 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
/* Strictness */
"strict": true, "strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"baseUrl": ".", "baseUrl": ".",
"esModuleInterop": true, "esModuleInterop": true,
"target": "ES2022", "target": "ES2022",
@@ -15,8 +11,6 @@
], ],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true, "noEmit": true,
"incremental": true, "incremental": true,
"jsx": "preserve", "jsx": "preserve",
@@ -47,10 +41,11 @@
"**/*.tsx", "**/*.tsx",
".next/types/**/*.ts", ".next/types/**/*.ts",
"redirects.js", "redirects.js",
"next-env.d.ts",
"next.config.js", "next.config.js",
"next-sitemap.config.cjs" "next-sitemap.config.cjs"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"
] ],
} }

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
include: ['tests/int/**/*.int.spec.ts'],
},
})

View File

@@ -0,0 +1,4 @@
// Any setup scripts you might need go here
// Load .env files
import 'dotenv/config'

View File

@@ -1,5 +1,5 @@
{ {
"name": "payload-cloud-mongodb-template", "name": "with-payload-cloud",
"version": "1.0.0", "version": "1.0.0",
"description": "A blank template to get started with Payload 3.0", "description": "A blank template to get started with Payload 3.0",
"license": "MIT", "license": "MIT",

View File

@@ -41,3 +41,10 @@ next-env.d.ts
.env .env
/media /media
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -1,6 +1,6 @@
# payload-postgres-template # with-postgres
payload-postgres-template with-postgres
## Attributes ## Attributes

View File

@@ -3,6 +3,15 @@ import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
// Your Next.js config here // Your Next.js config here
webpack: (webpackConfig) => {
webpackConfig.resolve.extensionAlias = {
'.cjs': ['.cts', '.cjs'],
'.js': ['.ts', '.tsx', '.js', '.jsx'],
'.mjs': ['.mts', '.mjs'],
}
return webpackConfig
},
} }
export default withPayload(nextConfig, { devBundleServerPackages: false }) export default withPayload(nextConfig, { devBundleServerPackages: false })

View File

@@ -1,5 +1,5 @@
{ {
"name": "payload-postgres-template", "name": "with-postgres",
"version": "1.0.0", "version": "1.0.0",
"description": "A blank template to get started with Payload 3.0", "description": "A blank template to get started with Payload 3.0",
"license": "MIT", "license": "MIT",
@@ -13,7 +13,10 @@
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types", "generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"test": "pnpm run test:int && pnpm run test:e2e",
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test",
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-postgres": "3.43.0", "@payloadcms/db-postgres": "3.43.0",
@@ -31,13 +34,21 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@playwright/test": "1.50.0",
"@testing-library/react": "16.3.0",
"@types/node": "^22.5.4", "@types/node": "^22.5.4",
"@types/react": "19.1.0", "@types/react": "19.1.0",
"@types/react-dom": "19.1.2", "@types/react-dom": "19.1.2",
"@vitejs/plugin-react": "4.5.2",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-config-next": "15.3.0", "eslint-config-next": "15.3.0",
"jsdom": "26.1.0",
"playwright": "1.50.0",
"playwright-core": "1.50.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"typescript": "5.7.3" "typescript": "5.7.3",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.3"
}, },
"engines": { "engines": {
"node": "^18.20.2 || >=20.9.0", "node": "^18.20.2 || >=20.9.0",

View File

@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import 'dotenv/config'
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm dev',
reuseExistingServer: true,
url: 'http://localhost:3000',
},
})

View File

@@ -0,0 +1,20 @@
import { test, expect, Page } from '@playwright/test'
test.describe('Frontend', () => {
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
const context = await browser.newContext()
page = await context.newPage()
})
test('can go on homepage', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page).toHaveTitle(/Payload Blank Template/)
const headging = page.locator('h1').first()
await expect(headging).toHaveText('Welcome to your new project.')
})
})

View File

@@ -0,0 +1,20 @@
import { getPayload, Payload } from 'payload'
import config from '@/payload.config'
import { describe, it, beforeAll, expect } from 'vitest'
let payload: Payload
describe('API', () => {
beforeAll(async () => {
const payloadConfig = await config
payload = await getPayload({ config: payloadConfig })
})
it('fetches users', async () => {
const users = await payload.find({
collection: 'users',
})
expect(users).toBeDefined()
})
})

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
include: ['tests/int/**/*.int.spec.ts'],
},
})

View File

@@ -0,0 +1,4 @@
// Any setup scripts you might need go here
// Load .env files
import 'dotenv/config'

View File

@@ -41,3 +41,10 @@ next-env.d.ts
.env .env
/media /media
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -3,6 +3,15 @@ import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
// Your Next.js config here // Your Next.js config here
webpack: (webpackConfig) => {
webpackConfig.resolve.extensionAlias = {
'.cjs': ['.cts', '.cjs'],
'.js': ['.ts', '.tsx', '.js', '.jsx'],
'.mjs': ['.mts', '.mjs'],
}
return webpackConfig
},
} }
export default withPayload(nextConfig, { devBundleServerPackages: false }) export default withPayload(nextConfig, { devBundleServerPackages: false })

View File

@@ -1,5 +1,5 @@
{ {
"name": "payload-vercel-mongodb-template", "name": "with-vercel-mongodb",
"version": "1.0.0", "version": "1.0.0",
"description": "A blank template to get started with Payload 3.0", "description": "A blank template to get started with Payload 3.0",
"license": "MIT", "license": "MIT",
@@ -12,7 +12,10 @@
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types", "generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"test": "pnpm run test:int && pnpm run test:e2e",
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test",
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-mongodb": "3.43.0", "@payloadcms/db-mongodb": "3.43.0",
@@ -30,15 +33,23 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@playwright/test": "1.50.0",
"@testing-library/react": "16.3.0",
"@types/node": "^22.5.4", "@types/node": "^22.5.4",
"@types/react": "19.1.0", "@types/react": "19.1.0",
"@types/react-dom": "19.1.2", "@types/react-dom": "19.1.2",
"@vitejs/plugin-react": "4.5.2",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-config-next": "15.3.0", "eslint-config-next": "15.3.0",
"jsdom": "26.1.0",
"playwright": "1.50.0",
"playwright-core": "1.50.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"typescript": "5.7.3" "typescript": "5.7.3",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.3"
}, },
"packageManager": "pnpm@10.12.1", "packageManager": "pnpm@10.12.3",
"engines": { "engines": {
"node": "^18.20.2 || >=20.9.0" "node": "^18.20.2 || >=20.9.0"
}, },

View File

@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import 'dotenv/config'
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm dev',
reuseExistingServer: true,
url: 'http://localhost:3000',
},
})

View File

@@ -0,0 +1,20 @@
import { test, expect, Page } from '@playwright/test'
test.describe('Frontend', () => {
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
const context = await browser.newContext()
page = await context.newPage()
})
test('can go on homepage', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page).toHaveTitle(/Payload Blank Template/)
const headging = page.locator('h1').first()
await expect(headging).toHaveText('Welcome to your new project.')
})
})

View File

@@ -0,0 +1,20 @@
import { getPayload, Payload } from 'payload'
import config from '@/payload.config'
import { describe, it, beforeAll, expect } from 'vitest'
let payload: Payload
describe('API', () => {
beforeAll(async () => {
const payloadConfig = await config
payload = await getPayload({ config: payloadConfig })
})
it('fetches users', async () => {
const users = await payload.find({
collection: 'users',
})
expect(users).toBeDefined()
})
})

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
include: ['tests/int/**/*.int.spec.ts'],
},
})

View File

@@ -0,0 +1,4 @@
// Any setup scripts you might need go here
// Load .env files
import 'dotenv/config'

View File

@@ -41,3 +41,10 @@ next-env.d.ts
.env .env
/media /media
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -3,6 +3,15 @@ import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
// Your Next.js config here // Your Next.js config here
webpack: (webpackConfig) => {
webpackConfig.resolve.extensionAlias = {
'.cjs': ['.cts', '.cjs'],
'.js': ['.ts', '.tsx', '.js', '.jsx'],
'.mjs': ['.mts', '.mjs'],
}
return webpackConfig
},
} }
export default withPayload(nextConfig, { devBundleServerPackages: false }) export default withPayload(nextConfig, { devBundleServerPackages: false })

View File

@@ -1,5 +1,5 @@
{ {
"name": "payload-vercel-postgres-template", "name": "with-vercel-postgres",
"version": "1.0.0", "version": "1.0.0",
"description": "A blank template to get started with Payload 3.0", "description": "A blank template to get started with Payload 3.0",
"license": "MIT", "license": "MIT",
@@ -13,7 +13,10 @@
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types", "generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"test": "pnpm run test:int && pnpm run test:e2e",
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test",
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-vercel-postgres": "3.43.0", "@payloadcms/db-vercel-postgres": "3.43.0",
@@ -31,15 +34,23 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@playwright/test": "1.50.0",
"@testing-library/react": "16.3.0",
"@types/node": "^22.5.4", "@types/node": "^22.5.4",
"@types/react": "19.1.0", "@types/react": "19.1.0",
"@types/react-dom": "19.1.2", "@types/react-dom": "19.1.2",
"@vitejs/plugin-react": "4.5.2",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-config-next": "15.3.0", "eslint-config-next": "15.3.0",
"jsdom": "26.1.0",
"playwright": "1.50.0",
"playwright-core": "1.50.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"typescript": "5.7.3" "typescript": "5.7.3",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.3"
}, },
"packageManager": "pnpm@10.12.1", "packageManager": "pnpm@10.12.3",
"engines": { "engines": {
"node": "^18.20.2 || >=20.9.0" "node": "^18.20.2 || >=20.9.0"
}, },

View File

@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import 'dotenv/config'
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm dev',
reuseExistingServer: true,
url: 'http://localhost:3000',
},
})

View File

@@ -1,5 +1,5 @@
{ {
"id": "80b6c48b-dc29-46a6-93d9-4c7ffb28e4d1", "id": "64c3b19c-9d51-4e55-9c9c-333d9b73bc28",
"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_20250616_201653_initial from './20250616_201653_initial' import * as migration_20250624_171210_initial from './20250624_171210_initial'
export const migrations = [ export const migrations = [
{ {
up: migration_20250616_201653_initial.up, up: migration_20250624_171210_initial.up,
down: migration_20250616_201653_initial.down, down: migration_20250624_171210_initial.down,
name: '20250616_201653_initial', name: '20250624_171210_initial',
}, },
] ]

View File

@@ -0,0 +1,20 @@
import { test, expect, Page } from '@playwright/test'
test.describe('Frontend', () => {
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
const context = await browser.newContext()
page = await context.newPage()
})
test('can go on homepage', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page).toHaveTitle(/Payload Blank Template/)
const headging = page.locator('h1').first()
await expect(headging).toHaveText('Welcome to your new project.')
})
})

View File

@@ -0,0 +1,20 @@
import { getPayload, Payload } from 'payload'
import config from '@/payload.config'
import { describe, it, beforeAll, expect } from 'vitest'
let payload: Payload
describe('API', () => {
beforeAll(async () => {
const payloadConfig = await config
payload = await getPayload({ config: payloadConfig })
})
it('fetches users', async () => {
const users = await payload.find({
collection: 'users',
})
expect(users).toBeDefined()
})
})

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
include: ['tests/int/**/*.int.spec.ts'],
},
})

View File

@@ -0,0 +1,4 @@
// Any setup scripts you might need go here
// Load .env files
import 'dotenv/config'

View File

@@ -11,3 +11,11 @@ public/media/
public/robots.txt public/robots.txt
public/sitemap*.xml public/sitemap*.xml
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@@ -20,6 +20,15 @@ const nextConfig = {
}), }),
], ],
}, },
webpack: (webpackConfig) => {
webpackConfig.resolve.extensionAlias = {
'.cjs': ['.cts', '.cjs'],
'.js': ['.ts', '.tsx', '.js', '.jsx'],
'.mjs': ['.mts', '.mjs'],
}
return webpackConfig
},
reactStrictMode: true, reactStrictMode: true,
redirects, redirects,
} }

View File

@@ -1,5 +1,5 @@
{ {
"name": "payload-vercel-website-template", "name": "with-vercel-website",
"version": "1.0.0", "version": "1.0.0",
"description": "Website template for Payload", "description": "Website template for Payload",
"license": "MIT", "license": "MIT",
@@ -17,7 +17,10 @@
"lint:fix": "cross-env NODE_OPTIONS=--no-deprecation next lint --fix", "lint:fix": "cross-env NODE_OPTIONS=--no-deprecation next lint --fix",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"reinstall": "cross-env NODE_OPTIONS=--no-deprecation rm -rf node_modules && rm pnpm-lock.yaml && pnpm --ignore-workspace install", "reinstall": "cross-env NODE_OPTIONS=--no-deprecation rm -rf node_modules && rm pnpm-lock.yaml && pnpm --ignore-workspace install",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"test": "pnpm run test:int && pnpm run test:e2e",
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test --config=playwright.config.ts",
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/admin-bar": "3.43.0", "@payloadcms/admin-bar": "3.43.0",
@@ -40,10 +43,11 @@
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "16.4.7",
"geist": "^1.3.0", "geist": "^1.3.0",
"graphql": "^16.8.2", "graphql": "^16.8.2",
"lucide-react": "^0.378.0", "lucide-react": "^0.378.0",
"next": "15.3.0", "next": "15.3.3",
"next-sitemap": "^4.2.3", "next-sitemap": "^4.2.3",
"payload": "3.43.0", "payload": "3.43.0",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
@@ -56,21 +60,29 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@playwright/test": "1.50.0",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"@testing-library/react": "16.3.0",
"@types/escape-html": "^1.0.2", "@types/escape-html": "^1.0.2",
"@types/node": "22.5.4", "@types/node": "22.5.4",
"@types/react": "19.1.0", "@types/react": "19.1.0",
"@types/react-dom": "19.1.2", "@types/react-dom": "19.1.2",
"@vitejs/plugin-react": "4.5.2",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-config-next": "15.3.0", "eslint-config-next": "15.3.0",
"jsdom": "26.1.0",
"playwright": "1.50.0",
"playwright-core": "1.50.0",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "5.7.3" "typescript": "5.7.3",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.3"
}, },
"packageManager": "pnpm@10.12.1", "packageManager": "pnpm@10.12.3",
"engines": { "engines": {
"node": "^18.20.2 || >=20.9.0" "node": "^18.20.2 || >=20.9.0"
}, },

View File

@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import 'dotenv/config'
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm dev',
reuseExistingServer: true,
url: 'http://localhost:3000',
},
})

View File

@@ -0,0 +1,20 @@
import { test, expect, Page } from '@playwright/test'
test.describe('Frontend', () => {
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
const context = await browser.newContext()
page = await context.newPage()
})
test('can go on homepage', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page).toHaveTitle(/Payload Website Template/)
const headging = page.locator('h1').first()
await expect(headging).toHaveText('Payload Website Template')
})
})

View File

@@ -0,0 +1,20 @@
import { getPayload, Payload } from 'payload'
import config from '@/payload.config'
import { describe, it, beforeAll, expect } from 'vitest'
let payload: Payload
describe('API', () => {
beforeAll(async () => {
const payloadConfig = await config
payload = await getPayload({ config: payloadConfig })
})
it('fetches users', async () => {
const users = await payload.find({
collection: 'users',
})
expect(users).toBeDefined()
})
})

View File

@@ -1,10 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
/* Strictness */
"strict": true, "strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"baseUrl": ".", "baseUrl": ".",
"esModuleInterop": true, "esModuleInterop": true,
"target": "ES2022", "target": "ES2022",
@@ -15,8 +11,6 @@
], ],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true, "noEmit": true,
"incremental": true, "incremental": true,
"jsx": "preserve", "jsx": "preserve",
@@ -47,10 +41,11 @@
"**/*.tsx", "**/*.tsx",
".next/types/**/*.ts", ".next/types/**/*.ts",
"redirects.js", "redirects.js",
"next-env.d.ts",
"next.config.js", "next.config.js",
"next-sitemap.config.cjs" "next-sitemap.config.cjs"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"
] ],
} }

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
include: ['tests/int/**/*.int.spec.ts'],
},
})

View File

@@ -0,0 +1,4 @@
// Any setup scripts you might need go here
// Load .env files
import 'dotenv/config'

View File

@@ -37,10 +37,22 @@ async function main() {
allTgzs, allTgzs,
}) })
// remove node_modules
await fs.rm(path.join(templatePath, 'node_modules'), { recursive: true, force: true })
// replace workspace:* from package.json with a real version so that it can be installed with pnpm
// without this step, even though the packages are built locally as tars
// it will error as it cannot contain workspace dependencies when installing with --ignore-workspace
const packageJsonPath = path.join(templatePath, 'package.json')
const initialPackageJson = await fs.readFile(packageJsonPath, 'utf-8')
const initialPackageJsonObj = JSON.parse(initialPackageJson)
updatePackageJSONDependencies({ latestVersion: '3.42.0', packageJson: initialPackageJsonObj })
await fs.writeFile(packageJsonPath, JSON.stringify(initialPackageJsonObj, null, 2))
execSync('pnpm add ./*.tgz --ignore-workspace', execOpts) execSync('pnpm add ./*.tgz --ignore-workspace', execOpts)
execSync('pnpm install --ignore-workspace', execOpts) execSync('pnpm install --ignore-workspace', execOpts)
const packageJsonPath = path.join(templatePath, 'package.json')
const packageJson = await fs.readFile(packageJsonPath, 'utf-8') const packageJson = await fs.readFile(packageJsonPath, 'utf-8')
const packageJsonObj = JSON.parse(packageJson) as { const packageJsonObj = JSON.parse(packageJson) as {
dependencies: Record<string, string> dependencies: Record<string, string>
@@ -64,7 +76,7 @@ async function main() {
packageJsonObj.pnpm = { overrides } packageJsonObj.pnpm = { overrides }
await fs.writeFile(packageJsonPath, JSON.stringify(packageJsonObj, null, 2)) await fs.writeFile(packageJsonPath, JSON.stringify(packageJsonObj, null, 2))
execSync('pnpm install --ignore-workspace --no-frozen-lockfile', execOpts) execSync('pnpm install --no-frozen-lockfile --ignore-workspace', execOpts)
await fs.writeFile( await fs.writeFile(
path.resolve(templatePath, '.env'), path.resolve(templatePath, '.env'),
// Populate POSTGRES_URL just in case it's needed // Populate POSTGRES_URL just in case it's needed
@@ -81,3 +93,28 @@ BLOB_READ_WRITE_TOKEN=vercel_blob_rw_TEST_asdf`,
function header(message: string, opts?: { enable?: boolean }) { function header(message: string, opts?: { enable?: boolean }) {
console.log(chalk.bold.green(`${message}\n`)) console.log(chalk.bold.green(`${message}\n`))
} }
/**
* Recursively updates a JSON object to replace all instances of `workspace:` with the latest version pinned.
*
* Does not return and instead modifies the `packageJson` object in place.
*/
export function updatePackageJSONDependencies(args: {
latestVersion: string
packageJson: Record<string, unknown>
}): void {
const { latestVersion, packageJson } = args
const updatedDependencies = Object.entries(packageJson.dependencies || {}).reduce(
(acc, [key, value]) => {
if (typeof value === 'string' && value.startsWith('workspace:')) {
acc[key] = `${latestVersion}`
} else {
acc[key] = value
}
return acc
},
{} as Record<string, string>,
)
packageJson.dependencies = updatedDependencies
}

View File

@@ -30,6 +30,10 @@ type TemplateVariation = {
envNames?: { envNames?: {
dbUri: string dbUri: string
} }
/**
* If the template is part of the workspace, then do not replace the package.json versions
*/
workspace?: boolean
generateLockfile?: boolean generateLockfile?: boolean
/** package.json name */ /** package.json name */
name: string name: string
@@ -69,7 +73,7 @@ async function main() {
let variations: TemplateVariation[] = [ let variations: TemplateVariation[] = [
{ {
name: 'payload-vercel-postgres-template', name: 'with-vercel-postgres',
db: 'vercel-postgres', db: 'vercel-postgres',
dirname: 'with-vercel-postgres', dirname: 'with-vercel-postgres',
envNames: { envNames: {
@@ -92,7 +96,7 @@ async function main() {
), ),
}, },
{ {
name: 'payload-vercel-website-template', name: 'with-vercel-website',
base: 'website', // This is the base template to copy from base: 'website', // This is the base template to copy from
db: 'vercel-postgres', db: 'vercel-postgres',
dirname: 'with-vercel-website', dirname: 'with-vercel-website',
@@ -116,7 +120,7 @@ async function main() {
), ),
}, },
{ {
name: 'payload-postgres-template', name: 'with-postgres',
db: 'postgres', db: 'postgres',
dirname: 'with-postgres', dirname: 'with-postgres',
sharp: true, sharp: true,
@@ -124,7 +128,7 @@ async function main() {
storage: 'localDisk', storage: 'localDisk',
}, },
{ {
name: 'payload-vercel-mongodb-template', name: 'with-vercel-mongodb',
db: 'mongodb', db: 'mongodb',
dirname: 'with-vercel-mongodb', dirname: 'with-vercel-mongodb',
envNames: { envNames: {
@@ -157,6 +161,8 @@ async function main() {
// The blank template is used as a base for create-payload-app functionality, // 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. // so we do not configure the payload.config.ts file, which leaves the placeholder comments.
configureConfig: false, configureConfig: false,
workspace: true,
base: 'none', // Do not copy from the base _template directory
}, },
{ {
name: 'website', name: 'website',
@@ -172,6 +178,7 @@ async function main() {
base: 'none', base: 'none',
skipDockerCompose: true, skipDockerCompose: true,
skipReadme: true, skipReadme: true,
workspace: true,
}, },
] ]
@@ -200,6 +207,7 @@ async function main() {
storage, storage,
vercelDeployButtonLink, vercelDeployButtonLink,
targetDeployment = 'default', targetDeployment = 'default',
workspace = false,
} = variation } = variation
header(`Generating ${name}...`) header(`Generating ${name}...`)
@@ -229,6 +237,7 @@ async function main() {
sharp, sharp,
storageAdapter: storage, storageAdapter: storage,
} }
await configurePayloadConfig(configureArgs) await configurePayloadConfig(configureArgs)
log('Configuring .env.example') log('Configuring .env.example')
@@ -255,18 +264,25 @@ async function main() {
// Fetch latest npm version of payload package: // Fetch latest npm version of payload package:
const payloadVersion = await getLatestPackageVersion({ packageName: 'payload' }) const payloadVersion = await getLatestPackageVersion({ packageName: 'payload' })
// Bump package.json versions // Bump package.json versions only in non-workspace templates such as Vercel variants
await bumpPackageJson({ // Workspace templates should always continue to point to `workspace:*` version of payload packages
templateDir: destDir, if (!workspace) {
latestVersion: payloadVersion, await bumpPackageJson({
}) templateDir: destDir,
latestVersion: payloadVersion,
})
}
if (generateLockfile) { if (generateLockfile) {
log('Generating pnpm-lock.yaml') log('Generating pnpm-lock.yaml')
execSyncSafe(`pnpm install --ignore-workspace --no-frozen-lockfile`, { cwd: destDir }) execSyncSafe(`pnpm install ${workspace ? '' : '--ignore-workspace'} --no-frozen-lockfile`, {
cwd: destDir,
})
} else { } else {
log('Installing dependencies without generating lockfile') log('Installing dependencies without generating lockfile')
execSyncSafe(`pnpm install --ignore-workspace --no-frozen-lockfile`, { cwd: destDir }) execSyncSafe(`pnpm install ${workspace ? '' : '--ignore-workspace'} --no-frozen-lockfile`, {
cwd: destDir,
})
await fs.rm(`${destDir}/pnpm-lock.yaml`, { force: true }) await fs.rm(`${destDir}/pnpm-lock.yaml`, { force: true })
} }
@@ -307,11 +323,13 @@ async function main() {
// Generate importmap // Generate importmap
log('Generating import map') log('Generating import map')
execSyncSafe(`pnpm --ignore-workspace generate:importmap`, { cwd: destDir }) execSyncSafe(`pnpm ${workspace ? '' : '--ignore-workspace'} generate:importmap`, {
cwd: destDir,
})
if (shouldBuild) { if (shouldBuild) {
log('Building...') log('Building...')
execSyncSafe(`pnpm --ignore-workspace build`, { cwd: destDir }) execSyncSafe(`pnpm ${workspace ? '' : '--ignore-workspace'} build`, { cwd: destDir })
} }
// TODO: Email? // TODO: Email?
@@ -514,9 +532,18 @@ async function getLatestPackageVersion({
packageName?: string packageName?: string
}) { }) {
try { try {
const response = await fetch(`https://registry.npmjs.org/${packageName}`) const response = await fetch(`https://registry.npmjs.org/-/package/${packageName}/dist-tags`)
const data = await response.json() const data = await response.json()
const latestVersion = data['dist-tags'].latest
// Monster chaining for type safety just checking for data.latest
const latestVersion =
data &&
typeof data === 'object' &&
'latest' in data &&
data.latest &&
typeof data.latest === 'string'
? data.latest
: null
log(`Found latest version of ${packageName}: ${latestVersion}`) log(`Found latest version of ${packageName}: ${latestVersion}`)