Compare commits

...

61 Commits

Author SHA1 Message Date
Elliot DeNolf
3ac9dae1d2 0.5.0-beta.1 2023-09-29 12:13:02 -04:00
Elliot DeNolf
9c165a73cb chore: proper postgres adapter import replacement 2023-09-29 12:12:49 -04:00
Elliot DeNolf
9b58915aff 0.5.0-beta.0 2023-09-29 12:02:05 -04:00
Elliot DeNolf
c1daeb3432 feat: bump template branch to 2.0 2023-09-29 12:02:01 -04:00
Elliot DeNolf
ff8acde322 chore: check DATABASE_URI key 2023-09-19 15:53:06 -04:00
Elliot DeNolf
1bade389e4 test: reorganize tests 2023-09-19 15:42:04 -04:00
Elliot DeNolf
489de652a3 chore(templates): update branch on starter urls temporarily 2023-09-19 15:38:31 -04:00
Elliot DeNolf
166b06e5d8 chore: replace DATABASE_URI env value 2023-09-19 15:11:27 -04:00
Elliot DeNolf
ed143c7a67 test: add debug for cli 2023-09-19 14:58:24 -04:00
Elliot DeNolf
b29e2ae685 test: dependency and config replacement tests 2023-09-19 14:58:10 -04:00
Elliot DeNolf
9bafc0fcbf feat: update templates with bundler and db adapter 2023-09-19 14:23:47 -04:00
Elliot DeNolf
b3db078a5f feat: implement db selection 2023-09-18 12:13:50 -04:00
Elliot DeNolf
17dbe066c1 0.4.2 2023-09-06 12:54:59 -04:00
Elliot DeNolf
5acc88ee9a fix: disregard yarn.lock file when using templates 2023-09-06 12:53:35 -04:00
Elliot DeNolf
d4983af3fc 0.4.1 2023-08-28 11:19:47 -04:00
Elliot DeNolf
6f2dcfd44e fix: all env vars copied from .env.example for starters 2023-08-28 11:18:52 -04:00
Elliot DeNolf
3df6435353 0.4.0 2023-08-17 16:04:52 -04:00
Elliot DeNolf
3ef03364be feat: remove static templates 2023-08-17 16:03:26 -04:00
Elliot DeNolf
5396af9bfc feat: use templates from payload/payloadcms/templates 2023-08-17 15:19:42 -04:00
Elliot DeNolf
56eddf3a93 0.3.34 2023-08-16 09:49:56 -04:00
Elliot DeNolf
6dd900e6b9 feat: add plugin template 2023-08-16 09:23:21 -04:00
Elliot DeNolf
ee93118688 0.3.33 2023-07-20 17:34:04 -04:00
Elliot DeNolf
47f9b89175 fix: help message rendering 2023-07-20 17:33:54 -04:00
Elliot DeNolf
04fd2d6e82 0.3.32 2023-07-06 10:40:28 -04:00
Elliot DeNolf
4cdc7cf3c4 revert: use standalone template repos until frontend handling done 2023-07-06 10:40:23 -04:00
Elliot DeNolf
fbaa1028e6 0.3.31 2023-07-06 10:31:04 -04:00
Elliot DeNolf
df26e19c16 feat: use templates inside payload repo instead of standalone repos 2023-07-06 10:30:58 -04:00
Elliot DeNolf
98501cf4c0 0.3.30 2023-07-05 11:25:00 -04:00
Elliot DeNolf
d0ac142871 fix: move cross-env from dev dep to dep 2023-07-05 11:23:38 -04:00
Elliot DeNolf
d60c66ebd6 feat: change user collection access to default on all templates 2023-07-05 11:23:22 -04:00
Elliot DeNolf
121d69faf9 0.3.29 2023-06-20 23:07:53 -04:00
Elliot DeNolf
0a2d7c858a chore: fix static dirs 2023-06-20 23:06:50 -04:00
Elliot DeNolf
0962e1e563 feat: support for git templates (#8)
* feat: add payload demo as template

* feat: rework templates, add descriptions, better help message

* wip: parse .env

* chore: remove js templates

* feat: populate mongo and secret from .env.example

* feat: add cloud templates

* test: fix test suite template dir
2023-06-20 22:47:04 -04:00
Elliot DeNolf
e43d6520c5 0.3.28 2023-05-09 13:39:19 -04:00
Elliot DeNolf
1123909960 fix: revert template serverURL back to localhost 2023-05-09 13:39:12 -04:00
Elliot DeNolf
bc1853c2e7 0.3.27 2023-05-09 10:59:13 -04:00
Elliot DeNolf
318b734f96 feat: use 127.0.0.1 for node compatibility 2023-05-09 10:58:20 -04:00
Elliot DeNolf
2734b1d54a 0.3.26 2023-04-19 09:48:09 -04:00
Elliot DeNolf
82510c1574 feat: remove .npmrc from templates 2023-04-19 09:48:04 -04:00
Elliot DeNolf
39686e3f05 0.3.25 2023-02-09 14:55:40 -05:00
Elliot DeNolf
482973559d feat: add media collection to blog template 2023-02-09 14:55:16 -05:00
Elliot DeNolf
600dbd72f4 0.3.24 2023-02-03 14:47:58 -05:00
Elliot DeNolf
785337dc5d chore: add rimraf to prepublish 2023-02-03 14:47:17 -05:00
Elliot DeNolf
14a35f35c3 0.3.23 2023-02-01 15:53:54 -05:00
Elliot DeNolf
ed2e176285 feat: add cross-env to graphql schema gen command 2023-02-01 14:51:11 -05:00
Elliot DeNolf
2d79280999 0.3.22 2023-01-31 13:15:13 -05:00
Elliot DeNolf
8b5084ab43 0.3.21 2023-01-31 13:14:45 -05:00
Elliot DeNolf
b19356597b chore: revert onInit passing payload
Signed-off-by: Elliot DeNolf <denolfe@gmail.com>
2023-01-31 13:14:09 -05:00
Elliot DeNolf
0bd412edbd feat: server init async and onInit use scoped payload 2023-01-19 15:23:19 -05:00
Elliot DeNolf
1a6ba25e5d 0.3.21-beta.0 2023-01-18 16:01:41 -05:00
Elliot DeNolf
26d0cd18a1 feat: add generate-types path and ts-node swc to tsconfig 2023-01-18 15:58:41 -05:00
Elliot DeNolf
31653fe76e 0.3.21 2023-01-16 11:34:53 -05:00
Elliot DeNolf
a6d52223d5 chore: update readme with template names 2023-01-16 11:23:33 -05:00
Elliot DeNolf
1bf7c4084c feat: remove javascript templates 2023-01-16 11:21:51 -05:00
Elliot DeNolf
419a3eef53 0.3.20 2023-01-02 15:50:08 -05:00
Elliot DeNolf
f62531bafd feat: pass through npm run command to package.json template 2023-01-02 15:50:02 -05:00
Elliot DeNolf
dc815dad14 0.3.19 2022-12-26 21:08:06 -05:00
Elliot DeNolf
bde2ce9b53 chore: stricter typing on prompts validation 2022-12-26 21:07:25 -05:00
Elliot DeNolf
041af0100c feat: use project name as package.json name 2022-12-26 21:00:05 -05:00
Elliot DeNolf
2e18c5b8cf feat: add Dockerfile 2022-12-21 00:41:22 -05:00
Elliot DeNolf
53f0c526f7 0.3.18 2022-11-20 12:38:48 -05:00
56 changed files with 654 additions and 1309 deletions

13
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"configurations": [
{
"command": "ts-node -T ./src/index.ts -n asdf -t blank --db mongodb --no-deps",
"cwd": "${workspaceFolder}",
"name": "Debug",
"request": "launch",
"type": "node-terminal"
},
]
}

