nextjs-cms 0.5.56 → 0.5.58

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 (90) hide show
  1. package/dist/api/index.d.ts +82 -45
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/lib/serverActions.d.ts +68 -5
  4. package/dist/api/lib/serverActions.d.ts.map +1 -1
  5. package/dist/api/lib/serverActions.js +33 -8
  6. package/dist/api/root.d.ts +953 -44
  7. package/dist/api/root.d.ts.map +1 -1
  8. package/dist/api/root.js +2 -1
  9. package/dist/api/routers/accountSettings.d.ts +1 -3
  10. package/dist/api/routers/accountSettings.d.ts.map +1 -1
  11. package/dist/api/routers/admins.d.ts +1 -3
  12. package/dist/api/routers/admins.d.ts.map +1 -1
  13. package/dist/api/routers/admins.js +1 -1
  14. package/dist/api/routers/auth.d.ts +1 -3
  15. package/dist/api/routers/auth.d.ts.map +1 -1
  16. package/dist/api/routers/categorySection.d.ts +1 -3
  17. package/dist/api/routers/categorySection.d.ts.map +1 -1
  18. package/dist/api/routers/cmsSettings.d.ts +1 -3
  19. package/dist/api/routers/cmsSettings.d.ts.map +1 -1
  20. package/dist/api/routers/cpanel.d.ts +1 -3
  21. package/dist/api/routers/cpanel.d.ts.map +1 -1
  22. package/dist/api/routers/files.d.ts +1 -3
  23. package/dist/api/routers/files.d.ts.map +1 -1
  24. package/dist/api/routers/gallery.d.ts +1 -3
  25. package/dist/api/routers/gallery.d.ts.map +1 -1
  26. package/dist/api/routers/gallery.js +7 -6
  27. package/dist/api/routers/googleAnalytics.d.ts +1 -3
  28. package/dist/api/routers/googleAnalytics.d.ts.map +1 -1
  29. package/dist/api/routers/hasItemsSection.d.ts +49 -5
  30. package/dist/api/routers/hasItemsSection.d.ts.map +1 -1
  31. package/dist/api/routers/navigation.d.ts +1 -3
  32. package/dist/api/routers/navigation.d.ts.map +1 -1
  33. package/dist/api/routers/simpleSection.d.ts +21 -4
  34. package/dist/api/routers/simpleSection.d.ts.map +1 -1
  35. package/dist/api/trpc/query-client.d.ts +3 -0
  36. package/dist/api/trpc/query-client.d.ts.map +1 -0
  37. package/dist/api/trpc/query-client.js +23 -0
  38. package/dist/api/trpc/server.d.ts +8 -0
  39. package/dist/api/trpc/server.d.ts.map +1 -0
  40. package/dist/api/trpc/server.js +23 -0
  41. package/dist/api/trpc.d.ts +6 -17
  42. package/dist/api/trpc.d.ts.map +1 -1
  43. package/dist/api/trpc.js +6 -9
  44. package/dist/auth/react.js +1 -1
  45. package/dist/core/config/config-loader.d.ts +1 -1
  46. package/dist/core/config/config-loader.d.ts.map +1 -1
  47. package/dist/core/config/config-loader.js +9 -6
  48. package/dist/core/config/index.d.ts +1 -0
  49. package/dist/core/config/index.d.ts.map +1 -1
  50. package/dist/core/config/index.js +1 -0
  51. package/dist/core/config/loader-with-esbuild.d.ts +7 -0
  52. package/dist/core/config/loader-with-esbuild.d.ts.map +1 -0
  53. package/dist/core/config/loader-with-esbuild.js +98 -0
  54. package/dist/core/config/loader-with-jiti.d.ts +13 -0
  55. package/dist/core/config/loader-with-jiti.d.ts.map +1 -0
  56. package/dist/core/config/loader-with-jiti.js +162 -0
  57. package/dist/core/config/loader.d.ts +1 -1
  58. package/dist/core/config/loader.d.ts.map +1 -1
  59. package/dist/core/config/loader.js +1 -75
  60. package/dist/core/factories/SectionFactory.d.ts +1 -109
  61. package/dist/core/factories/SectionFactory.d.ts.map +1 -1
  62. package/dist/core/factories/SectionFactory.js +1 -452
  63. package/dist/core/factories/section-factory-with-esbuild.d.ts +110 -0
  64. package/dist/core/factories/section-factory-with-esbuild.d.ts.map +1 -0
  65. package/dist/core/factories/section-factory-with-esbuild.js +509 -0
  66. package/dist/core/factories/section-factory-with-jiti.d.ts +113 -0
  67. package/dist/core/factories/section-factory-with-jiti.d.ts.map +1 -0
  68. package/dist/core/factories/section-factory-with-jiti.js +556 -0
  69. package/dist/core/fields/document.d.ts +0 -1
  70. package/dist/core/fields/document.d.ts.map +1 -1
  71. package/dist/core/fields/document.js +5 -4
  72. package/dist/core/fields/photo.d.ts +10 -7
  73. package/dist/core/fields/photo.d.ts.map +1 -1
  74. package/dist/core/fields/photo.js +44 -17
  75. package/dist/core/fields/richText.d.ts +0 -1
  76. package/dist/core/fields/richText.d.ts.map +1 -1
  77. package/dist/core/fields/richText.js +5 -4
  78. package/dist/core/fields/video.d.ts +0 -1
  79. package/dist/core/fields/video.d.ts.map +1 -1
  80. package/dist/core/fields/video.js +5 -4
  81. package/dist/core/sections/section.d.ts +17 -15
  82. package/dist/core/sections/section.d.ts.map +1 -1
  83. package/dist/core/sections/section.js +28 -5
  84. package/dist/core/submit/submit.d.ts.map +1 -1
  85. package/dist/core/submit/submit.js +13 -12
  86. package/dist/translations/dictionaries/ar.json +1 -0
  87. package/dist/translations/dictionaries/en.json +1 -0
  88. package/dist/translations/index.d.ts.map +1 -1
  89. package/dist/translations/index.js +1 -3
  90. package/package.json +18 -9
