Compare commits
61 Commits
v0.3.18-be
...
v0.5.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ac9dae1d2 | ||
|
|
9c165a73cb | ||
|
|
9b58915aff | ||
|
|
c1daeb3432 | ||
|
|
ff8acde322 | ||
|
|
1bade389e4 | ||
|
|
489de652a3 | ||
|
|
166b06e5d8 | ||
|
|
ed143c7a67 | ||
|
|
b29e2ae685 | ||
|
|
9bafc0fcbf | ||
|
|
b3db078a5f | ||
|
|
17dbe066c1 | ||
|
|
5acc88ee9a | ||
|
|
d4983af3fc | ||
|
|
6f2dcfd44e | ||
|
|
3df6435353 | ||
|
|
3ef03364be | ||
|
|
5396af9bfc | ||
|
|
56eddf3a93 | ||
|
|
6dd900e6b9 | ||
|
|
ee93118688 | ||
|
|
47f9b89175 | ||
|
|
04fd2d6e82 | ||
|
|
4cdc7cf3c4 | ||
|
|
fbaa1028e6 | ||
|
|
df26e19c16 | ||
|
|
98501cf4c0 | ||
|
|
d0ac142871 | ||
|
|
d60c66ebd6 | ||
|
|
121d69faf9 | ||
|
|
0a2d7c858a | ||
|
|
0962e1e563 | ||
|
|
e43d6520c5 | ||
|
|
1123909960 | ||
|
|
bc1853c2e7 | ||
|
|
318b734f96 | ||
|
|
2734b1d54a | ||
|
|
82510c1574 | ||
|
|
39686e3f05 | ||
|
|
482973559d | ||
|
|
600dbd72f4 | ||
|
|
785337dc5d | ||
|
|
14a35f35c3 | ||
|
|
ed2e176285 | ||
|
|
2d79280999 | ||
|
|
8b5084ab43 | ||
|
|
b19356597b | ||
|
|
0bd412edbd | ||
|
|
1a6ba25e5d | ||
|
|
26d0cd18a1 | ||
|
|
31653fe76e | ||
|
|
a6d52223d5 | ||
|
|
1bf7c4084c | ||
|
|
419a3eef53 | ||
|
|
f62531bafd | ||
|
|
dc815dad14 | ||
|
|
bde2ce9b53 | ||
|
|
041af0100c | ||
|
|
2e18c5b8cf | ||
|
|
53f0c526f7 |
13
.vscode/launch.json
vendored
Normal file
13
.vscode/launch.json
vendored
Normal 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"
|
||||
},
|
||||
]
|
||||
}
|
||||
23
README.md
23
README.md
@@ -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
|
||||
```
|
||||
|
||||
10
package.json
10
package.json
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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`
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"exec": "node server.js"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
legacy-peer-deps=true
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
96
src/lib/configure-payload-config.ts
Normal file
96
src/lib/configure-payload-config.ts
Normal 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')
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
59
src/lib/packages.ts
Normal 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,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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
96
src/lib/select-db.ts
Normal 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)
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
61
src/main.ts
61
src/main.ts
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
],
|
||||
});
|
||||
@@ -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);
|
||||
@@ -1,18 +0,0 @@
|
||||
const Categories = {
|
||||
slug: 'categories',
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
timestamps: false,
|
||||
}
|
||||
|
||||
export default Categories;
|
||||
@@ -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;
|
||||
@@ -1,18 +0,0 @@
|
||||
const Tags = {
|
||||
slug: 'tags',
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
timestamps: false,
|
||||
}
|
||||
|
||||
export default Tags;
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
],
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
],
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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'),
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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'),
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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'),
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
36
src/types.ts
36
src/types.ts
@@ -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'
|
||||
|
||||
@@ -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:')}
|
||||
|
||||
|
||||
19
yarn.lock
19
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user