mirror of
https://github.com/xtr-dev/payload-feature-flags.git
synced 2025-12-10 02:43:25 +00:00
Bump version to 0.0.5
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,3 +47,4 @@ yarn-error.log*
|
|||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
|
/dev.db
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
|
||||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||||
import { MongoMemoryReplSet } from 'mongodb-memory-server'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { buildConfig } from 'payload'
|
import { buildConfig } from 'payload'
|
||||||
import { payloadFeatureFlags } from '../src/index.js'
|
import { payloadFeatureFlags } from '../src/index.js'
|
||||||
@@ -9,6 +8,7 @@ import { fileURLToPath } from 'url'
|
|||||||
|
|
||||||
import { testEmailAdapter } from './helpers/testEmailAdapter.js'
|
import { testEmailAdapter } from './helpers/testEmailAdapter.js'
|
||||||
import { seed } from './seed.js'
|
import { seed } from './seed.js'
|
||||||
|
import {sqliteAdapter} from "@payloadcms/db-sqlite"
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
@@ -17,221 +17,203 @@ if (!process.env.ROOT_DIR) {
|
|||||||
process.env.ROOT_DIR = dirname
|
process.env.ROOT_DIR = dirname
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildConfigWithMemoryDB = async () => {
|
export default buildConfig({
|
||||||
// Use in-memory MongoDB for both test and development
|
admin: {
|
||||||
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV !== 'production') {
|
importMap: {
|
||||||
console.log('🗃️ Starting MongoDB Memory Server...')
|
baseDir: path.resolve(dirname),
|
||||||
const memoryDB = await MongoMemoryReplSet.create({
|
},
|
||||||
replSet: {
|
},
|
||||||
count: 1,
|
collections: [
|
||||||
dbName: 'payload-feature-flags-dev',
|
{
|
||||||
|
slug: 'posts',
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'title',
|
||||||
},
|
},
|
||||||
})
|
fields: [
|
||||||
|
{
|
||||||
const uri = memoryDB.getUri()
|
name: 'title',
|
||||||
process.env.DATABASE_URI = `${uri}&retryWrites=true`
|
type: 'text',
|
||||||
console.log('✅ MongoDB Memory Server started successfully')
|
required: true,
|
||||||
}
|
},
|
||||||
|
{
|
||||||
return buildConfig({
|
name: 'content',
|
||||||
admin: {
|
type: 'richText',
|
||||||
importMap: {
|
},
|
||||||
baseDir: path.resolve(dirname),
|
{
|
||||||
|
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: [
|
],
|
||||||
{
|
db: sqliteAdapter({
|
||||||
slug: 'posts',
|
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: {
|
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',
|
name: 'environment',
|
||||||
type: 'text',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'content',
|
|
||||||
type: 'richText',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: ['draft', 'published'],
|
options: [
|
||||||
defaultValue: 'draft',
|
{ 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',
|
type: 'date',
|
||||||
|
admin: {
|
||||||
|
description: 'Optional expiration date for temporary flags',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'jiraTicket',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
description: 'Related JIRA ticket or issue number',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
hooks: {
|
||||||
{
|
beforeChange: [
|
||||||
slug: 'pages',
|
async ({ data, req, operation }) => {
|
||||||
admin: {
|
// Auto-assign current user as owner for new flags
|
||||||
useAsTitle: 'title',
|
if (operation === 'create' && !data.owner && req.user) {
|
||||||
},
|
data.owner = req.user.id
|
||||||
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,
|
|
||||||
|
|
||||||
// Custom collection configuration
|
// Log flag changes for audit trail
|
||||||
collectionOverrides: {
|
if (req.user) {
|
||||||
admin: {
|
console.log(`Feature flag "${data.name}" ${operation} by ${req.user.email}`)
|
||||||
useAsTitle: 'name',
|
}
|
||||||
group: 'Configuration',
|
|
||||||
description: 'Manage feature flags for the development environment',
|
return data
|
||||||
},
|
},
|
||||||
access: {
|
],
|
||||||
// Only authenticated users can read/manage feature flags
|
afterChange: [
|
||||||
read: ({ req: { user } }) => !!user,
|
async ({ doc, req, operation }) => {
|
||||||
create: ({ req: { user } }) => !!user,
|
// Send notification for critical flag changes
|
||||||
update: ({ req: { user } }) => user?.role === 'admin',
|
if (doc.environment === 'production' && req.user) {
|
||||||
delete: ({ req: { user } }) => user?.role === 'admin',
|
console.log(`🚨 Production feature flag "${doc.name}" was ${operation === 'create' ? 'created' : 'modified'} by ${req.user.email}`)
|
||||||
},
|
}
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
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,
|
secret: process.env.PAYLOAD_SECRET || 'dev-secret-key-change-in-production',
|
||||||
typescript: {
|
sharp,
|
||||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
typescript: {
|
||||||
},
|
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||||
})
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
export default buildConfigWithMemoryDB()
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/payload-feature-flags",
|
"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",
|
"description": "Feature flags plugin for Payload CMS - manage feature toggles, A/B tests, and gradual rollouts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Reference in New Issue
Block a user