@@ -0,0 +1,509 @@
1
+ import { glob } from 'glob';
2
+ import { cloneDeep } from 'lodash-es';
3
+ import { join, resolve } from 'path';
4
+ import chalk from 'chalk';
5
+ import { eq } from 'drizzle-orm';
6
+ import chokidar from 'chokidar';
7
+ import fs, { existsSync, mkdirSync, statSync } from 'fs';
8
+ import { createRequire } from 'module';
9
+ import { pathToFileURL } from 'url';
10
+ import { db } from '../../db/client.js';
11
+ import { AdminPrivilegesTable } from '../../db/schema.js';
12
+ import { getCMSConfig } from '../config/index.js';
13
+ import crypto from 'crypto';
14
+ const cmsConfig = await getCMSConfig();
15
+ const hotMarkerFile = resolve(process.cwd(), 'components/form/helpers/_section-hot-reload.js');
16
+ // Create a require function that works in ES modules
17
+ const safeRequire = createRequire(import.meta.url);
18
+ /**
19
+ * Register esbuild-register only once per process.
20
+ * Node's require cache ensures each TS module is evaluated once.
21
+ */
22
+ let tsLoaderRegistered = false;
23
+ /**
24
+ * When we runtime-load TS (via esbuild-register) using CommonJS `require`, our TS sources may contain NodeNext-style
25
+ * specifiers like `./foo.js` that point at `./foo.ts` in the source tree. Node's CJS resolver won't find `foo.js`.
26
+ *
27
+ * In normal builds this is fine because `dist/foo.js` exists, but in dev/tests we load from `src/`.
28
+ * This helper patches Node's CJS resolver to fall back from `*.js` → `*.ts` for relative imports.
29
+ */
30
+ const withPatchedCjsResolveJsToTs = (fn) => {
31
+ const NodeModule = safeRequire('module');
32
+ const originalResolve = NodeModule._resolveFilename;
33
+ NodeModule._resolveFilename = function (request, parent, isMain, options) {
34
+ try {
35
+ return originalResolve.call(this, request, parent, isMain, options);
36
+ }
37
+ catch (err) {
38
+ if (typeof request === 'string' && /^(\.{1,2}\/.*)\.js$/.test(request)) {
39
+ const tsRequest = request.replace(/\.js$/, '.ts');
40
+ try {
41
+ return originalResolve.call(this, tsRequest, parent, isMain, options);
42
+ }
43
+ catch {
44
+ // fall through to rethrow original error
45
+ }
46
+ }
47
+ throw err;
48
+ }
49
+ };
50
+ try {
51
+ return fn();
52
+ }
53
+ finally {
54
+ NodeModule._resolveFilename = originalResolve;
55
+ }
56
+ };
57
+ const loadEsbuildRegister = () => {
58
+ const id = 'esbuild-register/dist/node'; // avoid inline literal in require call
59
+ return safeRequire(id);
60
+ };
61
+ /**
62
+ * Runtime module import version token.
63
+ * Node caches ESM modules by URL. Adding `?v=` busts the cache when files change.
64
+ */
65
+ let sectionImportVersion = 0;
66
+ const getEsbuild = () => safeRequire('esbuild');
67
+ const cacheRoot = resolve(process.cwd(), '.nextjs-cms', 'sections');
68
+ if (!existsSync(cacheRoot))
69
+ mkdirSync(cacheRoot, { recursive: true });
70
+ const hashKey = (s) => crypto.createHash('sha1').update(s).digest('hex');
71
+ const ensureBundledSection = async (entryAbsPath) => {
72
+ const esbuild = getEsbuild();
73
+ // Make output stable per file + mtime (so edits re-bundle)
74
+ const st = statSync(entryAbsPath);
75
+ const key = hashKey(`${entryAbsPath}|${st.mtimeMs}`);
76
+ const outFile = join(cacheRoot, `${key}.mjs`);
77
+ if (existsSync(outFile))
78
+ return outFile;
79
+ // Bundle so extensionless imports work (esbuild resolves them)
80
+ await esbuild.build({
81
+ entryPoints: [entryAbsPath],
82
+ outfile: outFile,
83
+ bundle: true,
84
+ platform: 'node',
85
+ format: 'esm',
86
+ target: 'node18', // or node20, whatever you support
87
+ sourcemap: 'inline',
88
+ logLevel: 'silent',
89
+ // This is the magic for your UX:
90
+ resolveExtensions: ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json'],
91
+ // Don’t bundle your library deps; keep them as runtime imports
92
+ packages: 'external',
93
+ });
94
+ return outFile;
95
+ };
96
+ const loadSectionModuleRuntime = async (absPath) => {
97
+ const isTs = absPath.endsWith('.ts') || absPath.endsWith('.cts') || absPath.endsWith('.mts');
98
+ // Keep tests using your old require-path + resolver patch (as before)
99
+ if (process.env.NODE_ENV === 'test') {
100
+ if (isTs && !tsLoaderRegistered) {
101
+ const { register } = loadEsbuildRegister();
102
+ register({ format: 'cjs', loader: 'ts' });
103
+ tsLoaderRegistered = true;
104
+ }
105
+ const mod = isTs ? withPatchedCjsResolveJsToTs(() => safeRequire(absPath)) : safeRequire(absPath);
106
+ return mod?.default;
107
+ }
108
+ // Dev/runtime: bundle then import the bundled output (Turbopack-safe)
109
+ const bundled = await ensureBundledSection(absPath);
110
+ const url = `${pathToFileURL(bundled).href}?v=${sectionImportVersion}`;
111
+ const mod = await import(/* turbopackIgnore: true */ url);
112
+ return mod?.default;
113
+ };
114
+ /**
115
+ * Log a message to the console if debug mode is enabled
116
+ * @param args - The arguments to log
117
+ * @returns void
118
+ */
119
+ const log = (...args) => {
120
+ if (!cmsConfig.debug)
121
+ return;
122
+ console.log(chalk.black.bgGreen(`[${new Date().toISOString()}][next-cms-debug]`, ...args));
123
+ };
124
+ export class SectionFactory {
125
+ /**
126
+ * These are the fixed sections that can not be present in the sections folder.
127
+ */
128
+ static fixedSections = ['admins', 'analytics', 'emails', 'dashboard'];
129
+ static isDev = process.env.NODE_ENV !== 'production';
130
+ static isProd = !this.isDev;
131
+ static sectionProcessingErrors = {};
132
+ static sectionFetchingErrors = {};
133
+ static errorCount = 0;
134
+ /**
135
+ * Global in-process cache for all section configs.
136
+ * Populated once per Node process via loadAllSections(),
137
+ * reused by all get*() calls until the process is restarted,
138
+ * so all subsequent calls reuse this array.
139
+ */
140
+ static allSectionsPromise = null;
141
+ static allSectionsLoaded = false;
142
+ static watcherStarted = false;
143
+ static bumpHotMarker() {
144
+ if (!SectionFactory.isDev)
145
+ return;
146
+ const content = `/**\n` +
147
+ ` * AUTO-GENERATED. DO NOT EDIT.\n` +
148
+ ` * This file is used to track the last time the sections schema was updated.\n` +
149
+ ` * It is used to trigger a hot reload of the section schema in development mode.\n` +
150
+ ` */\n` +
151
+ `\n` +
152
+ `export const revalidate = 0\n` +
153
+ `\n` +
154
+ `// @refresh reset\n` +
155
+ `\n` +
156
+ `export const configLastUpdated = ${Date.now()}\n`;
157
+ try {
158
+ fs.writeFileSync(hotMarkerFile, content, 'utf8');
159
+ log('Bumped _section-hot-reload.js to trigger Next Fast Refresh');
160
+ }
161
+ catch (err) {
162
+ console.error('Failed to bump _section-hot-reload.js:', err);
163
+ }
164
+ }
165
+ // ---------- PUBLIC API ----------
166
+ /**
167
+ * Get all sections
168
+ * @param type - The type of sections to get
169
+ * @returns The sections
170
+ */
171
+ static async getSections(type) {
172
+ return await this.get({ type });
173
+ }
174
+ /**
175
+ * Get all accessible sections for an admin
176
+ * @param type - The type of section to get
177
+ * @param admin - The admin to get the sections for
178
+ * @returns The sections for the admin
179
+ */
180
+ static async getSectionsForAdmin({ type, admin, }) {
181
+ const sections = await this.get({ type, admin });
182
+ const privileges = await db
183
+ .select({
184
+ sectionName: AdminPrivilegesTable.sectionName,
185
+ operations: AdminPrivilegesTable.operations,
186
+ })
187
+ .from(AdminPrivilegesTable)
188
+ .where(eq(AdminPrivilegesTable.adminId, admin.id));
189
+ const fixedSections = privileges
190
+ .filter((privilege) => this.fixedSections.includes(privilege.sectionName))
191
+ .map((privilege) => privilege.sectionName);
192
+ const simpleSections = sections.filter((section) => section.type === 'simple');
193
+ const hasItemsSections = sections.filter((section) => section.type === 'has_items');
194
+ const categorySections = sections.filter((section) => section.type === 'category');
195
+ return {
196
+ simple: simpleSections,
197
+ has_items: hasItemsSections,
198
+ category: categorySections,
199
+ fixed: fixedSections,
200
+ };
201
+ }
202
+ /**
203
+ * Get a section
204
+ * @param name - The name of the section to get
205
+ * @param type - The type of section to get
206
+ * @returns The section
207
+ */
208
+ static async getSection({ name, type, }) {
209
+ const section = await this.get({ name, type });
210
+ return section[0] ? section[0] : null;
211
+ }
212
+ /**
213
+ * Get an accessible section for an admin
214
+ * @param name - The name of the section to get
215
+ * @param type - The type of section to get
216
+ * @param admin - The admin to get the section for
217
+ * @returns The section for the admin
218
+ */
219
+ static async getSectionForAdmin({ name, type, admin, }) {
220
+ const section = await this.get({ type, admin, name });
221
+ return section[0] ? section[0] : null;
222
+ }
223
+ /**
224
+ * Create a Section instance from a section config
225
+ * The config must have a build() method (created via helper functions like simpleSection(), hasItemsSection(), etc.)
226
+ *
227
+ * Note: This factory only accepts configs. If you already have a Section instance,
228
+ * use it directly - don't pass it to this factory.
229
+ *
230
+ * @param config - A section config object with a build() method
231
+ * @returns A Section instance
232
+ * @throws Error if config doesn't have a build() method
233
+ */
234
+ static create(config) {
235
+ if (typeof config.build === 'function') {
236
+ return config.build();
237
+ }
238
+ throw new Error('Section config must have a build() method. Use helper functions like simpleSection(), hasItemsSection(), categorySection() to create configs.');
239
+ }
240
+ // ---------- INTERNAL CORE ----------
241
+ /**
242
+ * Return a clone of the section(s) info to get a fresh copy of the section(s).
243
+ * Not cloning will cause the original section(s) (in *.section.ts file) to be modified!
244
+ * Each *.section.ts file must have exactly one default export (arrays are not allowed).
245
+ *
246
+ * @param name - The name of the section to get
247
+ * @param type - The type of section to get
248
+ * @param admin - The admin to get the section for
249
+ * @returns The section(s)
250
+ */
251
+ static async get({ name, type, admin, }) {
252
+ try {
253
+ const allSections = await this.loadAllSections();
254
+ /**
255
+ * Always show errors if there are any.
256
+ * This is to make sure the errors are shown on every request (technically, every time the section is requested).
257
+ * This way devs can notice the errors and fix them.
258
+ */
259
+ if (this.errorCount > 0) {
260
+ console.log(`\n${chalk.bgRed.bold(` Errors while fetching sections `)}${chalk.red.bold('-------')}\n`);
261
+ if (Object.keys(this.sectionProcessingErrors).length > 0) {
262
+ console.log(`${chalk.bold.redBright(`Processing errors:`)}`);
263
+ for (const [file, errors] of Object.entries(this.sectionProcessingErrors)) {
264
+ console.log('');
265
+ console.log(chalk.bold(`[${file}]:`));
266
+ for (const error of errors) {
267
+ console.error(error);
268
+ }
269
+ console.log('');
270
+ }
271
+ }
272
+ if (Object.keys(this.sectionFetchingErrors).length > 0) {
273
+ console.log(`\n${chalk.bold.redBright(`Importing errors:`)}`);
274
+ for (const [file, errors] of Object.entries(this.sectionFetchingErrors)) {
275
+ console.log('');
276
+ console.log(chalk.bold(`[${file}]:`));
277
+ for (const error of errors) {
278
+ console.error(error);
279
+ }
280
+ console.log('');
281
+ }
282
+ }
283
+ console.log(`${chalk.red.bold('-------')}`);
284
+ }
285
+ const types = type
286
+ ? Array.isArray(type)
287
+ ? type
288
+ : [type]
289
+ : ['has_items', 'simple', 'category'];
290
+ // Filter by type first
291
+ let filtered = allSections.filter((section) => types.includes(section.type));
292
+ // If admin is provided, restrict to privileged sections
293
+ if (admin && admin.id) {
294
+ const privileges = await db
295
+ .select({
296
+ sectionName: AdminPrivilegesTable.sectionName,
297
+ operations: AdminPrivilegesTable.operations,
298
+ })
299
+ .from(AdminPrivilegesTable)
300
+ .where(eq(AdminPrivilegesTable.adminId, admin.id));
301
+ const privilegedSections = [];
302
+ privileges.forEach((privilege) => {
303
+ if (admin.requiredRole) {
304
+ if (privilege.operations.includes(admin.requiredRole)) {
305
+ privilegedSections.push(privilege.sectionName);
306
+ }
307
+ }
308
+ else {
309
+ privilegedSections.push(privilege.sectionName);
310
+ }
311
+ });
312
+ filtered = filtered.filter((section) => privilegedSections.includes(section.name));
313
+ }
314
+ if (name) {
315
+ const found = filtered.find((section) => section.name === name);
316
+ return found ? [cloneDeep(found)] : [];
317
+ }
318
+ return filtered.map((section) => cloneDeep(section));
319
+ }
320
+ catch (err) {
321
+ console.error('Error loading section configs:', err);
322
+ return [];
323
+ }
324
+ }
325
+ /**
326
+ * Load all *.section.ts files once, import their default exports,
327
+ * and cache the resulting configs for the lifetime of the process
328
+ * (until invalidated in dev by file changes).
329
+ */
330
+ static async loadAllSections() {
331
+ if (this.allSectionsPromise)
332
+ return this.allSectionsPromise;
333
+ if (this.isDev) {
334
+ // In dev, watch for changes and reload on demand
335
+ // This will lazy load on first get() as well
336
+ this.startWatcher();
337
+ }
338
+ this.allSectionsPromise = (async () => {
339
+ if (!this.allSectionsLoaded) {
340
+ log('Loading all sections from disk...', this.isDev ? '(dev mode)' : '(production mode)');
341
+ }
342
+ const sections = [];
343
+ try {
344
+ const sectionFiles = await glob('**/*.section.ts', {
345
+ cwd: cmsConfig.sections.path,
346
+ });
347
+ for (const file of sectionFiles) {
348
+ try {
349
+ const absPath = resolve(cmsConfig.sections.path, file);
350
+ const defaultExport = await loadSectionModuleRuntime(absPath);
351
+ if (!defaultExport) {
352
+ if (!this.sectionFetchingErrors[file]) {
353
+ this.sectionFetchingErrors[file] = [];
354
+ }
355
+ this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file must have a default export. Skipping.'));
356
+ this.errorCount++;
357
+ continue;
358
+ }
359
+ if (Array.isArray(defaultExport)) {
360
+ if (!this.sectionFetchingErrors[file]) {
361
+ this.sectionFetchingErrors[file] = [];
362
+ }
363
+ this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file exports an array. Each file must export exactly one section. Skipping.'));
364
+ this.errorCount++;
365
+ continue;
366
+ }
367
+ if (!defaultExport.type || !defaultExport.name) {
368
+ if (!this.sectionFetchingErrors[file]) {
369
+ this.sectionFetchingErrors[file] = [];
370
+ }
371
+ this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)('Section file default export must have "type" and "name". Skipping.'));
372
+ this.errorCount++;
373
+ continue;
374
+ }
375
+ sections.push(defaultExport);
376
+ }
377
+ catch (importErr) {
378
+ /**
379
+ * Guardrail: extensioned imports inside section files
380
+ * This usually means the developer wrote:
381
+ * import x from './other.section.ts'
382
+ * or './other.section.js'
383
+ * which breaks once sections are bundled.
384
+ */
385
+ if (importErr instanceof Error &&
386
+ importErr.message.includes('Cannot find module') &&
387
+ importErr.message.includes('.section')) {
388
+ this.sectionProcessingErrors[file] ??= [];
389
+ this.sectionProcessingErrors[file].push(`❌ Invalid section import detected.
390
+
391
+ Sections MUST use extensionless relative imports:
392
+
393
+ ✅ import exampleSection from './example.section'
394
+ ❌ import exampleSection from './example.section.ts'
395
+ ❌ import exampleSection from './example.section.js'
396
+
397
+ This file is bundled with esbuild, so Node never resolves the import directly.
398
+ If you added an extension manually, remove it.`);
399
+ this.errorCount++;
400
+ continue;
401
+ }
402
+ /**
403
+ * Normal error handling
404
+ * Only display the error message, not the stack trace.
405
+ */
406
+ if (importErr instanceof Error) {
407
+ /**
408
+ * Only display the error message, not the stack trace.
409
+ */
410
+ if (!this.sectionProcessingErrors[file]) {
411
+ this.sectionProcessingErrors[file] = [];
412
+ }
413
+ this.sectionProcessingErrors[file].push(importErr.message);
414
+ }
415
+ else {
416
+ if (!this.sectionFetchingErrors[file]) {
417
+ this.sectionFetchingErrors[file] = [];
418
+ }
419
+ this.sectionFetchingErrors[file].push(chalk.rgb(255, 100, 0)(`Unknown error type: ${typeof importErr}`));
420
+ throw new Error(`${chalk.bgRed(` Error importing ${file}: `)}, ${importErr}`);
421
+ }
422
+ this.errorCount++;
423
+ /**
424
+ * Tests-only:
425
+ * remove from require cache so it will be reloaded next time.
426
+ * In dev/runtime, cache busting is handled via versioned ESM imports.
427
+ */
428
+ if (process.env.NODE_ENV === 'test') {
429
+ try {
430
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
431
+ delete safeRequire.cache?.[resolve(cmsConfig.sections.path, file)];
432
+ }
433
+ catch {
434
+ // ignore
435
+ }
436
+ }
437
+ }
438
+ }
439
+ }
440
+ catch (err) {
441
+ console.error('Error finding section files:', err);
442
+ }
443
+ this.allSectionsLoaded = true;
444
+ /**
445
+ * If `strict` mode is enabled,
446
+ * don't return sections if there are errors.
447
+ */
448
+ if (cmsConfig.sections.strict && this.errorCount > 0) {
449
+ return [];
450
+ }
451
+ log(`Loaded ${sections.length} section(s)`);
452
+ return sections;
453
+ })();
454
+ return this.allSectionsPromise;
455
+ }
456
+ // ---------- DEV WATCHER & CACHE INVALIDATION ----------
457
+ static startWatcher() {
458
+ if (!this.isDev || this.watcherStarted)
459
+ return;
460
+ this.watcherStarted = true;
461
+ const watcher = chokidar.watch('**/*.section.ts', {
462
+ cwd: cmsConfig.sections.path,
463
+ ignoreInitial: true,
464
+ });
465
+ log('Starting section watcher in dev mode...');
466
+ /**
467
+ * Invalidate section cache + bust runtime import cache.
468
+ * For Turbopack runtime import we increment sectionImportVersion which changes the URL (`?v=`).
469
+ * We still bump the hot marker to trigger Fast Refresh + react-query invalidation.
470
+ */
471
+ const invalidateForRelPath = (relPath) => {
472
+ // Bust ESM import cache (Node caches by URL)
473
+ sectionImportVersion++;
474
+ // Reset our in-process caches
475
+ this.sectionProcessingErrors = {};
476
+ this.sectionFetchingErrors = {};
477
+ this.errorCount = 0;
478
+ this.allSectionsPromise = null;
479
+ this.allSectionsLoaded = false;
480
+ log('Invalidated section cache due to change in', relPath);
481
+ this.bumpHotMarker();
482
+ };
483
+ watcher
484
+ .on('add', (path) => {
485
+ log('Section file added:', path);
486
+ invalidateForRelPath(path);
487
+ })
488
+ .on('change', (path) => {
489
+ log('Section file changed:', path);
490
+ invalidateForRelPath(path);
491
+ })
492
+ .on('unlink', (path) => {
493
+ log('Section file removed:', path);
494
+ invalidateForRelPath(path);
495
+ });
496
+ }
497
+ static init() {
498
+ if (this.isProd) {
499
+ // Preload all sections at startup in production
500
+ void this.loadAllSections();
501
+ }
502
+ }
503
+ /**
504
+ * Run initialization once when the module is loaded
505
+ */
506
+ static {
507
+ this.init();
508
+ }
509
+ }
@@ -0,0 +1,113 @@
1
+ import { HasItemsSection, SimpleSection, CategorySection } from '../sections/index.js';
2
+ import type { CategorySectionConfig, HasItemsSectionConfig, SimpleSectionConfig } from '../sections/index.js';
3
+ import type { SectionTypes } from '../types/index.js';
4
+ type AnySectionConfig = HasItemsSectionConfig | SimpleSectionConfig | CategorySectionConfig;
5
+ export declare class SectionFactory {
6
+ /**
7
+ * These are the fixed sections that can not be present in the sections folder.
8
+ */
9
+ static readonly fixedSections: string[];
10
+ private static readonly isDev;
11
+ private static readonly isProd;
12
+ private static sectionProcessingErrors;
13
+ private static sectionFetchingErrors;
14
+ private static errorCount;
15
+ /**
16
+ * Global in-process cache for all section configs.
17
+ * Populated once per Node process via loadAllSections(),
18
+ * reused by all get*() calls until the process is restarted,
19
+ * so all subsequent calls reuse this array.
20
+ */
21
+ private static allSectionsPromise;
22
+ private static allSectionsLoaded;
23
+ private static watcherStarted;
24
+ private static configVersion;
25
+ private static sectionWatcher;
26
+ private static ensureConfigFresh;
27
+ static bumpHotMarker(): void;
28
+ /**
29
+ * Get all sections
30
+ * @param type - The type of sections to get
31
+ * @returns The sections
32
+ */
33
+ static getSections(type?: SectionTypes | SectionTypes[]): Promise<AnySectionConfig[]>;
34
+ /**
35
+ * Get all accessible sections for an admin
36
+ * @param type - The type of section to get
37
+ * @param admin - The admin to get the sections for
38
+ * @returns The sections for the admin
39
+ */
40
+ static getSectionsForAdmin({ type, admin, }: {
41
+ type?: SectionTypes | SectionTypes[];
42
+ admin: {
43
+ id: string;
44
+ requiredRole?: 'C' | 'U' | 'D';
45
+ };
46
+ }): Promise<{
47
+ simple: SimpleSectionConfig[];
48
+ has_items: HasItemsSectionConfig[];
49
+ category: CategorySectionConfig[];
50
+ fixed: string[];
51
+ }>;
52
+ /**
53
+ * Get a section
54
+ * @param name - The name of the section to get
55
+ * @param type - The type of section to get
56
+ * @returns The section
57
+ */
58
+ static getSection({ name, type, }: {
59
+ name: string;
60
+ type?: SectionTypes | SectionTypes[];
61
+ }): Promise<AnySectionConfig | null>;
62
+ /**
63
+ * Get an accessible section for an admin
64
+ * @param name - The name of the section to get
65
+ * @param type - The type of section to get
66
+ * @param admin - The admin to get the section for
67
+ * @returns The section for the admin
68
+ */
69
+ static getSectionForAdmin({ name, type, admin, }: {
70
+ name: string;
71
+ type?: SectionTypes | SectionTypes[];
72
+ admin: {
73
+ id: string;
74
+ requiredRole?: 'C' | 'U' | 'D';
75
+ };
76
+ }): Promise<AnySectionConfig | null>;
77
+ /**
78
+ * Create a Section instance from a section config
79
+ * The config must have a build() method (created via helper functions like simpleSection(), hasItemsSection(), etc.)
80
+ *
81
+ * Note: This factory only accepts configs. If you already have a Section instance,
82
+ * use it directly - don't pass it to this factory.
83
+ *
84
+ * @param config - A section config object with a build() method
85
+ * @returns A Section instance
86
+ * @throws Error if config doesn't have a build() method
87
+ */
88
+ static create(config: {
89
+ build: () => HasItemsSection | SimpleSection | CategorySection;
90
+ [key: string]: any;
91
+ }): HasItemsSection | SimpleSection | CategorySection;
92
+ /**
93
+ * Return a clone of the section(s) info to get a fresh copy of the section(s).
94
+ * Not cloning will cause the original section(s) (in *.section.ts file) to be modified!
95
+ * Each *.section.ts file must have exactly one default export (arrays are not allowed).
96
+ *
97
+ * @param name - The name of the section to get
98
+ * @param type - The type of section to get
99
+ * @param admin - The admin to get the section for
100
+ * @returns The section(s)
101
+ */
102
+ private static get;
103
+ /**
104
+ * Load all *.section.ts files once, import their default exports,
105
+ * and cache the resulting configs for the lifetime of the process
106
+ * (until invalidated in dev by file changes).
107
+ */
108
+ private static loadAllSections;
109
+ private static startWatcher;
110
+ private static init;
111
+ }
112
+ export {};
113
+ //# sourceMappingURL=section-factory-with-jiti.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-factory-with-jiti.d.ts","sourceRoot":"","sources":["../../../src/core/factories/section-factory-with-jiti.ts"],"names":[],"mappings":"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"}