nextjs-cms 0.7.1 → 0.7.3

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 (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +290 -290
  3. package/dist/api/index.d.ts +3 -3
  4. package/dist/api/lib/serverActions.d.ts +3 -3
  5. package/dist/api/root.d.ts +6 -6
  6. package/dist/api/routers/hasItemsSection.d.ts +2 -2
  7. package/dist/api/routers/simpleSection.d.ts +1 -1
  8. package/dist/cli/lib/db-config.js +10 -10
  9. package/dist/core/config/config-loader.d.ts +23 -3
  10. package/dist/core/config/config-loader.d.ts.map +1 -1
  11. package/dist/core/config/config-loader.js +32 -6
  12. package/dist/core/config/loader-with-jiti.d.ts.map +1 -1
  13. package/dist/core/config/loader-with-jiti.js +19 -15
  14. package/dist/core/db/table-checker/MysqlTable.js +8 -8
  15. package/dist/core/factories/section-factory-with-esbuild.d.ts.map +1 -1
  16. package/dist/core/factories/section-factory-with-esbuild.js +23 -9
  17. package/dist/core/factories/section-factory-with-jiti.d.ts.map +1 -1
  18. package/dist/core/factories/section-factory-with-jiti.js +51 -33
  19. package/dist/core/factories/section-name-validation.d.ts +10 -0
  20. package/dist/core/factories/section-name-validation.d.ts.map +1 -0
  21. package/dist/core/factories/section-name-validation.js +31 -0
  22. package/dist/core/fields/photo.d.ts +127 -85
  23. package/dist/core/fields/photo.d.ts.map +1 -1
  24. package/dist/core/fields/photo.js +83 -72
  25. package/dist/core/fields/richText.d.ts +9 -9
  26. package/dist/core/fields/select.d.ts +1 -1
  27. package/dist/core/sections/category.d.ts +42 -36
  28. package/dist/core/sections/category.d.ts.map +1 -1
  29. package/dist/core/sections/category.js +6 -1
  30. package/dist/core/sections/hasItems.d.ts +156 -54
  31. package/dist/core/sections/hasItems.d.ts.map +1 -1
  32. package/dist/core/sections/hasItems.js +6 -11
  33. package/dist/core/sections/section.d.ts +33 -20
  34. package/dist/core/sections/section.d.ts.map +1 -1
  35. package/dist/core/sections/section.js +37 -3
  36. package/dist/core/sections/simple.d.ts +14 -8
  37. package/dist/core/sections/simple.d.ts.map +1 -1
  38. package/dist/core/sections/simple.js +6 -1
  39. package/dist/core/submit/submit.js +1 -1
  40. package/dist/translations/client.d.ts.map +1 -1
  41. package/dist/translations/server.d.ts.map +1 -1
  42. package/dist/validators/photo.js +1 -1
  43. package/package.json +2 -1
  44. package/dist/translations/dictionaries/ar.d.ts +0 -433
  45. package/dist/translations/dictionaries/ar.d.ts.map +0 -1
  46. package/dist/translations/dictionaries/ar.js +0 -444
  47. package/dist/translations/dictionaries/en.d.ts +0 -433
  48. package/dist/translations/dictionaries/en.d.ts.map +0 -1
  49. package/dist/translations/dictionaries/en.js +0 -444
@@ -66,7 +66,7 @@ export declare const createSimpleSectionPage: (session: Session, sectionName: st
66
66
  thumbnail?: {
67
67
  width: number;
68
68
  height: number;
69
- crop: boolean;
69
+ fit: "cover" | "contain";
70
70
  quality: number;
71
71
  };
72
72
  } | undefined;
@@ -113,7 +113,7 @@ export declare const createEditPage: (session: Session, sectionName: string, sec
113
113
  thumbnail?: {
114
114
  width: number;
115
115
  height: number;
116
- crop: boolean;
116
+ fit: "cover" | "contain";
117
117
  quality: number;
118
118
  };
119
119
  } | undefined;
@@ -173,7 +173,7 @@ export declare const createNewPage: (session: Session, sectionName: string) => P
173
173
  thumbnail?: {
174
174
  width: number;
175
175
  height: number;
176
- crop: boolean;
176
+ fit: "cover" | "contain";
177
177
  quality: number;
178
178
  };
179
179
  } | undefined;
