Compare commits

..

23 Commits

Author SHA1 Message Date
Elliot DeNolf
4b9e87bb4d chore(release): db-postgres/0.1.8 2023-10-15 21:04:46 -04:00
Elliot DeNolf
ff5e174497 chore(script): multiselect publishing prompt 2023-10-15 20:53:16 -04:00
Elliot DeNolf
34017e1758 chore(release): payload/2.0.7 2023-10-15 20:49:18 -04:00
James Mikrut
23d95526ab Merge pull request #3666 from payloadcms/fix/empty-in-array
fix: empty inArray no longer crashes postgres
2023-10-15 18:05:15 -04:00
James
59b87fdb21 chore: typo 2023-10-15 17:55:30 -04:00
James
f2ac1f7d48 chore: merge main 2023-10-15 17:31:24 -04:00
James
8d4f39af5e Merge branch 'main' of github.com:payloadcms/payload into fix/empty-in-array 2023-10-15 17:29:51 -04:00
James
c4ac341d75 fix: empty inArray no longer crashes postgres 2023-10-15 17:28:13 -04:00
Elliot DeNolf
27589482dd chore(release): @payloadcms/db-postgres/0.1.7 2023-10-15 14:12:02 -04:00
James Mikrut
d7ab4b7062 Merge pull request #3642 from payloadcms/fix/#3568-postgres-relationships-in-array
fix(db-postgres): query relationship in array alias
2023-10-15 11:31:57 -04:00
James
2c8fbf1be3 chore: adds specificity to tests 2023-10-15 11:08:16 -04:00
James
eec88f8f1b Merge branch 'fix/#3568-postgres-relationships-in-array' of github.com:payloadcms/payload into fix/#3568-postgres-relationships-in-array 2023-10-15 10:43:32 -04:00
James
1481ef97b5 Merge branch 'main' of github.com:payloadcms/payload 2023-10-15 09:44:19 -04:00
James
a89e89fb80 chore: documents pagination: false 2023-10-15 09:44:00 -04:00
Alessio Gravili
7e7eeb059d chore(richtext-lexical): add 'use client' to field and cell 2023-10-15 14:25:36 +02:00
Alessio Gravili
dc2a502dcc perf(richtext-lexical): remove unnecessary prop drilling and load hooks being run for initialState 2023-10-15 14:07:24 +02:00
Elliot DeNolf
a9a5ba82d8 chore: update pnpm-lock.yaml 2023-10-14 21:09:15 -04:00
James
e6e8fae1c5 Merge branch 'main' of github.com:payloadcms/payload 2023-10-14 19:24:50 -04:00
James
a05868a7f3 chore: remove unnecessary peer dep 2023-10-14 19:24:32 -04:00
Elliot DeNolf
f27cd26575 chore(release): richtext-lexical/0.1.11 2023-10-14 17:35:44 -04:00
Dan Ribbens
b0083b7c07 test: fix missing variable 2023-10-14 15:32:06 -04:00
Dan Ribbens
21649537a6 fix(db-postgres): query relationship path inside arrays 2023-10-14 15:17:42 -04:00
Dan Ribbens
2c67eff059 fix(db-postgres): query relationship in array alias 2023-10-13 13:32:44 -04:00
23 changed files with 393 additions and 176 deletions

View File

