Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
fab0708
chore(create-payload-app): add ts-morph and create AST directory stru…
denolfe Nov 15, 2025
68ba02e
feat(create-payload-app): add AST transformation types
denolfe Nov 15, 2025
d791fea
feat(create-payload-app): add error formatting utility
denolfe Nov 15, 2025
a1934a6
feat(create-payload-app): add findImportDeclaration helper
denolfe Nov 15, 2025
57d21cf
feat(create-payload-app): add addImportDeclaration helper
denolfe Nov 15, 2025
3101b0d
feat(create-payload-app): add removeImportDeclaration helper
denolfe Nov 15, 2025
02741b7
feat(create-payload-app): add buildConfig detection
denolfe Nov 15, 2025
10f80c9
feat(create-payload-app): add database adapter transformation
denolfe Nov 15, 2025
849e2cf
feat(create-payload-app): add storage adapter transformation
denolfe Nov 15, 2025
92e1fc7
feat(create-payload-app): add sharp removal transformation
denolfe Nov 15, 2025
9bfb7ae
feat(create-payload-app): add structural validation
denolfe Nov 15, 2025
157d93a
feat(create-payload-app): add file writing with prettier formatting
denolfe Nov 15, 2025
0aad07e
feat(create-payload-app): add package.json transformation
denolfe Nov 15, 2025
ef4671b
feat(create-payload-app): add high-level configurePayloadConfig API
denolfe Nov 15, 2025
73e0b8c
refactor(create-payload-app): replace string manipulation with AST
denolfe Nov 15, 2025
736991e
refactor(templates): remove comment markers and normalize structures
denolfe Nov 15, 2025
cca5fee
chore(create-payload-app): remove obsolete replacements.ts
denolfe Nov 15, 2025
d3d58a0
chore(create-payload-app): update imports and dependencies
denolfe Nov 15, 2025
624cd33
test(create-payload-app): add AST integration tests
denolfe Nov 15, 2025
2db29e6
docs(create-payload-app): document AST architecture
denolfe Nov 15, 2025
845662e
fix(create-payload-app): resolve TypeScript errors in AST files
denolfe Nov 15, 2025
fefa73c
chore(create-payload-app): remove empty plugin-project.ts file
denolfe Nov 15, 2025
ab5bce8
fix(create-payload-app): add missing storage adapter types (s3Storage…
denolfe Nov 15, 2025
2018bae
chore(create-payload-app): remove obsolete replacements.ts
denolfe Nov 15, 2025
323537d
refactor(create-payload-app): centralize adapter configuration to red…
denolfe Nov 15, 2025
7a1b25a
refactor(create-payload-app): prefer types over interfaces in AST code
denolfe Nov 15, 2025
926b449
feat(create-payload-app): add comprehensive debug logging to AST oper…
denolfe Nov 15, 2025
8e80e92
refactor(create-payload-app): eliminate debugMode parameters from AST…
denolfe Nov 15, 2025
71b27c4
refactor(create-payload-app): use object parameters for AST functions…
denolfe Nov 15, 2025
dfbc1ec
refactor(create-payload-app): use execSync for prettier formatting in…
denolfe Nov 15, 2025
b951751
feat(create-payload-app): preserve import and property positions when…
denolfe Nov 15, 2025
0ca30a6
chore: cleanup, db types as const
denolfe Nov 16, 2025
39a84bd
chore: add upload test artifact to .gitignore
denolfe Nov 16, 2025
56a8ba4
fix: use proper adapter replacements
denolfe Nov 16, 2025
7c37f33
fix: always use npx for executing prettier
denolfe Nov 16, 2025
a64d413
fix: remove @vercel/postgres import
denolfe Nov 16, 2025
9bda19e
chore: template cleanup
denolfe Nov 16, 2025
69317ec
fix: properly add to plugins array
denolfe Nov 17, 2025
20d136e
refactor: co-locate unit tests
denolfe Nov 18, 2025
964c77c
chore: adjust ast parsing approach
denolfe Nov 26, 2025
3099bbb
test: add more int tests, handle alias
denolfe Nov 26, 2025
dc72293
test: remove unnecessary/redundant tests
denolfe Nov 26, 2025
be0c578
test: remove more
denolfe Nov 26, 2025
eca5750
chore: improve package management flow
denolfe Nov 26, 2025
5812c22
chore: update CLAUDE.md
denolfe Nov 26, 2025
1544a86
fix: handle storage adapters in gen-templates, add AST workaround
denolfe Nov 26, 2025
8146668
chore: use pure functions
denolfe Dec 3, 2025
8a23079
chore: sourceFile return values
denolfe Dec 3, 2025
95f2c74
chore: get replacements from main
denolfe Dec 3, 2025
0c69fbd
chore: remove replacements for merge conflict
denolfe Dec 3, 2025
d4e6665
Merge branch 'main' into feat/cpa-ast-parsing
denolfe Dec 3, 2025
9d24990
refactor(cpa): consolidate adapter types to single source of truth
denolfe Dec 3, 2025
db53c82
Merge remote-tracking branch 'origin' into feat/cpa-ast-parsing
denolfe Dec 3, 2025
fe5a8f2
Merge branch 'main' into feat/cpa-ast-parsing
denolfe Dec 4, 2025
b22e402
Merge branch 'main' into feat/cpa-ast-parsing
denolfe Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ test/media
*payloadtests.db-shm
*payloadtests.db-wal
/versions
no-restrict-file-*

# Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=node,macos,windows,webstorm,sublimetext,visualstudiocode
Expand Down
120 changes: 120 additions & 0 deletions packages/create-payload-app/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# create-payload-app

Package for creating new Payload projects with custom configurations.

## Architecture

### AST-Based File Modification

create-payload-app uses AST (Abstract Syntax Tree) parsing with ts-morph to modify template files during project creation.

**3-Phase Pipeline:**

1. **Detection** - Parse files and locate expected structures
2. **Transformation** - Pure functions modify AST
3. **Modification** - Validate, write, format with prettier

### File Structure

```txt
src/lib/ast/
├── types.ts # Shared types for AST operations
├── utils.ts # Low-level AST helpers
├── utils.spec.ts # Unit tests for utils
├── payload-config.ts # Payload config transformations
├── payload-config.spec.ts # Unit tests for payload-config
├── package-json.ts # package.json modifications
└── package-json.spec.ts # Unit tests for package-json
```

### Key Functions

**High-level API:**

- `configurePayloadConfig(filePath, options)` - Main entry point for payload config
- `updatePackageJson(filePath, options)` - Update package.json

**Transformations:**

- `addDatabaseAdapter(sourceFile, adapter, envVar)` - Add/replace db adapter
- `addStorageAdapter(sourceFile, adapter)` - Add storage plugin
- `removeSharp(sourceFile)` - Remove sharp dependency

### Templates

All templates follow standard structure:

- `buildConfig()` call with object literal argument
- `db` property for database adapter
- `plugins` array (can be empty)

No comment markers needed - AST finds structure by code patterns.

### Package Management Flows

create-payload-app has two distinct flows for handling package installation:

#### Flow 1: `--init-next` (Next.js Integration)

```mermaid
sequenceDiagram
participant CLI
participant initNext
participant FS as File System
participant PM as Package Manager
participant AST as AST Operations

CLI->>initNext: --init-next with dbType
initNext->>FS: Copy template files
initNext->>PM: pnpm add payload @payloadcms/next @payloadcms/db-{type}
PM->>FS: Install packages to node_modules
initNext->>AST: configurePayloadConfig(filePath, {db})
AST->>FS: Update payload.config.ts (imports/config)
AST->>FS: Update package.json (dependencies)
initNext->>CLI: ✓ Project ready
```

**Key points:**

- Uses `pnpm add` to install specific packages
- Packages installed before AST modifications
- No final `pnpm install` step

#### Flow 2: Template/Example Creation

```mermaid
sequenceDiagram
participant CLI
participant createProject
participant FS as File System
participant AST as AST Operations
participant PM as Package Manager

CLI->>createProject: --template with dbType
createProject->>FS: Copy template/example files
createProject->>AST: configurePayloadConfig(projectDir, {db})
AST->>FS: Update payload.config.ts (imports/config)
AST->>FS: Update package.json (dependencies)
createProject->>PM: pnpm install
PM->>FS: Install packages from package.json
createProject->>CLI: ✓ Project ready
```

**Key points:**

- Updates package.json first
- Single `pnpm install` at end installs all dependencies
- Package manager resolves dependencies from package.json

**Package Operations:**

- **Install**: Runs actual `pnpm add` or `pnpm install` command (Flow 1 or Flow 2 respectively)
- **Package removal**: AST removes imports and updates package.json (orphaned packages cleaned up on next install)
- No explicit `pnpm remove` - package.json modifications only

### Testing

**Unit tests:** Test individual transformation functions
**Integration tests:** Test full create-payload-app flows

Run tests: `pnpm --filter create-payload-app test`
4 changes: 3 additions & 1 deletion packages/create-payload-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@
"figures": "^6.1.0",
"fs-extra": "^9.0.1",
"globby": "11.1.0",
"prettier": "^3.5.0",
"tar": "^7.4.3",
"terminal-link": "^2.1.1"
"terminal-link": "^2.1.1",
"ts-morph": "^21.0.1"
},
"devDependencies": {
"@types/esprima": "^4.0.6",
Expand Down
155 changes: 155 additions & 0 deletions packages/create-payload-app/src/ast-integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import * as fs from 'fs'
import * as fse from 'fs-extra'
import * as path from 'path'
import * as os from 'os'
import { configurePayloadConfig } from './lib/configure-payload-config'
import type { DbType, StorageAdapterType } from './types'
import { DB_ADAPTER_CONFIG, STORAGE_ADAPTER_CONFIG } from './lib/ast/adapter-config'

interface TestCase {
name: string
template: string
dbType: DbType
storageAdapter: StorageAdapterType
}

const TEST_CASES: TestCase[] = [
{
name: 'blank + mongodb + localDisk',
template: 'blank',
dbType: 'mongodb',
storageAdapter: 'localDisk',
},
{
name: 'blank + postgres + vercelBlobStorage',
template: 'blank',
dbType: 'postgres',
storageAdapter: 'vercelBlobStorage',
},
{
name: 'website + mongodb + s3Storage',
template: 'website',
dbType: 'mongodb',
storageAdapter: 's3Storage',
},
{
name: 'website + postgres + localDisk',
template: 'website',
dbType: 'postgres',
storageAdapter: 'localDisk',
},
{
name: 'ecommerce + mongodb + localDisk',
template: 'ecommerce',
dbType: 'mongodb',
storageAdapter: 'localDisk',
},
{
name: 'ecommerce + postgres + r2Storage',
template: 'ecommerce',
dbType: 'postgres',
storageAdapter: 'r2Storage',
},
]

describe('AST Integration Tests', () => {
let tempDir: string
const templatesRoot = path.resolve(__dirname, '../../..', 'templates')

beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'payload-ast-integration-'))
})