View File

@@ -9,15 +9,26 @@ CLI for easily starting new Payload project
USAGE
$ npx create-payload-app
$ npx create-payload-app my-project
$ npx create-payload-app -n my-project -t blog
OPTIONS
--name my-payload-app Set project name
--template template_name Choose specific template
-n my-payload-app Set project name
-t template_name Choose specific template
Available templates: js-blank, js-blog, js-todo, ts-blank, ts-blog, ts-todo
Available templates:
--use-npm Use npm to install dependencies
--no-deps Do not install any dependencies
--help Show help
blank Blank Template
website Website Template
ecommerce E-commerce Template
plugin Template for creating a Payload plugin
payload-demo Payload demo site at https://demo.payloadcms.com
payload-website Payload website CMS at https://payloadcms.com
--use-npm Use npm to install dependencies
--use-yarn Use yarn to install dependencies
--use-pnpm Use pnpm to install dependencies
--no-deps Do not install any dependencies
-h Show help
```

View File

@@ -7,12 +7,13 @@
"scripts": {
"build": "tsc && yarn copyfiles",
"copyfiles": "copyfiles -u 1 \"src/templates/**\" \"src/lib/common-files/**\" dist",
"clean": "rimraf dist",
"typecheck": "tsc --noEmit",
"lint": "eslint \"src/**/*.ts\"",
"lint:fix": "eslint \"src/**/*.ts\" --fix",
"lint-staged": "lint-staged --quiet",
"test": "jest",
"prepublishOnly": "yarn test && yarn build",
"prepublishOnly": "yarn test && yarn clean && yarn build",
"prepare": "husky install",
"release:beta": "yarn publish --tag beta",
"release": "yarn publish"
@@ -33,17 +34,17 @@
"fs-extra": "^9.0.1",
"handlebars": "^4.7.7",
"ora": "^5.1.0",
"prompts": "^2.4.0",
"prompts": "^2.4.2",
"terminal-link": "^2.1.1"
},
"version": "0.3.18-beta.1",
"version": "0.5.0-beta.1",
"devDependencies": {
"@types/command-exists": "^1.2.0",
"@types/degit": "^2.8.3",
"@types/fs-extra": "^9.0.12",
"@types/jest": "^27.0.3",
"@types/node": "^16.6.2",
"@types/prompts": "^2.4.0",
"@types/prompts": "^2.4.1",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"copyfiles": "^2.4.1",
@@ -55,6 +56,7 @@
"jest": "^27.4.5",
"lint-staged": "^13.0.3",
"prettier": "^2.3.2",
"rimraf": "^4.1.2",
"ts-jest": "^27.1.1",
"typescript": "^4.8.4"
},

View File

@@ -1,9 +0,0 @@
# {{projectName}}
This project was created using create-payload-app using the {{templateName}} template.
## How to Use
`yarn dev` will start up your application and reload on any changes.
If you have docker and docker-compose installed, you can run `docker-compose up`

View File

@@ -1,35 +0,0 @@
version: '3'
services:
payload:
image: node:18-alpine
ports:
- "3000:3000"
volumes:
- .:/home/node/app
- node_modules:/home/node/app/node_modules
working_dir: /home/node/app/
command: sh -c "{{installCmd}} && {{devCmd}}"
depends_on:
- mongo
environment:
MONGODB_URI: mongodb://mongo:27017/payload
PORT: 3000
NODE_ENV: development
PAYLOAD_SECRET: TESTING
mongo:
image: mongo:latest
ports:
- "27017:27017"
command:
- --storageEngine=wiredTiger
volumes:
- data:/data/db
logging:
driver: none
volumes:
data:
node_modules:

View File

@@ -1,166 +0,0 @@
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# Support for Project snippet scope
.vscode/*.code-snippets
# Ignore code-workspaces
*.code-workspace
# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode

View File

@@ -1,3 +0,0 @@
{
"exec": "node server.js"
}

View File

@@ -1,20 +0,0 @@
{
"name": "payload-template-{{templateName}}",
"description": "Payload project created from {{templateName}} template",
"version": "1.0.0",
"main": "server.js",
"license": "MIT",
"scripts": {
"dev": "nodemon",
"build": "payload build",
"serve": "NODE_ENV=production node server.js"
},
"dependencies": {
"payload": "1.1.17",
"dotenv": "^8.2.0",
"express": "^4.17.1"
},
"devDependencies": {
"nodemon": "^2.0.6"
}
}

View File

@@ -1 +0,0 @@
legacy-peer-deps=true

View File

@@ -1,4 +0,0 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
}

View File

@@ -1,30 +0,0 @@
{
"name": "payload-template-{{templateName}}",
"description": "Payload project created from {{templateName}} template",
"version": "1.0.0",
"main": "dist/server.js",
"license": "MIT",
"scripts": {
"dev": "cross-env 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 copyfiles && yarn build:payload && yarn build:server",
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema"
},
"dependencies": {
"payload": "1.1.17",
"dotenv": "^8.2.0",
"express": "^4.17.1"
},
"devDependencies": {
"@types/express": "^4.17.9",
"cross-env": "^7.0.3",
"nodemon": "^2.0.6",
"ts-node": "^9.1.1",
"copyfiles": "^2.4.1",
"typescript": "^4.8.4"
}
}

View File

@@ -1,28 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react"
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist",
"build",
],
"ts-node": {
"transpileOnly": true
}
}

View File

@@ -0,0 +1,96 @@
import fse from 'fs-extra'
import path from 'path'
import type { DbDetails } from '../types'
import { warning } from '../utils/log'
import { bundlerPackages, dbPackages } from './packages'
/** Update payload config with necessary imports and adapters */
export async function configurePayloadConfig(args: {
projectDir: string
dbDetails: DbDetails | undefined
}): Promise<void> {
if (!args.dbDetails) {
return
}
// Update package.json
const packageJsonPath = path.resolve(args.projectDir, 'package.json')
try {
const packageObj = await fse.readJson(packageJsonPath)
const dbPackage = dbPackages[args.dbDetails.type]
const bundlerPackage = bundlerPackages['webpack']
packageObj.dependencies[dbPackage.packageName] = 'latest'
packageObj.dependencies[bundlerPackage.packageName] = 'latest'
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
} catch (err: unknown) {
warning('Unable to update name in package.json')
}
try {
const possiblePaths = [
path.resolve(args.projectDir, 'src/payload.config.ts'),
path.resolve(args.projectDir, 'src/payload/payload.config.ts'),
]
let payloadConfigPath: string | undefined
possiblePaths.forEach(p => {
if (fse.pathExistsSync(p) && !payloadConfigPath) {
payloadConfigPath = p
}
})
if (!payloadConfigPath) {
warning('Unable to update payload.config.ts with plugins')
return
}
const configContent = fse.readFileSync(payloadConfigPath, 'utf-8')
const configLines = configContent.split('\n')
const dbReplacement = dbPackages[args.dbDetails.type]
const bundlerReplacement = bundlerPackages['webpack']
let dbConfigStartLineIndex: number | undefined
let dbConfigEndLineIndex: number | undefined
configLines.forEach((l, i) => {
if (l.includes('// database-adapter-import')) {
configLines[i] = dbReplacement.importReplacement
}
if (l.includes('// bundler-import')) {
configLines[i] = bundlerReplacement.importReplacement
}
if (l.includes('// bundler-config')) {
configLines[i] = bundlerReplacement.configReplacement
}
if (l.includes('// database-adapter-config-start')) {
dbConfigStartLineIndex = i
}
if (l.includes('// database-adapter-config-end')) {
dbConfigEndLineIndex = i
}
})
if (!dbConfigStartLineIndex || !dbConfigEndLineIndex) {
warning('Unable to update payload.config.ts with database adapter import')
} else {
// Replaces lines between `// database-adapter-config-start` and `// database-adapter-config-end`
configLines.splice(
dbConfigStartLineIndex,
dbConfigEndLineIndex - dbConfigStartLineIndex + 1,
...dbReplacement.configReplacement,
)
}
fse.writeFileSync(payloadConfigPath, configLines.join('\n'))
} catch (err: unknown) {
warning('Unable to update payload.config.ts with plugins')
}
}