@@ -297,7 +297,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
297
297
  thumbnail?: {
298
298
  width: number;
299
299
  height: number;
300
- crop: boolean;
300
+ fit: "cover" | "contain";
301
301
  quality: number;
302
302
  };
303
303
  } | undefined;
@@ -346,7 +346,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
346
346
  thumbnail?: {
347
347
  width: number;
348
348
  height: number;
349
- crop: boolean;
349
+ fit: "cover" | "contain";
350
350
  quality: number;
351
351
  };
352
352
  } | undefined;
@@ -443,7 +443,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
443
443
  thumbnail?: {
444
444
  width: number;
445
445
  height: number;
446
- crop: boolean;
446
+ fit: "cover" | "contain";
447
447
  quality: number;
448
448
  };
449
449
  } | undefined;
@@ -1170,7 +1170,7 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
1170
1170
  thumbnail?: {
1171
1171
  width: number;
1172
1172
  height: number;
1173
- crop: boolean;
1173
+ fit: "cover" | "contain";
1174
1174
  quality: number;
1175
1175
  };
1176
1176
  } | undefined;
@@ -1219,7 +1219,7 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
1219
1219
  thumbnail?: {
1220
1220
  width: number;
1221
1221
  height: number;
1222
- crop: boolean;
1222
+ fit: "cover" | "contain";
1223
1223
  quality: number;
1224
1224
  };
1225
1225
  } | undefined;
@@ -1316,7 +1316,7 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
1316
1316
  thumbnail?: {
1317
1317
  width: number;
1318
1318
  height: number;
1319
- crop: boolean;
1319
+ fit: "cover" | "contain";
1320
1320
  quality: number;
1321
1321
  };
1322
1322
  } | undefined;
@@ -82,7 +82,7 @@ export declare const hasItemsSectionRouter: import("@trpc/server").TRPCBuiltRout
82
82
  thumbnail?: {
83
83
  width: number;
84
84
  height: number;
85
- crop: boolean;
85
+ fit: "cover" | "contain";
86
86
  quality: number;
87
87
  };
88
88
  } | undefined;
@@ -131,7 +131,7 @@ export declare const hasItemsSectionRouter: import("@trpc/server").TRPCBuiltRout
131
131
  thumbnail?: {
132
132
  width: number;
133
133
  height: number;
134
- crop: boolean;
134
+ fit: "cover" | "contain";
135
135
  quality: number;
136
136
  };
137
137
  } | undefined;
@@ -46,7 +46,7 @@ export declare const simpleSectionRouter: import("@trpc/server").TRPCBuiltRouter
46
46
  thumbnail?: {
47
47
  width: number;
48
48
  height: number;
49
- crop: boolean;
49
+ fit: "cover" | "contain";
50
50
  quality: number;
51
51
  };
52
52
  } | undefined;
