reactjstypescriptpackage.jsonpayload-cms

Troubleshooting 'npm run generate:types' Command Issue


so i get an error by doing npm run generate:types :

PS C:\Users\hacke\complete_fullstack_digital_marketplace_app> npm run generate:types

> complete_fullstack_digital_marketplace_app@0.1.0 generate:types
> cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types

C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\fields\config\sanitize.js:52
                        throw new _errors.InvalidFieldRelationship(field, relationship);
                              ^

InvalidFieldRelationship: Field Product file(s) has invalid relationship 'product_files'.
    at C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\fields\config\sanitize.js:52:31
    at Array.forEach (<anonymous>)
    at C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\fields\config\sanitize.js:50:31
    at Array.map (<anonymous>)
    at sanitizeFields (C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\fields\config\sanitize.js:25:19)
    at sanitizeCollection (C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\collections\config\sanitize.js:135:53)
    at C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\config\sanitize.js:81:85
    at Array.map (<anonymous>)
    at sanitizeConfig (C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\config\sanitize.js:81:45)
    at buildConfig (C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\config\build.js:21:41) {
  data: null,
  isOperational: true,
  isPublic: false,
  status: 500
}

Node.js v20.10.0.



i do not know what is the issue becasue everything worked till this point and i need to generate typscript types based on configuration in src/payload.config.ts recognise collection.

github repo: https://github.com/RenePung/complete_fullstack_digital_marketplace_app

package.json :

{
  "name": "complete_fullstack_digital_marketplace_app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
    "generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.3.2",
    "@payloadcms/bundler-webpack": "^1.0.5",
    "@payloadcms/db-mongodb": "^1.0.8",
    "@payloadcms/richtext-slate": "^1.2.0",
    "@radix-ui/react-dialog": "^1.0.5",
    "@radix-ui/react-dropdown-menu": "^2.0.6",
    "@radix-ui/react-label": "^2.0.2",
    "@radix-ui/react-separator": "^1.0.3",
    "@radix-ui/react-slot": "^1.0.2",
    "@tanstack/react-query": "^4.36.1",
    "@trpc/client": "^10.44.1",
    "@trpc/next": "^10.44.1",
    "@trpc/react-query": "^10.44.1",
    "@trpc/server": "^10.44.1",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.0.0",
    "cross-env": "^7.0.3",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "lucide-react": "^0.292.0",
    "next": "14.0.3",
    "nodemailer": "^6.9.7",
    "payload": "^2.2.2",
    "react": "^18",
    "react-dom": "^18",
    "react-hook-form": "^7.48.2",
    "sonner": "^1.2.4",
    "tailwind-merge": "^2.0.0",
    "tailwindcss-animate": "^1.0.7",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^20",
    "@types/nodemailer": "^6.4.14",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.0.3",
    "nodemon": "^3.0.1",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

Products.ts file :

import { PRODUCT_CATEGORIES } from "../../config";
import { CollectionConfig } from "payload/types";

export  const Products: CollectionConfig = {
    slug: "products",
    admin: {
        useAsTitle: "name"
    },
    access: {},
    fields: [
        {
            name: "user",
            type: "relationship",
            relationTo: "users",
            required: true,
            hasMany: false,
            admin: {
                condition: () => false
            },
        },
        {
            name: "name",
            label: "Name",
            type: "text",
            required: true,
        },
        {
            name: "description",
            type: "textarea",
            label: "Product details",
        },
        {
            name: "price",
            label: "Price in USD",
            min: 0,
            max: 1000,
            type: "number",
            required: true,
        },
        {
            name: "category",
            label: "Category",
            type: "select",
            options: PRODUCT_CATEGORIES.map(({ label, value }) => ({ label, value })
            ),
            required: true,
        },
        {
            name: "product_files",
            label: "Product file(s)",
            type: "relationship",
            required: true,
            relationTo: "product_files",
            hasMany: false,
        },
        {
            name: "approvedForSale",
            label: "Product Status",
            type: "select",
            defaultValue: "pending",
            access: {
                create: ({ req }) => req.user.role === "admin",
                read: ({ req }) => req.user.role === "admin",
                update: ({ req }) => req.user.role === "admin",
            },
            options: [
                {
                    label: "Pending verification",
                    value: "pending",
                },
                {
                    label: "Approved",
                    value: "approved",
                },
                {
                    label: "Denied",
                    value: "denied",
                },
            ],
        },
        {
            name: "priceId",
            access: {
                create: () => false,
                read: () => false,
                update: () => false,
            },
            type: "text",
            admin: {
                hidden: true,
            },
        },
        {
            name: "stripeId",
            access: {
                create: () => false,
                read: () => false,
                update: () => false,
            },
            type: "text",
            admin: {
                hidden: true,
            },
        },
        {
            name: "images",
            type: "array",
            label: "Product images",
            minRows: 1,
            maxRows: 4,
            required: true,
            labels: {
                singular: "Image",
                plural: "Images",
            },
            fields: [
                {
                    name: "image",
                    type: "upload",
                    relationTo: "media",
                    required: true,
                },
            ],
        },
    ],
}


ProductFile.ts file :

import { User } from "../payload-types";
import { BeforeChangeHook } from "payload/dist/collections/config/types";
import { Access, CollectionConfig } from "payload/types";

const addUser: BeforeChangeHook = ({ req, data }) => {
    const user = req.user as User | null
    return {...data, user: user?.id}
}

const yourOwnAndPurchased: Access = async ({ req }) => {
    const user = req.user as User | null

    if( user?.role === "admin" ) return true
    if( !user ) return false

    const {} = await req.payload.find({
        collection: "products"
    })
}

export const ProductFiles: CollectionConfig = {
    slug: "product_files",
    admin: {
        hidden: ({ user }) => user.role !== "admin",
    },
    hooks: {
        beforeChange: [addUser]
    },
    access: {
        read: yourOwnAndPurchased
    },
    upload: {
        staticURL: "/product_files",
        staticDir: "product_files",
        mimeTypes: ["image/*", "font/*", "application/postscript"],
    },
    fields: [
        {
            name: "user",
            type: "relationship",
            relationTo: "users",
            admin: {
                condition: () => false
            },
            hasMany: false,
            required: true,
        },
    ],
}

payload.config.ts file :

import { mongooseAdapter } from "@payloadcms/db-mongodb";
import { slateEditor } from "@payloadcms/richtext-slate";
import { buildConfig } from "payload/config";
import { webpackBundler } from "@payloadcms/bundler-webpack";
import path from "path";
import { Users } from "./collections/Users";
import dotenv from "dotenv";
import { Products } from "./collections/Products/Products";
import { Media } from "./collections/Media";
//*****************************IMPORTS*****************************************************

dotenv.config({
    path: path.resolve(__dirname, "../.env"),
})

export default buildConfig({
    serverURL: process.env.NEXT_PUBLIC_SERVER_URL || '',
    collections: [Users, Products, Media],
    routes: {
        admin: '/sell'
    },
    admin: {
        user: "users",
        bundler: webpackBundler(),
        meta: {
            titleSuffix: "- HippoHub",
            favicon: "/favicon.ico",
            ogImage: "/thumbnail.jpg",
        },
    },
    rateLimit: {
        max: 2000,
    },
    editor: slateEditor({}),
    db: mongooseAdapter({
        url: process.env.MONGODB_URL!,
    }),
    typescript: {
        outputFile: path.resolve(__dirname, 'payload-types.ts'),
    },
})



Solution

  • You just need to import and use the ProductFiles collection in your payload config file.