View File

@@ -1,11 +1,8 @@
import fse from 'fs-extra'
import path from 'path'
import type { CliArgs, ProjectTemplate } from '../types'
import {
createProject,
getLatestPayloadVersion,
updatePayloadVersion,
} from './create-project'
import type { BundlerType, CliArgs, DbType, ProjectTemplate } from '../types'
import { createProject } from './create-project'
import { bundlerPackages, dbPackages } from './packages'
const projectDir = path.resolve(__dirname, './tmp')
describe('createProject', () => {
@@ -26,53 +23,112 @@ describe('createProject', () => {
describe('#createProject', () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const args = { _: ['project-name'], '--no-deps': true } as CliArgs
const args = {
_: ['project-name'],
'--db': 'mongodb',
'--no-deps': true,
} as CliArgs
const packageManager = 'yarn'
it('creates static project', async () => {
const expectedPayloadVersion = await getLatestPayloadVersion()
it('creates starter project', async () => {
const projectName = 'starter-project'
const template: ProjectTemplate = {
name: 'ts-todo',
type: 'static',
language: 'typescript',
name: 'blank',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/blank',
description: 'Blank Template',
}
await createProject(args, projectDir, template, packageManager)
await createProject({
cliArgs: args,
projectName,
projectDir,
template,
packageManager,
})
const packageJsonPath = path.resolve(projectDir, 'package.json')
const packageJson = fse.readJsonSync(packageJsonPath)
expect(packageJson.dependencies.payload).toBe(expectedPayloadVersion)
// Check package name comtains template name
expect(packageJson.name).toContain('ts-todo')
// Check all common files are create
assertProjectFileExists('.npmrc')
assertProjectFileExists('.gitignore')
assertProjectFileExists('nodemon.json')
assertProjectFileExists('README.md')
assertProjectFileExists('tsconfig.json')
assertProjectFileExists('docker-compose.yml')
// Check package name and description
expect(packageJson.name).toEqual(projectName)
})
})
describe('#updatePayloadVersion', () => {
it('updates payload version in package.json', async () => {
it('creates plugin template', async () => {
const projectName = 'plugin'
const template: ProjectTemplate = {
name: 'plugin',
type: 'plugin',
url: 'https://github.com/payloadcms/payload-plugin-template',
description: 'Template for creating a Payload plugin',
}
await createProject({
cliArgs: args,
projectName,
projectDir,
template,
packageManager,
})
const packageJsonPath = path.resolve(projectDir, 'package.json')
await fse.mkdir(projectDir)
await fse.writeJson(
packageJsonPath,
{ dependencies: { payload: '0.0.1' } },
{ spaces: 2 },
)
await updatePayloadVersion(projectDir)
const modified = await fse.readJson(packageJsonPath)
expect(modified.dependencies.payload).not.toBe('0.0.1')
const packageJson = fse.readJsonSync(packageJsonPath)
// Check package name and description
expect(packageJson.name).toEqual(projectName)
})
describe('db adapters and bundlers', () => {
it.each([
['mongodb', 'webpack'],
['postgres', 'webpack'],
])('update config and deps: %s, %s', async (db, bundler) => {
const projectName = 'starter-project'
const template: ProjectTemplate = {
name: 'blank',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/blank#2.0',
description: 'Blank Template',
}
await createProject({
cliArgs: args,
projectName,
projectDir,
template,
packageManager,
dbDetails: {
dbUri: `${db}://localhost:27017/create-project-test`,
type: db as DbType,
},
})
const dbReplacement = dbPackages[db as DbType]
const bundlerReplacement = bundlerPackages[bundler as BundlerType]
const packageJsonPath = path.resolve(projectDir, 'package.json')
const packageJson = fse.readJsonSync(packageJsonPath)
// Check deps
expect(packageJson.dependencies[dbReplacement.packageName]).toBeDefined()
expect(
packageJson.dependencies[bundlerReplacement.packageName],
).toBeDefined()
const payloadConfigPath = path.resolve(projectDir, 'src/payload.config.ts')
const content = fse.readFileSync(payloadConfigPath, 'utf-8')
// Check payload.config.ts
expect(content).not.toContain('// database-adapter-import')
expect(content).toContain(dbReplacement.importReplacement)
expect(content).not.toContain('// database-adapter-config-start')
expect(content).not.toContain('// database-adapter-config-end')
expect(content).toContain(dbReplacement.configReplacement.join('\n'))
expect(content).not.toContain('// bundler-config-import')
expect(content).toContain(bundlerReplacement.importReplacement)
expect(content).not.toContain('// bundler-config')
expect(content).toContain(bundlerReplacement.configReplacement)
})
})
})
})
async function assertProjectFileExists(fileName: string) {
const filePath = path.resolve(projectDir, fileName)
expect(await fse.pathExists(filePath)).toBe(true)
}

View File

@@ -6,8 +6,8 @@ import ora from 'ora'
import degit from 'degit'
import { success, error, warning } from '../utils/log'
import type { CliArgs, ProjectTemplate } from '../types'
import { writeCommonFiles } from './write-common-files'
import type { CliArgs, DbDetails, PackageManager, ProjectTemplate } from '../types'
import { configurePayloadConfig } from './configure-payload-config'
async function createOrFindProjectDir(projectDir: string): Promise<void> {
const pathExists = await fse.pathExists(projectDir)
@@ -16,107 +16,67 @@ async function createOrFindProjectDir(projectDir: string): Promise<void> {
}
}
async function installDeps(
args: CliArgs,
dir: string,
packageManager: string,
): Promise<boolean> {
if (args['--no-deps']) {
async function installDeps(args: {
cliArgs: CliArgs
projectDir: string
packageManager: PackageManager
}): Promise<boolean> {
const { cliArgs, projectDir, packageManager } = args
if (cliArgs['--no-deps']) {
return true
}
const cmd = packageManager === 'yarn' ? 'yarn' : 'npm install --legacy-peer-deps'
let installCmd = 'npm install --legacy-peer-deps'
if (packageManager === 'yarn') {
installCmd = 'yarn'
} else if (packageManager === 'pnpm') {
installCmd = 'pnpm install'
}
try {
await execa.command(cmd, {
cwd: path.resolve(dir),
await execa.command(installCmd, {
cwd: path.resolve(projectDir),
})
return true
} catch (err: unknown) {
console.log({ err })
return false
}
}
export async function getLatestPayloadVersion(
betaFlag = false,
): Promise<false | string> {
try {
let packageWithTag = 'payload'
if (betaFlag) packageWithTag += '@beta'
const { stdout } = await execa(`npm info ${packageWithTag} version`, [], {
shell: true,
})
return `^${stdout}`
} catch (err: unknown) {
if (err instanceof Error) {
console.error(err.message)
console.error(err.stack)
}
return false
}
}
export async function createProject(args: {
cliArgs: CliArgs
projectName: string
projectDir: string
template: ProjectTemplate
packageManager: PackageManager
dbDetails?: DbDetails
}): Promise<void> {
const { cliArgs, projectName, projectDir, template, packageManager, dbDetails } =
args
export async function updatePayloadVersion(
projectDir: string,
betaFlag = false,
): Promise<void> {
const payloadVersion = await getLatestPayloadVersion(betaFlag)
if (!payloadVersion) {
warning(
'Error retrieving latest Payload version. Please update your package.json manually.',
)
return
}
const packageJsonPath = path.resolve(projectDir, 'package.json')
try {
const packageObj = await fse.readJson(packageJsonPath)
packageObj.dependencies.payload = payloadVersion
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
} catch (err: unknown) {
warning(
'Unable to write Payload version to package.json. Please update your package.json manually.',
)
}
}
export async function createProject(
args: CliArgs,
projectDir: string,
template: ProjectTemplate,
packageManager: string,
): Promise<void> {
await createOrFindProjectDir(projectDir)
const templateDir = path.resolve(__dirname, `../templates/${template.name}`)
console.log(
`\n Creating a new Payload app in ${chalk.green(path.resolve(projectDir))}\n`,
)
console.log(`\n Creating project in ${chalk.green(path.resolve(projectDir))}\n`)
if (template.type === 'starter') {
if ('url' in template) {
const emitter = degit(template.url)
await emitter.clone(projectDir)
} else {
try {
await fse.copy(templateDir, projectDir, { recursive: true })
await writeCommonFiles(projectDir, template, packageManager)
success('Project directory created')
} catch (err: unknown) {
const msg =
'Unable to copy template files. Please check template name or directory permissions.'
error(msg)
if (err instanceof Error) {
console.error({ err })
}
process.exit(1)
}
}
const spinner = ora('Checking latest Payload version...').start()
await updatePayloadVersion(projectDir, args['--beta'])
await updatePackageJSON({ projectName, projectDir })
await configurePayloadConfig({ projectDir, dbDetails })
// Remove yarn.lock file. This is only desired in Payload Cloud.
const lockPath = path.resolve(projectDir, 'yarn.lock')
if (fse.existsSync(lockPath)) {
await fse.remove(lockPath)
}
spinner.text = 'Installing dependencies...'
const result = await installDeps(args, projectDir, packageManager)
const result = await installDeps({ cliArgs, projectDir, packageManager })
spinner.stop()
spinner.clear()
if (result) {
@@ -125,3 +85,18 @@ export async function createProject(
error('Error installing dependencies')
}
}
export async function updatePackageJSON(args: {
projectName: string
projectDir: string
}): Promise<void> {
const { projectName, projectDir } = args
const packageJsonPath = path.resolve(projectDir, 'package.json')
try {
const packageObj = await fse.readJson(packageJsonPath)
packageObj.name = projectName
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
} catch (err: unknown) {
warning('Unable to update name in package.json')
}
}

View File

@@ -1,35 +0,0 @@
import prompts from 'prompts'
import slugify from '@sindresorhus/slugify'
import type { CliArgs } from '../types'
export async function getDatabaseConnection(
args: CliArgs,
projectName: string,
): Promise<string> {
if (args['--db']) return args['--db']
const response = await prompts(
{
type: 'text',
name: 'value',
message: 'Enter MongoDB connection',
initial: `mongodb://localhost/${
projectName === '.'
? `payload-${getRandomDigitSuffix()}`
: slugify(projectName)
}`,
validate: (value: string) => value.length,
},
{
onCancel: () => {
process.exit(0)
},
},
)
return response.value
}
function getRandomDigitSuffix(): string {
return (Math.random() * Math.pow(10, 6)).toFixed(0)
}

59
src/lib/packages.ts Normal file
View File

@@ -0,0 +1,59 @@
import type { BundlerType, DbType } from '../types'
type DbAdapterReplacement = {
packageName: string
importReplacement: string
configReplacement: string[]
}
type BundlerReplacement = {
packageName: string
importReplacement: string
configReplacement: string
}
const mongodbReplacement: DbAdapterReplacement = {
packageName: '@payloadcms/db-mongodb',
importReplacement: "import { mongooseAdapter } from '@payloadcms/db-mongodb'",
// Replacement between `// database-adapter-config-start` and `// database-adapter-config-end`
configReplacement: [
' db: mongooseAdapter({',
' url: process.env.DATABASE_URI,',
' }),',
],
}
const postgresReplacement: DbAdapterReplacement = {
packageName: '@payloadcms/db-postgres',
importReplacement: "import { postgresAdapter } from '@payloadcms/db-postgres'",
configReplacement: [
' db: postgresAdapter({',
' client: {',
' connectionString: process.env.DATABASE_URI,',
' },',
' }),',
],
}
export const dbPackages: Record<DbType, DbAdapterReplacement> = {
mongodb: mongodbReplacement,
postgres: postgresReplacement,
}
const webpackReplacement: BundlerReplacement = {
packageName: '@payloadcms/bundler-webpack',
importReplacement: "import { webpackBundler } from '@payloadcms/bundler-webpack'",
// Replacement of line containing `// bundler-config`
configReplacement: ' bundler: webpackBundler(),',
}
const viteReplacement: BundlerReplacement = {
packageName: '@payloadcms/bundler-vite',
importReplacement: "import { viteBundler } from '@payloadcms/bundler-vite'",
configReplacement: ' bundler: viteBundler(),',
}
export const bundlerPackages: Record<BundlerType, BundlerReplacement> = {
webpack: webpackReplacement,
vite: viteReplacement,
}

View File

@@ -1,32 +0,0 @@
import prompts from 'prompts'
import type { CliArgs } from '../types'
export async function parseLanguage(args: CliArgs): Promise<string> {
if (args['--template']) return args['--template']
const response = await prompts(
{
type: 'select',
name: 'value',
message: 'Choose language',
choices: [
{
title: 'javascript',
value: 'js',
},
{
title: 'typescript',
value: 'ts',
},
],
validate: (value: string) => value.length,
},
{
onCancel: () => {
process.exit(0)
},
},
)
return response.value
}

View File

@@ -10,7 +10,7 @@ export async function parseProjectName(args: CliArgs): Promise<string> {
type: 'text',
name: 'value',
message: 'Project name?',
validate: (value: string) => value.length,
validate: (value: string) => !!value.length,
},
{
onCancel: () => {

View File

@@ -4,7 +4,6 @@ import type { CliArgs, ProjectTemplate } from '../types'
export async function parseTemplate(
args: CliArgs,
validTemplates: ProjectTemplate[],
language: string,
): Promise<ProjectTemplate> {
if (args['--template']) {
const templateName = args['--template']
@@ -13,19 +12,19 @@ export async function parseTemplate(
return template
}
const filteredTemplates = validTemplates
.filter(d => d.name.startsWith(language))
.map(t => t.name.replace(`${language}-`, ''))
const response = await prompts(
{
type: 'select',
name: 'value',
message: 'Choose project template',
choices: filteredTemplates.map(p => {
return { title: p, value: `${language}-${p}` }
choices: validTemplates.map(p => {
return {
title: p.name,
value: p.name,
description: p.description,
}
}),
validate: (value: string) => value.length,
validate: (value: string) => !!value.length,
},
{
onCancel: () => {
@@ -34,7 +33,6 @@ export async function parseTemplate(
},
)
// const template = `${language}-${response.value}`
const template = validTemplates.find(t => t.name === response.value)
if (!template) throw new Error('Template is undefined')

96
src/lib/select-db.ts Normal file
View File

@@ -0,0 +1,96 @@
import prompts from 'prompts'
import slugify from '@sindresorhus/slugify'
import type { CliArgs, DbDetails, DbType } from '../types'
type DbChoice = {
value: DbType
title: string
dbConnectionPrefix: `${string}/`
}
const dbChoiceRecord: Record<DbType, DbChoice> = {
mongodb: {
value: 'mongodb',
title: 'MongoDB',
dbConnectionPrefix: 'mongodb://127.0.0.1/',
},
postgres: {
value: 'postgres',
title: 'PostgreSQL',
dbConnectionPrefix: 'postgres://127.0.0.1:5432/',
},
}
export async function selectDb(
args: CliArgs,
projectName: string,
): Promise<DbDetails> {
let dbType: DbType | undefined = undefined
if (args['--db']) {
if (
!Object.values(dbChoiceRecord).some(
dbChoice => dbChoice.value === args['--db'],
)
) {
throw new Error(
`Invalid database type given. Valid types are: ${Object.values(
dbChoiceRecord,
)
.map(dbChoice => dbChoice.value)
.join(', ')}`,
)
}
dbType = args['--db'] as DbType
} else {
const dbTypeRes = await prompts(
{
type: 'select',
name: 'value',
message: 'Select a database',
choices: Object.values(dbChoiceRecord).map(dbChoice => {
return {
title: dbChoice.title,
value: dbChoice.value,
}
}),
validate: (value: string) => !!value.length,
},
{
onCancel: () => {
process.exit(0)
},
},
)
dbType = dbTypeRes.value
}
const dbChoice = dbChoiceRecord[dbType as DbType]
const dbUriRes = await prompts(
{
type: 'text',
name: 'value',
message: `Enter ${dbChoice.title} connection string`,
initial: `${dbChoice.dbConnectionPrefix}${
projectName === '.'
? `payload-${getRandomDigitSuffix()}`
: slugify(projectName)
}`,
validate: (value: string) => !!value.length,
},
{
onCancel: () => {
process.exit(0)
},
},
)
return {
type: dbChoice.value,
dbUri: dbUriRes.value,
}
}
function getRandomDigitSuffix(): string {
return (Math.random() * Math.pow(10, 6)).toFixed(0)
}

View File

@@ -1,34 +1,53 @@
import path from 'path'
import fs from 'fs'
import { error, info } from '../utils/log'
import type { ProjectTemplate } from '../types'
import { error, info } from '../utils/log'
export async function validateTemplate(templateName: string): Promise<boolean> {
const validTemplates = await getValidTemplates()
if (!validTemplates.map(t => t.name).includes(templateName)) {
error(`'${templateName}' is not a valid template.`)
info(`Valid templates: ${validTemplates.join(', ')}`)
info(`Valid templates: ${validTemplates.map(t => t.name).join(', ')}`)
return false
}
return true
}
export async function getValidTemplates(): Promise<ProjectTemplate[]> {
const templateDir = path.resolve(__dirname, '../templates')
const dirs = getDirectories(templateDir)
const templates: ProjectTemplate[] = dirs.map(name => {
return {
name,
type: 'static',
language: name.startsWith('js-') ? 'javascript' : 'typescript',
}
})
return templates
}
function getDirectories(dir: string): string[] {
return fs.readdirSync(dir).filter(file => {
return fs.statSync(`${dir}/${file}`).isDirectory()
})
return [
{
name: 'blank',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/blank#2.0',
description: 'Blank Template',
},
{
name: 'website',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/website#2.0',
description: 'Website Template',
},
{
name: 'ecommerce',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/ecommerce#2.0',
description: 'E-commerce Template',
},
{
name: 'plugin',
type: 'plugin',
url: 'https://github.com/payloadcms/payload-plugin-template',
description: 'Template for creating a Payload plugin',
},
{
name: 'payload-demo',
type: 'starter',
url: 'https://github.com/payloadcms/public-demo',
description: 'Payload demo site at https://demo.payloadcms.com',
},
{
name: 'payload-website',
type: 'starter',
url: 'https://github.com/payloadcms/website-cms',
description: 'Payload website CMS at https://payloadcms.com',
},
]
}

View File

@@ -1,67 +0,0 @@
import path from 'path'
import fse from 'fs-extra'
import handlebars from 'handlebars'
import type { StaticTemplate } from '../types'
export async function writeCommonFiles(
projectDir: string,
template: StaticTemplate,
packageManager: string,
): Promise<void> {
const commonFilesDir = path.resolve(__dirname, 'common-files')
// .npmrc
const npmrc = path.resolve(commonFilesDir, 'npmrc.template')
const npmrcDest = path.resolve(projectDir, '.npmrc')
await fse.copy(npmrc, npmrcDest)
// .gitignore
const gi = path.resolve(commonFilesDir, 'gitignore.template')
const giDest = path.resolve(projectDir, '.gitignore')
await fse.copy(gi, giDest)
// package.json
const packageJsonTemplate = await fse.readFile(
path.resolve(commonFilesDir, template.language, 'package.template.json'),
'utf8',
)
const packageJson = handlebars.compile(packageJsonTemplate)({
templateName: template.name,
})
await fse.writeFile(path.resolve(projectDir, 'package.json'), packageJson)
// nodemon.json
const nodemon = path.resolve(commonFilesDir, template.language, 'nodemon.json')
const nodemonDest = path.resolve(projectDir, 'nodemon.json')
await fse.copy(nodemon, nodemonDest)
// README.md
const readmeTemplate = await fse.readFile(
path.resolve(commonFilesDir, 'README.template.md'),
'utf8',
)
const readme = handlebars.compile(readmeTemplate)({
projectName: path.basename(projectDir),
templateName: template.name,
})
await fse.writeFile(path.resolve(projectDir, 'README.md'), readme)
// tsconfig.json
if (template.language === 'typescript') {
const tsconfig = path.resolve(commonFilesDir, template.language, 'tsconfig.json')
const tsconfigDest = path.resolve(projectDir, 'tsconfig.json')
await fse.copy(tsconfig, tsconfigDest)
}
// docker-compose.yml
const dockerComposeTemplate = await fse.readFile(
path.resolve(commonFilesDir, 'docker-compose.template.yml'),
'utf8',
)
const dockerCompose = handlebars.compile(dockerComposeTemplate)(
packageManager === 'yarn'
? { installCmd: 'yarn install', devCmd: 'yarn dev' }
: { installCmd: 'npm install', devCmd: 'npm run dev' },
)
await fse.writeFile(path.resolve(projectDir, 'docker-compose.yml'), dockerCompose)
}

View File

@@ -1,17 +1,57 @@
import slugify from '@sindresorhus/slugify'
import path from 'path'
import fs from 'fs-extra'
import type { ProjectTemplate } from '../types'
import { error, success } from '../utils/log'
export async function writeEnvFile(
projectName: string,
databaseUri: string,
payloadSecret: string,
): Promise<void> {
const content = `MONGODB_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
/** Parse and swap .env.example values and write .env */
export async function writeEnvFile(args: {
databaseUri: string
payloadSecret: string
template: ProjectTemplate
projectDir: string
}): Promise<void> {
const { databaseUri, payloadSecret, template, projectDir } = args
try {
const projectDir = `./${slugify(projectName)}`
await fs.outputFile(`${projectDir}/.env`, content)
if (
template.type === 'starter' &&
fs.existsSync(path.join(projectDir, '.env.example'))
) {
// Parse .env file into key/value pairs
const envFile = await fs.readFile(
path.join(projectDir, '.env.example'),
'utf8',
)
const envWithValues: string[] = envFile
.split('\n')
.filter(e => e)
.map(line => {
if (line.startsWith('#') || !line.includes('=')) return line
const split = line.split('=')
const key = split[0]
let value = split[1]
if (
key === 'MONGODB_URI' ||
key === 'MONGO_URL' ||
key === 'DATABASE_URI'
) {
value = databaseUri
}
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
value = payloadSecret
}
return `${key}=${value}`
})
// Write new .env file
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
} else {
const content = `MONGODB_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
await fs.outputFile(`${projectDir}/.env`, content)
}
success('.env file created')
} catch (err: unknown) {
error('Unable to write .env file')

View File

@@ -2,14 +2,13 @@ import slugify from '@sindresorhus/slugify'
import arg from 'arg'
import commandExists from 'command-exists'
import { createProject } from './lib/create-project'
import { getDatabaseConnection } from './lib/get-db-connection'
import { selectDb } from './lib/select-db'
import { generateSecret } from './lib/generate-secret'
import { parseLanguage } from './lib/parse-language'
import { parseProjectName } from './lib/parse-project-name'
import { parseTemplate } from './lib/parse-template'
import { getValidTemplates, validateTemplate } from './lib/templates'
import { writeEnvFile } from './lib/write-env-file'
import type { CliArgs } from './types'
import type { CliArgs, PackageManager } from './types'
import { success } from './utils/log'
import { helpMessage, successMessage, welcomeMessage } from './utils/messages'
@@ -26,6 +25,8 @@ export class Main {
'--db': String,
'--secret': String,
'--use-npm': Boolean,
'--use-yarn': Boolean,
'--use-pnpm': Boolean,
'--no-deps': Boolean,
'--dry-run': Boolean,
'--beta': Boolean,
@@ -55,18 +56,42 @@ export class Main {
console.log(welcomeMessage)
const projectName = await parseProjectName(this.args)
const language = await parseLanguage(this.args)
const validTemplates = await getValidTemplates()
const template = await parseTemplate(this.args, validTemplates, language)
const databaseUri = await getDatabaseConnection(this.args, projectName)
const payloadSecret = await generateSecret()
const template = await parseTemplate(this.args, validTemplates)
const projectDir =
projectName === '.' ? process.cwd() : `./${slugify(projectName)}`
const packageManager = await getPackageManager(this.args)
if (!this.args['--dry-run']) {
await createProject(this.args, projectDir, template, packageManager)
await writeEnvFile(projectName, databaseUri, payloadSecret)
if (template.type !== 'plugin') {
const dbDetails = await selectDb(this.args, projectName)
const payloadSecret = await generateSecret()
if (!this.args['--dry-run']) {
await createProject({
cliArgs: this.args,
projectName,
projectDir,
template,
packageManager,
dbDetails,
})
await writeEnvFile({
databaseUri: dbDetails.dbUri,
payloadSecret,
template,
projectDir,
})
}
} else {
if (!this.args['--dry-run']) {
await createProject({
cliArgs: this.args,
projectName,
projectDir,
template,
packageManager,
})
}
}
success('Payload project successfully created')
@@ -77,14 +102,22 @@ export class Main {
}
}
async function getPackageManager(args: CliArgs): Promise<string> {
let packageManager: string
async function getPackageManager(args: CliArgs): Promise<PackageManager> {
let packageManager: PackageManager = 'npm'
if (args['--use-npm']) {
packageManager = 'npm'
} else if (args['--use-yarn']) {
packageManager = 'yarn'
} else if (args['--use-pnpm']) {
packageManager = 'pnpm'
} else {
try {
await commandExists('yarn')
packageManager = 'yarn'
if (await commandExists('yarn')) {
packageManager = 'yarn'
} else if (await commandExists('pnpm')) {
packageManager = 'pnpm'
}
} catch (error: unknown) {
packageManager = 'npm'
}

View File

@@ -1,15 +0,0 @@
// Example Collection - For reference only, this must be added to payload.config.js to be used.
const Examples = {
slug: 'examples',
admin: {
useAsTitle: 'someField',
},
fields: [
{
name: 'someField',
type: 'text',
},
],
}
export default Examples;

View File

@@ -1,16 +0,0 @@
const Users = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
access: {
read: () => true,
},
fields: [
// Email added by default
// Add more fields as needed
],
};
export default Users;

View File

@@ -1,15 +0,0 @@
import { buildConfig } from 'payload/config';
import Examples from './collections/Examples';
import Users from './collections/Users';
export default buildConfig({
serverURL: 'http://localhost:3000',
admin: {
user: Users.slug,
},
collections: [
Users,
// Add Collections here
// Examples
],
});

View File

@@ -1,24 +0,0 @@
const express = require('express');
const payload = require('payload');
require('dotenv').config();
const app = express();
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin');
});
// Initialize Payload
payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
},
});
// Add your own express routes here
app.listen(3000);

View File

@@ -1,18 +0,0 @@
const Categories = {
slug: 'categories',
admin: {
useAsTitle: 'name',
},
access: {
read: () => true,
},
fields: [
{
name: 'name',
type: 'text',
},
],
timestamps: false,
}
export default Categories;

View File

@@ -1,60 +0,0 @@
const Posts = {
slug: 'posts',
admin: {
defaultColumns: ['title', 'author', 'category', 'tags', 'status'],
useAsTitle: 'title',
},
access: {
read: () => true,
},
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'author',
type: 'relationship',
relationTo: 'users',
},
{
name: 'publishedDate',
type: 'date',
},
{
name: 'category',
type: 'relationship',
relationTo: 'categories'
},
{
name: 'tags',
type: 'relationship',
relationTo: 'tags',
hasMany: true,
},
{
name: 'content',
type: 'richText'
},
{
name: 'status',
type: 'select',
options: [
{
value: 'draft',
label: 'Draft',
},
{
value: 'published',
label: 'Published',
},
],
defaultValue: 'draft',
admin: {
position: 'sidebar',
}
}
],
}
export default Posts;

View File

@@ -1,18 +0,0 @@
const Tags = {
slug: 'tags',
admin: {
useAsTitle: 'name',
},
access: {
read: () => true,
},
fields: [
{
name: 'name',
type: 'text',
},
],
timestamps: false,
}
export default Tags;

View File

@@ -1,19 +0,0 @@
const Users = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
access: {
read: () => true,
},
fields: [
// Email added by default
{
name: 'name',
type: 'text',
}
],
};
export default Users;

View File

@@ -1,18 +0,0 @@
import { buildConfig } from 'payload/config';
import Categories from './collections/Categories';
import Posts from './collections/Posts';
import Tags from './collections/Tags';
import Users from './collections/Users';
export default buildConfig({
serverURL: 'http://localhost:3000',
admin: {
user: Users.slug,
},
collections: [
Categories,
Posts,
Tags,
Users,
],
});

View File

@@ -1,24 +0,0 @@
const express = require('express');
const payload = require('payload');
require('dotenv').config();
const app = express();
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin');
});
// Initialize Payload
payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
},
});
// Add your own express routes here
app.listen(3000);

View File

@@ -1,36 +0,0 @@
const Todo = {
slug: 'todos',
admin: {
defaultColumns: ['listName', 'tasks', 'updatedAt'],
useAsTitle: 'listName',
},
access: {
create: () => true,
read: () => true,
update: () => true,
delete: () => true,
},
fields: [
{
name: 'listName',
type: 'text',
},
{
name: 'tasks',
type: 'array',
fields: [
{
name: 'name',
type: 'text',
},
{
name: 'complete',
type: 'checkbox',
defaultValue: false,
}
]
},
],
}
export default Todo;

View File

@@ -1,16 +0,0 @@
const Users = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
access: {
read: () => true,
},
fields: [
// Email added by default
// Add more fields as needed
],
};
export default Users;

View File

@@ -1,14 +0,0 @@
import { buildConfig } from 'payload/config';
import TodoLists from './collections/TodoLists';
import Users from './collections/Users';
export default buildConfig({
serverURL: 'http://localhost:3000',
admin: {
user: Users.slug,
},
collections: [
TodoLists,
Users,
],
});

View File

@@ -1,24 +0,0 @@
const express = require('express');
const payload = require('payload');
require('dotenv').config();
const app = express();
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin');
});
// Initialize Payload
payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
},
});
// Add your own express routes here
app.listen(3000);

View File

@@ -1,17 +0,0 @@
import { CollectionConfig } from 'payload/types';
// Example Collection - For reference only, this must be added to payload.config.ts to be used.
const Examples: CollectionConfig = {
slug: 'examples',
admin: {
useAsTitle: 'someField',
},
fields: [
{
name: 'someField',
type: 'text',
},
],
}
export default Examples;

View File

@@ -1,18 +0,0 @@
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;

View File

@@ -1,22 +0,0 @@
import { buildConfig } from 'payload/config';
import path from 'path';
// import Examples from './collections/Examples';
import Users from './collections/Users';
export default buildConfig({
serverURL: 'http://localhost:3000',
admin: {
user: Users.slug,
},
collections: [
Users,
// Add Collections here
// Examples,
],
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
graphQL: {
schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
},
});

View File

@@ -1,24 +0,0 @@
import express from 'express';
import payload from 'payload';
require('dotenv').config();
const app = express();
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin');
});
// Initialize Payload
payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
},
})
// Add your own express routes here
app.listen(3000);

