@kfiross44/payload-push 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # PayloadCMS Push Plugin
2
+
3
+ The **PayloadCMS Push Plugin** provides a unified interface for sending
4
+ push notifications from within Payload applications.
5
+
6
+ It is designed as an extensible, adapter-based system that supports
7
+ multiple push providers. Currently, the plugin includes a **Firebase
8
+ adapter** powered by the Firebase Admin SDK and Firebase Cloud Messaging
9
+ (FCM), with additional providers planned for future releases (such as
10
+ OneSignal).
11
+
12
+ ------------------------------------------------------------------------
13
+
14
+ ## Available Adapters
15
+
16
+ ### Firebase (FCM)
17
+
18
+ The Firebase Push Adapter integrates **PayloadCMS** with **Firebase
19
+ Cloud Messaging (FCM)** using the official Firebase Admin SDK. It
20
+ enables transactional and broadcast push notification workflows across
21
+ web, iOS, and Android devices.
22
+
23
+ Built on top of Firebase and Firebase Cloud Messaging (FCM), this
24
+ adapter allows teams to send:
25
+
26
+ - Single-device notifications
27
+ - Multicast push notifications
28
+ - Topic-based broadcast notifications
29
+ - Platform-specific (Android / APNs) configurations
30
+
31
+ By leveraging the Firebase Admin SDK, organisations retain full control
32
+ over credentials, infrastructure, and deployment topology while
33
+ simplifying push delivery from within Payload.
34
+
35
+ ------------------------------------------------------------------------
36
+
37
+ ## Roadmap
38
+
39
+ The plugin is designed to support multiple providers through adapters.
40
+ Planned integrations may include:
41
+
42
+ - OneSignal
43
+ - Additional self-hosted or API-first push services
44
+ - Requested by community
45
+
46
+ ------------------------------------------------------------------------
47
+
48
+ ## Installation
49
+
50
+ ``` sh
51
+ pnpm add firebase-admin
52
+ ```
53
+
54
+ ------------------------------------------------------------------------
55
+
56
+ ## Prerequisites
57
+
58
+ 1. Create a Firebase project\
59
+ 2. Enable Firebase Cloud Messaging (FCM)\
60
+ 3. Generate a **Service Account Key (JSON)**\
61
+ 4. Store the service account JSON securely (environment variable or
62
+ secret manager)
63
+
64
+ ------------------------------------------------------------------------
65
+
66
+ ## Usage
67
+
68
+ ### Firebase Adapter
69
+ ``` ts
70
+ // payload.config.ts
71
+ import { buildConfig } from 'payload'
72
+ import { firebaseAdapter } from '@kfiross/payload-push'
73
+
74
+ export default buildConfig({
75
+ push: firebaseAdapter({
76
+ serviceAccountJSON: JSON.parse(
77
+ process.env.FIREBASE_SERVICE_ACCOUNT_JSON!
78
+ ),
79
+ }),
80
+ })
81
+ ```
82
+
83
+ ------------------------------------------------------------------------
84
+
85
+ ## Examples
86
+
87
+ ### Firebase Adapter
88
+ * Sending a Push Notification
89
+ ``` ts
90
+ await payload.push.send({
91
+ title: 'New Booking Confirmed',
92
+ body: 'Your booking has been successfully confirmed.',
93
+ data: {
94
+ bookingId: '12345',
95
+ },
96
+ options: {
97
+ token: '<device-fcm-token>',
98
+ },
99
+ })
100
+ ```
101
+
102
+ * Sending to Multiple Devices
103
+
104
+ ``` ts
105
+ await payload.push.send({
106
+ title: 'System Update',
107
+ body: 'We have updated our terms of service.',
108
+ options: {
109
+ tokens: ['token-1', 'token-2'],
110
+ },
111
+ })
112
+ ```
113
+
114
+ * Sending to a Topic
115
+
116
+ ``` ts
117
+ await payload.push.send({
118
+ title: 'Weekly Newsletter',
119
+ body: 'Check out what’s new this week!',
120
+ options: {
121
+ topic: 'weekly-updates',
122
+ },
123
+ })
124
+ ```
125
+
126
+ ------------------------------------------------------------------------
127
+
128
+ ## Configuration
129
+
130
+ ### Firebase Adapter
131
+
132
+ | Option | Type | Required | Default | Description |
133
+ |--------------------|--------|----------|---------|-------------------------------------------|
134
+ | serviceAccountJSON | string | Yes | - | Firebase service account credentials JSON |
135
+
136
+
137
+
138
+ Inside `options`, you may provide:
139
+
140
+ | Option | Type | Description |
141
+ |----------------------|----------|------------------------|
142
+ | token | string | Single device token |
143
+ | tokens | string[] | Multiple device tokens |
144
+ | topic | string | Subscribed topic name |
145
+ | android | object | Android-specific FCM config |
146
+ | apns | object | iOS/APNs-specific config |
147
+
148
+ ------------------------------------------------------------------------
149
+
150
+ ## Example Environment Variable Setup
151
+
152
+ You may define (for FCM for example):
153
+ ``` bash
154
+ FIREBASE_SERVICE_ACCOUNT_JSON='{"type":"service_account", ...}'
155
+ ```
156
+
157
+ ------------------------------------------------------------------------
158
+
159
+ ## License
160
+
161
+ This project is licensed under the MIT License.
@@ -0,0 +1,33 @@
1
+ import type { BatchResponse } from 'firebase-admin/messaging';
2
+ import type { PushAdapter } from '../types/index.js';
3
+ type ServiceAccountJSONData = {
4
+ auth_provider_x509_cert_url: string;
5
+ auth_uri: string;
6
+ client_email: string;
7
+ client_id: string;
8
+ client_x509_cert_url: string;
9
+ private_key: string;
10
+ private_key_id: string;
11
+ project_id: string;
12
+ token_uri: string;
13
+ type: string;
14
+ universe_domain: string;
15
+ };
16
+ export type FirebaseAdapterArgs = {
17
+ serviceAccountJSON: ServiceAccountJSONData;
18
+ };
19
+ type FirebasePushAdapter = PushAdapter<firebaseResponse>;
20
+ type firebaseError = {
21
+ error: {
22
+ code: string;
23
+ message: string;
24
+ };
25
+ };
26
+ type firebaseResponse = {
27
+ messageId: string;
28
+ } | BatchResponse | firebaseError;
29
+ /**
30
+ * Push adapter for [firebase](https://firebase.com) Admin API
31
+ */
32
+ export declare const firebaseAdapter: ({ serviceAccountJSON }: FirebaseAdapterArgs) => FirebasePushAdapter;
33
+ export {};
@@ -0,0 +1,185 @@
1
+ // import { APIError } from "payload"
2
+ import admin from 'firebase-admin';
3
+ // export const firebaseAdapter = (args: firebaseAdapterArgs): FirebasePushAdapter => {
4
+ // const {
5
+ // apiKey,
6
+ // scheduledAt,
7
+ // firebaseUrl,
8
+ // variables,
9
+ // } = args
10
+ // const adapter: FirebasePushAdapter = () => ({
11
+ // name: "firebase",
12
+ // // defaultFromName,
13
+ // // defaultFromAddress,
14
+ // sendPush: async (message) => {
15
+ // const sendPushOptions = mapPayloadToFirebasePush(
16
+ // // defaultFromName,
17
+ // // defaultFromAddress,
18
+ // message
19
+ // )
20
+ // const payload = {
21
+ // ...sendPushOptions,
22
+ // ...(scheduledAt ? { scheduledAt } : {}),
23
+ // // ...(templateId ? { templateId } : {}),
24
+ // ...(variables ? { variables } : {}),
25
+ // }
26
+ // const res = await fetch(`${firebaseUrl}/api/v1/emails`, {
27
+ // body: JSON.stringify(payload),
28
+ // headers: {
29
+ // Authorization: `Bearer ${apiKey}`,
30
+ // "Content-Type": "application/json",
31
+ // },
32
+ // method: "POST",
33
+ // })
34
+ // const data = (await res.json()) as firebaseResponse
35
+ // if ("emailId" in data) {
36
+ // return data
37
+ // }
38
+ // else {
39
+ // const statusCode = res.status
40
+ // let formattedError = `Error sending email: ${statusCode}`
41
+ // if ("error" in data) {
42
+ // formattedError += ` ${data.error.code} - ${data.error.message}`
43
+ // }
44
+ // throw new APIError(formattedError, statusCode)
45
+ // }
46
+ // },
47
+ // })
48
+ /**
49
+ * Push adapter for [firebase](https://firebase.com) Admin API
50
+ */ export const firebaseAdapter = ({ serviceAccountJSON })=>{
51
+ // Initialize firebase-admin if not already done
52
+ if (!admin.apps.length) {
53
+ if (!serviceAccountJSON) {
54
+ throw new Error('Missing service account json data');
55
+ }
56
+ const creds = serviceAccountJSON || undefined;
57
+ try {
58
+ admin.initializeApp({
59
+ credential: creds ? admin.credential.cert(creds) : admin.credential.applicationDefault()
60
+ });
61
+ console.log('✅ Firebase admin app initialized', admin.app().name);
62
+ } catch (e) {
63
+ console.error(e);
64
+ console.log('❌ Firebase admin app initialized failed', e);
65
+ }
66
+ }
67
+ const fcm = admin.messaging();
68
+ const adapter = ()=>({
69
+ name: 'firebase-admin',
70
+ /**
71
+ * Sends push notifications using Firebase Admin SDK
72
+ */ async sendPush (message) {
73
+ const sendPushOptions = mapPayloadToFirebasePush(message);
74
+ const notification = {
75
+ body: message.body,
76
+ title: message.title
77
+ };
78
+ console.log({
79
+ sendPushOptions
80
+ });
81
+ try {
82
+ if (sendPushOptions.topic) {
83
+ const payload = {
84
+ data: message.data,
85
+ notification,
86
+ topic: sendPushOptions.topic
87
+ };
88
+ const res = await fcm.send(payload);
89
+ console.log('✅ Push to single token:', res);
90
+ if (res) {
91
+ return {
92
+ messageId: res
93
+ };
94
+ }
95
+ }
96
+ if (sendPushOptions.tokens && sendPushOptions.tokens.length) {
97
+ const res = await fcm.sendEachForMulticast({
98
+ data: message.data,
99
+ notification,
100
+ tokens: sendPushOptions.tokens
101
+ });
102
+ console.log('✅ Push multicast result:', res);
103
+ return res;
104
+ }
105
+ if (sendPushOptions.token) {
106
+ const res = await fcm.send({
107
+ data: message.data,
108
+ notification,
109
+ token: sendPushOptions.token
110
+ });
111
+ console.log('✅ Push to token:', res);
112
+ return {
113
+ messageId: res
114
+ };
115
+ }
116
+ throw new Error('No token(s) or topic provided');
117
+ } catch (err) {
118
+ console.error('❌ Firebase push error:', err);
119
+ throw err;
120
+ }
121
+ }
122
+ });
123
+ return adapter;
124
+ };
125
+ function mapPayloadToFirebasePush(message) {
126
+ const pushOptions = {
127
+ body: message.body,
128
+ title: message.title
129
+ };
130
+ if (!message.options) {
131
+ return pushOptions;
132
+ }
133
+ if (message.options['token']) {
134
+ const token = message.options['token'];
135
+ pushOptions.token = token;
136
+ }
137
+ if (message.options['tokens']) {
138
+ const tokens = message.options['tokens'];
139
+ pushOptions.tokens = tokens;
140
+ }
141
+ if (message.options['topic']) {
142
+ const topic = message.options['topic'];
143
+ pushOptions.topic = topic;
144
+ }
145
+ if (message.options['android']) {
146
+ const android = message.options['android'];
147
+ pushOptions.android = android;
148
+ }
149
+ if (message.options['apns']) {
150
+ const apns = message.options['apns'];
151
+ pushOptions.apns = apns;
152
+ }
153
+ // if (message.text?.toString().trim().length > 0) {
154
+ // pushOptions.text = message.text
155
+ // } else {
156
+ // pushOptions.text = "Please view this email in an HTML-compatible client."
157
+ // }
158
+ // if (message.html?.toString().trim()) {
159
+ // pushOptions.html = message.html.toString()
160
+ // }
161
+ // if (message.attachments?.length) {
162
+ // if (message.attachments.length > 10) {
163
+ // throw new APIError("Maximum of 10 attachments allowed", 400)
164
+ // }
165
+ // pushOptions.attachments = mapAttachments(message.attachments)
166
+ // }
167
+ // if (message.replyTo) {
168
+ // pushOptions.replyTo = mapAddresses(message.replyTo)
169
+ // }
170
+ // if (message.cc) {
171
+ // pushOptions.cc = mapAddresses(message.cc)
172
+ // }
173
+ // if (message.bcc) {
174
+ // pushOptions.bcc = mapAddresses(message.bcc)
175
+ // }
176
+ return pushOptions;
177
+ }
178
+ // type Attachment = {
179
+ // /** Content of an attached file. */
180
+ // content: string
181
+ // /** Name of attached file. */
182
+ // filename: string
183
+ // }
184
+
185
+ //# sourceMappingURL=push-firebase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/push-firebase.ts"],"sourcesContent":["import type { BatchResponse, TopicMessage } from 'firebase-admin/messaging'\n\n// import { APIError } from \"payload\"\n\nimport admin from 'firebase-admin'\n\nimport type { PushAdapter, SendPushOptions } from '../types/index.js'\n\ntype ServiceAccountJSONData = {\n auth_provider_x509_cert_url: string\n auth_uri: string\n client_email: string\n client_id: string\n client_x509_cert_url: string\n private_key: string\n private_key_id: string\n project_id: string\n token_uri: string\n type: string\n universe_domain: string\n}\n\nexport type FirebaseAdapterArgs = {\n serviceAccountJSON: ServiceAccountJSONData\n}\n\ntype FirebasePushAdapter = PushAdapter<firebaseResponse>\n\ntype firebaseError = {\n error: {\n code: string\n message: string\n }\n}\n\ntype firebaseResponse = { messageId: string } | BatchResponse | firebaseError\n\n// export const firebaseAdapter = (args: firebaseAdapterArgs): FirebasePushAdapter => {\n// \tconst {\n// \t\tapiKey,\n// \t\tscheduledAt,\n// \t\tfirebaseUrl,\n// \t\tvariables,\n// \t} = args\n\n// \tconst adapter: FirebasePushAdapter = () => ({\n// \t\tname: \"firebase\",\n// \t\t// defaultFromName,\n// \t\t// defaultFromAddress,\n// \t\tsendPush: async (message) => {\n// \t\t\tconst sendPushOptions = mapPayloadToFirebasePush(\n// \t\t\t\t// defaultFromName,\n// \t\t\t\t// defaultFromAddress,\n// \t\t\t\tmessage\n// \t\t\t)\n\n// \t\t\tconst payload = {\n// \t\t\t\t...sendPushOptions,\n// \t\t\t\t...(scheduledAt ? { scheduledAt } : {}),\n// \t\t\t\t// ...(templateId ? { templateId } : {}),\n// \t\t\t\t...(variables ? { variables } : {}),\n// \t\t\t}\n\n// \t\t\tconst res = await fetch(`${firebaseUrl}/api/v1/emails`, {\n// \t\t\t\tbody: JSON.stringify(payload),\n// \t\t\t\theaders: {\n// \t\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n// \t\t\t\t\t\"Content-Type\": \"application/json\",\n// \t\t\t\t},\n// \t\t\t\tmethod: \"POST\",\n// \t\t\t})\n\n// \t\t\tconst data = (await res.json()) as firebaseResponse\n\n// \t\t\tif (\"emailId\" in data) {\n// \t\t\t\treturn data\n// \t\t\t}\n// else {\n// \t\t\t\tconst statusCode = res.status\n// \t\t\t\tlet formattedError = `Error sending email: ${statusCode}`\n// \t\t\t\tif (\"error\" in data) {\n// \t\t\t\t\tformattedError += ` ${data.error.code} - ${data.error.message}`\n// \t\t\t\t}\n\n// \t\t\t\tthrow new APIError(formattedError, statusCode)\n// \t\t\t}\n// \t\t},\n// \t})\n\n/**\n * Push adapter for [firebase](https://firebase.com) Admin API\n */\nexport const firebaseAdapter = ({ serviceAccountJSON }: FirebaseAdapterArgs) => {\n // Initialize firebase-admin if not already done\n if (!admin.apps.length) {\n if (!serviceAccountJSON) {\n throw new Error('Missing service account json data')\n }\n\n const creds = serviceAccountJSON || undefined\n\n try {\n admin.initializeApp({\n credential: creds\n ? admin.credential.cert(creds as admin.ServiceAccount)\n : admin.credential.applicationDefault(),\n })\n console.log('✅ Firebase admin app initialized', admin.app().name)\n } catch (e) {\n console.error(e)\n console.log('❌ Firebase admin app initialized failed', e)\n }\n }\n\n const fcm = admin.messaging()\n\n const adapter: FirebasePushAdapter = () => ({\n name: 'firebase-admin',\n /**\n * Sends push notifications using Firebase Admin SDK\n */\n async sendPush(message: {\n body: string\n data?: Record<string, string>\n options?: Record<string, string>\n title: string\n }) {\n const sendPushOptions = mapPayloadToFirebasePush(message)\n\n const notification = {\n body: message.body,\n title: message.title,\n }\n\n console.log({ sendPushOptions })\n\n try {\n if (sendPushOptions.topic) {\n const payload: TopicMessage = {\n data: message.data,\n notification,\n topic: sendPushOptions.topic,\n }\n\n const res = await fcm.send(payload)\n console.log('✅ Push to single token:', res)\n if (res) {\n return { messageId: res }\n }\n }\n if (sendPushOptions.tokens && sendPushOptions.tokens.length) {\n const res = await fcm.sendEachForMulticast({\n data: message.data,\n notification,\n tokens: sendPushOptions.tokens,\n })\n console.log('✅ Push multicast result:', res)\n return res\n }\n\n if (sendPushOptions.token) {\n const res = await fcm.send({\n data: message.data,\n notification,\n token: sendPushOptions.token,\n })\n console.log('✅ Push to token:', res)\n\n return { messageId: res }\n }\n\n throw new Error('No token(s) or topic provided')\n } catch (err) {\n console.error('❌ Firebase push error:', err)\n throw err\n }\n },\n })\n return adapter\n}\n\nfunction mapPayloadToFirebasePush(message: SendPushOptions): firebasePushOptions {\n const pushOptions: Partial<firebasePushOptions> = {\n body: message.body,\n title: message.title,\n }\n\n if (!message.options) {\n return pushOptions as firebasePushOptions\n }\n\n if (message.options['token']) {\n const token = message.options['token']\n pushOptions.token = token\n }\n\n if (message.options['tokens']) {\n const tokens = message.options['tokens']\n pushOptions.tokens = tokens\n }\n\n if (message.options['topic']) {\n const topic = message.options['topic']\n pushOptions.topic = topic\n }\n\n if (message.options['android']) {\n const android = message.options['android']\n pushOptions.android = android\n }\n\n if (message.options['apns']) {\n const apns = message.options['apns']\n pushOptions.apns = apns\n }\n\n // if (message.text?.toString().trim().length > 0) {\n // \tpushOptions.text = message.text\n // } else {\n // \tpushOptions.text = \"Please view this email in an HTML-compatible client.\"\n // }\n\n // if (message.html?.toString().trim()) {\n // \tpushOptions.html = message.html.toString()\n // }\n\n // if (message.attachments?.length) {\n // \tif (message.attachments.length > 10) {\n // \t\tthrow new APIError(\"Maximum of 10 attachments allowed\", 400)\n // \t}\n // \tpushOptions.attachments = mapAttachments(message.attachments)\n // }\n\n // if (message.replyTo) {\n // \tpushOptions.replyTo = mapAddresses(message.replyTo)\n // }\n\n // if (message.cc) {\n // \tpushOptions.cc = mapAddresses(message.cc)\n // }\n\n // if (message.bcc) {\n // \tpushOptions.bcc = mapAddresses(message.bcc)\n // }\n\n return pushOptions as firebasePushOptions\n}\n\n// function mapFromAddress(\n// \taddress: SendPushOptions[\"from\"],\n// \tdefaultFromName: string,\n// \tdefaultFromAddress: string\n// ): firebasePushOptions[\"from\"] {\n// \tif (!address) {\n// \t\treturn `${defaultFromName} <${defaultFromAddress}>`\n// \t}\n\n// \tif (typeof address === \"string\") {\n// \t\treturn address\n// \t}\n\n// \treturn `${address.name} <${address.address}>`\n// }\n\n// function mapAddresses(\n// \taddresses: SendPushOptions[\"to\"]\n// ): firebasePushOptions[\"to\"] {\n// \tif (!addresses) {\n// \t\treturn \"\"\n// \t}\n\n// \tif (typeof addresses === \"string\") {\n// \t\treturn addresses\n// \t}\n\n// \tif (Array.isArray(addresses)) {\n// \t\treturn addresses.map((address) =>\n// \t\t\ttypeof address === \"string\" ? address : address.address\n// \t\t)\n// \t}\n\n// \treturn [addresses.address]\n// }\n\n// function mapAttachments(\n// \tattachments: SendPushOptions[\"attachments\"]\n// ): firebasePushOptions[\"attachments\"] {\n// \tif (!attachments) {\n// \t\treturn []\n// \t}\n\n// \tif (attachments.length > 10) {\n// \t\tthrow new APIError(\"Maximum of 10 attachments allowed\", 400)\n// \t}\n\n// \treturn attachments.map((attachment) => {\n// \t\tif (!attachment.filename || !attachment.content) {\n// \t\t\tthrow new APIError(\"Attachment is missing filename or content\", 400)\n// \t\t}\n\n// \t\tif (typeof attachment.content === \"string\") {\n// \t\t\treturn {\n// \t\t\t\tcontent: Buffer.from(attachment.content).toString(\"base64\"),\n// \t\t\t\tfilename: attachment.filename,\n// \t\t\t}\n// \t\t}\n\n// \t\tif (attachment.content instanceof Buffer) {\n// \t\t\treturn {\n// \t\t\t\tcontent: attachment.content.toString(\"base64\"),\n// \t\t\t\tfilename: attachment.filename,\n// \t\t\t}\n// \t\t}\n\n// \t\tthrow new APIError(\"Attachment content must be a string or a buffer\", 400)\n// \t})\n// }\n\ntype firebasePushOptions = {\n android?: admin.messaging.AndroidConfig\n apns?: admin.messaging.ApnsConfig\n body: string\n /**\n * The date and time to send the email. If not provided, the email will be sent immediately.\n */\n scheduledAt?: string\n title: string\n token?: string\n tokens?: string[]\n\n topic?: string\n}\n\n// type Attachment = {\n// \t/** Content of an attached file. */\n// \tcontent: string\n// \t/** Name of attached file. */\n// \tfilename: string\n// }\n"],"names":["admin","firebaseAdapter","serviceAccountJSON","apps","length","Error","creds","undefined","initializeApp","credential","cert","applicationDefault","console","log","app","name","e","error","fcm","messaging","adapter","sendPush","message","sendPushOptions","mapPayloadToFirebasePush","notification","body","title","topic","payload","data","res","send","messageId","tokens","sendEachForMulticast","token","err","pushOptions","options","android","apns"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { PayloadHandler } from 'payload';
2
+ import type { PayloadPushPluginConfig } from '../index.js';
3
+ declare const fcmPushEndpointHandler: (pluginOptions: PayloadPushPluginConfig) => PayloadHandler;
4
+ export default fcmPushEndpointHandler;
@@ -0,0 +1,60 @@
1
+ import { payloadPush } from '../payloadPush.js';
2
+ const fcmPushEndpointHandler = (pluginOptions)=>{
3
+ const handler = async (req)=>{
4
+ const adapterName = pluginOptions.pushAdapter.name;
5
+ try {
6
+ if (!req) {
7
+ return Response.json({});
8
+ }
9
+ const { payload } = req;
10
+ // @ts-ignore
11
+ const data = await req.json();
12
+ payload.logger.info({
13
+ data
14
+ });
15
+ const { body, title, topic } = data;
16
+ if (!req.user) {
17
+ return Response.json({
18
+ error: 'Unauthorized',
19
+ success: false
20
+ }, {
21
+ status: 401
22
+ });
23
+ }
24
+ // 2️⃣ Optional: restrict to admin users
25
+ // if (!req.user.roles?.some(r => r === 'admin')) {
26
+ // return Response.json({ success: false, error: 'Forbidden' }, { status: 403 })
27
+ // }
28
+ payloadPush.init(payload, pluginOptions.pushAdapter);
29
+ payload.logger.info(`Sending Push sent via ${adapterName}...`);
30
+ payload.logger.info('title=' + title);
31
+ payload.logger.info('body=' + body);
32
+ payload.logger.info('topic=' + topic);
33
+ await payloadPush.sendPush({
34
+ body,
35
+ title,
36
+ // data,
37
+ options: {
38
+ topic
39
+ }
40
+ });
41
+ payload.logger.info(`📱 Push sent via ${adapterName}`);
42
+ return Response.json({
43
+ success: true
44
+ }, {
45
+ status: 200
46
+ });
47
+ } catch (err) {
48
+ return Response.json({
49
+ error: 'Push failed',
50
+ success: false
51
+ }, {
52
+ status: 500
53
+ });
54
+ }
55
+ };
56
+ return handler;
57
+ };
58
+ export default fcmPushEndpointHandler;
59
+
60
+ //# sourceMappingURL=fcmPushEndpointHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/fcmPushEndpointHandler.ts"],"sourcesContent":["import type { PayloadHandler, PayloadRequest } from 'payload'\n\nimport type { PayloadPushPluginConfig } from '../index.js'\n\nimport { payloadPush } from '../payloadPush.js'\n\nconst fcmPushEndpointHandler = (pluginOptions: PayloadPushPluginConfig) => {\n const handler: PayloadHandler = async (req: PayloadRequest) => {\n const adapterName = pluginOptions.pushAdapter.name\n try {\n if (!req) {\n return Response.json({})\n }\n const { payload } = req\n\n // @ts-ignore\n const data = await req.json()\n\n payload.logger.info({ data })\n\n const { body, title, topic } = data as Record<string, string>\n\n if (!req.user) {\n return Response.json({ error: 'Unauthorized', success: false }, { status: 401 })\n }\n\n // 2️⃣ Optional: restrict to admin users\n // if (!req.user.roles?.some(r => r === 'admin')) {\n // return Response.json({ success: false, error: 'Forbidden' }, { status: 403 })\n // }\n\n payloadPush.init(payload, pluginOptions.pushAdapter)\n\n payload.logger.info(`Sending Push sent via ${adapterName}...`)\n payload.logger.info('title=' + title)\n payload.logger.info('body=' + body)\n payload.logger.info('topic=' + topic)\n\n await payloadPush.sendPush({\n body,\n title,\n // data,\n options: { topic },\n })\n\n payload.logger.info(`📱 Push sent via ${adapterName}`)\n\n return Response.json({ success: true }, { status: 200 })\n } catch (err) {\n return Response.json({ error: 'Push failed', success: false }, { status: 500 })\n }\n }\n\n return handler\n}\n\nexport default fcmPushEndpointHandler\n"],"names":["payloadPush","fcmPushEndpointHandler","pluginOptions","handler","req","adapterName","pushAdapter","name","Response","json","payload","data","logger","info","body","title","topic","user","error","success","status","init","sendPush","options","err"],"mappings":"AAIA,SAASA,WAAW,QAAQ,oBAAmB;AAE/C,MAAMC,yBAAyB,CAACC;IAC9B,MAAMC,UAA0B,OAAOC;QACrC,MAAMC,cAAcH,cAAcI,WAAW,CAACC,IAAI;QAClD,IAAI;YACF,IAAI,CAACH,KAAK;gBACR,OAAOI,SAASC,IAAI,CAAC,CAAC;YACxB;YACA,MAAM,EAAEC,OAAO,EAAE,GAAGN;YAEpB,aAAa;YACb,MAAMO,OAAO,MAAMP,IAAIK,IAAI;YAE3BC,QAAQE,MAAM,CAACC,IAAI,CAAC;gBAAEF;YAAK;YAE3B,MAAM,EAAEG,IAAI,EAAEC,KAAK,EAAEC,KAAK,EAAE,GAAGL;YAE/B,IAAI,CAACP,IAAIa,IAAI,EAAE;gBACb,OAAOT,SAASC,IAAI,CAAC;oBAAES,OAAO;oBAAgBC,SAAS;gBAAM,GAAG;oBAAEC,QAAQ;gBAAI;YAChF;YAEA,wCAAwC;YACxC,mDAAmD;YACnD,mFAAmF;YACnF,IAAI;YAEJpB,YAAYqB,IAAI,CAACX,SAASR,cAAcI,WAAW;YAEnDI,QAAQE,MAAM,CAACC,IAAI,CAAC,CAAC,sBAAsB,EAAER,YAAY,GAAG,CAAC;YAC7DK,QAAQE,MAAM,CAACC,IAAI,CAAC,WAAWE;YAC/BL,QAAQE,MAAM,CAACC,IAAI,CAAC,UAAUC;YAC9BJ,QAAQE,MAAM,CAACC,IAAI,CAAC,WAAWG;YAE/B,MAAMhB,YAAYsB,QAAQ,CAAC;gBACzBR;gBACAC;gBACA,QAAQ;gBACRQ,SAAS;oBAAEP;gBAAM;YACnB;YAEAN,QAAQE,MAAM,CAACC,IAAI,CAAC,CAAC,iBAAiB,EAAER,aAAa;YAErD,OAAOG,SAASC,IAAI,CAAC;gBAAEU,SAAS;YAAK,GAAG;gBAAEC,QAAQ;YAAI;QACxD,EAAE,OAAOI,KAAK;YACZ,OAAOhB,SAASC,IAAI,CAAC;gBAAES,OAAO;gBAAeC,SAAS;YAAM,GAAG;gBAAEC,QAAQ;YAAI;QAC/E;IACF;IAEA,OAAOjB;AACT;AAEA,eAAeF,uBAAsB"}
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=index.d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["index.d.ts"],"sourcesContent":["import type { Config } from 'payload';\nimport type { PushAdapter } from './types/index.js';\nexport type PayloadPushPluginConfig = {\n disabled?: boolean;\n pushAdapter: PushAdapter;\n};\nexport declare const payloadPushPlugin: (pluginOptions?: PayloadPushPluginConfig) => (config: Config) => Config;\n"],"names":[],"mappings":"AAMA,WAAgH"}
@@ -0,0 +1,7 @@
1
+ import type { Config } from 'payload';
2
+ import type { PushAdapter } from './types/index.js';
3
+ export type PayloadPushPluginConfig = {
4
+ disabled?: boolean;
5
+ pushAdapter: PushAdapter;
6
+ };
7
+ export declare const payloadPushPlugin: (pluginOptions?: PayloadPushPluginConfig) => (config: Config) => Config;
package/dist/index.js ADDED
@@ -0,0 +1,48 @@
1
+ import fcmPushEndpointHandler from './endpoints/fcmPushEndpointHandler.js';
2
+ import { payloadPush } from './payloadPush.js';
3
+ export const payloadPushPlugin = (pluginOptions)=>(config)=>{
4
+ if (!config.collections) {
5
+ config.collections = [];
6
+ }
7
+ /**
8
+ * If the plugin is disabled, we still want to keep added collections/fields so the database schema is consistent which is important for migrations.
9
+ * If your plugin heavily modifies the database schema, you may want to remove this property.
10
+ */ if (pluginOptions?.disabled) {
11
+ return config;
12
+ }
13
+ if (!pluginOptions?.pushAdapter) {
14
+ throw new Error('pushAdapter is missing');
15
+ }
16
+ if (!config.endpoints) {
17
+ config.endpoints = [];
18
+ }
19
+ if (!config.endpoints) {
20
+ config.endpoints = [];
21
+ }
22
+ if (!config.admin) {
23
+ config.admin = {};
24
+ }
25
+ if (!config.admin.components) {
26
+ config.admin.components = {};
27
+ }
28
+ config.endpoints.push({
29
+ handler: fcmPushEndpointHandler(pluginOptions),
30
+ method: 'post',
31
+ path: '/test-push/firebase'
32
+ });
33
+ const incomingOnInit = config.onInit;
34
+ config.onInit = async (payload)=>{
35
+ // Ensure we are executing any existing onInit functions before running our own.
36
+ if (incomingOnInit) {
37
+ await incomingOnInit(payload);
38
+ }
39
+ payloadPush.init(payload, pluginOptions.pushAdapter);
40
+ // Optionally inject into payload so user can use: payload.push.sendPush()
41
+ // @ts-ignore
42
+ payload.push = payloadPush;
43
+ payload.logger.info('📱 Payload Push initialized with custom adapter');
44
+ };
45
+ return config;
46
+ };
47
+
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { PushAdapter } from './types/index.js'\n\nimport fcmPushEndpointHandler from './endpoints/fcmPushEndpointHandler.js'\nimport { payloadPush } from './payloadPush.js'\n\nexport type PayloadPushPluginConfig = {\n disabled?: boolean\n pushAdapter: PushAdapter\n}\n\nexport const payloadPushPlugin =\n (pluginOptions?: PayloadPushPluginConfig) =>\n (config: Config): Config => {\n if (!config.collections) {\n config.collections = []\n }\n\n /**\n * If the plugin is disabled, we still want to keep added collections/fields so the database schema is consistent which is important for migrations.\n * If your plugin heavily modifies the database schema, you may want to remove this property.\n */\n if (pluginOptions?.disabled) {\n return config\n }\n\n if (!pluginOptions?.pushAdapter) {\n throw new Error('pushAdapter is missing')\n }\n\n if (!config.endpoints) {\n config.endpoints = []\n }\n\n if (!config.endpoints) {\n config.endpoints = []\n }\n\n if (!config.admin) {\n config.admin = {}\n }\n\n if (!config.admin.components) {\n config.admin.components = {}\n }\n\n config.endpoints.push({\n handler: fcmPushEndpointHandler(pluginOptions),\n method: 'post',\n path: '/test-push/firebase',\n })\n\n const incomingOnInit = config.onInit\n\n config.onInit = async (payload) => {\n // Ensure we are executing any existing onInit functions before running our own.\n if (incomingOnInit) {\n await incomingOnInit(payload)\n }\n\n payloadPush.init(payload, pluginOptions.pushAdapter)\n\n // Optionally inject into payload so user can use: payload.push.sendPush()\n // @ts-ignore\n payload.push = payloadPush\n\n payload.logger.info('📱 Payload Push initialized with custom adapter')\n }\n\n return config\n }\n"],"names":["fcmPushEndpointHandler","payloadPush","payloadPushPlugin","pluginOptions","config","collections","disabled","pushAdapter","Error","endpoints","admin","components","push","handler","method","path","incomingOnInit","onInit","payload","init","logger","info"],"mappings":"AAIA,OAAOA,4BAA4B,wCAAuC;AAC1E,SAASC,WAAW,QAAQ,mBAAkB;AAO9C,OAAO,MAAMC,oBACX,CAACC,gBACD,CAACC;QACC,IAAI,CAACA,OAAOC,WAAW,EAAE;YACvBD,OAAOC,WAAW,GAAG,EAAE;QACzB;QAEA;;;KAGC,GACD,IAAIF,eAAeG,UAAU;YAC3B,OAAOF;QACT;QAEA,IAAI,CAACD,eAAeI,aAAa;YAC/B,MAAM,IAAIC,MAAM;QAClB;QAEA,IAAI,CAACJ,OAAOK,SAAS,EAAE;YACrBL,OAAOK,SAAS,GAAG,EAAE;QACvB;QAEA,IAAI,CAACL,OAAOK,SAAS,EAAE;YACrBL,OAAOK,SAAS,GAAG,EAAE;QACvB;QAEA,IAAI,CAACL,OAAOM,KAAK,EAAE;YACjBN,OAAOM,KAAK,GAAG,CAAC;QAClB;QAEA,IAAI,CAACN,OAAOM,KAAK,CAACC,UAAU,EAAE;YAC5BP,OAAOM,KAAK,CAACC,UAAU,GAAG,CAAC;QAC7B;QAEAP,OAAOK,SAAS,CAACG,IAAI,CAAC;YACpBC,SAASb,uBAAuBG;YAChCW,QAAQ;YACRC,MAAM;QACR;QAEA,MAAMC,iBAAiBZ,OAAOa,MAAM;QAEpCb,OAAOa,MAAM,GAAG,OAAOC;YACrB,gFAAgF;YAChF,IAAIF,gBAAgB;gBAClB,MAAMA,eAAeE;YACvB;YAEAjB,YAAYkB,IAAI,CAACD,SAASf,cAAcI,WAAW;YAEnD,0EAA0E;YAC1E,aAAa;YACbW,QAAQN,IAAI,GAAGX;YAEfiB,QAAQE,MAAM,CAACC,IAAI,CAAC;QACtB;QAEA,OAAOjB;IACT,EAAC"}
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=payloadPush.d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["payloadPush.d.ts"],"sourcesContent":["import type { Payload } from 'payload';\nimport type { PushAdapter } from './types/index.js';\ndeclare class PayloadPush {\n private adapter?;\n private payload?;\n init(payload: Payload, adapterFactory: PushAdapter): void;\n sendPush(message: {\n body: string;\n data?: Record<string, any>;\n options?: Record<string, any>;\n title: string;\n }): Promise<unknown>;\n}\nexport declare const payloadPush: PayloadPush;\nexport {};\n"],"names":[],"mappings":"AAcA,WAAU"}
@@ -0,0 +1,15 @@
1
+ import type { Payload } from 'payload';
2
+ import type { PushAdapter } from './types/index.js';
3
+ declare class PayloadPush {
4
+ private adapter?;
5
+ private payload?;
6
+ init(payload: Payload, adapterFactory: PushAdapter): void;
7
+ sendPush(message: {
8
+ body: string;
9
+ data?: Record<string, any>;
10
+ options?: Record<string, any>;
11
+ title: string;
12
+ }): Promise<unknown>;
13
+ }
14
+ export declare const payloadPush: PayloadPush;
15
+ export {};
@@ -0,0 +1,32 @@
1
+ class PayloadPush {
2
+ adapter;
3
+ payload;
4
+ init(payload, adapterFactory) {
5
+ this.payload = payload;
6
+ this.adapter = adapterFactory({
7
+ payload
8
+ });
9
+ }
10
+ async sendPush(message) {
11
+ if (!this.payload) {
12
+ throw new Error('PayloadPush not initialized');
13
+ }
14
+ if (!this.adapter) {
15
+ throw new Error('Push adapter not initialized');
16
+ }
17
+ if (typeof this.adapter.sendPush !== 'function') {
18
+ throw new Error('Push adapter missing sendPush() method');
19
+ }
20
+ try {
21
+ const result = await this.adapter.sendPush(message);
22
+ this.payload.logger.info(`📤 Push sent via ${this.adapter.name}`);
23
+ return result;
24
+ } catch (err) {
25
+ this.payload.logger.error('❌ Push send failed', err);
26
+ throw err;
27
+ }
28
+ }
29
+ }
30
+ export const payloadPush = new PayloadPush();
31
+
32
+ //# sourceMappingURL=payloadPush.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/payloadPush.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nimport type { PushAdapter } from './types/index.js'\n\nclass PayloadPush {\n private adapter?: ReturnType<PushAdapter>\n private payload?: Payload\n\n init(payload: Payload, adapterFactory: PushAdapter) {\n this.payload = payload\n this.adapter = adapterFactory({ payload })\n }\n\n async sendPush(message: {\n body: string\n data?: Record<string, any>\n options?: Record<string, any>\n title: string\n }) {\n if (!this.payload) {\n throw new Error('PayloadPush not initialized')\n }\n if (!this.adapter) {\n throw new Error('Push adapter not initialized')\n }\n\n if (typeof this.adapter.sendPush !== 'function') {\n throw new Error('Push adapter missing sendPush() method')\n }\n\n try {\n const result = await this.adapter.sendPush(message)\n this.payload.logger.info(`📤 Push sent via ${this.adapter.name}`)\n return result\n } catch (err: any) {\n this.payload.logger.error('❌ Push send failed', err)\n throw err\n }\n }\n}\n\nexport const payloadPush = new PayloadPush()\n"],"names":["PayloadPush","adapter","payload","init","adapterFactory","sendPush","message","Error","result","logger","info","name","err","error","payloadPush"],"mappings":"AAIA,MAAMA;IACIC,QAAiC;IACjCC,QAAiB;IAEzBC,KAAKD,OAAgB,EAAEE,cAA2B,EAAE;QAClD,IAAI,CAACF,OAAO,GAAGA;QACf,IAAI,CAACD,OAAO,GAAGG,eAAe;YAAEF;QAAQ;IAC1C;IAEA,MAAMG,SAASC,OAKd,EAAE;QACD,IAAI,CAAC,IAAI,CAACJ,OAAO,EAAE;YACjB,MAAM,IAAIK,MAAM;QAClB;QACA,IAAI,CAAC,IAAI,CAACN,OAAO,EAAE;YACjB,MAAM,IAAIM,MAAM;QAClB;QAEA,IAAI,OAAO,IAAI,CAACN,OAAO,CAACI,QAAQ,KAAK,YAAY;YAC/C,MAAM,IAAIE,MAAM;QAClB;QAEA,IAAI;YACF,MAAMC,SAAS,MAAM,IAAI,CAACP,OAAO,CAACI,QAAQ,CAACC;YAC3C,IAAI,CAACJ,OAAO,CAACO,MAAM,CAACC,IAAI,CAAC,CAAC,iBAAiB,EAAE,IAAI,CAACT,OAAO,CAACU,IAAI,EAAE;YAChE,OAAOH;QACT,EAAE,OAAOI,KAAU;YACjB,IAAI,CAACV,OAAO,CAACO,MAAM,CAACI,KAAK,CAAC,sBAAsBD;YAChD,MAAMA;QACR;IACF;AACF;AAEA,OAAO,MAAME,cAAc,IAAId,cAAa"}
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=global.d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/global.d.ts"],"sourcesContent":["import type { payloadPush } from '../payloadPush.js'\nimport { BasePayload } from 'payload'\n\ndeclare module 'payload' {\n interface BasePayload {\n /**\n * Custom push method added to the Payload instance\n */\n push: typeof payloadPush.sendPush\n }\n}\n"],"names":[],"mappings":"AACA,WAAqC"}
@@ -0,0 +1,26 @@
1
+ import type { Payload } from 'payload';
2
+ /**
3
+ * Options for sending an push notification. Allows access to the PayloadRequest object
4
+ */
5
+ export type SendPushOptions = {
6
+ title: string;
7
+ body: string;
8
+ data?: Record<string, any>;
9
+ options?: Record<string, any>;
10
+ };
11
+ /**
12
+ * Email adapter after it has been initialized. This is used internally by Payload.
13
+ */
14
+ export type InitializedPushAdapter<TSendPushResponse = unknown> = ReturnType<PushAdapter<TSendPushResponse>>;
15
+ /**
16
+ * Push adapter interface. Allows a generic type for the response of the sendEmail method.
17
+ *
18
+ * This is the interface to use if you are creating a new push notification adapter.
19
+ */
20
+ export type PushAdapter<TSendPushResponse = unknown> = ({ payload }: {
21
+ payload: Payload;
22
+ }) => {
23
+ name: string;
24
+ sendPush: (message: SendPushOptions) => Promise<TSendPushResponse>;
25
+ };
26
+ export {};
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["import type { Payload } from 'payload'\n// type Prettify<T> = {\n// [K in keyof T]: T[K];\n// } & NonNullable<unknown>;\n\n/**\n * Options for sending an push notification. Allows access to the PayloadRequest object\n */\nexport type SendPushOptions = {\n title: string\n body: string\n data?: Record<string, any>\n options?: Record<string, any>\n}\n//Prettify<Nodpush notificationerSendMailOptions>;\n/**\n * Email adapter after it has been initialized. This is used internally by Payload.\n */\nexport type InitializedPushAdapter<TSendPushResponse = unknown> = ReturnType<\n PushAdapter<TSendPushResponse>\n>\n/**\n * Push adapter interface. Allows a generic type for the response of the sendEmail method.\n *\n * This is the interface to use if you are creating a new push notification adapter.\n */\nexport type PushAdapter<TSendPushResponse = unknown> = ({ payload }: { payload: Payload }) => {\n // defaultFromAddress: string;\n // defaultFromName: string;\n name: string\n sendPush: (message: SendPushOptions) => Promise<TSendPushResponse>\n}\nexport {}\n"],"names":[],"mappings":"AAgCA,WAAS"}
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@kfiross44/payload-push",
3
+ "version": "0.9.0",
4
+ "description": "A blank template to get started with Payload 3.0",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "payload",
9
+ "cms",
10
+ "push",
11
+ "fcm",
12
+ "push-message",
13
+ "typescript",
14
+ "react",
15
+ "nextjs"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "import": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "devDependencies": {
30
+ "@changesets/cli": "^2.30.0",
31
+ "@eslint/eslintrc": "^3.2.0",
32
+ "@payloadcms/db-mongodb": "3.37.0",
33
+ "@payloadcms/db-postgres": "3.37.0",
34
+ "@payloadcms/db-sqlite": "3.37.0",
35
+ "@payloadcms/eslint-config": "3.9.0",
36
+ "@payloadcms/next": "3.37.0",
37
+ "@payloadcms/richtext-lexical": "3.37.0",
38
+ "@payloadcms/ui": "3.37.0",
39
+ "@playwright/test": "1.58.2",
40
+ "@swc-node/register": "1.10.9",
41
+ "@swc/cli": "0.6.0",
42
+ "@types/node": "22.19.9",
43
+ "@types/react": "19.2.9",
44
+ "@types/react-dom": "19.2.3",
45
+ "copyfiles": "2.4.1",
46
+ "cross-env": "^7.0.3",
47
+ "eslint": "^9.23.0",
48
+ "eslint-config-next": "15.4.11",
49
+ "graphql": "^16.8.1",
50
+ "mongodb-memory-server": "10.1.4",
51
+ "next": "15.4.11",
52
+ "open": "^10.1.0",
53
+ "payload": "3.37.0",
54
+ "prettier": "^3.4.2",
55
+ "qs-esm": "7.0.2",
56
+ "react": "19.2.1",
57
+ "react-dom": "19.2.1",
58
+ "rimraf": "3.0.2",
59
+ "sharp": "0.34.2",
60
+ "sort-package-json": "^2.10.0",
61
+ "typescript": "5.7.3",
62
+ "vite-tsconfig-paths": "6.0.5",
63
+ "vitest": "4.0.18"
64
+ },
65
+ "peerDependencies": {
66
+ "firebase-admin": "^13.5.0",
67
+ "payload": "^3.37.0"
68
+ },
69
+ "engines": {
70
+ "node": "^18.20.2 || >=20.9.0",
71
+ "pnpm": "^9 || ^10"
72
+ },
73
+ "publishConfig": {
74
+ "access": "public"
75
+ },
76
+ "registry": "https://registry.npmjs.org/",
77
+ "scripts": {
78
+ "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
79
+ "format": "bun x prettier --write '**/*.{js,jsx,ts,tsx,json,md,yaml,yml}'",
80
+ "release": "bun run build && changeset publish",
81
+ "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths --ignore **/*.d.ts",
82
+ "build:types": "tsc --outDir dist --rootDir ./src",
83
+ "clean": "rimraf {dist,*.tsbuildinfo}",
84
+ "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
85
+ "dev": "next dev dev --turbo",
86
+ "dev:generate-importmap": "pnpm dev:payload generate:importmap",
87
+ "dev:generate-types": "pnpm dev:payload generate:types",
88
+ "dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
89
+ "generate:importmap": "pnpm dev:generate-importmap",
90
+ "generate:types": "pnpm dev:generate-types",
91
+ "lint": "eslint",
92
+ "lint:fix": "eslint ./src --fix",
93
+ "test": "pnpm test:int && pnpm test:e2e",
94
+ "test:e2e": "playwright test",
95
+ "test:int": "vitest"
96
+ }
97
+ }