nextjs-cms 0.5.9 → 0.5.10

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 (258) hide show
  1. package/dist/api/axios/axiosInstance.d.ts +1 -1
  2. package/dist/api/axios/axiosInstance.js +8 -8
  3. package/dist/api/index.d.ts +855 -855
  4. package/dist/api/index.d.ts.map +1 -1
  5. package/dist/api/index.js +12 -12
  6. package/dist/api/lib/serverActions.d.ts +239 -239
  7. package/dist/api/lib/serverActions.d.ts.map +1 -1
  8. package/dist/api/lib/serverActions.js +834 -834
  9. package/dist/api/root.d.ts +828 -828
  10. package/dist/api/root.js +30 -30
  11. package/dist/api/routers/accountSettings.d.ts +60 -60
  12. package/dist/api/routers/accountSettings.js +108 -108
  13. package/dist/api/routers/admins.d.ts +105 -105
  14. package/dist/api/routers/admins.js +219 -219
  15. package/dist/api/routers/auth.d.ts +47 -47
  16. package/dist/api/routers/auth.js +25 -25
  17. package/dist/api/routers/categorySection.d.ts +103 -103
  18. package/dist/api/routers/categorySection.js +38 -38
  19. package/dist/api/routers/cmsSettings.d.ts +48 -48
  20. package/dist/api/routers/cmsSettings.js +51 -51
  21. package/dist/api/routers/cpanel.d.ts +83 -83
  22. package/dist/api/routers/cpanel.js +216 -216
  23. package/dist/api/routers/files.d.ts +47 -47
  24. package/dist/api/routers/files.js +23 -23
  25. package/dist/api/routers/gallery.d.ts +35 -35
  26. package/dist/api/routers/gallery.js +62 -62
  27. package/dist/api/routers/googleAnalytics.d.ts +30 -30
  28. package/dist/api/routers/googleAnalytics.js +7 -7
  29. package/dist/api/routers/hasItemsSection.d.ts +139 -139
  30. package/dist/api/routers/hasItemsSection.js +34 -34
  31. package/dist/api/routers/navigation.d.ts +51 -51
  32. package/dist/api/routers/navigation.js +11 -11
  33. package/dist/api/routers/simpleSection.d.ts +57 -57
  34. package/dist/api/routers/simpleSection.js +12 -12
  35. package/dist/api/trpc.d.ts +106 -106
  36. package/dist/api/trpc.js +72 -72
  37. package/dist/auth/axios/axiosInstance.d.ts +1 -1
  38. package/dist/auth/axios/axiosInstance.js +8 -8
  39. package/dist/auth/csrf.d.ts +29 -29
  40. package/dist/auth/csrf.js +76 -76
  41. package/dist/auth/hooks/index.d.ts +3 -3
  42. package/dist/auth/hooks/index.d.ts.map +1 -1
  43. package/dist/auth/hooks/index.js +3 -3
  44. package/dist/auth/hooks/useAxiosPrivate.d.ts +4 -4
  45. package/dist/auth/hooks/useAxiosPrivate.js +74 -74
  46. package/dist/auth/hooks/useRefreshToken.d.ts +6 -6
  47. package/dist/auth/hooks/useRefreshToken.js +79 -79
  48. package/dist/auth/index.d.ts +22 -22
  49. package/dist/auth/index.js +44 -44
  50. package/dist/auth/jwt.d.ts +5 -5
  51. package/dist/auth/jwt.js +25 -25
  52. package/dist/auth/lib/actions.d.ts +32 -32
  53. package/dist/auth/lib/actions.d.ts.map +1 -1
  54. package/dist/auth/lib/actions.js +209 -209
  55. package/dist/auth/lib/client.d.ts +3 -3
  56. package/dist/auth/lib/client.js +46 -46
  57. package/dist/auth/lib/index.d.ts +2 -2
  58. package/dist/auth/lib/index.d.ts.map +1 -1
  59. package/dist/auth/lib/index.js +2 -2
  60. package/dist/auth/react.d.ts +105 -105
  61. package/dist/auth/react.d.ts.map +1 -1
  62. package/dist/auth/react.js +347 -347
  63. package/dist/auth/trpc.d.ts +5 -5
  64. package/dist/auth/trpc.d.ts.map +1 -1
  65. package/dist/auth/trpc.js +81 -81
  66. package/dist/core/config/config-loader.d.ts +91 -91
  67. package/dist/core/config/config-loader.js +230 -230
  68. package/dist/core/config/index.d.ts +2 -2
  69. package/dist/core/config/index.d.ts.map +1 -1
  70. package/dist/core/config/index.js +1 -1
  71. package/dist/core/config/loader.d.ts +1 -1
  72. package/dist/core/config/loader.js +42 -42
  73. package/dist/core/db/index.d.ts +1 -1
  74. package/dist/core/db/index.d.ts.map +1 -1
  75. package/dist/core/db/index.js +1 -1
  76. package/dist/core/db/table-checker/DbTable.d.ts +5 -5
  77. package/dist/core/db/table-checker/DbTable.js +5 -5
  78. package/dist/core/db/table-checker/MysqlTable.d.ts +33 -33
  79. package/dist/core/db/table-checker/MysqlTable.d.ts.map +1 -1
  80. package/dist/core/db/table-checker/MysqlTable.js +94 -94
  81. package/dist/core/db/table-checker/index.d.ts +1 -1
  82. package/dist/core/db/table-checker/index.d.ts.map +1 -1
  83. package/dist/core/db/table-checker/index.js +1 -1
  84. package/dist/core/factories/FieldFactory.d.ts +123 -123
  85. package/dist/core/factories/FieldFactory.d.ts.map +1 -1
  86. package/dist/core/factories/FieldFactory.js +411 -411
  87. package/dist/core/factories/SectionFactory.d.ts +109 -109
  88. package/dist/core/factories/SectionFactory.d.ts.map +1 -1
  89. package/dist/core/factories/SectionFactory.js +415 -415
  90. package/dist/core/factories/index.d.ts +2 -2
  91. package/dist/core/factories/index.d.ts.map +1 -1
  92. package/dist/core/factories/index.js +2 -2
  93. package/dist/core/fields/checkbox.d.ts +62 -62
  94. package/dist/core/fields/checkbox.d.ts.map +1 -1
  95. package/dist/core/fields/checkbox.js +62 -62
  96. package/dist/core/fields/color.d.ts +83 -83
  97. package/dist/core/fields/color.d.ts.map +1 -1
  98. package/dist/core/fields/color.js +91 -91
  99. package/dist/core/fields/date.d.ts +99 -99
  100. package/dist/core/fields/date.d.ts.map +1 -1
  101. package/dist/core/fields/date.js +108 -108
  102. package/dist/core/fields/document.d.ts +179 -179
  103. package/dist/core/fields/document.d.ts.map +1 -1
  104. package/dist/core/fields/document.js +277 -277
  105. package/dist/core/fields/field-group.d.ts +17 -17
  106. package/dist/core/fields/field-group.d.ts.map +1 -1
  107. package/dist/core/fields/field-group.js +6 -6
  108. package/dist/core/fields/field.d.ts +125 -125
  109. package/dist/core/fields/field.d.ts.map +1 -1
  110. package/dist/core/fields/field.js +148 -148
  111. package/dist/core/fields/fileField.d.ts +14 -14
  112. package/dist/core/fields/fileField.d.ts.map +1 -1
  113. package/dist/core/fields/fileField.js +5 -5
  114. package/dist/core/fields/index.d.ts +64 -64
  115. package/dist/core/fields/index.d.ts.map +1 -1
  116. package/dist/core/fields/index.js +18 -18
  117. package/dist/core/fields/map.d.ts +166 -166
  118. package/dist/core/fields/map.d.ts.map +1 -1
  119. package/dist/core/fields/map.js +152 -152
  120. package/dist/core/fields/number.d.ts +185 -185
  121. package/dist/core/fields/number.d.ts.map +1 -1
  122. package/dist/core/fields/number.js +241 -241
  123. package/dist/core/fields/password.d.ts +108 -108
  124. package/dist/core/fields/password.d.ts.map +1 -1
  125. package/dist/core/fields/password.js +133 -133
  126. package/dist/core/fields/photo.d.ts +288 -288
  127. package/dist/core/fields/photo.d.ts.map +1 -1
  128. package/dist/core/fields/photo.js +410 -410
  129. package/dist/core/fields/richText.d.ts +294 -294
  130. package/dist/core/fields/richText.d.ts.map +1 -1
  131. package/dist/core/fields/richText.js +338 -338
  132. package/dist/core/fields/select.d.ts +365 -365
  133. package/dist/core/fields/select.d.ts.map +1 -1
  134. package/dist/core/fields/select.js +499 -499
  135. package/dist/core/fields/selectMultiple.d.ts +235 -235
  136. package/dist/core/fields/selectMultiple.d.ts.map +1 -1
  137. package/dist/core/fields/selectMultiple.js +417 -417
  138. package/dist/core/fields/tags.d.ts +130 -130
  139. package/dist/core/fields/tags.d.ts.map +1 -1
  140. package/dist/core/fields/tags.js +105 -105
  141. package/dist/core/fields/text.d.ts +135 -135
  142. package/dist/core/fields/text.d.ts.map +1 -1
  143. package/dist/core/fields/text.js +157 -157
  144. package/dist/core/fields/textArea.d.ts +106 -106
  145. package/dist/core/fields/textArea.d.ts.map +1 -1
  146. package/dist/core/fields/textArea.js +126 -126
  147. package/dist/core/fields/video.d.ts +147 -147
  148. package/dist/core/fields/video.d.ts.map +1 -1
  149. package/dist/core/fields/video.js +248 -248
  150. package/dist/core/helpers/entity.d.ts +7 -7
  151. package/dist/core/helpers/entity.js +27 -27
  152. package/dist/core/helpers/index.d.ts +4 -4
  153. package/dist/core/helpers/index.d.ts.map +1 -1
  154. package/dist/core/helpers/index.js +3 -3
  155. package/dist/core/index.d.ts +7 -7
  156. package/dist/core/index.d.ts.map +1 -1
  157. package/dist/core/index.js +7 -7
  158. package/dist/core/sections/category.d.ts +282 -282
  159. package/dist/core/sections/category.d.ts.map +1 -1
  160. package/dist/core/sections/category.js +147 -147
  161. package/dist/core/sections/hasItems.d.ts +631 -631
  162. package/dist/core/sections/hasItems.d.ts.map +1 -1
  163. package/dist/core/sections/hasItems.js +144 -144
  164. package/dist/core/sections/index.d.ts +4 -4
  165. package/dist/core/sections/index.d.ts.map +1 -1
  166. package/dist/core/sections/index.js +4 -4
  167. package/dist/core/sections/section.d.ts +225 -225
  168. package/dist/core/sections/section.d.ts.map +1 -1
  169. package/dist/core/sections/section.js +341 -341
  170. package/dist/core/sections/simple.d.ts +98 -98
  171. package/dist/core/sections/simple.d.ts.map +1 -1
  172. package/dist/core/sections/simple.js +95 -95
  173. package/dist/core/security/dom.d.ts +10 -10
  174. package/dist/core/security/dom.js +92 -92
  175. package/dist/core/submit/ItemEditSubmit.d.ts +75 -75
  176. package/dist/core/submit/ItemEditSubmit.js +186 -186
  177. package/dist/core/submit/NewItemSubmit.d.ts +13 -13
  178. package/dist/core/submit/NewItemSubmit.js +93 -93
  179. package/dist/core/submit/SimpleSectionSubmit.d.ts +12 -12
  180. package/dist/core/submit/SimpleSectionSubmit.js +93 -93
  181. package/dist/core/submit/index.d.ts +4 -4
  182. package/dist/core/submit/index.js +4 -4
  183. package/dist/core/submit/submit.d.ts +115 -115
  184. package/dist/core/submit/submit.js +479 -479
  185. package/dist/core/types/index.d.ts +279 -279
  186. package/dist/core/types/index.d.ts.map +1 -1
  187. package/dist/core/types/index.js +1 -1
  188. package/dist/db/client.d.ts +8 -8
  189. package/dist/db/client.d.ts.map +1 -1
  190. package/dist/db/client.js +19 -19
  191. package/dist/db/config.d.ts +5 -5
  192. package/dist/db/config.js +22 -22
  193. package/dist/db/drizzle.config.d.ts +5 -5
  194. package/dist/db/drizzle.config.js +18 -18
  195. package/dist/db/index.d.ts +2 -2
  196. package/dist/db/index.js +3 -3
  197. package/dist/db/schema.d.ts +638 -638
  198. package/dist/db/schema.js +73 -73
  199. package/dist/index.d.ts +7 -7
  200. package/dist/index.d.ts.map +1 -1
  201. package/dist/index.js +7 -7
  202. package/dist/translations/index.d.ts +2 -2
  203. package/dist/translations/index.js +15 -15
  204. package/dist/utils/CpanelApi.d.ts +24 -24
  205. package/dist/utils/CpanelApi.js +64 -64
  206. package/dist/utils/constants.d.ts +13 -13
  207. package/dist/utils/constants.js +61 -61
  208. package/dist/utils/index.d.ts +4 -4
  209. package/dist/utils/index.d.ts.map +1 -1
  210. package/dist/utils/index.js +4 -4
  211. package/dist/utils/utils.d.ts +59 -59
  212. package/dist/utils/utils.js +132 -132
  213. package/dist/validators/checkbox.d.ts +3 -3
  214. package/dist/validators/checkbox.d.ts.map +1 -1
  215. package/dist/validators/checkbox.js +12 -12
  216. package/dist/validators/color.d.ts +3 -3
  217. package/dist/validators/color.d.ts.map +1 -1
  218. package/dist/validators/color.js +7 -7
  219. package/dist/validators/date.d.ts +3 -3
  220. package/dist/validators/date.d.ts.map +1 -1
  221. package/dist/validators/date.js +5 -5
  222. package/dist/validators/document.d.ts +3 -3
  223. package/dist/validators/document.d.ts.map +1 -1
  224. package/dist/validators/document.js +57 -57
  225. package/dist/validators/index.d.ts +14 -14
  226. package/dist/validators/index.d.ts.map +1 -1
  227. package/dist/validators/index.js +14 -14
  228. package/dist/validators/map.d.ts +3 -3
  229. package/dist/validators/map.d.ts.map +1 -1
  230. package/dist/validators/map.js +5 -5
  231. package/dist/validators/number.d.ts +3 -3
  232. package/dist/validators/number.d.ts.map +1 -1
  233. package/dist/validators/number.js +20 -20
  234. package/dist/validators/password.d.ts +3 -3
  235. package/dist/validators/password.d.ts.map +1 -1
  236. package/dist/validators/password.js +11 -11
  237. package/dist/validators/photo.d.ts +3 -3
  238. package/dist/validators/photo.d.ts.map +1 -1
  239. package/dist/validators/photo.js +100 -100
  240. package/dist/validators/richText.d.ts +3 -3
  241. package/dist/validators/richText.d.ts.map +1 -1
  242. package/dist/validators/richText.js +8 -8
  243. package/dist/validators/select-multiple.d.ts +9 -9
  244. package/dist/validators/select-multiple.d.ts.map +1 -1
  245. package/dist/validators/select-multiple.js +20 -20
  246. package/dist/validators/select.d.ts +3 -3
  247. package/dist/validators/select.d.ts.map +1 -1
  248. package/dist/validators/select.js +5 -5
  249. package/dist/validators/text.d.ts +3 -3
  250. package/dist/validators/text.d.ts.map +1 -1
  251. package/dist/validators/text.js +7 -7
  252. package/dist/validators/textarea.d.ts +3 -3
  253. package/dist/validators/textarea.d.ts.map +1 -1
  254. package/dist/validators/textarea.js +7 -7
  255. package/dist/validators/video.d.ts +3 -3
  256. package/dist/validators/video.d.ts.map +1 -1
  257. package/dist/validators/video.js +57 -57
  258. package/package.json +2 -3