View File

@@ -1,20 +0,0 @@
import { CollectionConfig } from 'payload/types';
const Categories: CollectionConfig = {
slug: 'categories',
admin: {
useAsTitle: 'name',
},
access: {
read: () => true,
},
fields: [
{
name: 'name',
type: 'text',
},
],
timestamps: false,
}
export default Categories;

View File

@@ -1,62 +0,0 @@
import { CollectionConfig } from 'payload/types';
const Posts: CollectionConfig = {
slug: 'posts',
admin: {
defaultColumns: ['title', 'author', 'category', 'tags', 'status'],
useAsTitle: 'title',
},
access: {
read: () => true,
},
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'author',
type: 'relationship',
relationTo: 'users',
},
{
name: 'publishedDate',
type: 'date',
},
{
name: 'category',
type: 'relationship',
relationTo: 'categories'
},
{
name: 'tags',
type: 'relationship',
relationTo: 'tags',
hasMany: true,
},
{
name: 'content',
type: 'richText'
},
{
name: 'status',
type: 'select',
options: [
{
value: 'draft',
label: 'Draft',
},
{
value: 'published',
label: 'Published',
},
],
defaultValue: 'draft',
admin: {
position: 'sidebar',
}
}
],
}
export default Posts;

View File

@@ -1,20 +0,0 @@
import { CollectionConfig } from 'payload/types';
const Tags: CollectionConfig = {
slug: 'tags',
admin: {
useAsTitle: 'name',
},
access: {
read: () => true,
},
fields: [
{
name: 'name',
type: 'text',
},
],
timestamps: false,
}
export default Tags;

