create-payload-app
Advanced tools
Comparing version 3.0.0-canary.4de9e42 to 3.0.0-canary.4eb1bfc
import * as p from '@clack/prompts'; | ||
import chalk from 'chalk'; | ||
import degit from 'degit'; | ||
import execa from 'execa'; | ||
@@ -11,2 +10,3 @@ import fse from 'fs-extra'; | ||
import { configurePayloadConfig } from './configure-payload-config.js'; | ||
import { downloadTemplate } from './download-template.js'; | ||
const filename = fileURLToPath(import.meta.url); | ||
@@ -30,2 +30,4 @@ const dirname = path.dirname(filename); | ||
installCmd = 'pnpm install'; | ||
} else if (packageManager === 'bun') { | ||
installCmd = 'bun install'; | ||
} | ||
@@ -59,4 +61,7 @@ try { | ||
} | ||
const emitter = degit(templateUrl); | ||
await emitter.clone(projectDir); | ||
await downloadTemplate({ | ||
name: template.name, | ||
branch: 'beta', | ||
projectDir | ||
}); | ||
} | ||
@@ -63,0 +68,0 @@ const spinner = p.spinner(); |
@@ -13,2 +13,3 @@ import { jest } from '@jest/globals'; | ||
beforeAll(()=>{ | ||
// eslint-disable-next-line no-console | ||
console.log = jest.fn(); | ||
@@ -33,3 +34,3 @@ }); | ||
'--db': 'mongodb', | ||
'--local-template': 'blank-3.0', | ||
'--local-template': 'blank', | ||
'--no-deps': true | ||
@@ -56,3 +57,3 @@ }; | ||
// Check package name and description | ||
expect(packageJson.name).toEqual(projectName); | ||
expect(packageJson.name).toStrictEqual(projectName); | ||
}); | ||
@@ -63,7 +64,7 @@ describe('creates project from template', ()=>{ | ||
[ | ||
'blank-3.0', | ||
'blank', | ||
'mongodb' | ||
], | ||
[ | ||
'blank-3.0', | ||
'blank', | ||
'postgres' | ||
@@ -101,5 +102,2 @@ ] | ||
}))?.[0]; | ||
if (!payloadConfigPath) { | ||
throw new Error(`Could not find payload.config.ts inside ${projectDir}`); | ||
} | ||
const content = fse.readFileSync(payloadConfigPath, 'utf-8'); | ||
@@ -106,0 +104,0 @@ // Check payload.config.ts |
@@ -14,17 +14,29 @@ import execa from 'execa'; | ||
detected = 'npm'; | ||
} else if (cliArgs?.['--use-bun'] || fse.existsSync(`${projectDir}/bun.lockb`)) { | ||
detected = 'bun'; | ||
} else if (await commandExists('pnpm')) { | ||
// Prefer pnpm if it's installed | ||
detected = 'pnpm'; | ||
} else { | ||
// Otherwise check for existing commands | ||
if (await commandExists('pnpm')) { | ||
detected = 'pnpm'; | ||
} else if (await commandExists('yarn')) { | ||
detected = 'yarn'; | ||
} else { | ||
detected = 'npm'; | ||
} | ||
// Otherwise check the execution environment | ||
detected = getEnvironmentPackageManager(); | ||
} | ||
return detected; | ||
} catch (error) { | ||
} catch (ignore) { | ||
return 'npm'; | ||
} | ||
} | ||
function getEnvironmentPackageManager() { | ||
const userAgent = process.env.npm_config_user_agent || ''; | ||
if (userAgent.startsWith('yarn')) { | ||
return 'yarn'; | ||
} | ||
if (userAgent.startsWith('pnpm')) { | ||
return 'pnpm'; | ||
} | ||
if (userAgent.startsWith('bun')) { | ||
return 'bun'; | ||
} | ||
return 'npm'; | ||
} | ||
async function commandExists(command) { | ||
@@ -31,0 +43,0 @@ try { |
@@ -29,3 +29,3 @@ import * as p from '@clack/prompts'; | ||
} | ||
const { hasTopLevelLayout, isPayloadInstalled, isSrcDir, nextAppDir, nextConfigType } = nextAppDetails; | ||
const { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigType } = nextAppDetails; | ||
if (!nextConfigType) { | ||
@@ -54,3 +54,3 @@ return { | ||
installSpinner.start('Installing Payload and dependencies...'); | ||
const configurationResult = installAndConfigurePayload({ | ||
const configurationResult = await installAndConfigurePayload({ | ||
...args, | ||
@@ -117,3 +117,3 @@ nextAppDetails, | ||
} | ||
function installAndConfigurePayload(args) { | ||
async function installAndConfigurePayload(args) { | ||
const { '--debug': debug, nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {}, nextConfigType, projectDir, useDistFiles } = args; | ||
@@ -127,3 +127,5 @@ if (!nextAppDir || !nextConfigPath) { | ||
const logDebug = (message)=>{ | ||
if (debug) origDebug(message); | ||
if (debug) { | ||
origDebug(message); | ||
} | ||
}; | ||
@@ -136,3 +138,3 @@ if (!fs.existsSync(projectDir)) { | ||
} | ||
const templateFilesPath = dirname.endsWith('dist') || useDistFiles ? path.resolve(dirname, '../..', 'dist/template') : path.resolve(dirname, '../../../../templates/blank-3.0'); | ||
const templateFilesPath = dirname.endsWith('dist') || useDistFiles ? path.resolve(dirname, '../..', 'dist/template') : path.resolve(dirname, '../../../../templates/blank'); | ||
logDebug(`Using template files from: ${templateFilesPath}`); | ||
@@ -156,5 +158,5 @@ if (!fs.existsSync(templateFilesPath)) { | ||
// This is a little clunky and needs to account for isSrcDir | ||
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug); | ||
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath)); | ||
// Wrap next.config.js with withPayload | ||
wrapNextConfig({ | ||
await wrapNextConfig({ | ||
nextConfigPath, | ||
@@ -186,3 +188,4 @@ nextConfigType | ||
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src')); | ||
const nextConfigPath = (await globby('next.config.*js', { | ||
// Match next.config.js, next.config.ts, next.config.mjs, next.config.cjs | ||
const nextConfigPath = (await globby('next.config.(\\w)?(t|j)s', { | ||
absolute: true, | ||
@@ -195,6 +198,38 @@ cwd: projectDir | ||
isSrcDir, | ||
nextConfigPath: undefined | ||
isSupportedNextVersion: false, | ||
nextConfigPath: undefined, | ||
nextVersion: null | ||
}; | ||
} | ||
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json')); | ||
// Check if Next.js version is new enough | ||
let nextVersion = null; | ||
if (packageObj.dependencies?.next) { | ||
nextVersion = packageObj.dependencies.next; | ||
// Match versions using regex matching groups | ||
const versionMatch = /(?<major>\d+)/.exec(nextVersion); | ||
if (!versionMatch) { | ||
p.log.warn(`Could not determine Next.js version from ${nextVersion}`); | ||
return { | ||
hasTopLevelLayout: false, | ||
isSrcDir, | ||
isSupportedNextVersion: false, | ||
nextConfigPath, | ||
nextVersion | ||
}; | ||
} | ||
const { major } = versionMatch.groups; | ||
const majorVersion = parseInt(major); | ||
if (majorVersion < 15) { | ||
return { | ||
hasTopLevelLayout: false, | ||
isSrcDir, | ||
isSupportedNextVersion: false, | ||
nextConfigPath, | ||
nextVersion | ||
}; | ||
} | ||
} | ||
const isSupportedNextVersion = true; | ||
// Check if Payload already installed | ||
if (packageObj.dependencies?.payload) { | ||
@@ -205,3 +240,5 @@ return { | ||
isSrcDir, | ||
nextConfigPath | ||
isSupportedNextVersion, | ||
nextConfigPath, | ||
nextVersion | ||
}; | ||
@@ -230,5 +267,7 @@ } | ||
isSrcDir, | ||
isSupportedNextVersion, | ||
nextAppDir, | ||
nextConfigPath, | ||
nextConfigType: configType | ||
nextConfigType: configType, | ||
nextVersion | ||
}; | ||
@@ -238,2 +277,5 @@ } | ||
const { nextConfigPath, packageObj } = args; | ||
if (nextConfigPath.endsWith('.ts')) { | ||
return 'ts'; | ||
} | ||
if (nextConfigPath.endsWith('.mjs')) { | ||
@@ -240,0 +282,0 @@ return 'esm'; |
import * as p from '@clack/prompts'; | ||
import slugify from '@sindresorhus/slugify'; | ||
export async function parseProjectName(args) { | ||
if (args['--name']) return slugify(args['--name']); | ||
if (args._[0]) return slugify(args._[0]); | ||
if (args['--name']) { | ||
return slugify(args['--name']); | ||
} | ||
if (args._[0]) { | ||
return slugify(args._[0]); | ||
} | ||
const projectName = await p.text({ | ||
message: 'Project name?', | ||
validate: (value)=>{ | ||
if (!value) return 'Please enter a project name.'; | ||
if (!value) { | ||
return 'Please enter a project name.'; | ||
} | ||
} | ||
@@ -11,0 +17,0 @@ }); |
@@ -6,3 +6,5 @@ import * as p from '@clack/prompts'; | ||
const template = validTemplates.find((t)=>t.name === templateName); | ||
if (!template) throw new Error('Invalid template given'); | ||
if (!template) { | ||
throw new Error('Invalid template given'); | ||
} | ||
return template; | ||
@@ -9,0 +11,0 @@ } |
@@ -22,5 +22,29 @@ const mongodbReplacement = { | ||
}; | ||
const vercelPostgresReplacement = { | ||
configReplacement: (envName = 'POSTGRES_URL')=>[ | ||
' db: vercelPostgresAdapter({', | ||
' pool: {', | ||
` connectionString: process.env.${envName} || '',`, | ||
' },', | ||
' }),' | ||
], | ||
importReplacement: "import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres'", | ||
packageName: '@payloadcms/db-vercel-postgres' | ||
}; | ||
const sqliteReplacement = { | ||
configReplacement: (envName = 'DATABASE_URI')=>[ | ||
' db: sqliteAdapter({', | ||
' client: {', | ||
` url: process.env.${envName} || '',`, | ||
' },', | ||
' }),' | ||
], | ||
importReplacement: "import { sqliteAdapter } from '@payloadcms/db-sqlite'", | ||
packageName: '@payloadcms/db-sqlite' | ||
}; | ||
export const dbReplacements = { | ||
mongodb: mongodbReplacement, | ||
postgres: postgresReplacement | ||
postgres: postgresReplacement, | ||
sqlite: sqliteReplacement, | ||
'vercel-postgres': vercelPostgresReplacement | ||
}; | ||
@@ -27,0 +51,0 @@ const vercelBlobStorageReplacement = { |
@@ -13,2 +13,13 @@ import * as p from '@clack/prompts'; | ||
value: 'postgres' | ||
}, | ||
sqlite: { | ||
dbConnectionPrefix: 'file:./', | ||
dbConnectionSuffix: '.db', | ||
title: 'SQLite (beta)', | ||
value: 'sqlite' | ||
}, | ||
'vercel-postgres': { | ||
dbConnectionPrefix: 'postgres://postgres:<password>@127.0.0.1:5432/', | ||
title: 'Vercel Postgres (beta)', | ||
value: 'vercel-postgres' | ||
} | ||
@@ -27,18 +38,14 @@ }; | ||
message: `Select a database`, | ||
options: [ | ||
{ | ||
label: 'MongoDB', | ||
value: 'mongodb' | ||
}, | ||
{ | ||
label: 'Postgres', | ||
value: 'postgres' | ||
} | ||
] | ||
options: Object.values(dbChoiceRecord).map((dbChoice)=>({ | ||
label: dbChoice.title, | ||
value: dbChoice.value | ||
})) | ||
}); | ||
if (p.isCancel(dbType)) process.exit(0); | ||
if (p.isCancel(dbType)) { | ||
process.exit(0); | ||
} | ||
} | ||
const dbChoice = dbChoiceRecord[dbType]; | ||
let dbUri = undefined; | ||
const initialDbUri = `${dbChoice.dbConnectionPrefix}${projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)}`; | ||
const initialDbUri = `${dbChoice.dbConnectionPrefix}${projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)}${dbChoice.dbConnectionSuffix || ''}`; | ||
if (args['--db-accept-recommended']) { | ||
@@ -53,3 +60,5 @@ dbUri = initialDbUri; | ||
}); | ||
if (p.isCancel(dbUri)) process.exit(0); | ||
if (p.isCancel(dbUri)) { | ||
process.exit(0); | ||
} | ||
} | ||
@@ -56,0 +65,0 @@ return { |
@@ -17,3 +17,3 @@ import { error, info } from '../utils/log.js'; | ||
description: 'Blank 3.0 Template', | ||
url: 'https://github.com/payloadcms/payload/templates/blank-3.0#beta' | ||
url: 'https://github.com/payloadcms/payload/templates/blank#beta' | ||
}, | ||
@@ -20,0 +20,0 @@ { |
@@ -12,6 +12,8 @@ import execa from 'execa'; | ||
export async function updatePayloadInProject(appDetails) { | ||
if (!appDetails.nextConfigPath) return { | ||
message: 'No Next.js config found', | ||
success: false | ||
}; | ||
if (!appDetails.nextConfigPath) { | ||
return { | ||
message: 'No Next.js config found', | ||
success: false | ||
}; | ||
} | ||
const projectDir = path.dirname(appDetails.nextConfigPath); | ||
@@ -63,3 +65,3 @@ const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json')); | ||
info(`Updating Payload Next.js files...`); | ||
const templateFilesPath = dirname.endsWith('dist') ? path.resolve(dirname, '../..', 'dist/template') : path.resolve(dirname, '../../../../templates/blank-3.0'); | ||
const templateFilesPath = process.env.JEST_WORKER_ID !== undefined ? path.resolve(dirname, '../../../../templates/blank') : path.resolve(dirname, '../..', 'dist/template'); | ||
const templateSrcDir = path.resolve(templateFilesPath, 'src/app/(payload)'); | ||
@@ -66,0 +68,0 @@ copyRecursiveSync(templateSrcDir, path.resolve(projectDir, appDetails.isSrcDir ? 'src/app' : 'app', '(payload)')); |
@@ -0,18 +1,18 @@ | ||
import type { NextConfigType } from '../types.js'; | ||
export declare const withPayloadStatement: { | ||
cjs: string; | ||
esm: string; | ||
ts: string; | ||
}; | ||
type NextConfigType = 'cjs' | 'esm'; | ||
export declare const wrapNextConfig: (args: { | ||
nextConfigPath: string; | ||
nextConfigType: NextConfigType; | ||
}) => void; | ||
}) => Promise<void>; | ||
/** | ||
* Parses config content with AST and wraps it with withPayload function | ||
*/ | ||
export declare function parseAndModifyConfigContent(content: string, configType: NextConfigType): { | ||
export declare function parseAndModifyConfigContent(content: string, configType: NextConfigType): Promise<{ | ||
modifiedConfigContent: string; | ||
success: boolean; | ||
}; | ||
export {}; | ||
}>; | ||
//# sourceMappingURL=wrap-next-config.d.ts.map |
@@ -0,13 +1,15 @@ | ||
import { parse } from '@swc/core'; | ||
import chalk from 'chalk'; | ||
import { Syntax, parseModule } from 'esprima-next'; | ||
import { parseModule, Syntax } from 'esprima-next'; | ||
import fs from 'fs'; | ||
import { log, warning } from '../utils/log.js'; | ||
export const withPayloadStatement = { | ||
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`, | ||
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n` | ||
cjs: `const { withPayload } = require("@payloadcms/next/withPayload");`, | ||
esm: `import { withPayload } from "@payloadcms/next/withPayload";`, | ||
ts: `import { withPayload } from "@payloadcms/next/withPayload";` | ||
}; | ||
export const wrapNextConfig = (args)=>{ | ||
export const wrapNextConfig = async (args)=>{ | ||
const { nextConfigPath, nextConfigType: configType } = args; | ||
const configContent = fs.readFileSync(nextConfigPath, 'utf8'); | ||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(configContent, configType); | ||
const { modifiedConfigContent: newConfig, success } = await parseAndModifyConfigContent(configContent, configType); | ||
if (!success) { | ||
@@ -20,54 +22,77 @@ return; | ||
* Parses config content with AST and wraps it with withPayload function | ||
*/ export function parseAndModifyConfigContent(content, configType) { | ||
content = withPayloadStatement[configType] + content; | ||
let ast; | ||
try { | ||
ast = parseModule(content, { | ||
loc: true | ||
}); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
warning(`Unable to parse Next config. Error: ${error.message} `); | ||
warnUserWrapNotSuccessful(configType); | ||
} | ||
return { | ||
modifiedConfigContent: content, | ||
success: false | ||
}; | ||
} | ||
if (configType === 'esm') { | ||
const exportDefaultDeclaration = ast.body.find((p)=>p.type === Syntax.ExportDefaultDeclaration); | ||
const exportNamedDeclaration = ast.body.find((p)=>p.type === Syntax.ExportNamedDeclaration); | ||
if (!exportDefaultDeclaration && !exportNamedDeclaration) { | ||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js'); | ||
} | ||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) { | ||
const modifiedConfigContent = insertBeforeAndAfter(content, exportDefaultDeclaration.declaration.loc); | ||
*/ export async function parseAndModifyConfigContent(content, configType) { | ||
content = withPayloadStatement[configType] + '\n' + content; | ||
if (configType === 'cjs' || configType === 'esm') { | ||
try { | ||
const ast = parseModule(content, { | ||
loc: true | ||
}); | ||
if (configType === 'cjs') { | ||
// Find `module.exports = X` | ||
const moduleExports = ast.body.find((p)=>p.type === Syntax.ExpressionStatement && p.expression?.type === Syntax.AssignmentExpression && p.expression.left?.type === Syntax.MemberExpression && p.expression.left.object?.type === Syntax.Identifier && p.expression.left.object.name === 'module' && p.expression.left.property?.type === Syntax.Identifier && p.expression.left.property.name === 'exports'); | ||
if (moduleExports && moduleExports.expression.right?.loc) { | ||
const modifiedConfigContent = insertBeforeAndAfter(content, moduleExports.expression.right.loc); | ||
return { | ||
modifiedConfigContent, | ||
success: true | ||
}; | ||
} | ||
return Promise.resolve({ | ||
modifiedConfigContent: content, | ||
success: false | ||
}); | ||
} else if (configType === 'esm') { | ||
const exportDefaultDeclaration = ast.body.find((p)=>p.type === Syntax.ExportDefaultDeclaration); | ||
const exportNamedDeclaration = ast.body.find((p)=>p.type === Syntax.ExportNamedDeclaration); | ||
if (!exportDefaultDeclaration && !exportNamedDeclaration) { | ||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js'); | ||
} | ||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) { | ||
const modifiedConfigContent = insertBeforeAndAfter(content, exportDefaultDeclaration.declaration.loc); | ||
return { | ||
modifiedConfigContent, | ||
success: true | ||
}; | ||
} else if (exportNamedDeclaration) { | ||
const exportSpecifier = exportNamedDeclaration.specifiers.find((s)=>s.type === 'ExportSpecifier' && s.exported?.name === 'default' && s.local?.type === 'Identifier' && s.local?.name); | ||
if (exportSpecifier) { | ||
warning('Could not automatically wrap next.config.js with withPayload.'); | ||
warning('Automatic wrapping of named exports as default not supported yet.'); | ||
warnUserWrapNotSuccessful(configType); | ||
return { | ||
modifiedConfigContent: content, | ||
success: false | ||
}; | ||
} | ||
} | ||
warning('Could not automatically wrap Next config with withPayload.'); | ||
warnUserWrapNotSuccessful(configType); | ||
return Promise.resolve({ | ||
modifiedConfigContent: content, | ||
success: false | ||
}); | ||
} | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
warning(`Unable to parse Next config. Error: ${error.message} `); | ||
warnUserWrapNotSuccessful(configType); | ||
} | ||
return { | ||
modifiedConfigContent, | ||
success: true | ||
modifiedConfigContent: content, | ||
success: false | ||
}; | ||
} else if (exportNamedDeclaration) { | ||
const exportSpecifier = exportNamedDeclaration.specifiers.find((s)=>s.type === 'ExportSpecifier' && s.exported?.name === 'default' && s.local?.type === 'Identifier' && s.local?.name); | ||
if (exportSpecifier) { | ||
warning('Could not automatically wrap next.config.js with withPayload.'); | ||
warning('Automatic wrapping of named exports as default not supported yet.'); | ||
} | ||
} else if (configType === 'ts') { | ||
const { moduleItems, parseOffset } = await compileTypeScriptFileToAST(content); | ||
const exportDefaultDeclaration = moduleItems.find((m)=>m.type === 'ExportDefaultExpression' && (m.expression.type === 'Identifier' || m.expression.type === 'CallExpression')); | ||
if (exportDefaultDeclaration) { | ||
if (!('span' in exportDefaultDeclaration.expression)) { | ||
warning('Could not automatically wrap Next config with withPayload.'); | ||
warnUserWrapNotSuccessful(configType); | ||
return { | ||
return Promise.resolve({ | ||
modifiedConfigContent: content, | ||
success: false | ||
}; | ||
}); | ||
} | ||
} | ||
warning('Could not automatically wrap Next config with withPayload.'); | ||
warnUserWrapNotSuccessful(configType); | ||
return { | ||
modifiedConfigContent: content, | ||
success: false | ||
}; | ||
} else if (configType === 'cjs') { | ||
// Find `module.exports = X` | ||
const moduleExports = ast.body.find((p)=>p.type === Syntax.ExpressionStatement && p.expression?.type === Syntax.AssignmentExpression && p.expression.left?.type === Syntax.MemberExpression && p.expression.left.object?.type === Syntax.Identifier && p.expression.left.object.name === 'module' && p.expression.left.property?.type === Syntax.Identifier && p.expression.left.property.name === 'exports'); | ||
if (moduleExports && moduleExports.expression.right?.loc) { | ||
const modifiedConfigContent = insertBeforeAndAfter(content, moduleExports.expression.right.loc); | ||
const modifiedConfigContent = insertBeforeAndAfterSWC(content, exportDefaultDeclaration.expression.span, parseOffset); | ||
return { | ||
@@ -78,13 +103,9 @@ modifiedConfigContent, | ||
} | ||
return { | ||
modifiedConfigContent: content, | ||
success: false | ||
}; | ||
} | ||
warning('Could not automatically wrap Next config with withPayload.'); | ||
warnUserWrapNotSuccessful(configType); | ||
return { | ||
return Promise.resolve({ | ||
modifiedConfigContent: content, | ||
success: false | ||
}; | ||
}); | ||
} | ||
@@ -95,3 +116,3 @@ function warnUserWrapNotSuccessful(configType) { | ||
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)} | ||
${chalk.bold(`Please manually wrap your existing Next config with the withPayload function. Here is an example:`)} | ||
@@ -104,3 +125,3 @@ ${withPayloadStatement[configType]} | ||
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'} | ||
${configType === 'cjs' ? 'module.exports = withPayload(nextConfig)' : 'export default withPayload(nextConfig)'} | ||
@@ -126,3 +147,42 @@ `; | ||
} | ||
function insertBeforeAndAfterSWC(content, span, /** | ||
* WARNING: This is ONLY for unit tests. Defaults to 0 otherwise. | ||
* | ||
* @see compileTypeScriptFileToAST | ||
*/ parseOffset) { | ||
const { end: preOffsetEnd, start: preOffsetStart } = span; | ||
const start = preOffsetStart - parseOffset; | ||
const end = preOffsetEnd - parseOffset; | ||
const insert = (pos, text)=>{ | ||
return content.slice(0, pos) + text + content.slice(pos); | ||
}; | ||
// insert ) after end | ||
content = insert(end - 1, ')'); | ||
// insert withPayload before start | ||
content = insert(start - 1, 'withPayload('); | ||
return content; | ||
} | ||
/** | ||
* Compile typescript to AST using the swc compiler | ||
*/ async function compileTypeScriptFileToAST(fileContent) { | ||
let parseOffset = 0; | ||
/** | ||
* WARNING: This is ONLY for unit tests. | ||
* | ||
* Multiple instances of swc DO NOT reset the .span.end value. | ||
* During unit tests, the .spawn.end value is read and accounted for. | ||
* | ||
* https://github.com/swc-project/swc/issues/1366 | ||
*/ if (process.env.NODE_ENV === 'test') { | ||
parseOffset = (await parse('')).span.end; | ||
} | ||
const module = await parse(fileContent, { | ||
syntax: 'typescript' | ||
}); | ||
return { | ||
moduleItems: module.body, | ||
parseOffset | ||
}; | ||
} | ||
//# sourceMappingURL=wrap-next-config.js.map |
import * as p from '@clack/prompts'; | ||
import { jest } from '@jest/globals'; | ||
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'; | ||
const tsConfigs = { | ||
defaultNextConfig: `import type { NextConfig } from "next"; | ||
const nextConfig: NextConfig = {}; | ||
export default nextConfig;`, | ||
nextConfigExportNamedDefault: `import type { NextConfig } from "next"; | ||
const nextConfig: NextConfig = {}; | ||
const wrapped = someFunc(asdf); | ||
export { wrapped as default }; | ||
`, | ||
nextConfigWithFunc: `import type { NextConfig } from "next"; | ||
const nextConfig: NextConfig = {}; | ||
export default someFunc(nextConfig); | ||
`, | ||
nextConfigWithFuncMultiline: `import type { NextConfig } from "next"; | ||
const nextConfig: NextConfig = {}; | ||
export default someFunc( | ||
nextConfig | ||
); | ||
`, | ||
nextConfigWithSpread: `import type { NextConfig } from "next"; | ||
const nextConfig: NextConfig = { | ||
...someConfig, | ||
}; | ||
export default nextConfig; | ||
` | ||
}; | ||
const esmConfigs = { | ||
@@ -51,21 +78,44 @@ defaultNextConfig: `/** @type {import('next').NextConfig} */ | ||
describe('parseAndInsertWithPayload', ()=>{ | ||
describe('ts', ()=>{ | ||
const configType = 'ts'; | ||
const importStatement = withPayloadStatement[configType]; | ||
it('should parse the default next config', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(tsConfigs.defaultNextConfig, configType); | ||
expect(modifiedConfigContent).toContain(importStatement); | ||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)'); | ||
}); | ||
it('should parse the config with a function', async ()=>{ | ||
const { modifiedConfigContent: modifiedConfigContent2 } = await parseAndModifyConfigContent(tsConfigs.nextConfigWithFunc, configType); | ||
expect(modifiedConfigContent2).toContain('withPayload(someFunc(nextConfig))'); | ||
}); | ||
it('should parse the config with a multi-lined function', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(tsConfigs.nextConfigWithFuncMultiline, configType); | ||
expect(modifiedConfigContent).toContain(importStatement); | ||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/); | ||
}); | ||
it('should parse the config with a spread', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(tsConfigs.nextConfigWithSpread, configType); | ||
expect(modifiedConfigContent).toContain(importStatement); | ||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)'); | ||
}); | ||
}); | ||
describe('esm', ()=>{ | ||
const configType = 'esm'; | ||
const importStatement = withPayloadStatement[configType]; | ||
it('should parse the default next config', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(esmConfigs.defaultNextConfig, configType); | ||
it('should parse the default next config', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(esmConfigs.defaultNextConfig, configType); | ||
expect(modifiedConfigContent).toContain(importStatement); | ||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)'); | ||
}); | ||
it('should parse the config with a function', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(esmConfigs.nextConfigWithFunc, configType); | ||
it('should parse the config with a function', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(esmConfigs.nextConfigWithFunc, configType); | ||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))'); | ||
}); | ||
it('should parse the config with a function on a new line', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(esmConfigs.nextConfigWithFuncMultiline, configType); | ||
it('should parse the config with a multi-lined function', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(esmConfigs.nextConfigWithFuncMultiline, configType); | ||
expect(modifiedConfigContent).toContain(importStatement); | ||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/); | ||
}); | ||
it('should parse the config with a spread', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(esmConfigs.nextConfigWithSpread, configType); | ||
it('should parse the config with a spread', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(esmConfigs.nextConfigWithSpread, configType); | ||
expect(modifiedConfigContent).toContain(importStatement); | ||
@@ -75,5 +125,5 @@ expect(modifiedConfigContent).toContain('withPayload(nextConfig)'); | ||
// Unsupported: export { wrapped as default } | ||
it('should give warning with a named export as default', ()=>{ | ||
it('should give warning with a named export as default', async ()=>{ | ||
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(()=>{}); | ||
const { modifiedConfigContent, success } = parseAndModifyConfigContent(esmConfigs.nextConfigExportNamedDefault, configType); | ||
const { modifiedConfigContent, success } = await parseAndModifyConfigContent(esmConfigs.nextConfigExportNamedDefault, configType); | ||
expect(modifiedConfigContent).toContain(importStatement); | ||
@@ -87,28 +137,28 @@ expect(success).toBe(false); | ||
const requireStatement = withPayloadStatement[configType]; | ||
it('should parse the default next config', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(cjsConfigs.defaultNextConfig, configType); | ||
it('should parse the default next config', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(cjsConfigs.defaultNextConfig, configType); | ||
expect(modifiedConfigContent).toContain(requireStatement); | ||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)'); | ||
}); | ||
it('should parse anonymous default config', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(cjsConfigs.anonConfig, configType); | ||
it('should parse anonymous default config', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(cjsConfigs.anonConfig, configType); | ||
expect(modifiedConfigContent).toContain(requireStatement); | ||
expect(modifiedConfigContent).toContain('withPayload({})'); | ||
}); | ||
it('should parse the config with a function', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(cjsConfigs.nextConfigWithFunc, configType); | ||
it('should parse the config with a function', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(cjsConfigs.nextConfigWithFunc, configType); | ||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))'); | ||
}); | ||
it('should parse the config with a function on a new line', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(cjsConfigs.nextConfigWithFuncMultiline, configType); | ||
it('should parse the config with a multi-lined function', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(cjsConfigs.nextConfigWithFuncMultiline, configType); | ||
expect(modifiedConfigContent).toContain(requireStatement); | ||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/); | ||
}); | ||
it('should parse the config with a named export as default', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(cjsConfigs.nextConfigExportNamedDefault, configType); | ||
it('should parse the config with a named export as default', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(cjsConfigs.nextConfigExportNamedDefault, configType); | ||
expect(modifiedConfigContent).toContain(requireStatement); | ||
expect(modifiedConfigContent).toContain('withPayload(wrapped)'); | ||
}); | ||
it('should parse the config with a spread', ()=>{ | ||
const { modifiedConfigContent } = parseAndModifyConfigContent(cjsConfigs.nextConfigWithSpread, configType); | ||
it('should parse the config with a spread', async ()=>{ | ||
const { modifiedConfigContent } = await parseAndModifyConfigContent(cjsConfigs.nextConfigWithSpread, configType); | ||
expect(modifiedConfigContent).toContain(requireStatement); | ||
@@ -115,0 +165,0 @@ expect(modifiedConfigContent).toContain('withPayload(nextConfig)'); |
@@ -18,7 +18,9 @@ import fs from 'fs-extra'; | ||
fileContents = `# Added by Payload\n` + envFile.split('\n').filter((e)=>e).map((line)=>{ | ||
if (line.startsWith('#') || !line.includes('=')) return 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') { | ||
if (key === 'MONGODB_URI' || key === 'MONGO_URL' || key === 'DATABASE_URI' || key === 'POSTGRES_URL') { | ||
value = databaseUri; | ||
@@ -25,0 +27,0 @@ } |
@@ -19,3 +19,3 @@ import * as p from '@clack/prompts'; | ||
import { error, info } from './utils/log.js'; | ||
import { feedbackOutro, helpMessage, moveMessage, successMessage, successfulNextInit } from './utils/messages.js'; | ||
import { feedbackOutro, helpMessage, moveMessage, successfulNextInit, successMessage } from './utils/messages.js'; | ||
export class Main { | ||
@@ -39,2 +39,3 @@ args; | ||
'--no-deps': Boolean, | ||
'--use-bun': Boolean, | ||
'--use-npm': Boolean, | ||
@@ -70,3 +71,8 @@ '--use-pnpm': Boolean, | ||
const nextAppDetails = await getNextAppDetails(process.cwd()); | ||
const { hasTopLevelLayout, isPayloadInstalled, nextAppDir, nextConfigPath } = nextAppDetails; | ||
const { hasTopLevelLayout, isPayloadInstalled, isSupportedNextVersion, nextAppDir, nextConfigPath, nextVersion } = nextAppDetails; | ||
if (nextConfigPath && !isSupportedNextVersion) { | ||
p.log.warn(`Next.js v${nextVersion} is unsupported. Next.js >= 15 is required to use Payload.`); | ||
p.outro(feedbackOutro()); | ||
process.exit(0); | ||
} | ||
// Upgrade Payload in existing project | ||
@@ -73,0 +79,0 @@ if (isPayloadInstalled && nextConfigPath) { |
@@ -10,3 +10,3 @@ import fs from 'fs'; | ||
/** | ||
* Copy the necessary template files from `templates/blank-3.0` to `dist/template` | ||
* Copy the necessary template files from `templates/blank` to `dist/template` | ||
* | ||
@@ -17,3 +17,3 @@ * Eventually, this should be replaced with using tar.x to stream from the git repo | ||
const outputPath = path.resolve(dirname, '../../dist/template'); | ||
const sourceTemplatePath = path.resolve(root, 'templates/blank-3.0'); | ||
const sourceTemplatePath = path.resolve(root, 'templates/blank'); | ||
if (!fs.existsSync(sourceTemplatePath)) { | ||
@@ -27,3 +27,3 @@ throw new Error(`Source path does not exist: ${sourceTemplatePath}`); | ||
} | ||
// Copy the src directory from `templates/blank-3.0` to `dist` | ||
// Copy the src directory from `templates/blank` to `dist` | ||
const srcPath = path.resolve(sourceTemplatePath, 'src'); | ||
@@ -30,0 +30,0 @@ const distSrcPath = path.resolve(outputPath, 'src'); |
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ | ||
/* DO NOT MODIFY it because it could be re-written at any time. */ | ||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ | ||
import config from '@payload-config' | ||
@@ -4,0 +4,0 @@ import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes' |
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ | ||
/* DO NOT MODIFY it because it could be re-written at any time. */ | ||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ | ||
import config from '@payload-config' | ||
@@ -4,0 +4,0 @@ import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes' |
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ | ||
/* DO NOT MODIFY it because it could be re-written at any time. */ | ||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ | ||
import config from '@payload-config' | ||
@@ -4,0 +4,0 @@ import { GRAPHQL_POST } from '@payloadcms/next/routes' |
@@ -18,2 +18,5 @@ // storage-adapter-import-placeholder | ||
user: Users.slug, | ||
importMap: { | ||
baseDir: path.resolve(dirname), | ||
}, | ||
}, | ||
@@ -20,0 +23,0 @@ collections: [Users, Media], |
@@ -18,2 +18,3 @@ import type arg from 'arg'; | ||
'--template-branch': StringConstructor; | ||
'--use-bun': BooleanConstructor; | ||
'--use-npm': BooleanConstructor; | ||
@@ -50,3 +51,3 @@ '--use-pnpm': BooleanConstructor; | ||
export type PackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'; | ||
export type DbType = 'mongodb' | 'postgres'; | ||
export type DbType = 'mongodb' | 'postgres' | 'sqlite' | 'vercel-postgres'; | ||
export type DbDetails = { | ||
@@ -61,9 +62,11 @@ dbUri: string; | ||
isSrcDir: boolean; | ||
isSupportedNextVersion: boolean; | ||
nextAppDir?: string; | ||
nextConfigPath?: string; | ||
nextConfigType?: NextConfigType; | ||
nextVersion: null | string; | ||
}; | ||
export type NextConfigType = 'cjs' | 'esm'; | ||
export type NextConfigType = 'cjs' | 'esm' | 'ts'; | ||
export type StorageAdapterType = 'localDisk' | 'payloadCloud' | 'vercelBlobStorage'; | ||
export {}; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -6,3 +6,3 @@ /** | ||
*/ | ||
export declare function copyRecursiveSync(src: string, dest: string, debug?: boolean): void; | ||
export declare function copyRecursiveSync(src: string, dest: string): void; | ||
//# sourceMappingURL=copy-recursive-sync.d.ts.map |
@@ -7,3 +7,3 @@ import fs from 'fs'; | ||
* @internal | ||
*/ export function copyRecursiveSync(src, dest, debug) { | ||
*/ export function copyRecursiveSync(src, dest) { | ||
const exists = fs.existsSync(src); | ||
@@ -10,0 +10,0 @@ const stats = exists && fs.statSync(src); |
@@ -35,2 +35,3 @@ /* eslint-disable no-console */ import chalk from 'chalk'; | ||
--use-pnpm Use pnpm to install dependencies | ||
--use-bun Use bun to install dependencies (experimental) | ||
--no-deps Do not install any dependencies | ||
@@ -37,0 +38,0 @@ -h Show help |
{ | ||
"name": "create-payload-app", | ||
"version": "3.0.0-canary.4de9e42", | ||
"version": "3.0.0-canary.4eb1bfc", | ||
"homepage": "https://payloadcms.com", | ||
@@ -44,6 +44,6 @@ "repository": { | ||
"@sindresorhus/slugify": "^1.1.0", | ||
"@swc/core": "1.7.10", | ||
"arg": "^5.0.0", | ||
"chalk": "^4.1.0", | ||
"comment-json": "^4.2.3", | ||
"degit": "^2.8.4", | ||
"esprima-next": "^6.0.3", | ||
@@ -54,11 +54,14 @@ "execa": "^5.0.0", | ||
"globby": "11.1.0", | ||
"tar": "^7.4.3", | ||
"terminal-link": "^2.1.1" | ||
}, | ||
"devDependencies": { | ||
"@types/degit": "^2.8.3", | ||
"@types/esprima": "^4.0.6", | ||
"@types/fs-extra": "^9.0.12", | ||
"@types/jest": "29.5.12", | ||
"@types/node": "20.12.5" | ||
"@types/node": "22.5.4" | ||
}, | ||
"engines": { | ||
"node": "^18.20.2 || >=20.9.0" | ||
}, | ||
"scripts": { | ||
@@ -68,3 +71,5 @@ "build": "pnpm pack-template-files && pnpm typecheck && pnpm build:swc", | ||
"clean": "rimraf {dist,*.tsbuildinfo}", | ||
"pack-template-files": "tsx src/scripts/pack-template-files.ts", | ||
"lint": "eslint .", | ||
"lint:fix": "eslint . --fix", | ||
"pack-template-files": "node --no-deprecation --import @swc-node/register/esm-register src/scripts/pack-template-files.ts", | ||
"test": "jest", | ||
@@ -71,0 +76,0 @@ "typecheck": "tsc" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
233605
4
116
2356
13
16
2
+ Added@swc/core@1.7.10
+ Addedtar@^7.4.3
+ Added@isaacs/cliui@8.0.2(transitive)
+ Added@isaacs/fs-minipass@4.0.1(transitive)
+ Added@pkgjs/parseargs@0.11.0(transitive)
+ Added@swc/core@1.7.10(transitive)
+ Added@swc/core-darwin-arm64@1.7.10(transitive)
+ Added@swc/core-darwin-x64@1.7.10(transitive)
+ Added@swc/core-linux-arm-gnueabihf@1.7.10(transitive)
+ Added@swc/core-linux-arm64-gnu@1.7.10(transitive)
+ Added@swc/core-linux-arm64-musl@1.7.10(transitive)
+ Added@swc/core-linux-x64-gnu@1.7.10(transitive)
+ Added@swc/core-linux-x64-musl@1.7.10(transitive)
+ Added@swc/core-win32-arm64-msvc@1.7.10(transitive)
+ Added@swc/core-win32-ia32-msvc@1.7.10(transitive)
+ Added@swc/core-win32-x64-msvc@1.7.10(transitive)
+ Added@swc/counter@0.1.3(transitive)
+ Added@swc/types@0.1.17(transitive)
+ Addedansi-regex@5.0.16.1.0(transitive)
+ Addedansi-styles@6.2.1(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@2.0.1(transitive)
+ Addedchownr@3.0.0(transitive)
+ Addedeastasianwidth@0.2.0(transitive)
+ Addedemoji-regex@8.0.09.2.2(transitive)
+ Addedforeground-child@3.3.0(transitive)
+ Addedglob@10.4.5(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedjackspeak@3.4.3(transitive)
+ Addedlru-cache@10.4.3(transitive)
+ Addedminimatch@9.0.5(transitive)
+ Addedminipass@7.1.2(transitive)
+ Addedminizlib@3.0.1(transitive)
+ Addedmkdirp@3.0.1(transitive)
+ Addedpackage-json-from-dist@1.0.1(transitive)
+ Addedpath-scurry@1.11.1(transitive)
+ Addedrimraf@5.0.10(transitive)
+ Addedsignal-exit@4.1.0(transitive)
+ Addedstring-width@4.2.35.1.2(transitive)
+ Addedstrip-ansi@6.0.17.1.0(transitive)
+ Addedtar@7.4.3(transitive)
+ Addedwrap-ansi@7.0.08.1.0(transitive)
+ Addedyallist@5.0.0(transitive)
- Removeddegit@^2.8.4
- Removeddegit@2.8.4(transitive)