Compare commits

...

13 Commits

Author SHA1 Message Date
Dan Ribbens
43cdb45620 fix: mongodb multiple error rollback transaction 2023-11-22 11:53:45 -05:00
Jessica Chowdhury
6364afb1dd docs: updates vite alias example and fixes broken link (#4224) 2023-11-21 14:34:18 -05:00
Radosław Kłos
56a4692662 fix: typo in polish translations (#4234) 2023-11-21 13:09:00 -05:00
Dan Ribbens
ef6b8e4235 test: graphql handle deleted relationships (#4229) 2023-11-21 13:07:13 -05:00
Elliot DeNolf
5f5290341a chore(plugin-cloud-storage): adjust prepublishOnly script 2023-11-21 10:28:35 -05:00
Elliot DeNolf
62403584ad chore(scripts): cleanup package details log 2023-11-21 10:28:17 -05:00
Jarrod Flesch
19fcfc27af fix: number field validation (#4233) 2023-11-21 10:12:26 -05:00
Elliot DeNolf
dcf14f5f71 chore(release): payload/2.2.1 [skip ci] 2023-11-21 10:08:07 -05:00
Elliot DeNolf
3a784a06cc fix: make outputSchema optional on richtext config (#4230) 2023-11-21 09:45:57 -05:00
Jessica Chowdhury
6eeae9d53b examples: updates blank template readme (#4216) 2023-11-21 08:40:01 -05:00
Jarrod Flesch
6044f810bd docs: correct PULL_REQUEST_TEMPLATE.md links 2023-11-21 08:37:57 -05:00
Elliot DeNolf
e68ca9363f fix(plugin-cloud-storage): adjust webpack aliasing for pnpm (#4228) 2023-11-20 16:53:30 -05:00
Elliot DeNolf
9963b8d945 chore(release): plugin-nested-docs/1.0.9 [skip ci] 2023-11-20 16:40:46 -05:00
24 changed files with 273 additions and 72 deletions

View File

@@ -12,8 +12,8 @@
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Change to the [templates](../templates/) directory (does not affect core functionality)
- [ ] Change to the [examples](../examples/) directory (does not affect core functionality)
- [ ] Change to the [templates](https://github.com/payloadcms/payload/tree/main/templates) directory (does not affect core functionality)
- [ ] Change to the [examples](https://github.com/payloadcms/payload/tree/main/examples) directory (does not affect core functionality)
- [ ] This change requires a documentation update
## Checklist:

View File

@@ -1,3 +1,10 @@
## [2.2.1](https://github.com/payloadcms/payload/compare/v2.2.0...v2.2.1) (2023-11-21)
### Bug Fixes
* make outputSchema optional on richtext config ([#4230](https://github.com/payloadcms/payload/issues/4230)) ([3a784a0](https://github.com/payloadcms/payload/commit/3a784a06cc6c42c96b8d6cf023d942e6661be7b5))
## [2.2.0](https://github.com/payloadcms/payload/compare/v2.1.1...v2.2.0) (2023-11-20)

View File

@@ -172,20 +172,34 @@ export default buildConfig({
collections: [Subscriptions],
admin: {
bundler: viteBundler(),
vite: (config) => {
return {
...config,
resolve: {
...config.resolve,
// highlight-start
alias: {
...config.resolve.alias,
// remember, vite aliases are exact-match only
'./hooks/createStripeSubscription': mockModulePath,
},
// highlight-end
},
vite: (incomingViteConfig) => {
const existingAliases = incomingViteConfig?.resolve?.alias || {};
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
// Pass the existing Vite aliases
if (Array.isArray(existingAliases)) {
aliasArray = existingAliases;
} else {
aliasArray = Object.values(existingAliases);
}
// highlight-start
// Add your own aliases using the find and replacement keys
// remember, vite aliases are exact-match only
aliasArray.push({
find: '../server-only-module',
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
});
// highlight-end
return {
...incomingViteConfig,
resolve: {
...(incomingViteConfig?.resolve || {}),
alias: aliasArray,
}
};
},
},
})

View File

@@ -60,17 +60,33 @@ export const buildConfig({
collections: [],
admin: {
bundler: viteBundler(),
vite: (incomingViteConfig) => ({
...incomingViteConfig,
resolve: {
...(incomingViteConfig?.resolve || {}),
alias: {
...(incomingViteConfig?.resolve?.alias || {}),
'../server-only-module': path.resolve(__dirname, './path/to/browser-safe-module.js'),
'../../server-only-module': path.resolve(__dirname, './path/to/browser-safe-module.js'),
}
vite: (incomingViteConfig) => {
const existingAliases = incomingViteConfig?.resolve?.alias || {};
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
// Pass the existing Vite aliases
if (Array.isArray(existingAliases)) {
aliasArray = existingAliases;
} else {
aliasArray = Object.values(existingAliases);
}
})
// Add your own aliases using the find and replacement keys
aliasArray.push({
find: '../server-only-module',
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
find: '../../server-only-module',
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
});
return {
...incomingViteConfig,
resolve: {
...(incomingViteConfig?.resolve || {}),
alias: aliasArray,
}
};
},
}
})
```
@@ -90,7 +106,8 @@ That plugin should create an alias to support Vite as follows:
```ts
{
// aliases go here
'payload-plugin-cool': path.resolve(__dirname, './my-admin-plugin.js')
find: 'payload-plugin-cool',
replacement: path.resolve(__dirname, './my-admin-plugin.js')
}
```
@@ -108,22 +125,36 @@ export const buildConfig({
collections: [],
admin: {
bundler: viteBundler(),
vite: (incomingViteConfig) => ({
...incomingViteConfig,
resolve: {
...(incomingViteConfig?.resolve || {}),
alias: {
...(incomingViteConfig?.resolve?.alias || {}),
// custom aliases go here
'../server-only-module': path.resolve(__dirname, './path/to/browser-safe-module.js'),
}
vite: (incomingViteConfig) => {
const existingAliases = incomingViteConfig?.resolve?.alias || {};
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
// Pass the existing Vite aliases
if (Array.isArray(existingAliases)) {
aliasArray = existingAliases;
} else {
aliasArray = Object.values(existingAliases);
}
})
// Add your own aliases using the find and replacement keys
aliasArray.push({
find: '../server-only-module',
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
});
return {
...incomingViteConfig,
resolve: {
...(incomingViteConfig?.resolve || {}),
alias: aliasArray,
}
};
},
}
})
```
Learn more about [aliasing server-only modules](http://localhost:3000/docs/admin/excluding-server-code#aliasing-server-only-modules).
Learn more about [aliasing server-only modules](https://payloadcms.com/docs/admin/excluding-server-code#aliasing-server-only-modules).
Even though there is a new property for Vite configs specifically, we have implemented some "compatibility" between Webpack and Vite out-of-the-box.

View File

@@ -1,23 +1,27 @@
import type { RollbackTransaction } from 'payload/database'
export const rollbackTransaction: RollbackTransaction = async function rollbackTransaction(
id = '',
) {
export const rollbackTransaction: RollbackTransaction = function rollbackTransaction(id = '') {
// if multiple operations are using the same transaction, the first will flow through and delete the session.
// subsequent calls should be ignored.
if (!this.sessions[id]) {
return
}
// when session exists but is not inTransaction something unexpected is happening to the session
// when session exists but inTransaction is false, it is no longer used and can be deleted
if (!this.sessions[id].inTransaction()) {
this.payload.logger.warn('rollbackTransaction called when no transaction exists')
delete this.sessions[id]
return
}
// the first call for rollback should be aborted and deleted causing any other operations with the same transaction to fail
await this.sessions[id].abortTransaction()
await this.sessions[id].endSession()
try {
// null coalesce needed when rollback is called multiple times with the same id synchronously
this.sessions?.[id].abortTransaction().then(() => {
// not supported by DocumentDB
this.sessions?.[id].endSession()
})
} catch (e) {
// no action needed
}
delete this.sessions[id]
}

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.2.0",
"version": "2.2.1",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",

View File

@@ -31,7 +31,7 @@ export type RichTextAdapter<
siblingDoc: Record<string, unknown>
}) => Promise<void> | null
outputSchema: ({
outputSchema?: ({
field,
isRequired,
}: {

View File

@@ -97,7 +97,7 @@ export default joi.object({
CellComponent: component.required(),
FieldComponent: component.required(),
afterReadPromise: joi.func().optional(),
outputSchema: joi.func().required(),
outputSchema: joi.func().optional(),
populationPromise: joi.func().optional(),
validate: joi.func().required(),
})

View File

@@ -151,7 +151,7 @@ export type BeginTransaction = (
options?: Record<string, unknown>,
) => Promise<null | number | string>
export type RollbackTransaction = (id: number | string) => Promise<void>
export type RollbackTransaction = (id: number | string) => Promise<void> | void
export type CommitTransaction = (id: number | string) => Promise<void>

View File

@@ -434,7 +434,7 @@ export const richText = baseField.keys({
CellComponent: componentSchema.required(),
FieldComponent: componentSchema.required(),
afterReadPromise: joi.func().optional(),
outputSchema: joi.func().required(),
outputSchema: joi.func().optional(),
populationPromise: joi.func().optional(),
validate: joi.func().required(),
})

View File

@@ -413,6 +413,11 @@ describe('Field Validations', () => {
const result = number(val, numberOptions)
expect(result).toBe(true)
})
it('should validate 0', () => {
const val = 0
const result = number(val, { ...numberOptions, required: true })
expect(result).toBe(true)
})
it('should validate 2', () => {
const val = 1.5
const result = number(val, numberOptions)

View File

@@ -205,8 +205,11 @@ export const number: Validate<unknown, unknown, NumberField> = (
if (typeof lengthValidationResult === 'string') return lengthValidationResult
}
if (!value && required) return t('validation:required')
if (!value && !required) return true
if (!value && !isNumber(value)) {
// if no value is present, validate based on required
if (required) return t('validation:required')
if (!required) return true
}
const numbersToValidate: number[] = Array.isArray(value) ? value : [value]

View File

@@ -164,7 +164,7 @@
"copied": "Skopiowano",
"copy": "Skopiuj",
"create": "Stwórz",
"createNew": "Stwórzy nowy",
"createNew": "Stwórz nowy",
"createNewLabel": "Stwórz nowy {{label}}",
"created": "Utworzono",
"createdAt": "Data utworzenia",

View File

@@ -132,10 +132,20 @@ function fieldsToJSONSchema(
}
case 'richText': {
fieldSchema = field.editor.outputSchema({
field,
isRequired,
})
if (field.editor.outputSchema) {
fieldSchema = field.editor.outputSchema({
field,
isRequired,
})
} else {
// Maintain backwards compatibility with existing rich text editors
fieldSchema = {
items: {
type: 'object',
},
type: withNullableJSONSchemaType('array', isRequired),
}
}
break
}

View File

@@ -10,14 +10,14 @@
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf {dist,*.tsbuildinfo}",
"prepublishOnly": "yarn clean && yarn build",
"prepublishOnly": "pnpm clean && pnpm build",
"test": "echo \"No tests available.\""
},
"peerDependencies": {
"@aws-sdk/client-s3": "^3.142.0",
"@aws-sdk/lib-storage": "^3.267.0",
"@azure/storage-blob": "^12.11.0",
"@azure/abort-controller": "^1.0.0",
"@azure/storage-blob": "^12.11.0",
"@google-cloud/storage": "^6.4.1",
"payload": "^1.7.2 || ^2.0.0"
},
@@ -49,6 +49,7 @@
"@azure/storage-blob": "^12.11.0",
"@google-cloud/storage": "^6.4.1",
"@types/express": "^4.17.9",
"@types/find-node-modules": "^2.1.2",
"cross-env": "^7.0.3",
"dotenv": "^8.2.0",
"nodemon": "^2.0.6",
@@ -58,6 +59,7 @@
"webpack": "^5.78.0"
},
"dependencies": {
"find-node-modules": "^2.1.3",
"range-parser": "^1.2.1"
}
}

View File

@@ -1,15 +1,37 @@
import type { Configuration as WebpackConfig } from 'webpack'
import findNodeModules from 'find-node-modules'
import fs from 'fs'
import path from 'path'
const packageName = '@payloadcms/plugin-cloud-storage'
const nodeModulesPaths = findNodeModules({ cwd: __dirname, relative: false })
export const extendWebpackConfig = (existingWebpackConfig: WebpackConfig): WebpackConfig => {
let nodeModulesPath = nodeModulesPaths.find((p) => {
const guess = path.resolve(p, `${packageName}/dist`)
if (fs.existsSync(guess)) {
return true
}
return false
})
if (!nodeModulesPath) {
nodeModulesPath = process.cwd()
}
const newConfig: WebpackConfig = {
...existingWebpackConfig,
resolve: {
...(existingWebpackConfig.resolve || {}),
alias: {
...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}),
'@payloadcms/plugin-cloud-storage/s3': path.resolve(__dirname, './mock.js'),
'@payloadcms/plugin-cloud-storage/s3$': path.resolve(
nodeModulesPath,
`./${packageName}/dist/adapters/s3/mock.js`,
),
},
fallback: {
...(existingWebpackConfig.resolve?.fallback ? existingWebpackConfig.resolve.fallback : {}),

View File

@@ -29,7 +29,7 @@ export const extendWebpackConfig =
},
}
return Object.entries(options.collections).reduce(
const modifiedConfig = Object.entries(options.collections).reduce(
(resultingWebpackConfig, [slug, collectionOptions]) => {
const matchedCollection = config.collections?.find((coll) => coll.slug === slug)
@@ -47,4 +47,6 @@ export const extendWebpackConfig =
},
newConfig,
)
return modifiedConfig
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "1.0.8",
"version": "1.0.9",
"description": "The official Nested Docs plugin for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

10
pnpm-lock.yaml generated
View File

@@ -1091,6 +1091,9 @@ importers:
'@azure/abort-controller':
specifier: ^1.0.0
version: 1.1.0
find-node-modules:
specifier: ^2.1.3
version: 2.1.3
range-parser:
specifier: ^1.2.1
version: 1.2.1
@@ -1110,6 +1113,9 @@ importers:
'@types/express':
specifier: ^4.17.9
version: 4.17.17
'@types/find-node-modules':
specifier: ^2.1.2
version: 2.1.2
cross-env:
specifier: ^7.0.3
version: 7.0.3
@@ -5864,6 +5870,10 @@ packages:
resolution: {integrity: sha512-rnh1kFUbwp5xEu54GhGDnzxm5/iJC3irypLO9+YECIal+XXKU5ki4I1h0Sm43KNpvKzFfyYFeGOm7o6tGI9Bgw==}
dev: true
/@types/find-node-modules@2.1.2:
resolution: {integrity: sha512-5hRcqDclY6MTkHXJBc5q79z5luG+IJRlGR01wluMVMM9lYogYc2sfclXTVU5Edp0Ja4viIOCDI1lXhFRlsNTKA==}
dev: true
/@types/fs-extra@11.0.2:
resolution: {integrity: sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==}
dependencies:

View File

@@ -27,7 +27,6 @@ export const getPackageDetails = async (pkg?: string): Promise<PackageDetails[]>
} else {
packageDirs = fse.readdirSync(packagesDir).filter((d) => d !== 'eslint-config-payload')
}
console.log(packageDirs)
const packageDetails = await Promise.all(
packageDirs.map(async (dirName) => {

View File

@@ -11,7 +11,7 @@ To spin up the project locally, follow these steps:
1. First clone the repo
1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env`
1. Next `yarn && yarn dev` (or `docker-compose up`, see [Docker](#docker))
1. Now `open http://localhost:8000/admin` to access the admin panel
1. Now `open http://localhost:3000/admin` to access the admin panel
1. Create your first admin user using the form on the page
That's it! Changes made in `./src` will be reflected in your app.

View File

@@ -321,19 +321,19 @@ export default buildConfigWithDefaults({
slug: 'payload-api-test-twos',
},
{
slug: 'content-type',
access: {
read: () => true,
},
fields: [
{
name: 'contentType',
type: 'text',
hooks: {
afterRead: [({ req }) => req.headers?.['content-type']],
},
type: 'text',
},
],
slug: 'content-type',
},
],
graphQL: {

View File

@@ -6,7 +6,7 @@ import payload from '../../packages/payload/src'
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
import { initPayloadTest } from '../helpers/configHelpers'
import { idToString } from '../helpers/idToString'
import configPromise, { errorOnHookSlug, pointSlug, slug } from './config'
import configPromise, { errorOnHookSlug, pointSlug, relationSlug, slug } from './config'
const title = 'title'
@@ -779,6 +779,86 @@ describe('collections-graphql', () => {
expect(totalDocs).toStrictEqual(1)
expect(docs[0].relationToCustomID.id).toStrictEqual(1)
})
it('should query a document with a deleted relationship', async () => {
const relation = await payload.create({
collection: relationSlug,
data: {
name: 'test',
},
})
await payload.create({
collection: slug,
data: {
relationField: relation.id,
title: 'has deleted relation',
},
})
await payload.delete({
id: relation.id,
collection: relationSlug,
})
const query = `query {
Posts(where: { title: { equals: "has deleted relation" }}) {
docs {
id
title
relationField {
id
}
}
totalDocs
}
}`
const response = await client.request(query)
const { docs } = response.Posts
expect(docs[0].relationField).toBeFalsy()
})
it('should query a document with a deleted relationship hasMany', async () => {
const relation = await payload.create({
collection: relationSlug,
data: {
name: 'test',
},
})
await payload.create({
collection: slug,
data: {
relationHasManyField: [relation.id],
title: 'has deleted relation hasMany',
},
})
await payload.delete({
id: relation.id,
collection: relationSlug,
})
const query = `query {
Posts(where: { title: { equals: "has deleted relation hasMany" }}) {
docs {
id
title
relationHasManyField {
id
}
}
totalDocs
}
}`
const response = await client.request(query)
const { docs } = response.Posts
expect(docs[0].relationHasManyField).toHaveLength(0)
})
})
})

View File

@@ -161,18 +161,30 @@ describe('database', () => {
},
req,
})
try {
await payload.create({
const promises = [
payload.create({
collection,
data: {
throwAfterChange: true,
title,
},
req,
})
} catch (error: unknown) {
// catch error and carry on
}),
// multiple documents should be able to fail without error
payload.create({
collection,
data: {
throwAfterChange: true,
title,
},
req,
}),
]
try {
await Promise.all(promises)
} catch (e) {
// catch errors and carry on
}
expect(req.transactionID).toBeFalsy()