afterEach(() => {
if (tempDir && fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true })
}
})

describe.each(TEST_CASES)('$name', ({ template, dbType, storageAdapter }) => {
it('successfully applies AST transformations', async () => {
// Setup: Copy template to temp directory
const templateDir = path.join(templatesRoot, template)
const testProjectDir = path.join(tempDir, template)

if (!fs.existsSync(templateDir)) {
throw new Error(`Template ${template} not found at ${templateDir}`)
}

fse.copySync(templateDir, testProjectDir)

const payloadConfigPath = path.join(testProjectDir, 'src', 'payload.config.ts')
const packageJsonPath = path.join(testProjectDir, 'package.json')

// Verify files exist before transformation
expect(fs.existsSync(payloadConfigPath)).toBe(true)
expect(fs.existsSync(packageJsonPath)).toBe(true)

// Apply transformations
await configurePayloadConfig({
dbType,
storageAdapter,
projectDirOrConfigPath: { projectDir: testProjectDir },
})

// Verify payload.config.ts transformations
const configContent = fs.readFileSync(payloadConfigPath, 'utf-8')

// Check database adapter import
const dbConfig = DB_ADAPTER_CONFIG[dbType]
expect(configContent).toContain(`from '${dbConfig.packageName}'`)
expect(configContent).toContain(`import { ${dbConfig.adapterName} }`)

// Check database adapter config
expect(configContent).toMatch(new RegExp(`db:\\s*${dbConfig.adapterName}\\(`))

// Check storage adapter if not localDisk
if (storageAdapter !== 'localDisk') {
const storageConfig = STORAGE_ADAPTER_CONFIG[storageAdapter]

if (storageConfig.packageName && storageConfig.adapterName) {
expect(configContent).toContain(`from '${storageConfig.packageName}'`)
expect(configContent).toContain(`import { ${storageConfig.adapterName} }`)
expect(configContent).toContain(`${storageConfig.adapterName}(`)
}
}

// Check that old mongodb adapter is removed if we switched to postgres
if (dbType === 'postgres') {
expect(configContent).not.toContain('@payloadcms/db-mongodb')
expect(configContent).not.toContain('mongooseAdapter')
}

// Check that plugins array exists if storage adapter was added
if (storageAdapter !== 'localDisk') {
expect(configContent).toContain('plugins:')
}

// Verify package.json transformations
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))

// Check that correct db adapter package is in dependencies
expect(packageJson.dependencies[dbConfig.packageName]).toBeDefined()

// Check that old db adapters are removed
Object.entries(DB_ADAPTER_CONFIG).forEach(([key, config]) => {
if (key !== dbType && config.packageName !== dbConfig.packageName) {
expect(packageJson.dependencies[config.packageName]).toBeUndefined()
}
})

// Note: Storage adapter dependencies are NOT automatically added to package.json
// by configurePayloadConfig - only the payload.config.ts is updated.
// This is expected behavior as storage adapters are typically installed separately.

// Verify file is valid TypeScript (basic syntax check)
expect(configContent).toContain('buildConfig')
expect(configContent).toContain('export default')

// Verify no placeholder comments remain
expect(configContent).not.toContain('database-adapter-import')
expect(configContent).not.toContain('database-adapter-config-start')
expect(configContent).not.toContain('storage-adapter-placeholder')
})
})
})
Loading
Loading