View File

@@ -1,21 +0,0 @@
import { CollectionConfig } from 'payload/types';
const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
access: {
read: () => true,
},
fields: [
// Email added by default
{
name: 'name',
type: 'text',
}
],
};
export default Users;

View File

@@ -1,25 +0,0 @@
import { buildConfig } from 'payload/config';
import path from 'path';
import Categories from './collections/Categories';
import Posts from './collections/Posts';
import Tags from './collections/Tags';
import Users from './collections/Users';
export default buildConfig({
serverURL: 'http://localhost:3000',
admin: {
user: Users.slug,
},
collections: [
Categories,
Posts,
Tags,
Users,
],
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts')
},
graphQL: {
schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
},
});

View File

@@ -1,24 +0,0 @@
import express from 'express';
import payload from 'payload';
require('dotenv').config();
const app = express();
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin');
});
// Initialize Payload
payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
},
})
// Add your own express routes here
app.listen(3000);

View File

@@ -1,38 +0,0 @@
import { CollectionConfig } from 'payload/types';
const Todo: CollectionConfig = {
slug: 'todos',
admin: {
defaultColumns: ['listName', 'tasks', 'updatedAt'],
useAsTitle: 'listName',
},
access: {
create: () => true,
read: () => true,
update: () => true,
delete: () => true,
},
fields: [
{
name: 'listName',
type: 'text',
},
{
name: 'tasks',
type: 'array',
fields: [
{
name: 'name',
type: 'text',
},
{
name: 'complete',
type: 'checkbox',
defaultValue: false,
}
]
},
],
}
export default Todo;

