@ainsleydev/payload-helper 0.0.40 → 0.1.1

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.
Files changed (54) hide show
  1. package/README.md +227 -0
  2. package/dist/cli/bin.js +20 -0
  3. package/dist/cli/bin.js.map +1 -1
  4. package/dist/cli/commands/preview-emails.d.ts +5 -0
  5. package/dist/cli/commands/preview-emails.js +123 -0
  6. package/dist/cli/commands/preview-emails.js.map +1 -0
  7. package/dist/collections/index.d.ts +3 -0
  8. package/dist/collections/index.js +4 -0
  9. package/dist/collections/index.js.map +1 -0
  10. package/dist/common/index.d.ts +1 -0
  11. package/dist/common/index.js +3 -0
  12. package/dist/common/index.js.map +1 -0
  13. package/dist/email/ForgotPasswordEmail.d.ts +38 -0
  14. package/dist/email/ForgotPasswordEmail.js +61 -0
  15. package/dist/email/ForgotPasswordEmail.js.map +1 -0
  16. package/dist/email/ForgotPasswordEmail.test.d.ts +1 -0
  17. package/dist/email/ForgotPasswordEmail.test.js +202 -0
  18. package/dist/email/ForgotPasswordEmail.test.js.map +1 -0
  19. package/dist/email/VerifyAccountEmail.d.ts +38 -0
  20. package/dist/email/VerifyAccountEmail.js +61 -0
  21. package/dist/email/VerifyAccountEmail.js.map +1 -0
  22. package/dist/email/VerifyAccountEmail.test.d.ts +1 -0
  23. package/dist/email/VerifyAccountEmail.test.js +212 -0
  24. package/dist/email/VerifyAccountEmail.test.js.map +1 -0
  25. package/dist/endpoints/index.d.ts +1 -0
  26. package/dist/endpoints/index.js +3 -0
  27. package/dist/endpoints/index.js.map +1 -0
  28. package/dist/globals/index.d.ts +5 -0
  29. package/dist/globals/index.js +6 -0
  30. package/dist/globals/index.js.map +1 -0
  31. package/dist/index.d.ts +15 -10
  32. package/dist/index.js +14 -66
  33. package/dist/index.js.map +1 -1
  34. package/dist/plugin/email.d.ts +10 -0
  35. package/dist/plugin/email.js +98 -0
  36. package/dist/plugin/email.js.map +1 -0
  37. package/dist/plugin/email.test.js +265 -0
  38. package/dist/plugin/email.test.js.map +1 -0
  39. package/dist/plugin.d.ts +9 -0
  40. package/dist/plugin.js +78 -0
  41. package/dist/plugin.js.map +1 -0
  42. package/dist/types.d.ts +134 -0
  43. package/dist/types.js +3 -1
  44. package/dist/types.js.map +1 -1
  45. package/dist/util/index.d.ts +4 -0
  46. package/dist/util/index.js +6 -0
  47. package/dist/util/index.js.map +1 -0
  48. package/package.json +47 -14
  49. package/dist/plugin/icon.d.ts +0 -6
  50. package/dist/plugin/icon.js +0 -26
  51. package/dist/plugin/icon.js.map +0 -1
  52. package/dist/plugin/logo.d.ts +0 -6
  53. package/dist/plugin/logo.js +0 -26
  54. package/dist/plugin/logo.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,68 +1,16 @@
1
- import { injectAdminIcon, injectAdminLogo } from './plugin/admin.js';
2
- import { cacheHookCollections, cacheHookGlobals } from './plugin/hooks.js';
3
- /**
4
- * Payload Helper Plugin for websites at ainsley.dev
5
- *
6
- * @constructor
7
- * @param pluginOptions
8
- */ export const payloadHelper = (pluginOptions)=>(incomingConfig)=>{
9
- // TODO: Validate Config
10
- let config = incomingConfig;
11
- // Update typescript generation file
12
- config.typescript = config.typescript || {};
13
- config.typescript.outputFile = './src/types/payload.ts';
14
- // Inject admin Logo component if logo config is provided
15
- if (pluginOptions.admin?.logo) {
16
- config = injectAdminLogo(config, pluginOptions.admin.logo, pluginOptions.siteName);
17
- }
18
- // Inject admin Icon component if icon config is provided
19
- if (pluginOptions.admin?.icon) {
20
- config = injectAdminIcon(config, pluginOptions.admin.icon, pluginOptions.siteName);
21
- }
22
- // Map collections & add hooks
23
- config.collections = (config.collections || []).map((collection)=>{
24
- if (collection.upload !== undefined && collection.upload !== true) {
25
- return collection;
26
- }
27
- const hooks = collection.hooks || {};
28
- // Add afterChange hook only if webServer is defined
29
- if (pluginOptions.webServer) {
30
- hooks.afterChange = [
31
- ...hooks.afterChange || [],
32
- cacheHookCollections({
33
- server: pluginOptions.webServer,
34
- slug: collection.slug,
35
- fields: collection.fields,
36
- isCollection: true
37
- })
38
- ];
39
- }
40
- return {
41
- ...collection,
42
- hooks
43
- };
44
- });
45
- // Map globals & add hooks
46
- config.globals = (config.globals || []).map((global)=>{
47
- const hooks = global.hooks || {};
48
- // Add afterChange hook only if webServer is defined
49
- if (pluginOptions.webServer) {
50
- hooks.afterChange = [
51
- ...hooks.afterChange || [],
52
- cacheHookGlobals({
53
- server: pluginOptions.webServer,
54
- slug: global.slug,
55
- fields: global.fields,
56
- isCollection: true
57
- })
58
- ];
59
- }
60
- return {
61
- ...global,
62
- hooks
63
- };
64
- });
65
- return config;
66
- };
1
+ // Main plugin
2
+ export { payloadHelper } from './plugin.js';
3
+ export { Media, imageSizes, imageSizesWithAvif, Redirects } from './collections/index.js';
4
+ export { Settings, Navigation, countries, languages } from './globals/index.js';
5
+ // Email Components
6
+ export { ForgotPasswordEmail } from './email/ForgotPasswordEmail.js';
7
+ export { VerifyAccountEmail } from './email/VerifyAccountEmail.js';
8
+ // Utilities
9
+ export { env, fieldHasName, validateURL, validatePostcode, htmlToLexical, lexicalToHtml } from './util/index.js';
10
+ // Common/Reusable
11
+ export { SEOFields } from './common/index.js';
12
+ // Endpoints
13
+ export { findBySlug } from './endpoints/index.js';
14
+ export { fieldMapper, schemas, addGoJSONSchema } from './plugin/schema.js';
67
15
 