@@ -1,415 +1,415 @@
1
- import { glob } from 'glob';
2
- import { cloneDeep } from 'lodash-es';
3
- import { resolve } from 'path';
4
- import chalk from 'chalk';
5
- import { eq } from 'drizzle-orm';
6
- import chokidar from 'chokidar';
7
- import fs from 'fs';
8
- import { db } from "../../db/client.js";
9
- import { AdminPrivilegesTable } from "../../db/schema.js";
10
- import { getCMSConfig } from "../config.js";
11
- const cmsConfig = getCMSConfig();
12
- const hotMarkerFile = resolve(process.cwd(), 'next-cms-sectioninfo.ts');
13
- const safeRequire = (id) => {
14
- const req = eval('require');
15
- return req(id);
16
- };
17
- /**
18
- * Register esbuild-register only once per process.
19
- * Node's require cache ensures each TS module is evaluated once.
20
- */
21
- let tsLoaderRegistered = false;
22
- /**
23
- * Require a TypeScript module at runtime.
24
- * @param absPath - The absolute path to the section file.
25
- * @returns The default export of the section file.
26
- */
27
- const requireModuleRuntime = (absPath) => {
28
- const isTs = absPath.endsWith('.ts') || absPath.endsWith('.cts') || absPath.endsWith('.mts');
29
- if (isTs && !tsLoaderRegistered) {
30
- try {
31
- const { register } = safeRequire('esbuild-register/dist/node');
32
- register({ format: 'cjs', loader: 'ts' });
33
- tsLoaderRegistered = true;
34
- }
35
- catch {
36
- throw new Error(`Cannot load section file '${absPath}' (TypeScript). Install 'esbuild-register' in nextjs-cms/cms or provide JS files.`);
37
- }
38
- }
39
- const mod = safeRequire(absPath);
40
- // Only return the default export, ignore named exports
41
- return mod?.default;
42
- };
43
- /**
44
- * Log a message to the console if debug mode is enabled
45
- * @param args - The arguments to log
46
- * @returns void
47
- */
48
- const log = (...args) => {
49
- if (!cmsConfig.debug)
50
- return;
51
- console.log(chalk.black.bgGreen(`[${new Date().toISOString()}][next-cms-debug]`, ...args));
52
- };
53
- export class SectionFactory {
54
- /**
55
- * These are the fixed sections that can not be present in the sections folder.
56
- */
57
- static fixedSections = ['admins', 'analytics', 'emails', 'dashboard'];
58
- static isDev = process.env.NODE_ENV !== 'production';
59
- static isProd = !this.isDev;
60
- static sectionProcessingErrors = {};
61
- static sectionFetchingErrors = {};
62
- static errorCount = 0;
63
- /**
64
- * Global in-process cache for all section configs.
65
- * Populated once per Node process via loadAllSections(),
66
- * reused by all get*() calls until the process is restarted,
67
- * so all subsequent calls reuse this array.
68
- */
69
- static allSectionsPromise = null;
70
- static allSectionsLoaded = false;
71
- static watcherStarted = false;
72
- static bumpHotMarker() {
73
- if (!SectionFactory.isDev)
74
- return;
75
- const content = `/**\n` +
76
- ` * AUTO-GENERATED. DO NOT EDIT.\n` +
77
- ` * This file is used to track the last time the sections schema was updated.\n` +
78
- ` * It is used to trigger a hot reload of the section schema in development mode.\n` +
79
- ` */\n` +
80
- `\n` +
81
- `export const revalidate = 0\n` +
82
- `\n` +
83
- `// @refresh reset\n` +
84
- `\n` +
85
- `export const sectionSchemaLastUpdated = ${Date.now()}\n`;
86
- try {
87
- fs.writeFileSync(hotMarkerFile, content, 'utf8');
88
- log('Bumped next-cms-sectioninfo.ts to trigger Next Fast Refresh');
89
- }
90
- catch (err) {
91
- console.error('Failed to bump next-cms-sectioninfo.ts:', err);
92
- }
93
- }
94
- // ---------- PUBLIC API ----------
95
- /**
96
- * Get all sections
97
- * @param type - The type of sections to get
98
- * @returns The sections
99
- */
100
- static async getSections(type) {
101
- return await this.get({ type });
102
- }
103
- /**
104
- * Get all accessible sections for an admin
105
- * @param type - The type of section to get
106
- * @param admin - The admin to get the sections for
107
- * @returns The sections for the admin
108
- */
109
- static async getSectionsForAdmin({ type, admin, }) {
110
- const sections = await this.get({ type, admin });
111
- const privileges = await db
112
- .select({
113
- sectionName: AdminPrivilegesTable.sectionName,
114
- operations: AdminPrivilegesTable.operations,
115
- })
116
- .from(AdminPrivilegesTable)
117
- .where(eq(AdminPrivilegesTable.adminId, admin.id));
118
- const fixedSections = privileges
119
- .filter((privilege) => this.fixedSections.includes(privilege.sectionName))
120
- .map((privilege) => privilege.sectionName);
121
- const simpleSections = sections.filter((section) => section.type === 'simple');
122
- const hasItemsSections = sections.filter((section) => section.type === 'has_items');
123
- const categorySections = sections.filter((section) => section.type === 'category');
124
- return {
125
- simple: simpleSections,
126
- has_items: hasItemsSections,
127
- category: categorySections,
128
- fixed: fixedSections,
129
- };
130
- }
131
- /**
132
- * Get a section
133
- * @param name - The name of the section to get
134
- * @param type - The type of section to get
135
- * @returns The section
136
- */
137
- static async getSection({ name, type, }) {
138
- const section = await this.get({ name, type });
139
- return section[0] ? section[0] : null;
140
- }
141
- /**
142
- * Get an accessible section for an admin
143
- * @param name - The name of the section to get
144
- * @param type - The type of section to get
145
- * @param admin - The admin to get the section for
146
- * @returns The section for the admin
147
- */
148
- static async getSectionForAdmin({ name, type, admin, }) {
149
- const section = await this.get({ type, admin, name });
150
- return section[0] ? section[0] : null;
151
- }
152
- /**
153
- * Create a Section instance from a section config
154
- * The config must have a build() method (created via helper functions like simpleSection(), hasItemsSection(), etc.)
155
- *
156
- * Note: This factory only accepts configs. If you already have a Section instance,
157
- * use it directly - don't pass it to this factory.
158
- *
159
- * @param config - A section config object with a build() method
160
- * @returns A Section instance
161
- * @throws Error if config doesn't have a build() method
162
- */
163
- static create(config) {
164
- if (typeof config.build === 'function') {
165
- return config.build();
166
- }
167
- throw new Error('Section config must have a build() method. Use helper functions like simpleSection(), hasItemsSection(), categorySection() to create configs.');
168
- }
169
- // ---------- INTERNAL CORE ----------
170
- /**
171
- * Return a clone of the section(s) info to get a fresh copy of the section(s).
172
- * Not cloning will cause the original section(s) (in *.section.ts file) to be modified!
173
- * Each *.section.ts file must have exactly one default export (arrays are not allowed).
174
-
175
- * @param name - The name of the section to get
176
- * @param type - The type of section to get
177
- * @param admin - The admin to get the section for
178
- * @returns The section(s)
179
- */
180
- static async get({ name, type, admin, }) {
181
- try {
182
- const allSections = await this.loadAllSections();
183
- /**
184
- * Always show errors if there are any.
185
- * This is to make sure the errors are shown on every request (technically, every time the section is requested).
186
- * This way devs can notice the errors and fix them.
187
- */
188
- if (this.errorCount > 0) {
189
- console.log(`\n${chalk.bgRed.bold(` Errors while fetching sections `)}${chalk.red.bold('-------')}\n`);
190
- if (Object.keys(this.sectionProcessingErrors).length > 0) {
191
- console.log(`${chalk.bold.redBright(`Processing errors:`)}`);
192
- for (const [file, errors] of Object.entries(this.sectionProcessingErrors)) {
193
- console.log('');
194
- console.log(chalk.bold(`[${file}]:`));
195
- for (const error of errors) {
196
- console.error(error);
197
- }
198
- console.log('');
199
- }
200
- }
201
- if (Object.keys(this.sectionFetchingErrors).length > 0) {
202
- console.log(`\n${chalk.bold.redBright(`Importing errors:`)}`);
203
- for (const [file, errors] of Object.entries(this.sectionFetchingErrors)) {
204
- console.log('');
205
- console.log(chalk.bold(`[${file}]:`));
206
- for (const error of errors) {
207
- console.error(error);
208
- }
209
- console.log('');
210
- }
211
- }
212
- console.log(`${chalk.red.bold('-------')}`);
213
- }
214
- const types = type
215
- ? Array.isArray(type)
216
- ? type
217
- : [type]
218
- : ['has_items', 'simple', 'category'];
219
- // Filter by type first
220
- let filtered = allSections.filter((section) => types.includes(section.type));
221
- // If admin is provided, restrict to privileged sections
222
- if (admin && admin.id) {
223
- const privileges = await db
224
- .select({
225
- sectionName: AdminPrivilegesTable.sectionName,
226
- operations: AdminPrivilegesTable.operations,
227
- })
228
- .from(AdminPrivilegesTable)
229
- .where(eq(AdminPrivilegesTable.adminId, admin.id));
230
- const privilegedSections = [];
231
- privileges.forEach((privilege) => {
232
- if (admin.requiredRole) {
233
- if (privilege.operations.includes(admin.requiredRole)) {
234
- privilegedSections.push(privilege.sectionName);
235
- }
236
- }
237
- else {
238
- privilegedSections.push(privilege.sectionName);
239
- }
240
- });
241
- filtered = filtered.filter((section) => privilegedSections.includes(section.name));
242
- }
243
- if (name) {
244
- const found = filtered.find((section) => section.name === name);
245
- return found ? [cloneDeep(found)] : [];
246
- }
247
- return filtered.map((section) => cloneDeep(section));
248
- }
249
- catch (err) {
250
- console.error('Error loading section configs:', err);
251
- return [];
252
- }
253
- }
254
- /**
255
- * Load all *.section.ts files once, require their default exports,
256
- * and cache the resulting configs for the lifetime of the process
257
- * (until invalidated in dev by file changes).
258
- */
259
- static async loadAllSections() {
260
- if (this.allSectionsPromise)
261
- return this.allSectionsPromise;
262
- if (this.isDev) {
263
- // In dev, watch for changes and reload on demand
264
- // This wll lazy load on first get() as well
265
- this.startWatcher();
266
- }
267
- this.allSectionsPromise = (async () => {
268
- if (!this.allSectionsLoaded) {
269
- log('Loading all sections from disk...', this.isDev ? '(dev mode)' : '(production mode)');
270
- }
271
- const sections = [];
272
- try {
273
- const sectionFiles = await glob('**/*.section.ts', {
274
- cwd: cmsConfig.sectionsFolder,
275
- });
276
- for (const file of sectionFiles) {
277
- try {
278
- const absPath = resolve(cmsConfig.sectionsFolder, file);
279
- const defaultExport = requireModuleRuntime(absPath);
280
- if (!defaultExport) {
281
- if (!this.sectionFetchingErrors[file]) {
282
- this.sectionFetchingErrors[file] = [];
283
- }
284
- this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file must have a default export. Skipping.'));
285
- this.errorCount++;
286
- continue;
287
- }
288
- if (Array.isArray(defaultExport)) {
289
- if (!this.sectionFetchingErrors[file]) {
290
- this.sectionFetchingErrors[file] = [];
291
- }
292
- this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file exports an array. Each file must export exactly one section. Skipping.'));
293
- this.errorCount++;
294
- continue;
295
- }
296
- if (!defaultExport.type || !defaultExport.name) {
297
- if (!this.sectionFetchingErrors[file]) {
298
- this.sectionFetchingErrors[file] = [];
299
- }
300
- this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file default export must have "type" and "name". Skipping.'));
301
- this.errorCount++;
302
- continue;
303
- }
304
- sections.push(defaultExport);
305
- }
306
- catch (importErr) {
307
- if (importErr instanceof Error) {
308
- /**
309
- * Only display the error message, not the stack trace.
310
- */
311
- if (!this.sectionProcessingErrors[file]) {
312
- this.sectionProcessingErrors[file] = [];
313
- }
314
- this.sectionProcessingErrors[file].push(importErr.message);
315
- }
316
- else {
317
- if (!this.sectionFetchingErrors[file]) {
318
- this.sectionFetchingErrors[file] = [];
319
- }
320
- this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)(`Unknown error type: ${typeof importErr}`));
321
- throw new Error(`${chalk.bgRed(` Error importing ${file}: `)}, ${importErr}`);
322
- }
323
- this.errorCount++;
324
- /**
325
- * Remove the section from the cache so it will be reloaded next time.
326
- */
327
- delete require.cache[resolve(cmsConfig.sectionsFolder, file)];
328
- }
329
- }
330
- }
331
- catch (err) {
332
- console.error('Error finding section files:', err);
333
- }
334
- this.allSectionsLoaded = true;
335
- /**
336
- * If `strict` mode is enabled,
337
- * don't return sections if there are errors.
338
- */
339
- if (cmsConfig.strict && this.errorCount > 0) {
340
- return [];
341
- }
342
- log(`Loaded ${sections.length} section(s)`);
343
- return sections;
344
- })();
345
- return this.allSectionsPromise;
346
- }
347
- // ---------- DEV WATCHER & CACHE INVALIDATION ----------
348
- static startWatcher() {
349
- if (!this.isDev || this.watcherStarted)
350
- return;
351
- this.watcherStarted = true;
352
- const watcher = chokidar.watch('**/*.section.ts', {
353
- cwd: cmsConfig.sectionsFolder,
354
- ignoreInitial: true,
355
- });
356
- log('Starting section watcher in dev mode...');
357
- /**
358
- * This is triggering nextjs to reload the section module when a section file is changed.
359
- * We used [refresh-reset](https://nextjs.org/docs/architecture/fast-refresh#:~:text=you%20can%20add-,//%20%40refresh%20reset,-anywhere%20in%20the) to let fast-refresh
360
- * trigger a react-query invalidation in the form component to re-fetch the new section config.
361
- * @param relPath
362
- */
363
- const invalidateForRelPath = (relPath) => {
364
- const absPath = resolve(cmsConfig.sectionsFolder, relPath);
365
- const req = eval('require');
366
- try {
367
- const resolved = req.resolve(absPath);
368
- if (req.cache[resolved]) {
369
- delete req.cache[resolved];
370
- log('Cleared require cache for', relPath);
371
- }
372
- // Also clear the require cache for the hot marker file
373
- if (req.cache[hotMarkerFile]) {
374
- delete req.cache[hotMarkerFile];
375
- log('Cleared require cache for', hotMarkerFile);
376
- }
377
- this.sectionProcessingErrors = {};
378
- this.sectionFetchingErrors = {};
379
- this.errorCount = 0;
380
- }
381
- catch {
382
- // ignore if not resolvable
383
- }
384
- this.allSectionsPromise = null;
385
- this.allSectionsLoaded = false;
386
- log('Invalidated section cache due to change in', relPath);
387
- this.bumpHotMarker();
388
- };
389
- watcher
390
- .on('add', (path) => {
391
- log('Section file added:', path);
392
- invalidateForRelPath(path);
393
- })
394
- .on('change', (path) => {
395
- log('Section file changed:', path);
396
- invalidateForRelPath(path);
397
- })
398
- .on('unlink', (path) => {
399
- log('Section file removed:', path);
400
- invalidateForRelPath(path);
401
- });
402
- }
403
- static init() {
404
- if (this.isProd) {
405
- // Preload all sections at startup in production
406
- void this.loadAllSections();
407
- }
408
- }
409
- /**
410
- * Run initialization once when the module is loaded
411
- */
412
- static {
413
- this.init();
414
- }
415
- }
1
+ import { glob } from 'glob';
2
+ import { cloneDeep } from 'lodash-es';
3
+ import { resolve } from 'path';
4
+ import chalk from 'chalk';
5
+ import { eq } from 'drizzle-orm';
6
+ import chokidar from 'chokidar';
7
+ import fs from 'fs';
8
+ import { db } from '../../db/client.js';
9
+ import { AdminPrivilegesTable } from '../../db/schema.js';
10
+ import { getCMSConfig } from '../config/index.js';
11
+ const cmsConfig = getCMSConfig();
12
+ const hotMarkerFile = resolve(process.cwd(), 'next-cms-sectioninfo.js');
13
+ const safeRequire = (id) => {
14
+ const req = eval('require');
15
+ return req(id);
16
+ };
17
+ /**
18
+ * Register esbuild-register only once per process.
19
+ * Node's require cache ensures each TS module is evaluated once.
20
+ */
21
+ let tsLoaderRegistered = false;
22
+ /**
23
+ * Require a TypeScript module at runtime.
24
+ * @param absPath - The absolute path to the section file.
25
+ * @returns The default export of the section file.
26
+ */
27
+ const requireModuleRuntime = (absPath) => {
28
+ const isTs = absPath.endsWith('.js') || absPath.endsWith('.cts') || absPath.endsWith('.mts');
29
+ if (isTs && !tsLoaderRegistered) {
30
+ try {
31
+ const { register } = safeRequire('esbuild-register/dist/node');
32
+ register({ format: 'cjs', loader: 'ts' });
33
+ tsLoaderRegistered = true;
34
+ }
35
+ catch {
36
+ throw new Error(`Cannot load section file '${absPath}' (TypeScript). Install 'esbuild-register' in nextjs-cms/cms or provide JS files.`);
37
+ }
38
+ }
39
+ const mod = safeRequire(absPath);
40
+ // Only return the default export, ignore named exports
41
+ return mod?.default;
42
+ };
43
+ /**
44
+ * Log a message to the console if debug mode is enabled
45
+ * @param args - The arguments to log
46
+ * @returns void
47
+ */
48
+ const log = (...args) => {
49
+ if (!cmsConfig.debug)
50
+ return;
51
+ console.log(chalk.black.bgGreen(`[${new Date().toISOString()}][next-cms-debug]`, ...args));
52
+ };
53
+ export class SectionFactory {
54
+ /**
55
+ * These are the fixed sections that can not be present in the sections folder.
56
+ */
57
+ static fixedSections = ['admins', 'analytics', 'emails', 'dashboard'];
58
+ static isDev = process.env.NODE_ENV !== 'production';
59
+ static isProd = !this.isDev;
60
+ static sectionProcessingErrors = {};
61
+ static sectionFetchingErrors = {};
62
+ static errorCount = 0;
63
+ /**
64
+ * Global in-process cache for all section configs.
65
+ * Populated once per Node process via loadAllSections(),
66
+ * reused by all get*() calls until the process is restarted,
67
+ * so all subsequent calls reuse this array.
68
+ */
69
+ static allSectionsPromise = null;
70
+ static allSectionsLoaded = false;
71
+ static watcherStarted = false;
72
+ static bumpHotMarker() {
73
+ if (!SectionFactory.isDev)
74
+ return;
75
+ const content = `/**\n` +
76
+ ` * AUTO-GENERATED. DO NOT EDIT.\n` +
77
+ ` * This file is used to track the last time the sections schema was updated.\n` +
78
+ ` * It is used to trigger a hot reload of the section schema in development mode.\n` +
79
+ ` */\n` +
80
+ `\n` +
81
+ `export const revalidate = 0\n` +
82
+ `\n` +
83
+ `// @refresh reset\n` +
84
+ `\n` +
85
+ `export const sectionSchemaLastUpdated = ${Date.now()}\n`;
86
+ try {
87
+ fs.writeFileSync(hotMarkerFile, content, 'utf8');
88
+ log('Bumped next-cms-sectioninfo.ts to trigger Next Fast Refresh');
89
+ }
90
+ catch (err) {
91
+ console.error('Failed to bump next-cms-sectioninfo.ts:', err);
92
+ }
93
+ }
94
+ // ---------- PUBLIC API ----------
95
+ /**
96
+ * Get all sections
97
+ * @param type - The type of sections to get
98
+ * @returns The sections
99
+ */
100
+ static async getSections(type) {
101
+ return await this.get({ type });
102
+ }
103
+ /**
104
+ * Get all accessible sections for an admin
105
+ * @param type - The type of section to get
106
+ * @param admin - The admin to get the sections for
107
+ * @returns The sections for the admin
108
+ */
109
+ static async getSectionsForAdmin({ type, admin, }) {
110
+ const sections = await this.get({ type, admin });
111
+ const privileges = await db
112
+ .select({
113
+ sectionName: AdminPrivilegesTable.sectionName,
114
+ operations: AdminPrivilegesTable.operations,
115
+ })
116
+ .from(AdminPrivilegesTable)
117
+ .where(eq(AdminPrivilegesTable.adminId, admin.id));
118
+ const fixedSections = privileges
119
+ .filter((privilege) => this.fixedSections.includes(privilege.sectionName))
120
+ .map((privilege) => privilege.sectionName);
121
+ const simpleSections = sections.filter((section) => section.type === 'simple');
122
+ const hasItemsSections = sections.filter((section) => section.type === 'has_items');
123
+ const categorySections = sections.filter((section) => section.type === 'category');
124
+ return {
125
+ simple: simpleSections,
126
+ has_items: hasItemsSections,
127
+ category: categorySections,
128
+ fixed: fixedSections,
129
+ };
130
+ }
131
+ /**
132
+ * Get a section
133
+ * @param name - The name of the section to get
134
+ * @param type - The type of section to get
135
+ * @returns The section
136
+ */
137
+ static async getSection({ name, type, }) {
138
+ const section = await this.get({ name, type });
139
+ return section[0] ? section[0] : null;
140
+ }
141
+ /**
142
+ * Get an accessible section for an admin
143
+ * @param name - The name of the section to get
144
+ * @param type - The type of section to get
145
+ * @param admin - The admin to get the section for
146
+ * @returns The section for the admin
147
+ */
148
+ static async getSectionForAdmin({ name, type, admin, }) {
149
+ const section = await this.get({ type, admin, name });
150
+ return section[0] ? section[0] : null;
151
+ }
152
+ /**
153
+ * Create a Section instance from a section config
154
+ * The config must have a build() method (created via helper functions like simpleSection(), hasItemsSection(), etc.)
155
+ *
156
+ * Note: This factory only accepts configs. If you already have a Section instance,
157
+ * use it directly - don't pass it to this factory.
158
+ *
159
+ * @param config - A section config object with a build() method
160
+ * @returns A Section instance
161
+ * @throws Error if config doesn't have a build() method
162
+ */
163
+ static create(config) {
164
+ if (typeof config.build === 'function') {
165
+ return config.build();
166
+ }
167
+ throw new Error('Section config must have a build() method. Use helper functions like simpleSection(), hasItemsSection(), categorySection() to create configs.');
168
+ }
169
+ // ---------- INTERNAL CORE ----------
170
+ /**
171
+ * Return a clone of the section(s) info to get a fresh copy of the section(s).
172
+ * Not cloning will cause the original section(s) (in *.section.ts file) to be modified!
173
+ * Each *.section.ts file must have exactly one default export (arrays are not allowed).
174
+
175
+ * @param name - The name of the section to get
176
+ * @param type - The type of section to get
177
+ * @param admin - The admin to get the section for
178
+ * @returns The section(s)
179
+ */
180
+ static async get({ name, type, admin, }) {
181
+ try {
182
+ const allSections = await this.loadAllSections();
183
+ /**
184
+ * Always show errors if there are any.
185
+ * This is to make sure the errors are shown on every request (technically, every time the section is requested).
186
+ * This way devs can notice the errors and fix them.
187
+ */
188
+ if (this.errorCount > 0) {
189
+ console.log(`\n${chalk.bgRed.bold(` Errors while fetching sections `)}${chalk.red.bold('-------')}\n`);
190
+ if (Object.keys(this.sectionProcessingErrors).length > 0) {
191
+ console.log(`${chalk.bold.redBright(`Processing errors:`)}`);
192
+ for (const [file, errors] of Object.entries(this.sectionProcessingErrors)) {
193
+ console.log('');
194
+ console.log(chalk.bold(`[${file}]:`));
195
+ for (const error of errors) {
196
+ console.error(error);
197
+ }
198
+ console.log('');
199
+ }
200
+ }
201
+ if (Object.keys(this.sectionFetchingErrors).length > 0) {
202
+ console.log(`\n${chalk.bold.redBright(`Importing errors:`)}`);
203
+ for (const [file, errors] of Object.entries(this.sectionFetchingErrors)) {
204
+ console.log('');
205
+ console.log(chalk.bold(`[${file}]:`));
206
+ for (const error of errors) {
207
+ console.error(error);
208
+ }
209
+ console.log('');
210
+ }
211
+ }
212
+ console.log(`${chalk.red.bold('-------')}`);
213
+ }
214
+ const types = type
215
+ ? Array.isArray(type)
216
+ ? type
217
+ : [type]
218
+ : ['has_items', 'simple', 'category'];
219
+ // Filter by type first
220
+ let filtered = allSections.filter((section) => types.includes(section.type));
221
+ // If admin is provided, restrict to privileged sections
222
+ if (admin && admin.id) {
223
+ const privileges = await db
224
+ .select({
225
+ sectionName: AdminPrivilegesTable.sectionName,
226
+ operations: AdminPrivilegesTable.operations,
227
+ })
228
+ .from(AdminPrivilegesTable)
229
+ .where(eq(AdminPrivilegesTable.adminId, admin.id));
230
+ const privilegedSections = [];
231
+ privileges.forEach((privilege) => {
232
+ if (admin.requiredRole) {
233
+ if (privilege.operations.includes(admin.requiredRole)) {
234
+ privilegedSections.push(privilege.sectionName);
235
+ }
236
+ }
237
+ else {
238
+ privilegedSections.push(privilege.sectionName);
239
+ }
240
+ });
241
+ filtered = filtered.filter((section) => privilegedSections.includes(section.name));
242
+ }
243
+ if (name) {
244
+ const found = filtered.find((section) => section.name === name);
245
+ return found ? [cloneDeep(found)] : [];
246
+ }
247
+ return filtered.map((section) => cloneDeep(section));
248
+ }
249
+ catch (err) {
250
+ console.error('Error loading section configs:', err);
251
+ return [];
252
+ }
253
+ }
254
+ /**
255
+ * Load all *.section.ts files once, require their default exports,
256
+ * and cache the resulting configs for the lifetime of the process
257
+ * (until invalidated in dev by file changes).
258
+ */
259
+ static async loadAllSections() {
260
+ if (this.allSectionsPromise)
261
+ return this.allSectionsPromise;
262
+ if (this.isDev) {
263
+ // In dev, watch for changes and reload on demand
264
+ // This wll lazy load on first get() as well
265
+ this.startWatcher();
266
+ }
267
+ this.allSectionsPromise = (async () => {
268
+ if (!this.allSectionsLoaded) {
269
+ log('Loading all sections from disk...', this.isDev ? '(dev mode)' : '(production mode)');
270
+ }
271
+ const sections = [];
272
+ try {
273
+ const sectionFiles = await glob('**/*.section.js', {
274
+ cwd: cmsConfig.sectionsFolder,
275
+ });
276
+ for (const file of sectionFiles) {
277
+ try {
278
+ const absPath = resolve(cmsConfig.sectionsFolder, file);
279
+ const defaultExport = requireModuleRuntime(absPath);
280
+ if (!defaultExport) {
281
+ if (!this.sectionFetchingErrors[file]) {
282
+ this.sectionFetchingErrors[file] = [];
283
+ }
284
+ this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file must have a default export. Skipping.'));
285
+ this.errorCount++;
286
+ continue;
287
+ }
288
+ if (Array.isArray(defaultExport)) {
289
+ if (!this.sectionFetchingErrors[file]) {
290
+ this.sectionFetchingErrors[file] = [];
291
+ }
292
+ this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file exports an array. Each file must export exactly one section. Skipping.'));
293
+ this.errorCount++;
294
+ continue;
295
+ }
296
+ if (!defaultExport.type || !defaultExport.name) {
297
+ if (!this.sectionFetchingErrors[file]) {
298
+ this.sectionFetchingErrors[file] = [];
299
+ }
300
+ this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file default export must have "type" and "name". Skipping.'));
301
+ this.errorCount++;
302
+ continue;
303
+ }
304
+ sections.push(defaultExport);
305
+ }
306
+ catch (importErr) {
307
+ if (importErr instanceof Error) {
308
+ /**
309
+ * Only display the error message, not the stack trace.
310
+ */
311
+ if (!this.sectionProcessingErrors[file]) {
312
+ this.sectionProcessingErrors[file] = [];
313
+ }
314
+ this.sectionProcessingErrors[file].push(importErr.message);
315
+ }
316
+ else {
317
+ if (!this.sectionFetchingErrors[file]) {
318
+ this.sectionFetchingErrors[file] = [];
319
+ }
320
+ this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)(`Unknown error type: ${typeof importErr}`));
321
+ throw new Error(`${chalk.bgRed(` Error importing ${file}: `)}, ${importErr}`);
322
+ }
323
+ this.errorCount++;
324
+ /**
325
+ * Remove the section from the cache so it will be reloaded next time.
326
+ */
327
+ delete require.cache[resolve(cmsConfig.sectionsFolder, file)];
328
+ }
329
+ }
330
+ }
331
+ catch (err) {
332
+ console.error('Error finding section files:', err);
333
+ }
334
+ this.allSectionsLoaded = true;
335
+ /**
336
+ * If `strict` mode is enabled,
337
+ * don't return sections if there are errors.
338
+ */
339
+ if (cmsConfig.strict && this.errorCount > 0) {
340
+ return [];
341
+ }
342
+ log(`Loaded ${sections.length} section(s)`);
343
+ return sections;
344
+ })();
345
+ return this.allSectionsPromise;
346
+ }
347
+ // ---------- DEV WATCHER & CACHE INVALIDATION ----------
348
+ static startWatcher() {
349
+ if (!this.isDev || this.watcherStarted)
350
+ return;
351
+ this.watcherStarted = true;
352
+ const watcher = chokidar.watch('**/*.section.js', {
353
+ cwd: cmsConfig.sectionsFolder,
354
+ ignoreInitial: true,
355
+ });
356
+ log('Starting section watcher in dev mode...');
357
+ /**
358
+ * This is triggering nextjs to reload the section module when a section file is changed.
359
+ * We used [refresh-reset](https://nextjs.org/docs/architecture/fast-refresh#:~:text=you%20can%20add-,//%20%40refresh%20reset,-anywhere%20in%20the) to let fast-refresh
360
+ * trigger a react-query invalidation in the form component to re-fetch the new section config.
361
+ * @param relPath
362
+ */
363
+ const invalidateForRelPath = (relPath) => {
364
+ const absPath = resolve(cmsConfig.sectionsFolder, relPath);
365
+ const req = eval('require');
366
+ try {
367
+ const resolved = req.resolve(absPath);
368
+ if (req.cache[resolved]) {
369
+ delete req.cache[resolved];
370
+ log('Cleared require cache for', relPath);
371
+ }
372
+ // Also clear the require cache for the hot marker file
373
+ if (req.cache[hotMarkerFile]) {
374
+ delete req.cache[hotMarkerFile];
375
+ log('Cleared require cache for', hotMarkerFile);
376
+ }
377
+ this.sectionProcessingErrors = {};
378
+ this.sectionFetchingErrors = {};
379
+ this.errorCount = 0;
380
+ }
381
+ catch {
382
+ // ignore if not resolvable
383
+ }
384
+ this.allSectionsPromise = null;
385
+ this.allSectionsLoaded = false;
386
+ log('Invalidated section cache due to change in', relPath);
387
+ this.bumpHotMarker();
388
+ };
389
+ watcher
390
+ .on('add', (path) => {
391
+ log('Section file added:', path);
392
+ invalidateForRelPath(path);
393
+ })
394
+ .on('change', (path) => {
395
+ log('Section file changed:', path);
396
+ invalidateForRelPath(path);
397
+ })
398
+ .on('unlink', (path) => {
399
+ log('Section file removed:', path);
400
+ invalidateForRelPath(path);
401
+ });
402
+ }
403
+ static init() {
404
+ if (this.isProd) {
405
+ // Preload all sections at startup in production
406
+ void this.loadAllSections();
407
+ }
408
+ }
409
+ /**
410
+ * Run initialization once when the module is loaded
411
+ */
412
+ static {
413
+ this.init();
414
+ }
415
+ }