initial working draft
This commit is contained in:
10
packages/plugin-redirects/.editorconfig
Normal file
10
packages/plugin-redirects/.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
max_line_length = null
|
||||
7
packages/plugin-redirects/.gitignore
vendored
Normal file
7
packages/plugin-redirects/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.env
|
||||
dist
|
||||
demo/uploads
|
||||
build
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
92
packages/plugin-redirects/README.md
Normal file
92
packages/plugin-redirects/README.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Payload Redirects Plugin
|
||||
|
||||
[](https://www.npmjs.com/package/@payloadcms/plugin-redirects)
|
||||
|
||||
A plugin for [Payload CMS](https://github.com/payloadcms/payload) to easily manage your redirects.
|
||||
|
||||
Core features:
|
||||
|
||||
- Adds a `redirects` collection to your config that:
|
||||
- includes a `from` and `to` fields
|
||||
- allows `to` to be a document reference
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn add @payloadcms/plugin-redirects
|
||||
# OR
|
||||
npm i @payloadcms/plugin-redirects
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
|
||||
|
||||
```js
|
||||
import { buildConfig } from "payload/config";
|
||||
import redirects from "@payloadcms/plugin-redirects";
|
||||
|
||||
const config = buildConfig({
|
||||
collections: [
|
||||
{
|
||||
slug: "pages",
|
||||
fields: [],
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
redirects({
|
||||
collections: ["pages"],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `collections` : string[] | optional
|
||||
|
||||
An array of collections slugs to populate in the `to` field of each redirect.
|
||||
|
||||
- `overrides` : object | optional
|
||||
|
||||
A partial collection config that allows you to override anything on the `redirects` collection.
|
||||
|
||||
## TypeScript
|
||||
|
||||
All types can be directly imported:
|
||||
|
||||
```js
|
||||
import { PluginConfig } from "@payloadcms/plugin-redirects/types";
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To actively develop or debug this plugin you can either work directly within the demo directory of this repo, or link your own project.
|
||||
|
||||
1. #### Internal Demo
|
||||
|
||||
This repo includes a fully working, self-seeding instance of Payload that installs the plugin directly from the source code. This is the easiest way to get started. To spin up this demo, follow these steps:
|
||||
|
||||
1. First clone the repo
|
||||
1. Then, `cd YOUR_PLUGIN_REPO && yarn && cd demo && yarn && yarn dev`
|
||||
1. Now open `http://localhost:3000/admin` in your browser
|
||||
1. Enter username `dev@payloadcms.com` and password `test`
|
||||
|
||||
That's it! Changes made in `./src` will be reflected in your demo. Keep in mind that the demo database is automatically seeded on every startup, any changes you make to the data get destroyed each time you reboot the app.
|
||||
|
||||
1. #### Linked Project
|
||||
|
||||
You can alternatively link your own project to the source code:
|
||||
|
||||
1. First clone the repo
|
||||
1. Then, `cd YOUR_PLUGIN_REPO && yarn && cd demo && cp env.example .env && yarn && yarn dev`
|
||||
1. Now `cd` back into your own project and run, `yarn link @payloadcms/plugin-redirects`
|
||||
1. If this plugin using React in any way, continue to the next step. Otherwise skip to step 7.
|
||||
1. From your own project, `cd node_modules/react && yarn link && cd ../react-dom && yarn link && cd ../../`
|
||||
1. Then, `cd YOUR_PLUGIN_REPO && yarn link react react-dom`
|
||||
|
||||
All set! You can now boot up your own project as normal, and your local copy of the plugin source code will be used. Keep in mind that changes to the source code require a rebuild, `yarn build`.
|
||||
|
||||
## Screenshots
|
||||
2
packages/plugin-redirects/demo/.env.example
Normal file
2
packages/plugin-redirects/demo/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
MONGODB_URI=mongodb://localhost/payload-plugin-redirects
|
||||
PAYLOAD_SECRET=kjnsaldkjfnasdljkfghbnseanljnuadlrigjandrg
|
||||
4
packages/plugin-redirects/demo/nodemon.json
Normal file
4
packages/plugin-redirects/demo/nodemon.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
}
|
||||
27
packages/plugin-redirects/demo/package.json
Normal file
27
packages/plugin-redirects/demo/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "payload-starter-typescript",
|
||||
"description": "Blank template - no collections",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/server.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
|
||||
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
|
||||
"build:server": "tsc",
|
||||
"build": "yarn build:payload && yarn build:server",
|
||||
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
|
||||
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "^1.1.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.9",
|
||||
"cross-env": "^7.0.3",
|
||||
"nodemon": "^2.0.6",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
29
packages/plugin-redirects/demo/src/collections/Pages.ts
Normal file
29
packages/plugin-redirects/demo/src/collections/Pages.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'excerpt',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default Pages;
|
||||
18
packages/plugin-redirects/demo/src/collections/Users.ts
Normal file
18
packages/plugin-redirects/demo/src/collections/Users.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
auth: true,
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
// Email added by default
|
||||
// Add more fields as needed
|
||||
],
|
||||
};
|
||||
|
||||
export default Users;
|
||||
49
packages/plugin-redirects/demo/src/payload.config.ts
Normal file
49
packages/plugin-redirects/demo/src/payload.config.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { buildConfig } from 'payload/config';
|
||||
import path from 'path';
|
||||
// import redirects from '../../dist';
|
||||
import redirects from '../../src'
|
||||
import Users from './collections/Users'
|
||||
import Pages from './collections/Pages'
|
||||
|
||||
export default buildConfig({
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
user: Users.slug,
|
||||
webpack: (config) => {
|
||||
const newConfig = {
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
react: path.join(__dirname, "../node_modules/react"),
|
||||
"react-dom": path.join(__dirname, "../node_modules/react-dom"),
|
||||
"payload": path.join(__dirname, "../node_modules/payload"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return newConfig;
|
||||
},
|
||||
},
|
||||
collections: [
|
||||
Users,
|
||||
Pages
|
||||
],
|
||||
localization: {
|
||||
locales: [
|
||||
'en',
|
||||
'es'
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
plugins: [
|
||||
redirects({
|
||||
collections: ['pages'],
|
||||
}),
|
||||
],
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, 'payload-types.ts')
|
||||
},
|
||||
});
|
||||
47
packages/plugin-redirects/demo/src/seed/index.ts
Normal file
47
packages/plugin-redirects/demo/src/seed/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Payload } from 'payload';
|
||||
|
||||
export const seed = async (payload: Payload) => {
|
||||
payload.logger.info('Seeding data...');
|
||||
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'dev@payloadcms.com',
|
||||
password: 'test',
|
||||
}
|
||||
});
|
||||
|
||||
const { id: homePageID } = await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'Home Page',
|
||||
slug: 'home',
|
||||
excerpt: 'This is the home page',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'redirects',
|
||||
data: {
|
||||
from: 'https://payloadcms.com/old',
|
||||
to: {
|
||||
type: 'reference',
|
||||
reference: {
|
||||
relationTo: 'pages',
|
||||
value: homePageID
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'redirects',
|
||||
data: {
|
||||
from: 'https://payloadcms.com/bad',
|
||||
to: {
|
||||
type: 'custom',
|
||||
url: 'https://payloadcms.com/good'
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
31
packages/plugin-redirects/demo/src/server.ts
Normal file
31
packages/plugin-redirects/demo/src/server.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import express from 'express';
|
||||
import payload from 'payload';
|
||||
import { seed } from './seed';
|
||||
|
||||
require('dotenv').config();
|
||||
const app = express();
|
||||
|
||||
// Redirect root to Admin panel
|
||||
app.get('/', (_, res) => {
|
||||
res.redirect('/admin');
|
||||
});
|
||||
|
||||
// Initialize Payload
|
||||
const start = async () => {
|
||||
await payload.initAsync({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
mongoURL: process.env.MONGODB_URI,
|
||||
express: app,
|
||||
onInit: () => {
|
||||
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
|
||||
},
|
||||
})
|
||||
|
||||
if (process.env.PAYLOAD_SEED === 'true') {
|
||||
await seed(payload);
|
||||
}
|
||||
|
||||
app.listen(3000);
|
||||
}
|
||||
|
||||
start();
|
||||
19
packages/plugin-redirects/demo/tsconfig.json
Normal file
19
packages/plugin-redirects/demo/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "../",
|
||||
"jsx": "react",
|
||||
},
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
9543
packages/plugin-redirects/demo/yarn.lock
Normal file
9543
packages/plugin-redirects/demo/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
38
packages/plugin-redirects/package.json
Normal file
38
packages/plugin-redirects/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "0.0.1",
|
||||
"homepage:": "https://payloadcms.com",
|
||||
"repository": "git@github.com:payloadcms/plugin-redirects.git",
|
||||
"description": "Redirects plugin for Payload CMS",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"payload",
|
||||
"cms",
|
||||
"plugin",
|
||||
"typescript",
|
||||
"react",
|
||||
"redirects",
|
||||
"nextjs"
|
||||
],
|
||||
"author": "dev@payloadcms.com",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"payload": "^0.18.5 || ^1.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"payload": "^1.0.9",
|
||||
"react": "^18.0.0",
|
||||
"typescript": "^4.5.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"types.js",
|
||||
"types.d.ts"
|
||||
]
|
||||
}
|
||||
34
packages/plugin-redirects/src/deepMerge.ts
Normal file
34
packages/plugin-redirects/src/deepMerge.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// @ts-nocheck
|
||||
|
||||
/**
|
||||
* Simple object check.
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isObject(item: unknown): boolean {
|
||||
return (item && typeof item === 'object' && !Array.isArray(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep merge two objects.
|
||||
* @param target
|
||||
* @param ...sources
|
||||
*/
|
||||
export default function deepMerge<T, R>(target: T, source: R): T {
|
||||
const output = { ...target };
|
||||
if (isObject(target) && isObject(source)) {
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (isObject(source[key])) {
|
||||
if (!(key in target)) {
|
||||
Object.assign(output, { [key]: source[key] });
|
||||
} else {
|
||||
output[key] = deepMerge(target[key], source[key]);
|
||||
}
|
||||
} else {
|
||||
Object.assign(output, { [key]: source[key] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
79
packages/plugin-redirects/src/index.ts
Normal file
79
packages/plugin-redirects/src/index.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Config } from 'payload/config';
|
||||
import deepMerge from './deepMerge';
|
||||
import { PluginConfig } from './types';
|
||||
|
||||
const redirects = (pluginConfig: PluginConfig) => (incomingConfig: Config): Config => ({
|
||||
...incomingConfig,
|
||||
collections: [
|
||||
...incomingConfig?.collections || [],
|
||||
deepMerge({
|
||||
slug: 'redirects',
|
||||
admin: {
|
||||
defaultColumns: [
|
||||
'from',
|
||||
'to.type',
|
||||
'createdAt',
|
||||
],
|
||||
},
|
||||
access: {
|
||||
read: (): boolean => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'from',
|
||||
label: 'From URL',
|
||||
index: true,
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'to',
|
||||
label: false,
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
label: 'To URL Type',
|
||||
type: 'radio',
|
||||
options: [
|
||||
{
|
||||
label: 'Internal link',
|
||||
value: 'reference',
|
||||
},
|
||||
{
|
||||
label: 'Custom URL',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
defaultValue: 'reference',
|
||||
admin: {
|
||||
layout: 'horizontal',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'reference',
|
||||
label: 'Document to redirect to',
|
||||
type: 'relationship',
|
||||
relationTo: pluginConfig?.collections || [],
|
||||
required: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.type === 'reference',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
label: 'Custom URL',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.type === 'custom',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}, pluginConfig?.overrides || {}),
|
||||
],
|
||||
});
|
||||
|
||||
export default redirects;
|
||||
6
packages/plugin-redirects/src/types.ts
Normal file
6
packages/plugin-redirects/src/types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { CollectionConfig } from "payload/types"
|
||||
|
||||
export type PluginConfig = {
|
||||
overrides?: Partial<CollectionConfig>
|
||||
collections?: string[]
|
||||
}
|
||||
18
packages/plugin-redirects/tsconfig.json
Normal file
18
packages/plugin-redirects/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"outDir": "./dist",
|
||||
"allowJs": true,
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "./dist",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
}
|
||||
1
packages/plugin-redirects/types.d.ts
vendored
Normal file
1
packages/plugin-redirects/types.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from './dist/types';
|
||||
1
packages/plugin-redirects/types.js
Normal file
1
packages/plugin-redirects/types.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/types');
|
||||
9385
packages/plugin-redirects/yarn.lock
Normal file
9385
packages/plugin-redirects/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user