View File

@@ -1,18 +0,0 @@
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;

View File

@@ -1,21 +0,0 @@
import { buildConfig } from 'payload/config';
import path from 'path';
import TodoLists from './collections/TodoLists';
import Users from './collections/Users';
export default buildConfig({
serverURL: 'http://localhost:3000',
admin: {
user: Users.slug,
},
collections: [
TodoLists,
Users,
],
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts')
},
graphQL: {
schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
},
});

View File

@@ -1,24 +0,0 @@
import express from 'express';
import payload from 'payload';
require('dotenv').config();
const app = express();
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin');
});
// Initialize Payload
payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
},
})
// Add your own express routes here
app.listen(3000);

View File

@@ -7,6 +7,8 @@ export interface Args extends arg.Spec {
'--db': StringConstructor
'--secret': StringConstructor
'--use-npm': BooleanConstructor
'--use-yarn': BooleanConstructor
'--use-pnpm': BooleanConstructor
'--no-deps': BooleanConstructor
'--dry-run': BooleanConstructor
'--beta': BooleanConstructor
@@ -17,19 +19,39 @@ export interface Args extends arg.Spec {
export type CliArgs = arg.Result<Args>
export type ProjectTemplate = StaticTemplate | GitTemplate
export interface StaticTemplate extends Template {
type: 'static'
}
export type ProjectTemplate = GitTemplate | PluginTemplate
/**
* Template that is cloned verbatim from a git repo
* Performs .env manipulation based upon input
*/
export interface GitTemplate extends Template {
type: 'starter'
url: string
}
/**
* Type specifically for the plugin template
* No .env manipulation is done
*/
export interface PluginTemplate extends Template {
type: 'plugin'
url: string
}
interface Template {
name: string
type: 'static' | 'starter'
language: 'typescript' | 'javascript'
type: ProjectTemplate['type']
description?: string
}
export type PackageManager = 'npm' | 'yarn' | 'pnpm'
export type DbType = 'mongodb' | 'postgres'
export type DbDetails = {
type: DbType
dbUri: string
}
export type BundlerType = 'webpack' | 'vite'

View File

@@ -2,6 +2,8 @@ import chalk from 'chalk'
import figures from 'figures'
import terminalLink from 'terminal-link'
import { getValidTemplates } from '../lib/templates'
import type { ProjectTemplate } from '../types'
import path from 'path'
const header = (message: string): string =>
`${chalk.yellow(figures.star)} ${chalk.bold(message)}`
@@ -10,32 +12,49 @@ export const welcomeMessage = chalk`
{green Welcome to Payload. Let's create a project! }
`
const spacer = ' '.repeat(8)
export async function helpMessage(): Promise<string> {
const validTemplates = await getValidTemplates()
return chalk`
{bold USAGE}
{dim $} {bold npx create-payload-app}
{dim $} {bold npx create-payload-app} my-project
{dim $} {bold npx create-payload-app} -n my-project -t blog
{bold OPTIONS}
--name {underline my-payload-app} Set project name
--template {underline template_name} Choose specific template
-n {underline my-payload-app} Set project name
-t {underline template_name} Choose specific template
{dim Available templates: ${validTemplates.map(t => t.name).join(', ')}}
{dim Available templates: ${formatTemplates(validTemplates)}}
--use-npm Use npm to install dependencies
--no-deps Do not install any dependencies
--help Show help
--use-npm Use npm to install dependencies
--use-yarn Use yarn to install dependencies
--use-pnpm Use pnpm to install dependencies
--no-deps Do not install any dependencies
-h Show help
`
}
function formatTemplates(templates: ProjectTemplate[]) {
return `\n\n${spacer}${templates
.map(t => `${t.name}${' '.repeat(28 - t.name.length)}${t.description}`)
.join(`\n${spacer}`)}`
}
export function successMessage(projectDir: string, packageManager: string): string {
return `
${header('Launch Application:')}
- cd ${projectDir}
- ${packageManager === 'yarn' ? 'yarn' : 'npm run'} dev
- ${
packageManager === 'yarn' ? 'yarn' : 'npm run'
} dev or follow directions in ${createTerminalLink(
'README.md',
`file://${path.resolve(projectDir, 'README.md')}`,
)}
${header('Documentation:')}

View File

@@ -692,10 +692,12 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17"
integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==
"@types/prompts@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-2.4.0.tgz#b4b8fdb70a635ad6b6b34e05545f8417f639a9c9"
integrity sha512-7th8Opn+0XlN0O6qzO7dXOPwL6rigq/EwRS2DntaTHwSw8cLaYKeAPt5dWEKHSL+ffVSUl1itTPUC06+FlsV4Q==
"@types/prompts@^2.4.1":
version "2.4.1"
resolved "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.1.tgz#d47adcb608a0afcd48121ff7c75244694a3a04c5"
integrity sha512-1Mqzhzi9W5KlooNE4o0JwSXGUDeQXKldbGn9NO4tpxwZbHXYd+WcKpCksG2lbhH7U9I9LigfsdVsP2QAY0lNPA==
dependencies:
"@types/node" "*"
"@types/semver@^7.3.12":
version "7.3.12"
@@ -3280,9 +3282,9 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
prompts@^2.0.1, prompts@^2.4.0:
prompts@^2.0.1, prompts@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
dependencies:
kleur "^3.0.3"
@@ -3411,6 +3413,11 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rimraf@^4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz#20dfbc98083bdfaa28b01183162885ef213dbf7c"
integrity sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"