diff --git a/.gitignore b/.gitignore index 6bb00e0..df97d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ yarn-error.log* /playwright-report/ /blob-report/ /playwright/.cache/ +/dev.db diff --git a/dev/payload.config.ts b/dev/payload.config.ts index 9f809cb..88692e6 100644 --- a/dev/payload.config.ts +++ b/dev/payload.config.ts @@ -1,6 +1,5 @@ -import { mongooseAdapter } from '@payloadcms/db-mongodb' + import { lexicalEditor } from '@payloadcms/richtext-lexical' -import { MongoMemoryReplSet } from 'mongodb-memory-server' import path from 'path' import { buildConfig } from 'payload' import { payloadFeatureFlags } from '../src/index.js' @@ -9,6 +8,7 @@ import { fileURLToPath } from 'url' import { testEmailAdapter } from './helpers/testEmailAdapter.js' import { seed } from './seed.js' +import {sqliteAdapter} from "@payloadcms/db-sqlite" const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -17,221 +17,203 @@ if (!process.env.ROOT_DIR) { process.env.ROOT_DIR = dirname } -const buildConfigWithMemoryDB = async () => { - // Use in-memory MongoDB for both test and development - if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV !== 'production') { - console.log('🗃️ Starting MongoDB Memory Server...') - const memoryDB = await MongoMemoryReplSet.create({ - replSet: { - count: 1, - dbName: 'payload-feature-flags-dev', +export default buildConfig({ + admin: { + importMap: { + baseDir: path.resolve(dirname), + }, + }, + collections: [ + { + slug: 'posts', + admin: { + useAsTitle: 'title', }, - }) - - const uri = memoryDB.getUri() - process.env.DATABASE_URI = `${uri}&retryWrites=true` - console.log('✅ MongoDB Memory Server started successfully') - } - - return buildConfig({ - admin: { - importMap: { - baseDir: path.resolve(dirname), + fields: [ + { + name: 'title', + type: 'text', + required: true, + }, + { + name: 'content', + type: 'richText', + }, + { + name: 'status', + type: 'select', + options: ['draft', 'published'], + defaultValue: 'draft', + }, + { + name: 'publishedAt', + type: 'date', + }, + ], + }, + { + slug: 'pages', + admin: { + useAsTitle: 'title', + }, + fields: [ + { + name: 'title', + type: 'text', + required: true, + }, + { + name: 'slug', + type: 'text', + required: true, + unique: true, + }, + { + name: 'content', + type: 'richText', + }, + { + name: 'layout', + type: 'select', + options: ['default', 'landing', 'sidebar'], + defaultValue: 'default', + }, + ], + }, + { + slug: 'users', + admin: { + useAsTitle: 'email', + }, + auth: true, + fields: [ + { + name: 'name', + type: 'text', + }, + { + name: 'role', + type: 'select', + options: ['admin', 'editor', 'user'], + defaultValue: 'user', + }, + ], + }, + { + slug: 'media', + fields: [ + { + name: 'alt', + type: 'text', + }, + ], + upload: { + staticDir: path.resolve(dirname, 'media'), }, }, - collections: [ - { - slug: 'posts', + ], + db: sqliteAdapter({ + client: { + url: process.env.DATABASE_URI || 'file:./dev.db', + }, + }), + editor: lexicalEditor(), + email: testEmailAdapter, + onInit: async (payload) => { + await seed(payload) + }, + plugins: [ + payloadFeatureFlags({ + // Enable all features + enableRollouts: true, + enableVariants: true, + enableApi: true, + defaultValue: false, + + // Custom collection configuration + collectionOverrides: { admin: { - useAsTitle: 'title', + useAsTitle: 'name', + group: 'Configuration', + description: 'Manage feature flags for the development environment', }, - fields: [ + access: { + // Only authenticated users can read/manage feature flags + read: ({ req: { user } }) => !!user, + create: ({ req: { user } }) => !!user, + update: ({ req: { user } }) => user?.role === 'admin', + delete: ({ req: { user } }) => user?.role === 'admin', + }, + fields: ({ defaultFields }) => [ + ...defaultFields, { - name: 'title', - type: 'text', - required: true, - }, - { - name: 'content', - type: 'richText', - }, - { - name: 'status', + name: 'environment', type: 'select', - options: ['draft', 'published'], - defaultValue: 'draft', + options: [ + { label: 'Development', value: 'development' }, + { label: 'Staging', value: 'staging' }, + { label: 'Production', value: 'production' }, + ], + required: true, + defaultValue: 'development', + admin: { + description: 'Which environment this flag applies to', + }, }, { - name: 'publishedAt', + name: 'owner', + type: 'relationship', + relationTo: 'users', + admin: { + description: 'Team member responsible for this feature flag', + }, + }, + { + name: 'expiresAt', type: 'date', + admin: { + description: 'Optional expiration date for temporary flags', + }, + }, + { + name: 'jiraTicket', + type: 'text', + admin: { + description: 'Related JIRA ticket or issue number', + }, }, ], - }, - { - slug: 'pages', - admin: { - useAsTitle: 'title', - }, - fields: [ - { - name: 'title', - type: 'text', - required: true, - }, - { - name: 'slug', - type: 'text', - required: true, - unique: true, - }, - { - name: 'content', - type: 'richText', - }, - { - name: 'layout', - type: 'select', - options: ['default', 'landing', 'sidebar'], - defaultValue: 'default', - }, - ], - }, - { - slug: 'users', - admin: { - useAsTitle: 'email', - }, - auth: true, - fields: [ - { - name: 'name', - type: 'text', - }, - { - name: 'role', - type: 'select', - options: ['admin', 'editor', 'user'], - defaultValue: 'user', - }, - ], - }, - { - slug: 'media', - fields: [ - { - name: 'alt', - type: 'text', - }, - ], - upload: { - staticDir: path.resolve(dirname, 'media'), - }, - }, - ], - db: mongooseAdapter({ - ensureIndexes: true, - url: process.env.DATABASE_URI || 'mongodb://localhost/payload-feature-flags-dev', - }), - editor: lexicalEditor(), - email: testEmailAdapter, - onInit: async (payload) => { - await seed(payload) - }, - plugins: [ - payloadFeatureFlags({ - // Enable all features - enableRollouts: true, - enableVariants: true, - enableApi: true, - defaultValue: false, + hooks: { + beforeChange: [ + async ({ data, req, operation }) => { + // Auto-assign current user as owner for new flags + if (operation === 'create' && !data.owner && req.user) { + data.owner = req.user.id + } - // Custom collection configuration - collectionOverrides: { - admin: { - useAsTitle: 'name', - group: 'Configuration', - description: 'Manage feature flags for the development environment', - }, - access: { - // Only authenticated users can read/manage feature flags - read: ({ req: { user } }) => !!user, - create: ({ req: { user } }) => !!user, - update: ({ req: { user } }) => user?.role === 'admin', - delete: ({ req: { user } }) => user?.role === 'admin', - }, - fields: ({ defaultFields }) => [ - ...defaultFields, - { - name: 'environment', - type: 'select', - options: [ - { label: 'Development', value: 'development' }, - { label: 'Staging', value: 'staging' }, - { label: 'Production', value: 'production' }, - ], - required: true, - defaultValue: 'development', - admin: { - description: 'Which environment this flag applies to', - }, - }, - { - name: 'owner', - type: 'relationship', - relationTo: 'users', - admin: { - description: 'Team member responsible for this feature flag', - }, - }, - { - name: 'expiresAt', - type: 'date', - admin: { - description: 'Optional expiration date for temporary flags', - }, - }, - { - name: 'jiraTicket', - type: 'text', - admin: { - description: 'Related JIRA ticket or issue number', - }, + // Log flag changes for audit trail + if (req.user) { + console.log(`Feature flag "${data.name}" ${operation} by ${req.user.email}`) + } + + return data + }, + ], + afterChange: [ + async ({ doc, req, operation }) => { + // Send notification for critical flag changes + if (doc.environment === 'production' && req.user) { + console.log(`🚨 Production feature flag "${doc.name}" was ${operation === 'create' ? 'created' : 'modified'} by ${req.user.email}`) + } }, ], - hooks: { - beforeChange: [ - async ({ data, req, operation }) => { - // Auto-assign current user as owner for new flags - if (operation === 'create' && !data.owner && req.user) { - data.owner = req.user.id - } - - // Log flag changes for audit trail - if (req.user) { - console.log(`Feature flag "${data.name}" ${operation} by ${req.user.email}`) - } - - return data - }, - ], - afterChange: [ - async ({ doc, req, operation }) => { - // Send notification for critical flag changes - if (doc.environment === 'production' && req.user) { - console.log(`🚨 Production feature flag "${doc.name}" was ${operation === 'create' ? 'created' : 'modified'} by ${req.user.email}`) - } - }, - ], - }, }, - }), - ], - secret: process.env.PAYLOAD_SECRET || 'dev-secret-key-change-in-production', - sharp, - typescript: { - outputFile: path.resolve(dirname, 'payload-types.ts'), - }, - }) -} - -export default buildConfigWithMemoryDB() + }, + }), + ], + secret: process.env.PAYLOAD_SECRET || 'dev-secret-key-change-in-production', + sharp, + typescript: { + outputFile: path.resolve(dirname, 'payload-types.ts'), + }, +}) diff --git a/package.json b/package.json index 7006a73..a27d608 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/payload-feature-flags", - "version": "0.0.4", + "version": "0.0.5", "description": "Feature flags plugin for Payload CMS - manage feature toggles, A/B tests, and gradual rollouts", "license": "MIT", "type": "module",