@@ -140,16 +140,16 @@ export async function dbConfigSetup(options) {
140
140
  /**
141
141
  * Prepare new environment variables block
142
142
  */
143
- const newEnvBlock = `
144
-
145
- ####
146
- # generated by the init script
147
- ####
148
- DB_HOST=${dbHost}
149
- DB_PORT=${dbPort}
150
- DB_NAME=${dbName}
151
- DB_USER=${dbUser}
152
- DB_PASSWORD='${dbPassword}'
143
+ const newEnvBlock = `
144
+
145
+ ####
146
+ # generated by the init script
147
+ ####
148
+ DB_HOST=${dbHost}
149
+ DB_PORT=${dbPort}
150
+ DB_NAME=${dbName}
151
+ DB_USER=${dbUser}
152
+ DB_PASSWORD='${dbPassword}'
153
153
  `;
154
154
  /**
155
155
  * Append the new credentials to the .env file
@@ -30,10 +30,23 @@ declare const cmsConfigSchema: z.ZodObject<{
30
30
  b: z.ZodNumber;
31
31
  alpha: z.ZodNumber;
32
32
  }, z.core.$strip>>;
33
+ image: z.ZodOptional<z.ZodObject<{
34
+ width: z.ZodNumber;
35
+ height: z.ZodNumber;
36
+ fit: z.ZodEnum<{
37
+ cover: "cover";
38
+ contain: "contain";
39
+ }>;
40
+ strict: z.ZodBoolean;
41
+ quality: z.ZodNumber;
42
+ }, z.core.$strip>>;
33
43
  thumbnail: z.ZodOptional<z.ZodObject<{
34
44
  width: z.ZodNumber;
35
45
  height: z.ZodNumber;
36
- crop: z.ZodBoolean;
46
+ fit: z.ZodEnum<{
47
+ cover: "cover";
48
+ contain: "contain";
49
+ }>;
37
50
  quality: z.ZodOptional<z.ZodNumber>;
38
51
  }, z.core.$strip>>;
39
52
  watermark: z.ZodOptional<z.ZodBoolean>;
@@ -112,11 +125,18 @@ export interface ComputedCMSConfig {
112
125
  b: number;
113
126
  alpha: number;
114
127
  };
128
+ image: {
129
+ width: number;
130
+ height: number;
131
+ fit: 'cover' | 'contain';
132
+ strict?: boolean;
133
+ quality?: number;
134
+ };
115
135
  thumbnail: {
116
136
  width: number;
117
137
  height: number;
118
- crop: boolean;
119
- quality: number;
138
+ fit?: 'cover' | 'contain';
139
+ quality?: number;
120
140
  };
121
141
  watermark: boolean;
122
142
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/core/config/config-loader.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAGxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AAEzE,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,iBAAkB,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAA;AAGrE,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8OnB,CAAA;AAGF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAEvD,MAAM,WAAW,mBAAmB;IAChC,KAAK,CAAC,EAAE,eAAe,CAAA;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,iEAAiE;IACjE,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;CAC/C;AAGD,MAAM,WAAW,iBAAiB;IAC9B,EAAE,EAAE;QACA,KAAK,EAAE,MAAM,CAAA;QACb,YAAY,EAAE,MAAM,CAAA;QACpB,IAAI,EAAE,MAAM,CAAA;QACZ,QAAQ,EAAE,MAAM,CAAA;KACnB,CAAA;IACD,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE;QACN,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,OAAO,CAAA;QACf,QAAQ,EAAE;YACN,oBAAoB,EAAE,OAAO,CAAA;SAChC,CAAA;KACJ,CAAA;IACD,KAAK,EAAE;QACH,MAAM,EAAE;YACJ,IAAI,EAAE,MAAM,CAAA;YACZ,iBAAiB,EAAE,OAAO,CAAA;SAC7B,CAAA;QACD,MAAM,EAAE;YACJ,UAAU,EAAE;gBAAE,CAAC,EAAE,MAAM,CAAC;gBAAC,CAAC,EAAE,MAAM,CAAC;gBAAC,CAAC,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,MAAM,CAAA;aAAE,CAAA;YAC9D,SAAS,EAAE;gBACP,KAAK,EAAE,MAAM,CAAA;gBACb,MAAM,EAAE,MAAM,CAAA;gBACd,IAAI,EAAE,OAAO,CAAA;gBACb,OAAO,EAAE,MAAM,CAAA;aAClB,CAAA;YACD,SAAS,EAAE,OAAO,CAAA;SACrB,CAAA;KACJ,CAAA;IACD,gBAAgB,EAAE;QACd,OAAO,EAAE;YACL,OAAO,EAAE,OAAO,CAAA;YAChB,MAAM,EAAE,MAAM,CAAA;YACd,QAAQ,EAAE,MAAM,CAAA;YAChB,MAAM,EAAE;gBACJ,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,CAAA;gBAChD,OAAO,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,CAAA;aACpD,CAAA;SACJ,CAAA;KACJ,CAAA;IACD,OAAO,EAAE,iBAAiB,EAAE,CAAA;IAC5B,IAAI,EAAE;QACF,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAA;QACrC,gBAAgB,EAAE,MAAM,CAAA;KAC3B,CAAA;IACD,SAAS,EAAE;QACP,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAC1B,CAAA;CACJ;AA4KD;;;;;;GAMG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAazE"}
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/core/config/config-loader.ts"],"names":[],"mappings":"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"}
@@ -91,8 +91,8 @@ const cmsConfigSchema = z.object({
91
91
  images: z
92
92
  .object({
93
93
  /**
94
- * Global background color for image resizing (used when crop is false).
95
- * Applied to both normal size images and thumbnails as a fallback.
94
+ * Global background color for image padding (used with fit: 'contain').
95
+ * Applied to both images and thumbnails as a fallback.
96
96
  */
97
97
  background: z
98
98
  .object({
@@ -103,13 +103,25 @@ const cmsConfigSchema = z.object({
103
103
  })
104
104
  .optional(),
105
105
  /**
106
- * Global images thumbnail configuration
106
+ * Global image defaults (used when individual fields don't specify size)
107
+ */
108
+ image: z
109
+ .object({
110
+ width: z.number(),
111
+ height: z.number(),
112
+ fit: z.enum(['cover', 'contain']),
113
+ strict: z.boolean(),
114
+ quality: z.number().min(1).max(100),
115
+ })
116
+ .optional(),
117
+ /**
118
+ * Global thumbnail defaults (used when individual fields don't specify thumbnail)
107
119
  */
108
120
  thumbnail: z
109
121
  .object({
110
122
  width: z.number(),
111
123
  height: z.number(),
112
- crop: z.boolean(),
124
+ fit: z.enum(['cover', 'contain']),
113
125
  quality: z.number().optional(),
114
126
  })
115
127
  .optional(),
@@ -278,10 +290,17 @@ function mergeConfig(defaults, userConfig) {
278
290
  },
279
291
  images: {
280
292
  background: userConfig.media?.images?.background ?? defaults.media.images.background,
293
+ image: {
294
+ width: userConfig.media?.images?.image?.width ?? defaults.media.images.image.width,
295
+ height: userConfig.media?.images?.image?.height ?? defaults.media.images.image.height,
296
+ fit: userConfig.media?.images?.image?.fit ?? defaults.media.images.image.fit,
297
+ strict: userConfig.media?.images?.image?.strict ?? defaults.media.images.image.strict,
298
+ quality: userConfig.media?.images?.image?.quality ?? defaults.media.images.image.quality,
299
+ },
281
300
  thumbnail: {
282
301
  width: userConfig.media?.images?.thumbnail?.width ?? defaults.media.images.thumbnail.width,
283
302
  height: userConfig.media?.images?.thumbnail?.height ?? defaults.media.images.thumbnail.height,
284
- crop: userConfig.media?.images?.thumbnail?.crop ?? defaults.media.images.thumbnail.crop,
303
+ fit: userConfig.media?.images?.thumbnail?.fit ?? defaults.media.images.thumbnail.fit,
285
304
  quality: userConfig.media?.images?.thumbnail?.quality ?? defaults.media.images.thumbnail.quality,
286
305
  },
287
306
  watermark: userConfig.media?.images?.watermark ?? defaults.media.images.watermark,
@@ -330,10 +349,17 @@ const defaultConfig = {
330
349
  },
331
350
  images: {
332
351
  background: { r: 0, g: 0, b: 0, alpha: 0 },
352
+ image: {
353
+ width: 1200,
354
+ height: 630,
355
+ fit: 'cover',
356
+ strict: false,
357
+ quality: 80,
358
+ },
333
359
  thumbnail: {
334
360
  width: 200,
335
361
  height: 200,
336
- crop: false,
362
+ fit: 'contain',
337
363
  quality: 80,
338
364
  },
339
365
  watermark: false,
@@ -1 +1 @@
1
- {"version":3,"file":"loader-with-jiti.d.ts","sourceRoot":"","sources":["../../../src/core/config/loader-with-jiti.ts"],"names":[],"mappings":"AAkDA,eAAO,MAAM,sBAAsB,cAAmC,CAAA;AAiHtE;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,OAAO,CA4BxD,CAAA"}
1
+ {"version":3,"file":"loader-with-jiti.d.ts","sourceRoot":"","sources":["../../../src/core/config/loader-with-jiti.ts"],"names":[],"mappings":"AAiDA,eAAO,MAAM,sBAAsB,cAAmC,CAAA;AAsHtE;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,OAAO,CA4BxD,CAAA"}
@@ -8,7 +8,6 @@
8
8
  // - Keeps your Node CJS resolve patch for tests (js specifier -> ts fallback)
9
9
  import { existsSync } from 'fs';
10
10
  import { resolve, join } from 'path';
11
- import chokidar from 'chokidar';
12
11
  import chalk from 'chalk';
13
12
  import fs from 'fs';
14
13
  let jitiInstance = null;
@@ -103,20 +102,25 @@ const startConfigWatcher = () => {
103
102
  configWatcherState.version++;
104
103
  await bumpHotMarker();
105
104
  };
106
- configWatcherState.watcher = chokidar
107
- .watch('cms.config.*', {
108
- cwd,
109
- ignoreInitial: true,
110
- atomic: true,
111
- awaitWriteFinish: {
112
- stabilityThreshold: 100,
113
- pollInterval: 20,
114
- },
115
- })
116
- .setMaxListeners(3)
117
- .on('add', async (path) => await invalidateForRelPath(path))
118
- .on('change', async (path) => await invalidateForRelPath(path))
119
- .on('unlink', async (path) => await invalidateForRelPath(path));
105
+ // Dynamically import chokidar to avoid bundling fsevents (native module) in Turbopack
106
+ import('chokidar').then((chokidar) => {
107
+ if (configWatcherState.watcher)
108
+ return;
109
+ configWatcherState.watcher = chokidar.default
110
+ .watch('cms.config.*', {
111
+ cwd,
112
+ ignoreInitial: true,
113
+ atomic: true,
114
+ awaitWriteFinish: {
115
+ stabilityThreshold: 100,
116
+ pollInterval: 20,
117
+ },
118
+ })
119
+ .setMaxListeners(3)
120
+ .on('add', async (path) => await invalidateForRelPath(path))
121
+ .on('change', async (path) => await invalidateForRelPath(path))
122
+ .on('unlink', async (path) => await invalidateForRelPath(path));
123
+ });
120
124
  };
121
125
  const findConfigFile = () => {
122
126
  const cwd = process.cwd();
@@ -22,10 +22,10 @@ export class MysqlTableChecker extends DbTableChecker {
22
22
  return _tables;
23
23
  }
24
24
  static async getColumns(tableName) {
25
- const statement = sql `
26
- SELECT COLUMN_NAME
27
- FROM information_schema.COLUMNS c
28
- inner join information_schema.TABLES t ON t.TABLE_NAME = c.TABLE_NAME
25
+ const statement = sql `
26
+ SELECT COLUMN_NAME
27
+ FROM information_schema.COLUMNS c
28
+ inner join information_schema.TABLES t ON t.TABLE_NAME = c.TABLE_NAME
29
29
  WHERE t.TABLE_NAME = ${tableName}`;
30
30
  const _cols = [];
31
31
  const _res = await db.execute(statement);
@@ -82,10 +82,10 @@ export class MysqlTableChecker extends DbTableChecker {
82
82
  fullTextKeys.push({ name: key, columns: _fullTextKeyMap[key] });
83
83
  }
84
84
  // Foreign keys should be retrieved from information_schema
85
- const [foreignKeys] = await db.execute(sql `
86
- SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
87
- FROM information_schema.KEY_COLUMN_USAGE
88
- WHERE TABLE_NAME = ${table} AND CONSTRAINT_NAME != 'PRIMARY' AND REFERENCED_TABLE_NAME IS NOT NULL;
85
+ const [foreignKeys] = await db.execute(sql `
86
+ SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
87
+ FROM information_schema.KEY_COLUMN_USAGE
88
+ WHERE TABLE_NAME = ${table} AND CONSTRAINT_NAME != 'PRIMARY' AND REFERENCED_TABLE_NAME IS NOT NULL;
89
89
  `);
90
90
  return { primaryKeys, uniqueKeys, indexKeys, foreignKeys, fullTextKeys };
91
91
  }
@@ -1 +1 @@
1
- {"version":3,"file":"section-factory-with-esbuild.d.ts","sourceRoot":"","sources":["../../../src/core/factories/section-factory-with-esbuild.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"section-factory-with-esbuild.d.ts","sourceRoot":"","sources":["../../../src/core/factories/section-factory-with-esbuild.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtF,OAAO,KAAK,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC7G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AA4CrD,eAAO,MAAM,uBAAuB,cAAoC,CAAA;AAsHxE,KAAK,gBAAgB,GAAG,qBAAqB,GAAG,mBAAmB,GAAG,qBAAqB,CAAA;AAE3F,qBAAa,cAAc;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAwC;IACrE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAc;IAE5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAQ;IAC5B,OAAO,CAAC,MAAM,CAAC,QAAQ;IAIvB,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAA+B;IACrE,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAA+B;IACnE,OAAO,CAAC,MAAM,CAAC,UAAU,CAAI;IAE7B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAA2C;IAC5E,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAQ;IACxC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAK;mBAEZ,iBAAiB;IAsBtC;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAKhC,MAAM,CAAC,aAAa,IAAI,IAAI;IAwB5B;;;;OAIG;WACU,mBAAmB,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAKnG;;;;OAIG;WACU,WAAW,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAI3F;;;;;OAKG;WACU,mBAAmB,CAAC,EAC7B,IAAI,EACJ,KAAK,GACR,EAAE;QACC,IAAI,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAA;QACpC,KAAK,EAAE;YACH,EAAE,EAAE,MAAM,CAAA;YACV,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;SACjC,CAAA;KACJ,GAAG,OAAO,CAAC;QACR,MAAM,EAAE,mBAAmB,EAAE,CAAA;QAC7B,SAAS,EAAE,qBAAqB,EAAE,CAAA;QAClC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;QACjC,KAAK,EAAE,MAAM,EAAE,CAAA;KAClB,CAAC;IA+BF;;;;;OAKG;WACU,UAAU,CAAC,EACpB,IAAI,EACJ,IAAI,GACP,EAAE;QACC,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAA;KACvC,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAKpC;;;;;;OAMG;WACU,kBAAkB,CAAC,EAC5B,IAAI,EACJ,IAAI,EACJ,KAAK,GACR,EAAE;QACC,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAA;QACpC,KAAK,EAAE;YACH,EAAE,EAAE,MAAM,CAAA;YACV,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;SACjC,CAAA;KACJ,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAKpC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;QAClB,KAAK,EAAE,MAAM,eAAe,GAAG,aAAa,GAAG,eAAe,CAAA;QAC9D,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KACrB,GAAG,eAAe,GAAG,aAAa,GAAG,eAAe;IAYrD;;;;;;;;;OASG;mBACkB,GAAG;IA6FxB;;;;OAIG;mBACkB,eAAe;IAsLpC,OAAO,CAAC,MAAM,CAAC,YAAY;IAqD3B,OAAO,CAAC,MAAM,CAAC,IAAI;CAatB"}
@@ -11,6 +11,7 @@ import { db } from '../../db/client.js';
11
11
  import { AdminPrivilegesTable } from '../../db/schema.js';
12
12
  import { getCMSConfig, getConfigImportVersion } from '../config/index.js';
13
13
  import crypto from 'crypto';
14
+ import { collectDuplicateSectionNameReport } from './section-name-validation.js';
14
15
  let cachedCmsConfig = null;
15
16
  let cachedCmsConfigVersion = -1;
16
17
  const getCmsConfigCached = async (currentVersion = getConfigImportVersion()) => {
@@ -387,6 +388,7 @@ export class SectionFactory {
387
388
  this.log('Loading all sections from disk...', this.isDev ? '(dev mode)' : '(production mode)');
388
389
  }
389
390
  const sections = [];
391
+ const sectionNameSources = [];
390
392
  try {
391
393
  const sectionFiles = await glob('**/*.section.ts', {
392
394
  cwd: cmsConfig.sections.path,
@@ -420,6 +422,7 @@ export class SectionFactory {
420
422
  continue;
421
423
  }
422
424
  sections.push(defaultExport);
425
+ sectionNameSources.push({ file, name: String(defaultExport.name).trim() });
423
426
  }
424
427
  catch (importErr) {
425
428
  /**
@@ -433,15 +436,15 @@ export class SectionFactory {
433
436
  importErr.message.includes('Cannot find module') &&
434
437
  importErr.message.includes('.section')) {
435
438
  this.sectionProcessingErrors[file] ??= [];
436
- this.sectionProcessingErrors[file].push(`❌ Invalid section import detected.
437
-
438
- Sections MUST use extensionless relative imports:
439
-
440
- ✅ import exampleSection from './example.section'
441
- ❌ import exampleSection from './example.section.ts'
442
- ❌ import exampleSection from './example.section.js'
443
-
444
- This file is bundled with esbuild, so Node never resolves the import directly.
439
+ this.sectionProcessingErrors[file].push(`❌ Invalid section import detected.
440
+
441
+ Sections MUST use extensionless relative imports:
442
+
443
+ ✅ import exampleSection from './example.section'
444
+ ❌ import exampleSection from './example.section.ts'
445
+ ❌ import exampleSection from './example.section.js'
446
+
447
+ This file is bundled with esbuild, so Node never resolves the import directly.
445
448
  If you added an extension manually, remove it.`);
446
449
  this.errorCount++;
447
450
  continue;
@@ -487,6 +490,17 @@ If you added an extension manually, remove it.`);
487
490
  catch (err) {
488
491
  console.error('Error finding section files:', err);
489
492
  }
493
+ const duplicateReport = collectDuplicateSectionNameReport(sectionNameSources);
494
+ const duplicateSectionNames = [...duplicateReport.duplicateDisplayNames].sort((a, b) => a.localeCompare(b));
495
+ if (duplicateSectionNames.length > 0) {
496
+ for (const [file, errors] of Object.entries(duplicateReport.errorsByFile)) {
497
+ this.sectionProcessingErrors[file] ??= [];
498
+ this.sectionProcessingErrors[file].push(...errors);
499
+ this.errorCount += errors.length;
500
+ }
501
+ console.error(chalk.red.bold(`[Sections Validation]: Duplicate section names detected: ${duplicateSectionNames.join(', ')}.`));
502
+ return [];
503
+ }
490
504
  this.allSectionsLoaded = true;
491
505
  /**
492
506
  * If `strict` mode is enabled,
@@ -1 +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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"}
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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"}
@@ -3,12 +3,12 @@ import { cloneDeep } from 'lodash-es';
3
3
  import { resolve } from 'path';
4
4
  import chalk from 'chalk';
5
5
  import { eq } from 'drizzle-orm';
6
- import chokidar from 'chokidar';
7
6
  import fs from 'fs';
8
7
  import { createRequire } from 'module';
9
8
  import { db } from '../../db/client.js';
10
9
  import { AdminPrivilegesTable } from '../../db/schema.js';
11
10
  import { getCMSConfig, getConfigImportVersion } from '../config/index.js';
11
+ import { collectDuplicateSectionNameReport } from './section-name-validation.js';
12
12
  const hotMarkerFile = resolve(process.cwd(), 'components/form/helpers/_section-hot-reload.js');
13
13
  const sectionWatcherState = (() => {
14
14
  const key = Symbol.for('nextjs-cms.sectionWatcher');
@@ -338,6 +338,7 @@ export class SectionFactory {
338
338
  this.log('Loading all sections from disk...', this.isDev ? '(dev mode)' : '(production mode)');
339
339
  }
340
340
  const sections = [];
341
+ const sectionNameSources = [];
341
342
  try {
342
343
  const sectionFiles = await glob('**/*.section.ts', {
343
344
  cwd: cmsConfig.sections.path,
@@ -371,6 +372,7 @@ export class SectionFactory {
371
372
  continue;
372
373
  }
373
374
  sections.push(defaultExport);
375
+ sectionNameSources.push({ file, name: String(defaultExport.name).trim() });
374
376
  }
375
377
  catch (importErr) {
376
378
  /**
@@ -384,15 +386,15 @@ export class SectionFactory {
384
386
  importErr.message.includes('Cannot find module') &&
385
387
  importErr.message.includes('.section')) {
386
388
  this.sectionProcessingErrors[file] ??= [];
387
- this.sectionProcessingErrors[file].push(`❌ Invalid section import detected.
388
-
389
- Sections MUST use extensionless relative imports:
390
-
391
- ✅ import exampleSection from './example.section'
392
- ❌ import exampleSection from './example.section.ts'
393
- ❌ import exampleSection from './example.section.js'
394
-
395
- This file is bundled with jiti, so Node never resolves the import directly.
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 jiti, so Node never resolves the import directly.
396
398
  If you added an extension manually, remove it.`);
397
399
  this.errorCount++;
398
400
  continue;
@@ -438,6 +440,17 @@ If you added an extension manually, remove it.`);
438
440
  catch (err) {
439
441
  console.error('Error finding section files:', err);
440
442
  }
443
+ const duplicateReport = collectDuplicateSectionNameReport(sectionNameSources);
444
+ const duplicateSectionNames = [...duplicateReport.duplicateDisplayNames].sort((a, b) => a.localeCompare(b));
445
+ if (duplicateSectionNames.length > 0) {
446
+ for (const [file, errors] of Object.entries(duplicateReport.errorsByFile)) {
447
+ this.sectionProcessingErrors[file] ??= [];
448
+ this.sectionProcessingErrors[file].push(...errors);
449
+ this.errorCount += errors.length;
450
+ }
451
+ console.error(chalk.red.bold(`[Sections Validation]: Duplicate section names detected: ${duplicateSectionNames.join(', ')}.`));
452
+ return [];
453
+ }
441
454
  this.allSectionsLoaded = true;
442
455
  /**
443
456
  * If `strict` mode is enabled,
@@ -490,30 +503,35 @@ If you added an extension manually, remove it.`);
490
503
  // Bump the hot marker to trigger Next.js Fast Refresh
491
504
  this.bumpHotMarker();
492
505
  };
493
- sectionWatcherState.watcher = chokidar
494
- .watch('**/*.section.ts', {
495
- cwd: cmsConfig.sections.path,
496
- ignoreInitial: true,
497
- atomic: true,
498
- awaitWriteFinish: {
499
- stabilityThreshold: 100,
500
- pollInterval: 20,
501
- },
502
- })
503
- .setMaxListeners(3)
504
- .on('add', (path) => {
505
- this.log('Section file added:', path);
506
- invalidateForRelPath(path);
507
- })
508
- .on('change', (path) => {
509
- this.log('Section file changed:', path);
510
- invalidateForRelPath(path);
511
- })
512
- .on('unlink', (path) => {
513
- this.log('Section file removed:', path);
514
- invalidateForRelPath(path);
506
+ // Dynamically import chokidar to avoid bundling fsevents (native module) in Turbopack
507
+ import('chokidar').then((chokidar) => {
508
+ if (sectionWatcherState.watcher)
509
+ return;
510
+ sectionWatcherState.watcher = chokidar.default
511
+ .watch('**/*.section.ts', {
512
+ cwd: cmsConfig.sections.path,
513
+ ignoreInitial: true,
514
+ atomic: true,
515
+ awaitWriteFinish: {
516
+ stabilityThreshold: 100,
517
+ pollInterval: 20,
518
+ },
519
+ })
520
+ .setMaxListeners(3)
521
+ .on('add', (path) => {
522
+ this.log('Section file added:', path);
523
+ invalidateForRelPath(path);
524
+ })
525
+ .on('change', (path) => {
526
+ this.log('Section file changed:', path);
527
+ invalidateForRelPath(path);
528
+ })
529
+ .on('unlink', (path) => {
530
+ this.log('Section file removed:', path);
531
+ invalidateForRelPath(path);
532
+ });
533
+ this.log('Starting section watcher in dev mode...');
515
534
  });
516
- this.log('Starting section watcher in dev mode...');
517
535
  }
518
536
  /**
519
537
  * Clear jiti's cache for a specific file path.
@@ -0,0 +1,10 @@
1
+ export type SectionNameSource = {
2
+ file: string;
3
+ name: string;
4
+ };
5
+ export type DuplicateSectionNameReport = {
6
+ duplicateDisplayNames: string[];
7
+ errorsByFile: Record<string, string[]>;
8
+ };
9
+ export declare function collectDuplicateSectionNameReport(sources: SectionNameSource[]): DuplicateSectionNameReport;
10
+ //# sourceMappingURL=section-name-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-name-validation.d.ts","sourceRoot":"","sources":["../../../src/core/factories/section-name-validation.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACrC,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;CACzC,CAAA;AAID,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,0BAA0B,CAqC1G"}
@@ -0,0 +1,31 @@
1
+ const normalizeSectionName = (name) => name.trim().toLowerCase();
2
+ export function collectDuplicateSectionNameReport(sources) {
3
+ const byNormalizedName = new Map();
4
+ for (const source of sources) {
5
+ const normalizedName = normalizeSectionName(source.name);
6
+ if (!byNormalizedName.has(normalizedName)) {
7
+ byNormalizedName.set(normalizedName, []);
8
+ }
9
+ byNormalizedName.get(normalizedName)?.push(source);
10
+ }
11
+ const duplicateDisplayNameSet = new Set();
12
+ const errorsByFile = {};
13
+ for (const group of byNormalizedName.values()) {
14
+ if (group.length < 2)
15
+ continue;
16
+ for (const entry of group) {
17
+ duplicateDisplayNameSet.add(entry.name);
18
+ const conflicts = group
19
+ .filter((other) => other.file !== entry.file)
20
+ .map((other) => `"${other.name}" in "${other.file}"`)
21
+ .join(', ');
22
+ const errors = errorsByFile[entry.file] ?? [];
23
+ errors.push(`Duplicate section name "${entry.name}" detected. Conflicts with: ${conflicts}. Section names must be globally unique (case-insensitive).`);
24
+ errorsByFile[entry.file] = errors;
25
+ }
26
+ }
27
+ return {
28
+ duplicateDisplayNames: [...duplicateDisplayNameSet],
29
+ errorsByFile,
30
+ };
31
+ }