322 lines
9.2 KiB
TypeScript
322 lines
9.2 KiB
TypeScript
/**
|
|
* This script generates variations of the templates into the `templates` directory.
|
|
*
|
|
* How to use:
|
|
*
|
|
* pnpm run script:gen-templates
|
|
*
|
|
* NOTE: You will likely have to commit by using the `--no-verify` flag to avoid the repo linting
|
|
* There is no way currently to have lint-staged ignore the templates directory.
|
|
*/
|
|
|
|
import type { DbType, StorageAdapterType } from 'packages/create-payload-app/src/types.js'
|
|
|
|
import chalk from 'chalk'
|
|
import { execSync } from 'child_process'
|
|
import { configurePayloadConfig } from 'create-payload-app/lib/configure-payload-config.js'
|
|
import { copyRecursiveSync } from 'create-payload-app/utils/copy-recursive-sync.js'
|
|
import * as fs from 'node:fs/promises'
|
|
import { fileURLToPath } from 'node:url'
|
|
import path from 'path'
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
type TemplateVariations = {
|
|
/** package.json name */
|
|
name: string
|
|
/** Base template to copy from */
|
|
base?: string
|
|
/** Directory in templates dir */
|
|
dirname: string
|
|
db: DbType
|
|
storage: StorageAdapterType
|
|
sharp: boolean
|
|
vercelDeployButtonLink?: string
|
|
envNames?: {
|
|
dbUri: string
|
|
}
|
|
configureConfig?: boolean
|
|
generateLockfile?: boolean
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error)
|
|
process.exit(1)
|
|
})
|
|
|
|
async function main() {
|
|
const templatesDir = path.resolve(dirname, '../templates')
|
|
|
|
// WARNING: This will need to be updated when this merges into main
|
|
const templateRepoUrlBase = `https://github.com/payloadcms/payload/tree/beta/templates`
|
|
|
|
const variations: TemplateVariations[] = [
|
|
{
|
|
name: 'payload-vercel-postgres-template',
|
|
dirname: 'with-vercel-postgres',
|
|
db: 'vercel-postgres',
|
|
storage: 'vercelBlobStorage',
|
|
sharp: false,
|
|
vercelDeployButtonLink:
|
|
`https://vercel.com/new/clone?repository-url=` +
|
|
encodeURI(
|
|
`${templateRepoUrlBase}/with-vercel-postgres` +
|
|
'&project-name=payload-project' +
|
|
'&env=PAYLOAD_SECRET' +
|
|
'&build-command=pnpm run ci' +
|
|
'&stores=[{"type":"postgres"},{"type":"blob"}]', // Postgres and Vercel Blob Storage
|
|
),
|
|
envNames: {
|
|
// This will replace the process.env.DATABASE_URI to process.env.POSTGRES_URL
|
|
dbUri: 'POSTGRES_URL',
|
|
},
|
|
},
|
|
{
|
|
name: 'payload-vercel-website-template',
|
|
base: 'website', // This is the base template to copy from
|
|
dirname: 'with-vercel-website',
|
|
db: 'vercel-postgres',
|
|
storage: 'vercelBlobStorage',
|
|
sharp: true,
|
|
vercelDeployButtonLink:
|
|
`https://vercel.com/new/clone?repository-url=` +
|
|
encodeURI(
|
|
`${templateRepoUrlBase}/with-vercel-website` +
|
|
'&project-name=payload-project' +
|
|
'&env=PAYLOAD_SECRET' +
|
|
'&build-command=pnpm run ci' +
|
|
'&stores=[{"type":"postgres"},{"type":"blob"}]', // Postgres and Vercel Blob Storage
|
|
),
|
|
envNames: {
|
|
// This will replace the process.env.DATABASE_URI to process.env.POSTGRES_URL
|
|
dbUri: 'POSTGRES_URL',
|
|
},
|
|
},
|
|
{
|
|
name: 'payload-postgres-template',
|
|
dirname: 'with-postgres',
|
|
db: 'postgres',
|
|
storage: 'localDisk',
|
|
sharp: true,
|
|
},
|
|
{
|
|
name: 'payload-vercel-mongodb-template',
|
|
dirname: 'with-vercel-mongodb',
|
|
db: 'mongodb',
|
|
storage: 'vercelBlobStorage',
|
|
sharp: false,
|
|
vercelDeployButtonLink:
|
|
`https://vercel.com/new/clone?repository-url=` +
|
|
encodeURI(
|
|
`${templateRepoUrlBase}/with-vercel-mongodb` +
|
|
'&project-name=payload-project' +
|
|
'&env=PAYLOAD_SECRET' +
|
|
'&build-command=pnpm run ci' +
|
|
'&stores=[{"type":"blob"}]' + // Vercel Blob Storage
|
|
'&integration-ids=oac_jnzmjqM10gllKmSrG0SGrHOH', // MongoDB Atlas
|
|
),
|
|
envNames: {
|
|
dbUri: 'MONGODB_URI',
|
|
},
|
|
},
|
|
{
|
|
name: 'blank',
|
|
dirname: 'blank',
|
|
db: 'mongodb',
|
|
storage: 'localDisk',
|
|
sharp: true,
|
|
// The blank template is used as a base for create-payload-app functionality,
|
|
// so we do not configure the payload.config.ts file, which leaves the placeholder comments.
|
|
configureConfig: false,
|
|
},
|
|
{
|
|
name: 'payload-cloud-mongodb-template',
|
|
dirname: 'with-payload-cloud',
|
|
db: 'mongodb',
|
|
generateLockfile: true,
|
|
storage: 'payloadCloud',
|
|
sharp: true,
|
|
},
|
|
]
|
|
|
|
for (const {
|
|
name,
|
|
base,
|
|
dirname,
|
|
db,
|
|
generateLockfile,
|
|
storage,
|
|
vercelDeployButtonLink,
|
|
envNames,
|
|
sharp,
|
|
configureConfig,
|
|
} of variations) {
|
|
header(`Generating ${name}...`)
|
|
const destDir = path.join(templatesDir, dirname)
|
|
copyRecursiveSync(path.join(templatesDir, base || '_template'), destDir, [
|
|
'node_modules',
|
|
'\\*\\.tgz',
|
|
'.next',
|
|
'.env$',
|
|
'pnpm-lock.yaml',
|
|
])
|
|
|
|
log(`Copied to ${destDir}`)
|
|
|
|
if (configureConfig !== false) {
|
|
log('Configuring payload.config.ts')
|
|
await configurePayloadConfig({
|
|
dbType: db,
|
|
packageJsonName: name,
|
|
projectDirOrConfigPath: { projectDir: destDir },
|
|
storageAdapter: storage,
|
|
sharp,
|
|
envNames,
|
|
})
|
|
|
|
log('Configuring .env.example')
|
|
// Replace DATABASE_URI with the correct env name if set
|
|
await writeEnvExample({
|
|
destDir,
|
|
envNames,
|
|
})
|
|
}
|
|
|
|
await generateReadme({
|
|
destDir,
|
|
data: {
|
|
name,
|
|
description: name, // TODO: Add descriptions
|
|
attributes: { db, storage },
|
|
...(vercelDeployButtonLink && { vercelDeployButtonLink }),
|
|
},
|
|
})
|
|
|
|
// Copy in initial migration if db is postgres. This contains user and media.
|
|
if (db === 'postgres' || db === 'vercel-postgres') {
|
|
// Add "ci" script to package.json
|
|
const packageJsonPath = path.join(destDir, 'package.json')
|
|
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'))
|
|
packageJson.scripts = packageJson.scripts || {}
|
|
packageJson.scripts.ci = 'payload migrate && pnpm build'
|
|
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
|
|
const migrationDestDir = path.join(destDir, 'src/migrations')
|
|
|
|
// Delete and recreate migrations directory
|
|
await fs.rm(migrationDestDir, { recursive: true, force: true })
|
|
await fs.mkdir(migrationDestDir, { recursive: true })
|
|
|
|
log(`Generating initial migrations in ${migrationDestDir}`)
|
|
|
|
execSyncSafe(`pnpm run payload migrate:create initial`, {
|
|
cwd: destDir,
|
|
env: {
|
|
...process.env,
|
|
BLOB_READ_WRITE_TOKEN: 'vercel_blob_rw_TEST_asdf',
|
|
DATABASE_URI: 'postgres://localhost:5432/payloadtests',
|
|
},
|
|
})
|
|
}
|
|
|
|
if (generateLockfile) {
|
|
log('Generating pnpm-lock.yaml')
|
|
execSyncSafe(`pnpm install --ignore-workspace`, { cwd: destDir })
|
|
}
|
|
|
|
// TODO: Email?
|
|
|
|
// TODO: Sharp?
|
|
|
|
log(`Done configuring payload config for ${destDir}/src/payload.config.ts`)
|
|
}
|
|
// TODO: Run prettier manually on the generated files, husky blows up
|
|
log('Running prettier on generated files...')
|
|
execSyncSafe(`pnpm prettier --write templates "*.{js,jsx,ts,tsx}"`)
|
|
|
|
log('Template generation complete!')
|
|
}
|
|
|
|
async function generateReadme({
|
|
destDir,
|
|
data: { name, description, attributes, vercelDeployButtonLink },
|
|
}: {
|
|
destDir: string
|
|
data: {
|
|
name: string
|
|
description: string
|
|
attributes: Pick<TemplateVariations, 'db' | 'storage'>
|
|
vercelDeployButtonLink?: string
|
|
}
|
|
}) {
|
|
let header = `# ${name}\n`
|
|
if (vercelDeployButtonLink) {
|
|
header += `\n[](${vercelDeployButtonLink})`
|
|
}
|
|
|
|
const readmeContent = `${header}
|
|
|
|
${description}
|
|
|
|
## Attributes
|
|
|
|
- **Database**: ${attributes.db}
|
|
- **Storage Adapter**: ${attributes.storage}
|
|
`
|
|
|
|
const readmePath = path.join(destDir, 'README.md')
|
|
await fs.writeFile(readmePath, readmeContent)
|
|
log('Generated README.md')
|
|
}
|
|
|
|
async function writeEnvExample({
|
|
destDir,
|
|
envNames,
|
|
}: {
|
|
destDir: string
|
|
envNames?: TemplateVariations['envNames']
|
|
}) {
|
|
const envExamplePath = path.join(destDir, '.env.example')
|
|
const envFileContents = await fs.readFile(envExamplePath, 'utf8')
|
|
const fileContents = envFileContents
|
|
.split('\n')
|
|
.filter((e) => e)
|
|
.map((l) =>
|
|
envNames?.dbUri && l.startsWith('DATABASE_URI')
|
|
? l.replace('DATABASE_URI', envNames.dbUri)
|
|
: l,
|
|
)
|
|
.join('\n')
|
|
|
|
await fs.writeFile(envExamplePath, fileContents)
|
|
}
|
|
|
|
function header(message: string) {
|
|
console.log(chalk.bold.green(`\n${message}\n`))
|
|
}
|
|
|
|
function log(message: string) {
|
|
console.log(chalk.dim(message))
|
|
}
|
|
function execSyncSafe(command: string, options?: Parameters<typeof execSync>[1]) {
|
|
try {
|
|
execSync(command, options)
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
const stderr = (error as any).stderr?.toString()
|
|
const stdout = (error as any).stdout?.toString()
|
|
|
|
if (stderr && stderr.trim()) {
|
|
console.error('Standard Error:', stderr)
|
|
} else if (stdout && stdout.trim()) {
|
|
console.error('Standard Output (likely contains error details):', stdout)
|
|
} else {
|
|
console.error('An unknown error occurred with no output.')
|
|
}
|
|
} else {
|
|
console.error('An unexpected error occurred:', error)
|
|
}
|
|
}
|
|
}
|