68
16
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config } from 'payload';\nimport { injectAdminIcon, injectAdminLogo } from './plugin/admin.js';\nimport { cacheHookCollections, cacheHookGlobals } from './plugin/hooks.js';\nimport type { PayloadHelperPluginConfig } from './types.js';\n\n/**\n * Payload Helper Plugin for websites at ainsley.dev\n *\n * @constructor\n * @param pluginOptions\n */\nexport const payloadHelper =\n\t(pluginOptions: PayloadHelperPluginConfig) =>\n\t(incomingConfig: Config): Config => {\n\t\t// TODO: Validate Config\n\n\t\tlet config = incomingConfig;\n\n\t\t// Update typescript generation file\n\t\tconfig.typescript = config.typescript || {};\n\t\tconfig.typescript.outputFile = './src/types/payload.ts';\n\n\t\t// Inject admin Logo component if logo config is provided\n\t\tif (pluginOptions.admin?.logo) {\n\t\t\tconfig = injectAdminLogo(config, pluginOptions.admin.logo, pluginOptions.siteName);\n\t\t}\n\n\t\t// Inject admin Icon component if icon config is provided\n\t\tif (pluginOptions.admin?.icon) {\n\t\t\tconfig = injectAdminIcon(config, pluginOptions.admin.icon, pluginOptions.siteName);\n\t\t}\n\n\t\t// Map collections & add hooks\n\t\tconfig.collections = (config.collections || []).map((collection): CollectionConfig => {\n\t\t\tif (collection.upload !== undefined && collection.upload !== true) {\n\t\t\t\treturn collection;\n\t\t\t}\n\n\t\t\tconst hooks = collection.hooks || {};\n\n\t\t\t// Add afterChange hook only if webServer is defined\n\t\t\tif (pluginOptions.webServer) {\n\t\t\t\thooks.afterChange = [\n\t\t\t\t\t...(hooks.afterChange || []),\n\t\t\t\t\tcacheHookCollections({\n\t\t\t\t\t\tserver: pluginOptions.webServer,\n\t\t\t\t\t\tslug: collection.slug,\n\t\t\t\t\t\tfields: collection.fields,\n\t\t\t\t\t\tisCollection: true,\n\t\t\t\t\t}),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...collection,\n\t\t\t\thooks,\n\t\t\t};\n\t\t});\n\n\t\t// Map globals & add hooks\n\t\tconfig.globals = (config.globals || []).map((global) => {\n\t\t\tconst hooks = global.hooks || {};\n\n\t\t\t// Add afterChange hook only if webServer is defined\n\t\t\tif (pluginOptions.webServer) {\n\t\t\t\thooks.afterChange = [\n\t\t\t\t\t...(hooks.afterChange || []),\n\t\t\t\t\tcacheHookGlobals({\n\t\t\t\t\t\tserver: pluginOptions.webServer,\n\t\t\t\t\t\tslug: global.slug,\n\t\t\t\t\t\tfields: global.fields,\n\t\t\t\t\t\tisCollection: true,\n\t\t\t\t\t}),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...global,\n\t\t\t\thooks,\n\t\t\t};\n\t\t});\n\n\t\treturn config;\n\t};\n\nexport type { IconProps } from './admin/components/Icon.js';\nexport type { LogoProps } from './admin/components/Logo.js';\nexport type {\n\tAdminConfig,\n\tAdminIconConfig,\n\tAdminLogoConfig,\n\tPayloadHelperPluginConfig,\n} from './types.js';\n"],"names":["injectAdminIcon","injectAdminLogo","cacheHookCollections","cacheHookGlobals","payloadHelper","pluginOptions","incomingConfig","config","typescript","outputFile","admin","logo","siteName","icon","collections","map","collection","upload","undefined","hooks","webServer","afterChange","server","slug","fields","isCollection","globals","global"],"mappings":"AACA,SAASA,eAAe,EAAEC,eAAe,QAAQ,oBAAoB;AACrE,SAASC,oBAAoB,EAAEC,gBAAgB,QAAQ,oBAAoB;AAG3E;;;;;CAKC,GACD,OAAO,MAAMC,gBACZ,CAACC,gBACD,CAACC;QACA,wBAAwB;QAExB,IAAIC,SAASD;QAEb,oCAAoC;QACpCC,OAAOC,UAAU,GAAGD,OAAOC,UAAU,IAAI,CAAC;QAC1CD,OAAOC,UAAU,CAACC,UAAU,GAAG;QAE/B,yDAAyD;QACzD,IAAIJ,cAAcK,KAAK,EAAEC,MAAM;YAC9BJ,SAASN,gBAAgBM,QAAQF,cAAcK,KAAK,CAACC,IAAI,EAAEN,cAAcO,QAAQ;QAClF;QAEA,yDAAyD;QACzD,IAAIP,cAAcK,KAAK,EAAEG,MAAM;YAC9BN,SAASP,gBAAgBO,QAAQF,cAAcK,KAAK,CAACG,IAAI,EAAER,cAAcO,QAAQ;QAClF;QAEA,8BAA8B;QAC9BL,OAAOO,WAAW,GAAG,AAACP,CAAAA,OAAOO,WAAW,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC;YACpD,IAAIA,WAAWC,MAAM,KAAKC,aAAaF,WAAWC,MAAM,KAAK,MAAM;gBAClE,OAAOD;YACR;YAEA,MAAMG,QAAQH,WAAWG,KAAK,IAAI,CAAC;YAEnC,oDAAoD;YACpD,IAAId,cAAce,SAAS,EAAE;gBAC5BD,MAAME,WAAW,GAAG;uBACfF,MAAME,WAAW,IAAI,EAAE;oBAC3BnB,qBAAqB;wBACpBoB,QAAQjB,cAAce,SAAS;wBAC/BG,MAAMP,WAAWO,IAAI;wBACrBC,QAAQR,WAAWQ,MAAM;wBACzBC,cAAc;oBACf;iBACA;YACF;YAEA,OAAO;gBACN,GAAGT,UAAU;gBACbG;YACD;QACD;QAEA,0BAA0B;QAC1BZ,OAAOmB,OAAO,GAAG,AAACnB,CAAAA,OAAOmB,OAAO,IAAI,EAAE,AAAD,EAAGX,GAAG,CAAC,CAACY;YAC5C,MAAMR,QAAQQ,OAAOR,KAAK,IAAI,CAAC;YAE/B,oDAAoD;YACpD,IAAId,cAAce,SAAS,EAAE;gBAC5BD,MAAME,WAAW,GAAG;uBACfF,MAAME,WAAW,IAAI,EAAE;oBAC3BlB,iBAAiB;wBAChBmB,QAAQjB,cAAce,SAAS;wBAC/BG,MAAMI,OAAOJ,IAAI;wBACjBC,QAAQG,OAAOH,MAAM;wBACrBC,cAAc;oBACf;iBACA;YACF;YAEA,OAAO;gBACN,GAAGE,MAAM;gBACTR;YACD;QACD;QAEA,OAAOZ;IACR,EAAE"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Main plugin\nexport { payloadHelper } from './plugin.js';\n\n// Types\nexport type {\n\tPayloadHelperPluginConfig,\n\tAdminConfig,\n\tAdminIconConfig,\n\tAdminLogoConfig,\n\tEmailConfig,\n\tEmailContentOverrides,\n\tSettingsConfig,\n\tWebServerConfig,\n} from './types.js';\n\n// Collections\nexport type { MediaArgs } from './collections/Media.js';\nexport { Media, imageSizes, imageSizesWithAvif, Redirects } from './collections/index.js';\n\n// Globals\nexport type { SettingsArgs } from './globals/Settings.js';\nexport { Settings, Navigation, countries, languages } from './globals/index.js';\n\n// Email Components\nexport { ForgotPasswordEmail } from './email/ForgotPasswordEmail.js';\nexport type { ForgotPasswordEmailProps } from './email/ForgotPasswordEmail.js';\nexport { VerifyAccountEmail } from './email/VerifyAccountEmail.js';\nexport type { VerifyAccountEmailProps } from './email/VerifyAccountEmail.js';\n\n// Admin Components\nexport type { IconProps } from './admin/components/Icon.js';\nexport type { LogoProps } from './admin/components/Logo.js';\n\n// Utilities\nexport {\n\tenv,\n\tfieldHasName,\n\tvalidateURL,\n\tvalidatePostcode,\n\thtmlToLexical,\n\tlexicalToHtml,\n} from './util/index.js';\n\n// Common/Reusable\nexport { SEOFields } from './common/index.js';\n\n// Endpoints\nexport { findBySlug } from './endpoints/index.js';\n\n// Schema utilities\nexport type { SchemaOptions } from './plugin/schema.js';\nexport { fieldMapper, schemas, addGoJSONSchema } from './plugin/schema.js';\n"],"names":["payloadHelper","Media","imageSizes","imageSizesWithAvif","Redirects","Settings","Navigation","countries","languages","ForgotPasswordEmail","VerifyAccountEmail","env","fieldHasName","validateURL","validatePostcode","htmlToLexical","lexicalToHtml","SEOFields","findBySlug","fieldMapper","schemas","addGoJSONSchema"],"mappings":"AAAA,cAAc;AACd,SAASA,aAAa,QAAQ,cAAc;AAgB5C,SAASC,KAAK,EAAEC,UAAU,EAAEC,kBAAkB,EAAEC,SAAS,QAAQ,yBAAyB;AAI1F,SAASC,QAAQ,EAAEC,UAAU,EAAEC,SAAS,EAAEC,SAAS,QAAQ,qBAAqB;AAEhF,mBAAmB;AACnB,SAASC,mBAAmB,QAAQ,iCAAiC;AAErE,SAASC,kBAAkB,QAAQ,gCAAgC;AAOnE,YAAY;AACZ,SACCC,GAAG,EACHC,YAAY,EACZC,WAAW,EACXC,gBAAgB,EAChBC,aAAa,EACbC,aAAa,QACP,kBAAkB;AAEzB,kBAAkB;AAClB,SAASC,SAAS,QAAQ,oBAAoB;AAE9C,YAAY;AACZ,SAASC,UAAU,QAAQ,uBAAuB;AAIlD,SAASC,WAAW,EAAEC,OAAO,EAAEC,eAAe,QAAQ,qBAAqB"}
@@ -0,0 +1,10 @@
1
+ import type { Config } from 'payload';
2
+ import type { EmailConfig } from '../types.js';
3
+ /**
4
+ * Injects email templates into all auth-enabled collections in the Payload config.
5
+ *
6
+ * @param config - The Payload configuration object
7
+ * @param emailConfig - The email configuration from plugin options
8
+ * @returns The modified Payload configuration with email templates injected
9
+ */
10
+ export declare const injectEmailTemplates: (config: Config, emailConfig: EmailConfig) => Config;
@@ -0,0 +1,98 @@
1
+ import { renderEmail } from '@ainsleydev/email-templates';
2
+ import { ForgotPasswordEmail } from '../email/ForgotPasswordEmail.js';
3
+ import { VerifyAccountEmail } from '../email/VerifyAccountEmail.js';
4
+ /**
5
+ * Injects email templates into all auth-enabled collections in the Payload config.
6
+ *
7
+ * @param config - The Payload configuration object
8
+ * @param emailConfig - The email configuration from plugin options
9
+ * @returns The modified Payload configuration with email templates injected
10
+ */ export const injectEmailTemplates = (config, emailConfig)=>{
11
+ // Get the website URL for branding, defaulting to Payload's serverUrl
12
+ const websiteUrl = emailConfig.frontEndUrl || config.serverURL || '';
13
+ // Merge user theme with websiteUrl for branding
14
+ const themeOverride = {
15
+ ...emailConfig.theme,
16
+ branding: {
17
+ ...emailConfig.theme?.branding,
18
+ websiteUrl
19
+ }
20
+ };
21
+ // Find all collections with auth enabled
22
+ const collectionsWithAuth = config.collections?.filter((collection)=>collection.auth) || [];
23
+ // If no collections with auth, return config unchanged
24
+ if (collectionsWithAuth.length === 0) {
25
+ return config;
26
+ }
27
+ // Inject email templates into each auth-enabled collection
28
+ const updatedCollections = config.collections?.map((collection)=>{
29
+ // Skip collections without auth
30
+ if (!collection.auth) {
31
+ return collection;
32
+ }
33
+ // Clone the collection to avoid mutation
34
+ const updatedCollection = {
35
+ ...collection
36
+ };
37
+ // Ensure auth is an object (it could be true or an object)
38
+ if (typeof updatedCollection.auth === 'boolean') {
39
+ updatedCollection.auth = {};
40
+ } else {
41
+ updatedCollection.auth = {
42
+ ...updatedCollection.auth
43
+ };
44
+ }
45
+ // Inject forgotPassword email template
46
+ const currentForgotPassword = updatedCollection.auth.forgotPassword;
47
+ updatedCollection.auth.forgotPassword = {
48
+ ...typeof currentForgotPassword === 'object' ? currentForgotPassword : {},
49
+ generateEmailHTML: async (args)=>{
50
+ const token = args?.token || '';
51
+ const user = args?.user || {};
52
+ const resetUrl = `${config.serverURL}/admin/reset/${token}`;
53
+ return renderEmail({
54
+ component: ForgotPasswordEmail,
55
+ props: {
56
+ user: {
57
+ firstName: user?.firstName,
58
+ email: user?.email
59
+ },
60
+ resetUrl,
61
+ content: emailConfig.forgotPassword
62
+ },
63
+ theme: themeOverride
64
+ });
65
+ }
66
+ };
67
+ // Inject verify email template
68
+ const currentVerify = updatedCollection.auth.verify;
69
+ updatedCollection.auth.verify = {
70
+ ...typeof currentVerify === 'object' ? currentVerify : {},
71
+ generateEmailHTML: async (args)=>{
72
+ const token = args?.token || '';
73
+ const user = args?.user || {};
74
+ // For verify emails, the token is used in the verification URL
75
+ const verifyUrl = `${config.serverURL}/admin/${collection.slug}/verify/${token}`;
76
+ return renderEmail({
77
+ component: VerifyAccountEmail,
78
+ props: {
79
+ user: {
80
+ firstName: user?.firstName,
81
+ email: user?.email
82
+ },
83
+ verifyUrl,
84
+ content: emailConfig.verifyAccount
85
+ },
86
+ theme: themeOverride
87
+ });
88
+ }
89
+ };
90
+ return updatedCollection;
91
+ });
92
+ return {
93
+ ...config,
94
+ collections: updatedCollections
95
+ };
96
+ };
97
+
98
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugin/email.ts"],"sourcesContent":["import { renderEmail } from '@ainsleydev/email-templates';\nimport type { Config } from 'payload';\n\nimport { ForgotPasswordEmail } from '../email/ForgotPasswordEmail.js';\nimport { VerifyAccountEmail } from '../email/VerifyAccountEmail.js';\nimport type { EmailConfig } from '../types.js';\n\n/**\n * Injects email templates into all auth-enabled collections in the Payload config.\n *\n * @param config - The Payload configuration object\n * @param emailConfig - The email configuration from plugin options\n * @returns The modified Payload configuration with email templates injected\n */\nexport const injectEmailTemplates = (config: Config, emailConfig: EmailConfig): Config => {\n\t// Get the website URL for branding, defaulting to Payload's serverUrl\n\tconst websiteUrl = emailConfig.frontEndUrl || config.serverURL || '';\n\n\t// Merge user theme with websiteUrl for branding\n\tconst themeOverride = {\n\t\t...emailConfig.theme,\n\t\tbranding: {\n\t\t\t...emailConfig.theme?.branding,\n\t\t\twebsiteUrl,\n\t\t},\n\t};\n\n\t// Find all collections with auth enabled\n\tconst collectionsWithAuth = config.collections?.filter((collection) => collection.auth) || [];\n\n\t// If no collections with auth, return config unchanged\n\tif (collectionsWithAuth.length === 0) {\n\t\treturn config;\n\t}\n\n\t// Inject email templates into each auth-enabled collection\n\tconst updatedCollections = config.collections?.map((collection) => {\n\t\t// Skip collections without auth\n\t\tif (!collection.auth) {\n\t\t\treturn collection;\n\t\t}\n\n\t\t// Clone the collection to avoid mutation\n\t\tconst updatedCollection = { ...collection };\n\n\t\t// Ensure auth is an object (it could be true or an object)\n\t\tif (typeof updatedCollection.auth === 'boolean') {\n\t\t\tupdatedCollection.auth = {};\n\t\t} else {\n\t\t\tupdatedCollection.auth = { ...updatedCollection.auth };\n\t\t}\n\n\t\t// Inject forgotPassword email template\n\t\tconst currentForgotPassword = updatedCollection.auth.forgotPassword;\n\t\tupdatedCollection.auth.forgotPassword = {\n\t\t\t...(typeof currentForgotPassword === 'object' ? currentForgotPassword : {}),\n\t\t\tgenerateEmailHTML: async (args) => {\n\t\t\t\tconst token = args?.token || '';\n\t\t\t\tconst user = args?.user || {};\n\t\t\t\tconst resetUrl = `${config.serverURL}/admin/reset/${token}`;\n\n\t\t\t\treturn renderEmail({\n\t\t\t\t\tcomponent: ForgotPasswordEmail,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\tfirstName: user?.firstName,\n\t\t\t\t\t\t\temail: user?.email,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tresetUrl,\n\t\t\t\t\t\tcontent: emailConfig.forgotPassword,\n\t\t\t\t\t},\n\t\t\t\t\ttheme: themeOverride,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\n\t\t// Inject verify email template\n\t\tconst currentVerify = updatedCollection.auth.verify;\n\t\tupdatedCollection.auth.verify = {\n\t\t\t...(typeof currentVerify === 'object' ? currentVerify : {}),\n\t\t\tgenerateEmailHTML: async (args) => {\n\t\t\t\tconst token = args?.token || '';\n\t\t\t\tconst user = args?.user || {};\n\t\t\t\t// For verify emails, the token is used in the verification URL\n\t\t\t\tconst verifyUrl = `${config.serverURL}/admin/${collection.slug}/verify/${token}`;\n\n\t\t\t\treturn renderEmail({\n\t\t\t\t\tcomponent: VerifyAccountEmail,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\tfirstName: user?.firstName,\n\t\t\t\t\t\t\temail: user?.email,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tverifyUrl,\n\t\t\t\t\t\tcontent: emailConfig.verifyAccount,\n\t\t\t\t\t},\n\t\t\t\t\ttheme: themeOverride,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\n\t\treturn updatedCollection;\n\t});\n\n\treturn {\n\t\t...config,\n\t\tcollections: updatedCollections,\n\t};\n};\n"],"names":["renderEmail","ForgotPasswordEmail","VerifyAccountEmail","injectEmailTemplates","config","emailConfig","websiteUrl","frontEndUrl","serverURL","themeOverride","theme","branding","collectionsWithAuth","collections","filter","collection","auth","length","updatedCollections","map","updatedCollection","currentForgotPassword","forgotPassword","generateEmailHTML","args","token","user","resetUrl","component","props","firstName","email","content","currentVerify","verify","verifyUrl","slug","verifyAccount"],"mappings":"AAAA,SAASA,WAAW,QAAQ,8BAA8B;AAG1D,SAASC,mBAAmB,QAAQ,kCAAkC;AACtE,SAASC,kBAAkB,QAAQ,iCAAiC;AAGpE;;;;;;CAMC,GACD,OAAO,MAAMC,uBAAuB,CAACC,QAAgBC;IACpD,sEAAsE;IACtE,MAAMC,aAAaD,YAAYE,WAAW,IAAIH,OAAOI,SAAS,IAAI;IAElE,gDAAgD;IAChD,MAAMC,gBAAgB;QACrB,GAAGJ,YAAYK,KAAK;QACpBC,UAAU;YACT,GAAGN,YAAYK,KAAK,EAAEC,QAAQ;YAC9BL;QACD;IACD;IAEA,yCAAyC;IACzC,MAAMM,sBAAsBR,OAAOS,WAAW,EAAEC,OAAO,CAACC,aAAeA,WAAWC,IAAI,KAAK,EAAE;IAE7F,uDAAuD;IACvD,IAAIJ,oBAAoBK,MAAM,KAAK,GAAG;QACrC,OAAOb;IACR;IAEA,2DAA2D;IAC3D,MAAMc,qBAAqBd,OAAOS,WAAW,EAAEM,IAAI,CAACJ;QACnD,gCAAgC;QAChC,IAAI,CAACA,WAAWC,IAAI,EAAE;YACrB,OAAOD;QACR;QAEA,yCAAyC;QACzC,MAAMK,oBAAoB;YAAE,GAAGL,UAAU;QAAC;QAE1C,2DAA2D;QAC3D,IAAI,OAAOK,kBAAkBJ,IAAI,KAAK,WAAW;YAChDI,kBAAkBJ,IAAI,GAAG,CAAC;QAC3B,OAAO;YACNI,kBAAkBJ,IAAI,GAAG;gBAAE,GAAGI,kBAAkBJ,IAAI;YAAC;QACtD;QAEA,uCAAuC;QACvC,MAAMK,wBAAwBD,kBAAkBJ,IAAI,CAACM,cAAc;QACnEF,kBAAkBJ,IAAI,CAACM,cAAc,GAAG;YACvC,GAAI,OAAOD,0BAA0B,WAAWA,wBAAwB,CAAC,CAAC;YAC1EE,mBAAmB,OAAOC;gBACzB,MAAMC,QAAQD,MAAMC,SAAS;gBAC7B,MAAMC,OAAOF,MAAME,QAAQ,CAAC;gBAC5B,MAAMC,WAAW,GAAGvB,OAAOI,SAAS,CAAC,aAAa,EAAEiB,OAAO;gBAE3D,OAAOzB,YAAY;oBAClB4B,WAAW3B;oBACX4B,OAAO;wBACNH,MAAM;4BACLI,WAAWJ,MAAMI;4BACjBC,OAAOL,MAAMK;wBACd;wBACAJ;wBACAK,SAAS3B,YAAYiB,cAAc;oBACpC;oBACAZ,OAAOD;gBACR;YACD;QACD;QAEA,+BAA+B;QAC/B,MAAMwB,gBAAgBb,kBAAkBJ,IAAI,CAACkB,MAAM;QACnDd,kBAAkBJ,IAAI,CAACkB,MAAM,GAAG;YAC/B,GAAI,OAAOD,kBAAkB,WAAWA,gBAAgB,CAAC,CAAC;YAC1DV,mBAAmB,OAAOC;gBACzB,MAAMC,QAAQD,MAAMC,SAAS;gBAC7B,MAAMC,OAAOF,MAAME,QAAQ,CAAC;gBAC5B,+DAA+D;gBAC/D,MAAMS,YAAY,GAAG/B,OAAOI,SAAS,CAAC,OAAO,EAAEO,WAAWqB,IAAI,CAAC,QAAQ,EAAEX,OAAO;gBAEhF,OAAOzB,YAAY;oBAClB4B,WAAW1B;oBACX2B,OAAO;wBACNH,MAAM;4BACLI,WAAWJ,MAAMI;4BACjBC,OAAOL,MAAMK;wBACd;wBACAI;wBACAH,SAAS3B,YAAYgC,aAAa;oBACnC;oBACA3B,OAAOD;gBACR;YACD;QACD;QAEA,OAAOW;IACR;IAEA,OAAO;QACN,GAAGhB,MAAM;QACTS,aAAaK;IACd;AACD,EAAE"}
@@ -0,0 +1,265 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+ import { injectEmailTemplates } from './email.js';
3
+ // Mock the email templates module
4
+ vi.mock('@ainsleydev/email-templates', ()=>({
5
+ renderEmail: vi.fn(async ()=>'<html>Mocked Email</html>')
6
+ }));
7
+ describe('injectEmailTemplates', ()=>{
8
+ const mockEmailConfig = {
9
+ frontEndUrl: 'https://example.com',
10
+ theme: {
11
+ branding: {
12
+ companyName: 'Test Company'
13
+ }
14
+ },
15
+ forgotPassword: {
16
+ heading: 'Reset Your Password',
17
+ bodyText: 'Click below to reset your password'
18
+ },
19
+ verifyAccount: {
20
+ heading: 'Verify Your Account',
21
+ bodyText: 'Click below to verify your account'
22
+ }
23
+ };
24
+ test('should inject email templates into auth-enabled collections', ()=>{
25
+ const config = {
26
+ collections: [
27
+ {
28
+ slug: 'users',
29
+ fields: [],
30
+ auth: true
31
+ }
32
+ ],
33
+ serverURL: 'https://api.example.com'
34
+ };
35
+ const result = injectEmailTemplates(config, mockEmailConfig);
36
+ const usersCollection = result.collections?.[0];
37
+ expect(usersCollection).toBeDefined();
38
+ expect(typeof usersCollection?.auth).toBe('object');
39
+ if (typeof usersCollection?.auth === 'object') {
40
+ expect(usersCollection.auth.forgotPassword).toBeDefined();
41
+ expect(usersCollection.auth.verify).toBeDefined();
42
+ expect(usersCollection.auth.forgotPassword?.generateEmailHTML).toBeDefined();
43
+ expect(usersCollection.auth.verify?.generateEmailHTML).toBeDefined();
44
+ }
45
+ });
46
+ test('should not inject email templates into non-auth collections', ()=>{
47
+ const config = {
48
+ collections: [
49
+ {
50
+ slug: 'posts',
51
+ fields: []
52
+ }
53
+ ],
54
+ serverURL: 'https://api.example.com'
55
+ };
56
+ const result = injectEmailTemplates(config, mockEmailConfig);
57
+ const postsCollection = result.collections?.[0];
58
+ expect(postsCollection).toBeDefined();
59
+ expect(postsCollection?.auth).toBeUndefined();
60
+ });
61
+ test('should return config unchanged when no auth-enabled collections exist', ()=>{
62
+ const config = {
63
+ collections: [
64
+ {
65
+ slug: 'posts',
66
+ fields: []
67
+ },
68
+ {
69
+ slug: 'media',
70
+ fields: []
71
+ }
72
+ ],
73
+ serverURL: 'https://api.example.com'
74
+ };
75
+ const result = injectEmailTemplates(config, mockEmailConfig);
76
+ expect(result.collections).toEqual(config.collections);
77
+ });
78
+ test('should handle auth as boolean and convert to object', ()=>{
79
+ const config = {
80
+ collections: [
81
+ {
82
+ slug: 'users',
83
+ fields: [],
84
+ auth: true
85
+ }
86
+ ],
87
+ serverURL: 'https://api.example.com'
88
+ };
89
+ const result = injectEmailTemplates(config, mockEmailConfig);
90
+ const usersCollection = result.collections?.[0];
91
+ expect(typeof usersCollection?.auth).toBe('object');
92
+ });
93
+ test('should preserve existing auth configuration', ()=>{
94
+ const config = {
95
+ collections: [
96
+ {
97
+ slug: 'users',
98
+ fields: [],
99
+ auth: {
100
+ tokenExpiration: 7200,
101
+ maxLoginAttempts: 5
102
+ }
103
+ }
104
+ ],
105
+ serverURL: 'https://api.example.com'
106
+ };
107
+ const result = injectEmailTemplates(config, mockEmailConfig);
108
+ const usersCollection = result.collections?.[0];
109
+ if (typeof usersCollection?.auth === 'object') {
110
+ expect(usersCollection.auth.tokenExpiration).toBe(7200);
111
+ expect(usersCollection.auth.maxLoginAttempts).toBe(5);
112
+ }
113
+ });
114
+ test('should merge theme with websiteUrl from frontEndUrl', ()=>{
115
+ const config = {
116
+ collections: [
117
+ {
118
+ slug: 'users',
119
+ fields: [],
120
+ auth: true
121
+ }
122
+ ],
123
+ serverURL: 'https://api.example.com'
124
+ };
125
+ const emailConfig = {
126
+ frontEndUrl: 'https://custom-frontend.com',
127
+ theme: {
128
+ branding: {
129
+ companyName: 'Test Company'
130
+ }
131
+ }
132
+ };
133
+ const result = injectEmailTemplates(config, emailConfig);
134
+ // The theme should have websiteUrl merged into branding
135
+ // This is tested indirectly through the generateEmailHTML function
136
+ const usersCollection = result.collections?.[0];
137
+ expect(usersCollection).toBeDefined();
138
+ });
139
+ test('should use serverURL when frontEndUrl is not provided', ()=>{
140
+ const config = {
141
+ collections: [
142
+ {
143
+ slug: 'users',
144
+ fields: [],
145
+ auth: true
146
+ }
147
+ ],
148
+ serverURL: 'https://api.example.com'
149
+ };
150
+ const emailConfig = {
151
+ // No frontEndUrl provided
152
+ theme: {
153
+ branding: {
154
+ companyName: 'Test Company'
155
+ }
156
+ }
157
+ };
158
+ const result = injectEmailTemplates(config, emailConfig);
159
+ const usersCollection = result.collections?.[0];
160
+ expect(usersCollection).toBeDefined();
161
+ // The websiteUrl should default to serverURL
162
+ });
163
+ test('should handle multiple auth-enabled collections', ()=>{
164
+ const config = {
165
+ collections: [
166
+ {
167
+ slug: 'users',
168
+ fields: [],
169
+ auth: true
170
+ },
171
+ {
172
+ slug: 'admins',
173
+ fields: [],
174
+ auth: {
175
+ tokenExpiration: 3600
176
+ }
177
+ },
178
+ {
179
+ slug: 'posts',
180
+ fields: []
181
+ }
182
+ ],
183
+ serverURL: 'https://api.example.com'
184
+ };
185
+ const result = injectEmailTemplates(config, mockEmailConfig);
186
+ expect(result.collections).toHaveLength(3);
187
+ const usersCollection = result.collections?.[0];
188
+ const adminsCollection = result.collections?.[1];
189
+ const postsCollection = result.collections?.[2];
190
+ // Users should have email templates
191
+ if (typeof usersCollection?.auth === 'object') {
192
+ expect(usersCollection.auth.forgotPassword?.generateEmailHTML).toBeDefined();
193
+ expect(usersCollection.auth.verify?.generateEmailHTML).toBeDefined();
194
+ }
195
+ // Admins should have email templates and preserve existing config
196
+ if (typeof adminsCollection?.auth === 'object') {
197
+ expect(adminsCollection.auth.forgotPassword?.generateEmailHTML).toBeDefined();
198
+ expect(adminsCollection.auth.verify?.generateEmailHTML).toBeDefined();
199
+ expect(adminsCollection.auth.tokenExpiration).toBe(3600);
200
+ }
201
+ // Posts should remain unchanged
202
+ expect(postsCollection?.auth).toBeUndefined();
203
+ });
204
+ test('should handle empty collections array', ()=>{
205
+ const config = {
206
+ collections: [],
207
+ serverURL: 'https://api.example.com'
208
+ };
209
+ const result = injectEmailTemplates(config, mockEmailConfig);
210
+ expect(result.collections).toEqual([]);
211
+ });
212
+ test('should handle config without collections property', ()=>{
213
+ const config = {
214
+ serverURL: 'https://api.example.com'
215
+ };
216
+ const result = injectEmailTemplates(config, mockEmailConfig);
217
+ expect(result.collections).toBeUndefined();
218
+ });
219
+ test('should preserve existing forgotPassword configuration', ()=>{
220
+ const config = {
221
+ collections: [
222
+ {
223
+ slug: 'users',
224
+ fields: [],
225
+ auth: {
226
+ forgotPassword: {
227
+ generateEmailSubject: ()=>'Custom Subject'
228
+ }
229
+ }
230
+ }
231
+ ],
232
+ serverURL: 'https://api.example.com'
233
+ };
234
+ const result = injectEmailTemplates(config, mockEmailConfig);
235
+ const usersCollection = result.collections?.[0];
236
+ if (typeof usersCollection?.auth === 'object') {
237
+ expect(usersCollection.auth.forgotPassword?.generateEmailSubject).toBeDefined();
238
+ expect(usersCollection.auth.forgotPassword?.generateEmailHTML).toBeDefined();
239
+ }
240
+ });
241
+ test('should preserve existing verify configuration', ()=>{
242
+ const config = {
243
+ collections: [
244
+ {
245
+ slug: 'users',
246
+ fields: [],
247
+ auth: {
248
+ verify: {
249
+ generateEmailSubject: ()=>'Custom Verify Subject'
250
+ }
251
+ }
252
+ }
253
+ ],
254
+ serverURL: 'https://api.example.com'
255
+ };
256
+ const result = injectEmailTemplates(config, mockEmailConfig);
257
+ const usersCollection = result.collections?.[0];
258
+ if (typeof usersCollection?.auth === 'object') {
259
+ expect(usersCollection.auth.verify?.generateEmailSubject).toBeDefined();
260
+ expect(usersCollection.auth.verify?.generateEmailHTML).toBeDefined();
261
+ }
262
+ });
263
+ });
264
+
265
+ //# sourceMappingURL=email.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugin/email.test.ts"],"sourcesContent":["import type { CollectionConfig, Config } from 'payload';\nimport { describe, expect, test, vi } from 'vitest';\nimport type { EmailConfig } from '../types.js';\nimport { injectEmailTemplates } from './email.js';\n\n// Mock the email templates module\nvi.mock('@ainsleydev/email-templates', () => ({\n\trenderEmail: vi.fn(async () => '<html>Mocked Email</html>'),\n}));\n\ndescribe('injectEmailTemplates', () => {\n\tconst mockEmailConfig: EmailConfig = {\n\t\tfrontEndUrl: 'https://example.com',\n\t\ttheme: {\n\t\t\tbranding: {\n\t\t\t\tcompanyName: 'Test Company',\n\t\t\t},\n\t\t},\n\t\tforgotPassword: {\n\t\t\theading: 'Reset Your Password',\n\t\t\tbodyText: 'Click below to reset your password',\n\t\t},\n\t\tverifyAccount: {\n\t\t\theading: 'Verify Your Account',\n\t\t\tbodyText: 'Click below to verify your account',\n\t\t},\n\t};\n\n\ttest('should inject email templates into auth-enabled collections', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'users',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: true,\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\tconst usersCollection = result.collections?.[0];\n\t\texpect(usersCollection).toBeDefined();\n\t\texpect(typeof usersCollection?.auth).toBe('object');\n\n\t\tif (typeof usersCollection?.auth === 'object') {\n\t\t\texpect(usersCollection.auth.forgotPassword).toBeDefined();\n\t\t\texpect(usersCollection.auth.verify).toBeDefined();\n\t\t\texpect(usersCollection.auth.forgotPassword?.generateEmailHTML).toBeDefined();\n\t\t\texpect(usersCollection.auth.verify?.generateEmailHTML).toBeDefined();\n\t\t}\n\t});\n\n\ttest('should not inject email templates into non-auth collections', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'posts',\n\t\t\t\t\tfields: [],\n\t\t\t\t\t// No auth property\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\tconst postsCollection = result.collections?.[0];\n\t\texpect(postsCollection).toBeDefined();\n\t\texpect(postsCollection?.auth).toBeUndefined();\n\t});\n\n\ttest('should return config unchanged when no auth-enabled collections exist', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'posts',\n\t\t\t\t\tfields: [],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: 'media',\n\t\t\t\t\tfields: [],\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\texpect(result.collections).toEqual(config.collections);\n\t});\n\n\ttest('should handle auth as boolean and convert to object', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'users',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: true,\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\tconst usersCollection = result.collections?.[0];\n\t\texpect(typeof usersCollection?.auth).toBe('object');\n\t});\n\n\ttest('should preserve existing auth configuration', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'users',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: {\n\t\t\t\t\t\ttokenExpiration: 7200,\n\t\t\t\t\t\tmaxLoginAttempts: 5,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\tconst usersCollection = result.collections?.[0];\n\t\tif (typeof usersCollection?.auth === 'object') {\n\t\t\texpect(usersCollection.auth.tokenExpiration).toBe(7200);\n\t\t\texpect(usersCollection.auth.maxLoginAttempts).toBe(5);\n\t\t}\n\t});\n\n\ttest('should merge theme with websiteUrl from frontEndUrl', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'users',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: true,\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst emailConfig: EmailConfig = {\n\t\t\tfrontEndUrl: 'https://custom-frontend.com',\n\t\t\ttheme: {\n\t\t\t\tbranding: {\n\t\t\t\t\tcompanyName: 'Test Company',\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, emailConfig);\n\n\t\t// The theme should have websiteUrl merged into branding\n\t\t// This is tested indirectly through the generateEmailHTML function\n\t\tconst usersCollection = result.collections?.[0];\n\t\texpect(usersCollection).toBeDefined();\n\t});\n\n\ttest('should use serverURL when frontEndUrl is not provided', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'users',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: true,\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst emailConfig: EmailConfig = {\n\t\t\t// No frontEndUrl provided\n\t\t\ttheme: {\n\t\t\t\tbranding: {\n\t\t\t\t\tcompanyName: 'Test Company',\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, emailConfig);\n\n\t\tconst usersCollection = result.collections?.[0];\n\t\texpect(usersCollection).toBeDefined();\n\t\t// The websiteUrl should default to serverURL\n\t});\n\n\ttest('should handle multiple auth-enabled collections', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'users',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: 'admins',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: {\n\t\t\t\t\t\ttokenExpiration: 3600,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: 'posts',\n\t\t\t\t\tfields: [],\n\t\t\t\t\t// No auth\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\texpect(result.collections).toHaveLength(3);\n\n\t\tconst usersCollection = result.collections?.[0];\n\t\tconst adminsCollection = result.collections?.[1];\n\t\tconst postsCollection = result.collections?.[2];\n\n\t\t// Users should have email templates\n\t\tif (typeof usersCollection?.auth === 'object') {\n\t\t\texpect(usersCollection.auth.forgotPassword?.generateEmailHTML).toBeDefined();\n\t\t\texpect(usersCollection.auth.verify?.generateEmailHTML).toBeDefined();\n\t\t}\n\n\t\t// Admins should have email templates and preserve existing config\n\t\tif (typeof adminsCollection?.auth === 'object') {\n\t\t\texpect(adminsCollection.auth.forgotPassword?.generateEmailHTML).toBeDefined();\n\t\t\texpect(adminsCollection.auth.verify?.generateEmailHTML).toBeDefined();\n\t\t\texpect(adminsCollection.auth.tokenExpiration).toBe(3600);\n\t\t}\n\n\t\t// Posts should remain unchanged\n\t\texpect(postsCollection?.auth).toBeUndefined();\n\t});\n\n\ttest('should handle empty collections array', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\texpect(result.collections).toEqual([]);\n\t});\n\n\ttest('should handle config without collections property', () => {\n\t\tconst config: Config = {\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\texpect(result.collections).toBeUndefined();\n\t});\n\n\ttest('should preserve existing forgotPassword configuration', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'users',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: {\n\t\t\t\t\t\tforgotPassword: {\n\t\t\t\t\t\t\tgenerateEmailSubject: () => 'Custom Subject',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\tconst usersCollection = result.collections?.[0];\n\t\tif (typeof usersCollection?.auth === 'object') {\n\t\t\texpect(usersCollection.auth.forgotPassword?.generateEmailSubject).toBeDefined();\n\t\t\texpect(usersCollection.auth.forgotPassword?.generateEmailHTML).toBeDefined();\n\t\t}\n\t});\n\n\ttest('should preserve existing verify configuration', () => {\n\t\tconst config: Config = {\n\t\t\tcollections: [\n\t\t\t\t{\n\t\t\t\t\tslug: 'users',\n\t\t\t\t\tfields: [],\n\t\t\t\t\tauth: {\n\t\t\t\t\t\tverify: {\n\t\t\t\t\t\t\tgenerateEmailSubject: () => 'Custom Verify Subject',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t] as CollectionConfig[],\n\t\t\tserverURL: 'https://api.example.com',\n\t\t};\n\n\t\tconst result = injectEmailTemplates(config, mockEmailConfig);\n\n\t\tconst usersCollection = result.collections?.[0];\n\t\tif (typeof usersCollection?.auth === 'object') {\n\t\t\texpect(usersCollection.auth.verify?.generateEmailSubject).toBeDefined();\n\t\t\texpect(usersCollection.auth.verify?.generateEmailHTML).toBeDefined();\n\t\t}\n\t});\n});\n"],"names":["describe","expect","test","vi","injectEmailTemplates","mock","renderEmail","fn","mockEmailConfig","frontEndUrl","theme","branding","companyName","forgotPassword","heading","bodyText","verifyAccount","config","collections","slug","fields","auth","serverURL","result","usersCollection","toBeDefined","toBe","verify","generateEmailHTML","postsCollection","toBeUndefined","toEqual","tokenExpiration","maxLoginAttempts","emailConfig","toHaveLength","adminsCollection","generateEmailSubject"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Config } from 'payload';
2
+ import type { PayloadHelperPluginConfig } from './types.js';
3
+ /**
4
+ * Payload Helper Plugin for websites at ainsley.dev
5
+ *
6
+ * @constructor
7
+ * @param pluginOptions
8
+ */
9
+ export declare const payloadHelper: (pluginOptions: PayloadHelperPluginConfig) => (incomingConfig: Config) => Config;
package/dist/plugin.js ADDED
@@ -0,0 +1,78 @@
1
+ import { injectAdminIcon, injectAdminLogo } from './plugin/admin.js';
2
+ import { injectEmailTemplates } from './plugin/email.js';
3
+ import { cacheHookCollections, cacheHookGlobals } from './plugin/hooks.js';
4
+ /**
5
+ * Payload Helper Plugin for websites at ainsley.dev
6
+ *
7
+ * @constructor
8
+ * @param pluginOptions
9
+ */ export const payloadHelper = (pluginOptions)=>(incomingConfig)=>{
10
+ // TODO: Validate Config
11
+ let config = incomingConfig;
12
+ // Update typescript generation file
13
+ config.typescript = config.typescript || {};
14
+ config.typescript.outputFile = './src/types/payload.ts';
15
+ // Inject admin Logo component if logo config is provided
16
+ if (pluginOptions.admin?.logo) {
17
+ config = injectAdminLogo(config, pluginOptions.admin.logo, pluginOptions.siteName);
18
+ }
19
+ // Inject admin Icon component if icon config is provided
20
+ if (pluginOptions.admin?.icon) {
21
+ config = injectAdminIcon(config, pluginOptions.admin.icon, pluginOptions.siteName);
22
+ }
23
+ // Inject email templates for auth-enabled collections if email config is provided
24
+ if (pluginOptions.email) {
25
+ config = injectEmailTemplates(config, pluginOptions.email);
26
+ }
27
+ // Map collections & add hooks
28
+ config.collections = (config.collections || []).map((collection)=>{
29
+ if (collection.upload !== undefined && collection.upload !== true) {
30
+ return collection;
31
+ }
32
+ const hooks = collection.hooks || {};
33
+ // Add afterChange hook only if webServer is defined
34
+ if (pluginOptions.webServer) {
35
+ hooks.afterChange = [
36
+ ...hooks.afterChange || [],
37
+ cacheHookCollections({
38
+ server: pluginOptions.webServer,
39
+ slug: collection.slug,
40
+ fields: collection.fields,
41
+ isCollection: true
42
+ })
43
+ ];
44
+ }
45
+ return {
46
+ ...collection,
47
+ hooks
48
+ };
49
+ });
50
+ // Map globals & add hooks
51
+ config.globals = (config.globals || []).map((global)=>{
52
+ const hooks = global.hooks || {};
53
+ // Add afterChange hook only if webServer is defined
54
+ if (pluginOptions.webServer) {
55
+ hooks.afterChange = [
56
+ ...hooks.afterChange || [],
57
+ cacheHookGlobals({
58
+ server: pluginOptions.webServer,
59
+ slug: global.slug,
60
+ fields: global.fields,
61
+ isCollection: true
62
+ })
63
+ ];
64
+ }
65
+ return {
66
+ ...global,
67
+ hooks
68
+ };
69
+ });
70
+ // Store plugin options in config.custom for CLI access (e.g., preview-emails command)
71
+ config.custom = {
72
+ ...config.custom,
73
+ payloadHelperOptions: pluginOptions
74
+ };
75
+ return config;
76
+ };
77
+
78
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { CollectionConfig, Config } from 'payload';\nimport { injectAdminIcon, injectAdminLogo } from './plugin/admin.js';\nimport { injectEmailTemplates } from './plugin/email.js';\nimport { cacheHookCollections, cacheHookGlobals } from './plugin/hooks.js';\nimport type { PayloadHelperPluginConfig } from './types.js';\n\n/**\n * Payload Helper Plugin for websites at ainsley.dev\n *\n * @constructor\n * @param pluginOptions\n */\nexport const payloadHelper =\n\t(pluginOptions: PayloadHelperPluginConfig) =>\n\t(incomingConfig: Config): Config => {\n\t\t// TODO: Validate Config\n\n\t\tlet config = incomingConfig;\n\n\t\t// Update typescript generation file\n\t\tconfig.typescript = config.typescript || {};\n\t\tconfig.typescript.outputFile = './src/types/payload.ts';\n\n\t\t// Inject admin Logo component if logo config is provided\n\t\tif (pluginOptions.admin?.logo) {\n\t\t\tconfig = injectAdminLogo(config, pluginOptions.admin.logo, pluginOptions.siteName);\n\t\t}\n\n\t\t// Inject admin Icon component if icon config is provided\n\t\tif (pluginOptions.admin?.icon) {\n\t\t\tconfig = injectAdminIcon(config, pluginOptions.admin.icon, pluginOptions.siteName);\n\t\t}\n\n\t\t// Inject email templates for auth-enabled collections if email config is provided\n\t\tif (pluginOptions.email) {\n\t\t\tconfig = injectEmailTemplates(config, pluginOptions.email);\n\t\t}\n\n\t\t// Map collections & add hooks\n\t\tconfig.collections = (config.collections || []).map((collection): CollectionConfig => {\n\t\t\tif (collection.upload !== undefined && collection.upload !== true) {\n\t\t\t\treturn collection;\n\t\t\t}\n\n\t\t\tconst hooks = collection.hooks || {};\n\n\t\t\t// Add afterChange hook only if webServer is defined\n\t\t\tif (pluginOptions.webServer) {\n\t\t\t\thooks.afterChange = [\n\t\t\t\t\t...(hooks.afterChange || []),\n\t\t\t\t\tcacheHookCollections({\n\t\t\t\t\t\tserver: pluginOptions.webServer,\n\t\t\t\t\t\tslug: collection.slug,\n\t\t\t\t\t\tfields: collection.fields,\n\t\t\t\t\t\tisCollection: true,\n\t\t\t\t\t}),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...collection,\n\t\t\t\thooks,\n\t\t\t};\n\t\t});\n\n\t\t// Map globals & add hooks\n\t\tconfig.globals = (config.globals || []).map((global) => {\n\t\t\tconst hooks = global.hooks || {};\n\n\t\t\t// Add afterChange hook only if webServer is defined\n\t\t\tif (pluginOptions.webServer) {\n\t\t\t\thooks.afterChange = [\n\t\t\t\t\t...(hooks.afterChange || []),\n\t\t\t\t\tcacheHookGlobals({\n\t\t\t\t\t\tserver: pluginOptions.webServer,\n\t\t\t\t\t\tslug: global.slug,\n\t\t\t\t\t\tfields: global.fields,\n\t\t\t\t\t\tisCollection: true,\n\t\t\t\t\t}),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...global,\n\t\t\t\thooks,\n\t\t\t};\n\t\t});\n\n\t\t// Store plugin options in config.custom for CLI access (e.g., preview-emails command)\n\t\tconfig.custom = {\n\t\t\t...config.custom,\n\t\t\tpayloadHelperOptions: pluginOptions,\n\t\t};\n\n\t\treturn config;\n\t};\n"],"names":["injectAdminIcon","injectAdminLogo","injectEmailTemplates","cacheHookCollections","cacheHookGlobals","payloadHelper","pluginOptions","incomingConfig","config","typescript","outputFile","admin","logo","siteName","icon","email","collections","map","collection","upload","undefined","hooks","webServer","afterChange","server","slug","fields","isCollection","globals","global","custom","payloadHelperOptions"],"mappings":"AACA,SAASA,eAAe,EAAEC,eAAe,QAAQ,oBAAoB;AACrE,SAASC,oBAAoB,QAAQ,oBAAoB;AACzD,SAASC,oBAAoB,EAAEC,gBAAgB,QAAQ,oBAAoB;AAG3E;;;;;CAKC,GACD,OAAO,MAAMC,gBACZ,CAACC,gBACD,CAACC;QACA,wBAAwB;QAExB,IAAIC,SAASD;QAEb,oCAAoC;QACpCC,OAAOC,UAAU,GAAGD,OAAOC,UAAU,IAAI,CAAC;QAC1CD,OAAOC,UAAU,CAACC,UAAU,GAAG;QAE/B,yDAAyD;QACzD,IAAIJ,cAAcK,KAAK,EAAEC,MAAM;YAC9BJ,SAASP,gBAAgBO,QAAQF,cAAcK,KAAK,CAACC,IAAI,EAAEN,cAAcO,QAAQ;QAClF;QAEA,yDAAyD;QACzD,IAAIP,cAAcK,KAAK,EAAEG,MAAM;YAC9BN,SAASR,gBAAgBQ,QAAQF,cAAcK,KAAK,CAACG,IAAI,EAAER,cAAcO,QAAQ;QAClF;QAEA,kFAAkF;QAClF,IAAIP,cAAcS,KAAK,EAAE;YACxBP,SAASN,qBAAqBM,QAAQF,cAAcS,KAAK;QAC1D;QAEA,8BAA8B;QAC9BP,OAAOQ,WAAW,GAAG,AAACR,CAAAA,OAAOQ,WAAW,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC;YACpD,IAAIA,WAAWC,MAAM,KAAKC,aAAaF,WAAWC,MAAM,KAAK,MAAM;gBAClE,OAAOD;YACR;YAEA,MAAMG,QAAQH,WAAWG,KAAK,IAAI,CAAC;YAEnC,oDAAoD;YACpD,IAAIf,cAAcgB,SAAS,EAAE;gBAC5BD,MAAME,WAAW,GAAG;uBACfF,MAAME,WAAW,IAAI,EAAE;oBAC3BpB,qBAAqB;wBACpBqB,QAAQlB,cAAcgB,SAAS;wBAC/BG,MAAMP,WAAWO,IAAI;wBACrBC,QAAQR,WAAWQ,MAAM;wBACzBC,cAAc;oBACf;iBACA;YACF;YAEA,OAAO;gBACN,GAAGT,UAAU;gBACbG;YACD;QACD;QAEA,0BAA0B;QAC1Bb,OAAOoB,OAAO,GAAG,AAACpB,CAAAA,OAAOoB,OAAO,IAAI,EAAE,AAAD,EAAGX,GAAG,CAAC,CAACY;YAC5C,MAAMR,QAAQQ,OAAOR,KAAK,IAAI,CAAC;YAE/B,oDAAoD;YACpD,IAAIf,cAAcgB,SAAS,EAAE;gBAC5BD,MAAME,WAAW,GAAG;uBACfF,MAAME,WAAW,IAAI,EAAE;oBAC3BnB,iBAAiB;wBAChBoB,QAAQlB,cAAcgB,SAAS;wBAC/BG,MAAMI,OAAOJ,IAAI;wBACjBC,QAAQG,OAAOH,MAAM;wBACrBC,cAAc;oBACf;iBACA;YACF;YAEA,OAAO;gBACN,GAAGE,MAAM;gBACTR;YACD;QACD;QAEA,sFAAsF;QACtFb,OAAOsB,MAAM,GAAG;YACf,GAAGtB,OAAOsB,MAAM;YAChBC,sBAAsBzB;QACvB;QAEA,OAAOE;IACR,EAAE"}