@@ -131,6 +131,7 @@ const result = await payload.find({
depth: 2,
page: 1,
limit: 10,
pagination: false, // If you want to disable pagination count, etc.
where: {}, // pass a `where` query here
sort: '-title',
locale: 'en',

View File

@@ -59,3 +59,7 @@ All Payload APIs support the pagination controls below. With them, you can creat
| ------- | --------------------------------------- |
| `limit` | Limits the number of documents returned |
| `page` | Get a specific page number |
### Disabling pagination within Local API
For `find` operations within the Local API, you can disable pagination to retrieve all documents from a collection by passing `pagination: false` to the `find` local operation. This is not supported in REST or GraphQL, however, because it could potentially lead to malicious activity.

View File

@@ -59,6 +59,7 @@
"fs-extra": "10.1.0",
"get-port": "5.1.1",
"glob": "8.1.0",
"graphql-request": "6.1.0",
"husky": "^8.0.3",
"isomorphic-fetch": "3.0.0",
"jest": "29.6.4",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.1.6",
"version": "0.1.8",
"description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",
@@ -34,9 +34,6 @@
"@types/to-snake-case": "1.0.0",
"payload": "workspace:*"
},
"peerDependencies": {
"better-sqlite3": "^8.5.0"
},
"publishConfig": {
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",

View File

@@ -33,7 +33,7 @@ export const findMany = async function find({
const db = adapter.sessions[req.transactionID]?.db || adapter.drizzle
const table = adapter.tables[tableName]
let limit = limitArg
let limit = limitArg ?? 10
let totalDocs: number
let totalPages: number
let hasPrevPage: boolean
@@ -119,7 +119,11 @@ export const findMany = async function find({
findManyArgs.where = inArray(adapter.tables[tableName].id, Object.keys(orderedIDMap))
} else {
findManyArgs.limit = limitArg === 0 ? undefined : limitArg
findManyArgs.offset = skip || (page - 1) * limitArg
const offset = skip || (page - 1) * limitArg
if (!Number.isNaN(offset)) findManyArgs.offset = offset
if (where) {
findManyArgs.where = where
}

View File

@@ -34,12 +34,14 @@ type Args = {
aliasTable?: GenericTable
collectionPath: string
columnPrefix?: string
constraintPath?: string
constraints?: Constraint[]
fields: (Field | TabAsField)[]
joinAliases: BuildQueryJoinAliases
joins: BuildQueryJoins
locale?: string
pathSegments: string[]
rootTableName?: string
selectFields: Record<string, GenericColumn>
tableName: string
}
@@ -53,17 +55,22 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix = '',
constraintPath: incomingConstraintPath,
constraints = [],
fields,
joinAliases,
joins,
locale: incomingLocale,
pathSegments: incomingSegments,
rootTableName: incomingRootTableName,
selectFields,
tableName,
}: Args): TableColumn => {
const fieldPath = incomingSegments[0]
let locale = incomingLocale
const rootTableName = incomingRootTableName || tableName
let constraintPath = incomingConstraintPath || ''
const field = flattenTopLevelFields(fields as Field[]).find(
(fieldToFind) => fieldAffectsData(fieldToFind) && fieldToFind.name === fieldPath,
) as Field | TabAsField
@@ -105,6 +112,7 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix,
constraintPath,
constraints,
fields: field.tabs.map((tab) => ({
...tab,
@@ -114,6 +122,7 @@ export const getTableColumnFromPath = ({
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -125,12 +134,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix: `${columnPrefix}${field.name}_`,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -140,12 +151,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -172,12 +185,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix: `${columnPrefix}${field.name}_`,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -185,6 +200,7 @@ export const getTableColumnFromPath = ({
case 'array': {
newTableName = `${tableName}_${toSnakeCase(field.name)}`
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
@@ -206,12 +222,14 @@ export const getTableColumnFromPath = ({
return getTableColumnFromPath({
adapter,
collectionPath,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -229,12 +247,14 @@ export const getTableColumnFromPath = ({
result = getTableColumnFromPath({
adapter,
collectionPath,
constraintPath: '',
constraints: blockConstraints,
fields: block.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields: blockSelectFields,
tableName: newTableName,
})
@@ -283,9 +303,8 @@ export const getTableColumnFromPath = ({
case 'relationship':
case 'upload': {
let relationshipFields
const relationTableName = `${tableName}_rels`
const relationTableName = `${rootTableName}_rels`
const newCollectionPath = pathSegments.slice(1).join('.')
const aliasRelationshipTableName = uuid()
const aliasRelationshipTable = alias(
adapter.tables[relationTableName],
@@ -295,7 +314,7 @@ export const getTableColumnFromPath = ({
// Join in the relationships table
joinAliases.push({
condition: eq(
(aliasTable || adapter.tables[tableName]).id,
(aliasTable || adapter.tables[rootTableName]).id,
aliasRelationshipTable.parent,
),
table: aliasRelationshipTable,
@@ -306,7 +325,7 @@ export const getTableColumnFromPath = ({
constraints.push({
columnName: 'path',
table: aliasRelationshipTable,
value: field.name,
value: `${constraintPath}${field.name}`,
})
let newAliasTable
@@ -368,6 +387,7 @@ export const getTableColumnFromPath = ({
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName: newTableName,
selectFields,
tableName: newTableName,
})

View File

@@ -100,7 +100,11 @@ export async function parseParams({
const val = where[relationOrPath][operator]
queryConstraints.forEach(({ columnName: col, table: constraintTable, value }) => {
constraints.push(operatorMap.equals(constraintTable[col], value))
if (typeof value === 'string' && value.indexOf('%') > -1) {
constraints.push(operatorMap.like(constraintTable[col], value))
} else {
constraints.push(operatorMap.equals(constraintTable[col], value))
}
})
if (['json', 'richText'].includes(field.type) && Array.isArray(pathSegments)) {
@@ -144,13 +148,19 @@ export async function parseParams({
break
}
const { operator: queryOperator, value: queryValue } = sanitizeQueryValue({
const sanitizedQueryValue = sanitizeQueryValue({
field,
operator,
relationOrPath,
val,
})
if (sanitizedQueryValue === null) {
break
}
const { operator: queryOperator, value: queryValue } = sanitizedQueryValue
if (queryOperator === 'not_equals' && queryValue !== null) {
constraints.push(
or(
@@ -159,7 +169,10 @@ export async function parseParams({
ne<any>(rawColumn || table[columnName], queryValue),
),
)
} else if (
break
}
if (
(field.type === 'relationship' || field.type === 'upload') &&
Array.isArray(queryValue) &&
operator === 'not_in'
@@ -170,11 +183,13 @@ export async function parseParams({
IS
NULL`,
)
} else {
constraints.push(
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
)
break
}
constraints.push(
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
)
}
}
}

View File

@@ -42,11 +42,17 @@ export const sanitizeQueryValue = ({
if (val.toLowerCase() === 'false') formattedValue = false
}
if (['all', 'in', 'not_in'].includes(operator) && typeof formattedValue === 'string') {
formattedValue = createArrayFromCommaDelineated(formattedValue)
if (['all', 'in', 'not_in'].includes(operator)) {
if (typeof formattedValue === 'string') {
formattedValue = createArrayFromCommaDelineated(formattedValue)
if (field.type === 'number') {
formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal))
if (field.type === 'number') {
formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal))
}
}
if (!Array.isArray(formattedValue) || formattedValue.length === 0) {
return null
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.0.6",
"version": "2.0.7",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",
@@ -187,7 +187,6 @@
"file-loader": "6.2.0",
"form-data": "3.0.1",
"get-port": "5.1.1",
"graphql-request": "6.1.0",
"mini-css-extract-plugin": "1.6.2",
"node-fetch": "2.6.12",
"nodemon": "3.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "0.1.10",
"version": "0.1.11",
"description": "The officially supported Lexical richtext adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -1,3 +1,4 @@
'use client'
import type { SerializedEditorState } from 'lexical'
import type { CellComponentProps, RichTextField } from 'payload/types'

View File

@@ -47,7 +47,7 @@ const RichText: React.FC<FieldProps> = (props) => {
validate: memoizedValidate,
})
const { errorMessage, initialValue, setValue, showError, value } = fieldType
const { errorMessage, setValue, showError, value } = fieldType
let valueToUse = value
@@ -87,7 +87,6 @@ const RichText: React.FC<FieldProps> = (props) => {
<LexicalProvider
editorConfig={editorConfig}
fieldProps={props}
initialState={initialValue}
onChange={(editorState, editor, tags) => {
let serializedEditorState = editorState.toJSON()
@@ -101,7 +100,6 @@ const RichText: React.FC<FieldProps> = (props) => {
setValue(serializedEditorState)
}}
readOnly={readOnly}
setValue={setValue}
value={value}
/>
<FieldDescription description={description} value={value} />

View File

@@ -1,3 +1,4 @@
'use client'
import { ShimmerEffect } from 'payload/components'
import React, { Suspense, lazy } from 'react'

View File

@@ -17,7 +17,9 @@ import { AddBlockHandlePlugin } from './plugins/handles/AddBlockHandlePlugin'
import { DraggableBlockPlugin } from './plugins/handles/DraggableBlockPlugin'
import { LexicalContentEditable } from './ui/ContentEditable'
export const LexicalEditor: React.FC<LexicalProviderProps> = (props) => {
export const LexicalEditor: React.FC<Pick<LexicalProviderProps, 'editorConfig' | 'onChange'>> = (
props,
) => {
const { editorConfig, onChange } = props
const [editor] = useLexicalComposerContext()

View File

@@ -14,28 +14,21 @@ import { getEnabledNodes } from './nodes'
export type LexicalProviderProps = {
editorConfig: SanitizedEditorConfig
fieldProps: FieldProps
initialState: SerializedEditorState
onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void
readOnly: boolean
setValue: (value: SerializedEditorState) => void
value: SerializedEditorState
}
export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
const { editorConfig, fieldProps, onChange, readOnly, setValue } = props
let { initialState, value } = props
const { editorConfig, fieldProps, onChange, readOnly } = props
let { value } = props
// Transform initialState through load hooks
if (editorConfig?.features?.hooks?.load?.length) {
editorConfig.features.hooks.load.forEach((hook) => {
initialState = hook({ incomingEditorState: initialState })
value = hook({ incomingEditorState: value })
})
}
if (
(value && Array.isArray(value) && !('root' in value)) ||
(initialState && Array.isArray(initialState) && !('root' in initialState))
) {
if (value && Array.isArray(value) && !('root' in value)) {
throw new Error(
'You have tried to pass in data from the old, Slate editor, to the new, Lexical editor. This is not supported. There is no automatic conversion from Slate to Lexical data available yet (coming soon). Please remove the data from the field and start again.',
)
@@ -49,7 +42,7 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
const initialConfig: InitialConfigType = {
editable: readOnly === true ? false : true,
editorState: initialState != null ? JSON.stringify(initialState) : undefined,
editorState: value != null ? JSON.stringify(value) : undefined,
namespace: editorConfig.lexical.namespace,
nodes: [...getEnabledNodes({ editorConfig })],
onError: (error: Error) => {
@@ -62,15 +55,7 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
<LexicalComposer initialConfig={initialConfig}>
<EditorConfigProvider editorConfig={editorConfig} fieldProps={fieldProps}>
<div className="editor-shell">
<LexicalEditorComponent
editorConfig={editorConfig}
fieldProps={fieldProps}
initialState={initialState}
onChange={onChange}
readOnly={readOnly}
setValue={setValue}
value={value}
/>
<LexicalEditorComponent editorConfig={editorConfig} onChange={onChange} />
</div>
</EditorConfigProvider>
</LexicalComposer>

14
pnpm-lock.yaml generated
View File

@@ -96,6 +96,9 @@ importers:
glob:
specifier: 8.1.0
version: 8.1.0
graphql-request:
specifier: 6.1.0
version: 6.1.0(graphql@16.8.1)
husky:
specifier: ^8.0.3
version: 8.0.3
@@ -418,9 +421,6 @@ importers:
'@libsql/client':
specifier: ^0.3.1
version: 0.3.4
better-sqlite3:
specifier: ^8.5.0
version: 8.6.0
console-table-printer:
specifier: 2.11.2
version: 2.11.2
@@ -429,7 +429,7 @@ importers:
version: 0.19.13-e99bac1
drizzle-orm:
specifier: 0.28.5
version: 0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(better-sqlite3@8.6.0)(pg@8.11.3)
version: 0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(pg@8.11.3)
pg:
specifier: 8.11.3
version: 8.11.3
@@ -943,9 +943,6 @@ importers:
get-port:
specifier: 5.1.1
version: 5.1.1
graphql-request:
specifier: 6.1.0
version: 6.1.0(graphql@16.8.1)
mini-css-extract-plugin:
specifier: 1.6.2
version: 1.6.2(webpack@5.88.2)
@@ -8447,7 +8444,7 @@ packages:
- supports-color
dev: false
/drizzle-orm@0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(better-sqlite3@8.6.0)(pg@8.11.3):
/drizzle-orm@0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(pg@8.11.3):
resolution: {integrity: sha512-6r6Iw4c38NAmW6TiKH3TUpGUQ1YdlEoLJOQptn8XPx3Z63+vFNKfAiANqrIiYZiMjKR9+NYAL219nFrmo1duXA==}
peerDependencies:
'@aws-sdk/client-rds-data': '>=3'
@@ -8511,7 +8508,6 @@ packages:
dependencies:
'@libsql/client': 0.3.4
'@types/pg': 8.10.2
better-sqlite3: 8.6.0
pg: 8.11.3
dev: false

View File

@@ -0,0 +1,79 @@
import path from 'path'
import fse from 'fs-extra'
import chalk from 'chalk'
import chalkTemplate from 'chalk-template'
import simpleGit from 'simple-git'
const git = simpleGit()
const packagesDir = path.resolve(__dirname, '../../packages')
export type PackageDetails = {
name: string
newCommits: number
shortName: string
packagePath: string
publishedVersion: string
publishDate: string
version: string
}
export const getPackageDetails = async (): Promise<PackageDetails[]> => {
const packageDirs = fse.readdirSync(packagesDir).filter((d) => d !== 'eslint-config-payload')
const packageDetails = await Promise.all(
packageDirs.map(async (dirName) => {
const packageJson = await fse.readJson(`${packagesDir}/${dirName}/package.json`)
const isPublic = packageJson.private !== true
if (!isPublic) return null
// Get published version from npm
const json = await fetch(`https://registry.npmjs.org/${packageJson.name}`).then((res) =>
res.json(),
)
const publishedVersion = json?.['dist-tags']?.latest
const publishDate = json?.time?.[publishedVersion]
const prevGitTag = `${dirName}/${packageJson.version}`
const prevGitTagHash = await git.revparse(prevGitTag)
const newCommits = await git.log({
from: prevGitTagHash,
file: `packages/${dirName}`,
})
return {
name: packageJson.name as string,
newCommits: newCommits.total,
shortName: dirName,
packagePath: `packages/${dirName}`,
publishedVersion,
publishDate,
version: packageJson.version,
}
}),
)
return packageDetails.filter((p): p is Exclude<typeof p, null> => p !== null)
}
export const showPackageDetails = (details: PackageDetails[]) => {
console.log(chalkTemplate`
{bold Packages:}
${details
.map((p) => {
const name = p?.newCommits ? chalk.bold.green(p?.shortName.padEnd(28)) : p?.shortName.padEnd(28)
const publishData = `${p?.publishedVersion} at ${p?.publishDate
.split(':')
.slice(0, 2)
.join(':')
.replace('T', ' ')}`
const newCommits = `${p?.newCommits ? `${chalk.bold.green(p?.newCommits)} new commits` : ''}`
return ` ${name}${publishData} ${newCommits}`
})
.join('\n')}
`)
}

View File

@@ -1,74 +1,10 @@
import path from 'path'
import fse from 'fs-extra'
import chalk from 'chalk'
import chalkTemplate from 'chalk-template'
import simpleGit from 'simple-git'
const git = simpleGit()
const packagesDir = path.resolve(__dirname, '../packages')
import { getPackageDetails, showPackageDetails } from './lib/getPackageDetails'
async function main() {
// List all public packages excluding eslint-config-payload
const packageDirs = fse.readdirSync(packagesDir).filter((d) => d !== 'eslint-config-payload')
const packageDetails = await Promise.all(
packageDirs.map(async (dirName) => {
const packageJson = await fse.readJson(`${packagesDir}/${dirName}/package.json`)
const isPublic = packageJson.private !== true
if (!isPublic) return null
// Get published version from npm
const json = await fetch(`https://registry.npmjs.org/${packageJson.name}`).then((res) =>
res.json(),
)
const publishedVersion = json?.['dist-tags']?.latest
const publishDate = json?.time?.[publishedVersion]
const prevGitTag = `${dirName}/${packageJson.version}`
const prevGitTagHash = await git.revparse(prevGitTag)
const newCommits = await git.log({
from: prevGitTagHash,
file: `packages/${dirName}`,
})
return {
name: packageJson.name,
newCommits: newCommits.total,
packageDir: dirName,
packagePath: `packages/${dirName}`,
publishedVersion,
publishDate,
version: packageJson.version,
}
}),
)
console.log(chalkTemplate`
{bold Packages:}
${packageDetails
.map((p) => {
const name = p?.newCommits
? chalk.bold.green(p?.packageDir.padEnd(28))
: p?.packageDir.padEnd(28)
const publishData = `${p?.publishedVersion} at ${p?.publishDate
.split(':')
.slice(0, 2)
.join(':')
.replace('T', ' ')}`
const newCommits = `${p?.newCommits ? `${chalk.bold.green(p?.newCommits)} new commits` : ''}`
return ` ${name}${publishData} ${newCommits}`
})
.join('\n')}
`)
const packageDetails = await getPackageDetails()
showPackageDetails(packageDetails)
}
// console.log(packageNames)
main().catch((error) => {
console.error(error)
process.exit(1)

View File

@@ -4,32 +4,49 @@ import chalk from 'chalk'
import prompts from 'prompts'
import minimist from 'minimist'
import chalkTemplate from 'chalk-template'
import { PackageDetails, getPackageDetails, showPackageDetails } from './lib/getPackageDetails'
const execOpts: ExecSyncOptions = { stdio: 'inherit' }
const args = minimist(process.argv.slice(2))
async function main() {
const { _: packageNames, tag = 'latest', bump = 'patch' } = args
const { tag = 'latest', bump = 'patch' } = args
if (packageNames.length === 0) {
const packageDetails = await getPackageDetails()
showPackageDetails(packageDetails)
const { packagesToRelease } = (await prompts({
type: 'multiselect',
name: 'packagesToRelease',
message: 'Select packages to release',
instructions: 'Space to select. Enter to submit.',
choices: packageDetails.map((p) => {
const title = p?.newCommits ? chalk.bold.green(p?.shortName) : p?.shortName
return {
title,
value: p.shortName,
}
}),
})) as { packagesToRelease: string[] }
if (!packagesToRelease) {
abort()
}
if (packagesToRelease.length === 0) {
abort('Please specify a package to publish')
}
if (packageNames.find((p) => p === 'payload' && packageNames.length > 1)) {
abort('Cannot publish payload with other packages')
if (packagesToRelease.find((p) => p === 'payload' && packagesToRelease.length > 1)) {
abort('Cannot publish payload with other packages. Release Payload first.')
}
// Get current version of each package from package.json
const packageDetails = await Promise.all(
packageNames.map(async (packageName) => {
const packageDir = `packages/${packageName}`
if (!(await fse.pathExists(packageDir))) {
abort(`Package path ${packageDir} does not exist`)
}
const packageObj = await fse.readJson(`${packageDir}/package.json`)
return { name: packageName, version: packageObj.version, dir: packageDir }
}),
const packageMap = packageDetails.reduce(
(acc, p) => {
acc[p.shortName] = p
return acc
},
{} as Record<string, PackageDetails>,
)
console.log(chalkTemplate`
@@ -38,10 +55,15 @@ async function main() {
{bold.yellow Bump: ${bump}}
{bold.yellow Tag: ${tag}}
${packageDetails.map((p) => ` ${p.name} - current: ${p.version}`).join('\n')}
${packagesToRelease
.map((p) => {
const { shortName, version } = packageMap[p]
return ` ${shortName.padEnd(24)} ${version}`
})
.join('\n')}
`)
const confirmPublish = await confirm(`Publish ${packageNames.length} package(s)?`)
const confirmPublish = await confirm(`Publish ${packagesToRelease.length} package(s)?`)
if (!confirmPublish) {
abort()
@@ -49,26 +71,26 @@ ${packageDetails.map((p) => ` ${p.name} - current: ${p.version}`).join('\n')}
const results: { name: string; success: boolean }[] = []
for (const pkg of packageDetails) {
const { dir, name } = pkg
for (const pkg of packagesToRelease) {
const { packagePath, shortName } = packageMap[pkg]
try {
console.log(chalk.bold(`\n\nPublishing ${name}...\n\n`))
console.log(chalk.bold(`\n\nPublishing ${shortName}...\n\n`))
execSync(`npm --no-git-tag-version --prefix ${dir} version ${bump}`, execOpts)
execSync(`git add ${dir}/package.json`, execOpts)
execSync(`npm --no-git-tag-version --prefix ${packagePath} version ${bump}`, execOpts)
execSync(`git add ${packagePath}/package.json`, execOpts)
const packageObj = await fse.readJson(`${dir}/package.json`)
const packageObj = await fse.readJson(`${packagePath}/package.json`)
const newVersion = packageObj.version
const tagName = `${name}/${newVersion}`
const tagName = `${shortName}/${newVersion}`
execSync(`git commit -m "chore(release): ${tagName}"`, execOpts)
execSync(`git tag -a ${tagName} -m "${tagName}"`, execOpts)
execSync(`pnpm publish -C ${dir} --no-git-checks`, execOpts)
results.push({ name, success: true })
execSync(`pnpm publish -C ${packagePath} --no-git-checks`, execOpts)
results.push({ name: shortName, success: true })
} catch (error) {
console.error(chalk.bold.red(`ERROR: ${error.message}`))
results.push({ name, success: false })
results.push({ name: shortName, success: false })
}
}

View File

@@ -3,63 +3,81 @@ import type { CollectionConfig } from '../../../../packages/payload/src/collecti
export const relationshipFieldsSlug = 'relationship-fields'
const RelationshipFields: CollectionConfig = {
slug: relationshipFieldsSlug,
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'relationship',
type: 'relationship',
relationTo: ['text-fields', 'array-fields'],
required: true,
type: 'relationship',
},
{
name: 'relationToSelf',
type: 'relationship',
relationTo: relationshipFieldsSlug,
type: 'relationship',
},
{
name: 'relationToSelfSelectOnly',
type: 'relationship',
relationTo: relationshipFieldsSlug,
admin: {
allowCreate: false,
},
relationTo: relationshipFieldsSlug,
type: 'relationship',
},
{
name: 'relationWithDynamicDefault',
type: 'relationship',
defaultValue: ({ user }) => user?.id,
relationTo: 'users',
defaultValue: ({ user }) => user.id,
type: 'relationship',
},
{
name: 'relationHasManyWithDynamicDefault',
type: 'relationship',
defaultValue: ({ user }) =>
user
? {
relationTo: 'users',
value: user.id,
}
: undefined,
relationTo: ['users'],
defaultValue: ({ user }) => ({
relationTo: 'users',
value: user.id,
}),
type: 'relationship',
},
{
name: 'relationshipWithMin',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true,
minRows: 2,
relationTo: 'text-fields',
type: 'relationship',
},
{
name: 'relationshipWithMax',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true,
maxRows: 2,
relationTo: 'text-fields',
type: 'relationship',
},
{
name: 'relationshipHasMany',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true,
relationTo: 'text-fields',
type: 'relationship',
},
{
name: 'array',
fields: [
{
name: 'relationship',
relationTo: 'text-fields',
type: 'relationship',
},
],
type: 'array',
},
],
slug: relationshipFieldsSlug,
}
export default RelationshipFields

View File

@@ -44,18 +44,18 @@ export default buildConfigWithDefaults({
collections: [
LexicalFields,
{
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [
{
name: 'canViewConditionalField',
type: 'checkbox',
defaultValue: true,
type: 'checkbox',
},
],
slug: 'users',
},
ArrayFields,
BlockFields,
@@ -81,8 +81,8 @@ export default buildConfigWithDefaults({
],
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
fallback: true,
locales: ['en', 'es'],
},
onInit: async (payload) => {
await payload.create({

View File

@@ -1093,7 +1093,7 @@ describe('fields', () => {
.locator('#field-relationship .relationship-add-new__relation-button--text-fields')
.click()
const textField = page.locator('#field-text')
const textField = page.locator('.drawer__content #field-text')
const textValue = 'hello'
await textField.fill(textValue)
@@ -1217,7 +1217,7 @@ describe('fields', () => {
.locator('#field-relationship .relationship-add-new__relation-button--text-fields')
.click()
await page.locator('#field-text').fill('something')
await page.locator('.drawer__content #field-text').fill('something')
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
await expect(page.locator('.Toastify')).toContainText('successfully')
@@ -1290,7 +1290,7 @@ describe('fields', () => {
await page.getByRole('button', { name: 'Edit Seeded text document' }).click()
// Fill 'text' field of 'Seeded text document'
await page.locator('#field-text').fill('some updated text value')
await page.locator('.drawer__content #field-text').fill('some updated text value')
// Save drawer (not parent page) with hotkey
await saveDocHotkeyAndAssert(page)

View File

@@ -21,6 +21,7 @@ import {
} from './collections/Group'
import { defaultNumber, numberDoc } from './collections/Number'
import { pointDoc } from './collections/Point'
import { relationshipFieldsSlug } from './collections/Relationship'
import { tabsDoc } from './collections/Tabs'
import {
localizedTextValue,
@@ -73,6 +74,122 @@ describe('Fields', () => {
})
})
describe('relationship', () => {
let textDoc
let otherTextDoc
let selfReferencing
let parent
let child
let grandChild
let relationshipInArray
const textDocText = 'text document'
const otherTextDocText = 'alt text'
const relationshipText = 'relationship text'
beforeAll(async () => {
textDoc = await payload.create({
collection: 'text-fields',
data: {
text: textDocText,
},
})
otherTextDoc = await payload.create({
collection: 'text-fields',
data: {
text: otherTextDocText,
},
})
const relationship = { relationTo: 'text-fields', value: textDoc.id }
parent = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationship,
text: relationshipText,
},
})
child = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationToSelf: parent.id,
relationship,
text: relationshipText,
},
})
grandChild = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationToSelf: child.id,
relationship,
text: relationshipText,
},
})
selfReferencing = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationship,
text: relationshipText,
},
})
relationshipInArray = await payload.create({
collection: relationshipFieldsSlug,
data: {
array: [
{
relationship: otherTextDoc.id,
},
],
relationship,
},
})
})
it('should query parent self-reference', async () => {
const childResult = await payload.find({
collection: relationshipFieldsSlug,
where: {
relationToSelf: { equals: parent.id },
},
})
const grandChildResult = await payload.find({
collection: relationshipFieldsSlug,
where: {
relationToSelf: { equals: child.id },
},
})
const anyChildren = await payload.find({
collection: relationshipFieldsSlug,
})
const allChildren = await payload.find({
collection: relationshipFieldsSlug,
where: {
'relationToSelf.text': { equals: relationshipText },
},
})
expect(childResult.docs[0].id).toStrictEqual(child.id)
expect(grandChildResult.docs[0].id).toStrictEqual(grandChild.id)
expect(allChildren.docs).toHaveLength(2)
})
it('should query relationship inside array', async () => {
const result = await payload.find({
collection: relationshipFieldsSlug,
where: {
'array.relationship.text': { equals: otherTextDocText },
},
})
expect(result.docs).toHaveLength(1)
expect(result.docs[0]).toMatchObject(relationshipInArray)
})
})
describe('timestamps', () => {
const tenMinutesAgo = new Date(Date.now() - 1000 * 60 * 10)
let doc
@@ -796,4 +913,19 @@ describe('Fields', () => {
expect(uploadElement.value.media.filename).toStrictEqual('payload.png')
})
})
describe('relationships', () => {
it('should not crash if querying with empty in operator', async () => {
const query = await payload.find({
collection: 'relationship-fields',
where: {
'relationship.value': {
in: [],
},
},
})
expect(query.docs).toBeDefined()
})
})
})