nextjs-cms 0.9.31 → 0.9.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/actions/pages.d.ts +3 -3
- package/dist/api/trpc/root.d.ts +3 -3
- package/dist/api/trpc/routers/navigation.d.ts +3 -3
- package/dist/api/trpc/server.d.ts +9 -9
- package/dist/cli/lib/update-sections.d.ts.map +1 -1
- package/dist/cli/lib/update-sections.js +217 -59
- package/dist/cli/utils/schema-generator.d.ts.map +1 -1
- package/dist/cli/utils/schema-generator.js +4 -1
- package/dist/core/config/config-loader.d.ts +2 -2
- package/dist/core/fields/date-range.d.ts +4 -4
- package/dist/core/fields/document.d.ts.map +1 -1
- package/dist/core/fields/document.js +13 -1
- package/dist/core/fields/photo.d.ts +6 -6
- package/dist/core/fields/photo.d.ts.map +1 -1
- package/dist/core/fields/photo.js +30 -9
- package/dist/core/fields/richText.d.ts +8 -8
- package/dist/core/fields/select.d.ts +13 -0
- package/dist/core/fields/select.d.ts.map +1 -1
- package/dist/core/fields/select.js +1 -0
- package/dist/core/fields/selectMultiple.d.ts +9 -0
- package/dist/core/fields/selectMultiple.d.ts.map +1 -1
- package/dist/core/fields/selectMultiple.js +1 -0
- package/dist/core/fields/video.d.ts +1 -0
- package/dist/core/fields/video.d.ts.map +1 -1
- package/dist/core/fields/video.js +18 -2
- package/dist/core/sections/category.d.ts +6 -6
- package/dist/core/sections/hasItems.d.ts +12 -12
- package/dist/core/sections/section.d.ts +4 -4
- package/dist/core/sections/simple.d.ts +6 -6
- package/dist/db/schema.d.ts +17 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +1 -0
- package/dist/translations/base/en.d.ts +6 -0
- package/dist/translations/base/en.d.ts.map +1 -1
- package/dist/translations/base/en.js +6 -0
- package/dist/translations/client.d.ts +76 -4
- package/dist/translations/client.d.ts.map +1 -1
- package/dist/translations/server.d.ts +76 -4
- package/dist/translations/server.d.ts.map +1 -1
- package/dist/validators/document.d.ts.map +1 -1
- package/dist/validators/document.js +2 -1
- package/dist/validators/file-extension.d.ts +3 -0
- package/dist/validators/file-extension.d.ts.map +1 -0
- package/dist/validators/file-extension.js +9 -0
- package/dist/validators/photo.d.ts.map +1 -1
- package/dist/validators/photo.js +2 -1
- package/dist/validators/video.d.ts.map +1 -1
- package/dist/validators/video.js +2 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-sections.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/update-sections.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"update-sections.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/update-sections.ts"],"names":[],"mappings":"AAklFA,wBAAsB,cAAc,CAAC,SAAS,UAAQ,iBAoBrD"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { intro, log, select, spinner } from '@clack/prompts';
|
|
3
|
+
import { intro, log, note, select, spinner } from '@clack/prompts';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { eq, sql } from 'drizzle-orm';
|
|
6
6
|
import { getCMSConfig } from '../../core/config/index.js';
|
|
@@ -180,7 +180,7 @@ function generateFieldSQL(input) {
|
|
|
180
180
|
fieldSQL += 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci';
|
|
181
181
|
break;
|
|
182
182
|
case is(input, TagsField):
|
|
183
|
-
fieldSQL += '
|
|
183
|
+
fieldSQL += 'LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci';
|
|
184
184
|
break;
|
|
185
185
|
default:
|
|
186
186
|
fieldSQL += 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci';
|
|
@@ -192,8 +192,8 @@ function generateFieldSQL(input) {
|
|
|
192
192
|
else if (!input.defaultValue) {
|
|
193
193
|
fieldSQL += ' DEFAULT NULL';
|
|
194
194
|
}
|
|
195
|
-
if (input.defaultValue !== null && input.defaultValue !== undefined
|
|
196
|
-
//
|
|
195
|
+
if (input.defaultValue !== null && input.defaultValue !== undefined) {
|
|
196
|
+
// TODO: Should we only add sql default when the field is required?
|
|
197
197
|
// to allow null values in non-required fields
|
|
198
198
|
// with defaultValue prop set.
|
|
199
199
|
// Example: Admin cleared the defaultValue of
|
|
@@ -379,6 +379,125 @@ function buildSelectDbFieldConfig(name, label, type = 'text', required = true) {
|
|
|
379
379
|
order: 0,
|
|
380
380
|
});
|
|
381
381
|
}
|
|
382
|
+
function emptyTableRegistryConfig() {
|
|
383
|
+
return { unmanagedColumns: [] };
|
|
384
|
+
}
|
|
385
|
+
function parseTableRegistryConfig(value, tableName) {
|
|
386
|
+
if (!value)
|
|
387
|
+
return emptyTableRegistryConfig();
|
|
388
|
+
try {
|
|
389
|
+
const parsed = JSON.parse(value);
|
|
390
|
+
const unmanagedColumns = Array.isArray(parsed.unmanagedColumns)
|
|
391
|
+
? parsed.unmanagedColumns.filter((column) => typeof column === 'string' && column.trim().length > 0)
|
|
392
|
+
: [];
|
|
393
|
+
return { unmanagedColumns: [...new Set(unmanagedColumns)] };
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
console.warn(chalk.yellow(` - Warning: Invalid config for table '${tableName}'. Ignoring table config.`));
|
|
397
|
+
return emptyTableRegistryConfig();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function serializeTableRegistryConfig(config) {
|
|
401
|
+
return JSON.stringify({
|
|
402
|
+
unmanagedColumns: [...new Set(config.unmanagedColumns)].sort(),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
async function updateTableRegistryConfig(tableName, config) {
|
|
406
|
+
await db
|
|
407
|
+
.update(NextJsCmsTablesTable)
|
|
408
|
+
.set({ config: serializeTableRegistryConfig(config) })
|
|
409
|
+
.where(eq(NextJsCmsTablesTable.tableName, tableName));
|
|
410
|
+
}
|
|
411
|
+
function columnTypeForSelectDbSummary(type) {
|
|
412
|
+
return type === 'number' ? 'INT' : 'VARCHAR(255)';
|
|
413
|
+
}
|
|
414
|
+
function getFieldSummaryType(field) {
|
|
415
|
+
switch (true) {
|
|
416
|
+
case is(field, NumberField):
|
|
417
|
+
return 'INT';
|
|
418
|
+
case is(field, TextField):
|
|
419
|
+
return `VARCHAR(${field.maxLength ?? 255})`;
|
|
420
|
+
case is(field, TextAreaField) || is(field, RichTextField):
|
|
421
|
+
return 'LONGTEXT';
|
|
422
|
+
case is(field, DateField):
|
|
423
|
+
return field.format === 'datetime' ? 'DATETIME' : 'DATE';
|
|
424
|
+
case is(field, CheckboxField):
|
|
425
|
+
return 'BOOLEAN';
|
|
426
|
+
default:
|
|
427
|
+
return 'configured';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function normalizeColumnTypeForSummary(type) {
|
|
431
|
+
const normalized = type.toUpperCase();
|
|
432
|
+
if (normalized === 'INT(11)')
|
|
433
|
+
return 'INT';
|
|
434
|
+
return normalized;
|
|
435
|
+
}
|
|
436
|
+
function formatExistingTableColumns(existingStructure) {
|
|
437
|
+
const columns = Object.values(existingStructure).map((column) => {
|
|
438
|
+
return ` - ${column.Field}: ${normalizeColumnTypeForSummary(column.Type)}`;
|
|
439
|
+
});
|
|
440
|
+
return columns.length > 0 ? columns : [' - none'];
|
|
441
|
+
}
|
|
442
|
+
function buildDesiredTableSummary(table) {
|
|
443
|
+
if (table.sectionType === 'selectDb' && table.selectDb) {
|
|
444
|
+
const identifierSummary = table.selectDb.identifierTypeExplicit
|
|
445
|
+
? ` - Identifier: ${table.selectDb.identifier}, identifierType: ${table.selectDb.identifierType} -> ${columnTypeForSelectDbSummary(table.selectDb.identifierType)}`
|
|
446
|
+
: ` - Identifier: ${table.selectDb.identifier}`;
|
|
447
|
+
const labelSummary = table.selectDb.labelTypeExplicit
|
|
448
|
+
? ` - Label: ${table.selectDb.label}, labelType: ${table.selectDb.labelType} -> ${columnTypeForSelectDbSummary(table.selectDb.labelType)}`
|
|
449
|
+
: ` - Label: ${table.selectDb.label}`;
|
|
450
|
+
return [identifierSummary, labelSummary];
|
|
451
|
+
}
|
|
452
|
+
return table.fields.flatMap((field) => {
|
|
453
|
+
return getFieldColumnName(field).map((column) => ` - ${column}: ${getFieldSummaryType(field)}`);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
function buildExistingTableDifferences(table, existingStructure) {
|
|
457
|
+
const configuredColumns = table.fields.flatMap(getFieldColumnName);
|
|
458
|
+
const existingColumns = Object.keys(existingStructure);
|
|
459
|
+
const missingColumns = configuredColumns.filter((column) => !existingColumns.includes(column));
|
|
460
|
+
const extraColumns = existingColumns.filter((column) => !configuredColumns.includes(column));
|
|
461
|
+
const typeMismatches = table.fields.flatMap((field) => {
|
|
462
|
+
return getFieldColumnName(field).flatMap((column) => {
|
|
463
|
+
const existingColumn = existingStructure[column];
|
|
464
|
+
if (!existingColumn)
|
|
465
|
+
return [];
|
|
466
|
+
const expectedType = normalizeColumnTypeForSummary(getFieldSummaryType(field));
|
|
467
|
+
const actualType = normalizeColumnTypeForSummary(existingColumn.Type);
|
|
468
|
+
if (expectedType === 'CONFIGURED' || expectedType === actualType)
|
|
469
|
+
return [];
|
|
470
|
+
return ` - ${column}: config ${expectedType}, table ${actualType}`;
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
const differences = [];
|
|
474
|
+
differences.push(` - Missing configured columns: ${missingColumns.length > 0 ? missingColumns.join(', ') : 'none'}`);
|
|
475
|
+
differences.push(` - Extra existing columns: ${extraColumns.length > 0 ? extraColumns.join(', ') : 'none'}`);
|
|
476
|
+
if (typeMismatches.length > 0) {
|
|
477
|
+
differences.push(' - Type mismatches:');
|
|
478
|
+
differences.push(...typeMismatches);
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
differences.push(' - Type mismatches: none');
|
|
482
|
+
}
|
|
483
|
+
return differences;
|
|
484
|
+
}
|
|
485
|
+
function buildExistingTablePromptMessage(table, existingStructure) {
|
|
486
|
+
return [
|
|
487
|
+
`Table '${table.name}' already exists in your database, here's the table and config summary:`,
|
|
488
|
+
'',
|
|
489
|
+
'Your config:',
|
|
490
|
+
...buildDesiredTableSummary(table),
|
|
491
|
+
'',
|
|
492
|
+
'Existing table:',
|
|
493
|
+
...formatExistingTableColumns(existingStructure),
|
|
494
|
+
'',
|
|
495
|
+
'Differences:',
|
|
496
|
+
...buildExistingTableDifferences(table, existingStructure),
|
|
497
|
+
'',
|
|
498
|
+
'How do you want to handle this table?',
|
|
499
|
+
].join('\n');
|
|
500
|
+
}
|
|
382
501
|
function buildLocaleFieldConfig() {
|
|
383
502
|
return textField({
|
|
384
503
|
name: 'locale',
|
|
@@ -440,6 +559,12 @@ async function ensureTableRegistryEntry(tableName, sectionName) {
|
|
|
440
559
|
console.error('Error inserting into __nextjs_cms_tables table:', error);
|
|
441
560
|
});
|
|
442
561
|
}
|
|
562
|
+
async function ensureTableRegistryConfigColumn() {
|
|
563
|
+
const columns = await MysqlTableChecker.getColumns('__nextjs_cms_tables');
|
|
564
|
+
if (columns.includes('config'))
|
|
565
|
+
return;
|
|
566
|
+
await db.execute(sql `ALTER TABLE __nextjs_cms_tables ADD COLUMN config LONGTEXT NULL`);
|
|
567
|
+
}
|
|
443
568
|
async function createTable(table, options) {
|
|
444
569
|
/**
|
|
445
570
|
* Generate the CREATE TABLE SQL
|
|
@@ -669,6 +794,14 @@ async function updateTable(table, s, ctx) {
|
|
|
669
794
|
* Filter out the fields that already exist in the table
|
|
670
795
|
*/
|
|
671
796
|
const existingFields = existingFieldsData ? Object.keys(existingFieldsData) : [];
|
|
797
|
+
const configuredFields = table.fields.flatMap(getFieldColumnName);
|
|
798
|
+
const tableConfig = parseTableRegistryConfig(table.config, table.name);
|
|
799
|
+
const prunedUnmanagedColumns = tableConfig.unmanagedColumns.filter((column) => existingFields.includes(column) && !configuredFields.includes(column));
|
|
800
|
+
if (prunedUnmanagedColumns.length !== tableConfig.unmanagedColumns.length) {
|
|
801
|
+
tableConfig.unmanagedColumns = prunedUnmanagedColumns;
|
|
802
|
+
await updateTableRegistryConfig(table.name, tableConfig);
|
|
803
|
+
}
|
|
804
|
+
const unmanagedColumns = new Set(tableConfig.unmanagedColumns);
|
|
672
805
|
const fieldsToAdd = table.fields.filter((field) => !getFieldColumnName(field).some((col) => existingFields.includes(col)));
|
|
673
806
|
/**
|
|
674
807
|
* Let's find out fields that need to be updated.
|
|
@@ -682,8 +815,9 @@ async function updateTable(table, s, ctx) {
|
|
|
682
815
|
* or if it has a destinationDb, TODO: Add the values to the destination table? (CRITICAL! Loss of data if not done)
|
|
683
816
|
* we should mark it for removal.
|
|
684
817
|
*/
|
|
685
|
-
let fieldsToRemove = existingFields.filter((existingField) => !
|
|
686
|
-
table.fields.some((field) =>
|
|
818
|
+
let fieldsToRemove = existingFields.filter((existingField) => !unmanagedColumns.has(existingField) &&
|
|
819
|
+
(!table.fields.some((field) => getFieldColumnName(field).includes(existingField)) ||
|
|
820
|
+
table.fields.some((field) => field.destinationDb && getFieldColumnName(field).includes(existingField))));
|
|
687
821
|
/**
|
|
688
822
|
* Check if there are fields to update
|
|
689
823
|
*/
|
|
@@ -907,15 +1041,18 @@ async function updateTable(table, s, ctx) {
|
|
|
907
1041
|
s.stop('Found ' + fieldsToRemove.length + ' field(s) to remove:');
|
|
908
1042
|
for (const field of fieldsToRemove) {
|
|
909
1043
|
const shouldRemove = await select({
|
|
910
|
-
message: `
|
|
1044
|
+
message: `Field '${field}' exists in table '${table.name}' but is not in your config.\n\nWhat do you want to do?`,
|
|
911
1045
|
options: [
|
|
912
|
-
{ value: '
|
|
913
|
-
{ value: '
|
|
1046
|
+
{ value: 'yes', label: 'Drop field' },
|
|
1047
|
+
{ value: 'no', label: 'Keep field' },
|
|
1048
|
+
{ value: 'ask_later', label: 'Ask me later' },
|
|
914
1049
|
],
|
|
915
1050
|
initialValue: 'no',
|
|
916
1051
|
});
|
|
917
1052
|
if (shouldRemove === 'yes') {
|
|
918
1053
|
log.info(chalk.red(`Removing field '${field}'`));
|
|
1054
|
+
tableConfig.unmanagedColumns = tableConfig.unmanagedColumns.filter((column) => column !== field);
|
|
1055
|
+
await updateTableRegistryConfig(table.name, tableConfig);
|
|
919
1056
|
alterTableSQLs.push({
|
|
920
1057
|
field: field,
|
|
921
1058
|
table: table.name,
|
|
@@ -923,8 +1060,13 @@ async function updateTable(table, s, ctx) {
|
|
|
923
1060
|
sql: `DROP COLUMN \`${field}\``,
|
|
924
1061
|
});
|
|
925
1062
|
}
|
|
1063
|
+
else if (shouldRemove === 'no') {
|
|
1064
|
+
tableConfig.unmanagedColumns = [...new Set([...tableConfig.unmanagedColumns, field])];
|
|
1065
|
+
await updateTableRegistryConfig(table.name, tableConfig);
|
|
1066
|
+
log.info(chalk.yellow(`- Field ${chalk.underline.italic(field)} kept as an unmanaged column.`));
|
|
1067
|
+
}
|
|
926
1068
|
else {
|
|
927
|
-
log.info(chalk.yellow(`- Field ${chalk.underline.italic(field)} not removed.`));
|
|
1069
|
+
log.info(chalk.yellow(`- Field ${chalk.underline.italic(field)} not removed. You will be asked again later.`));
|
|
928
1070
|
}
|
|
929
1071
|
}
|
|
930
1072
|
s.start();
|
|
@@ -961,7 +1103,7 @@ async function updateTable(table, s, ctx) {
|
|
|
961
1103
|
sqlErrors += keyErrors;
|
|
962
1104
|
s.stop(chalk.italic.hex(`${sqlErrors > 0 ? `#FFA500` : `#fafafa`}`)(`- Table \`${table.name}\` modified successfully ${sqlErrors > 0 ? `with ${sqlErrors} error(s).` : ''}`));
|
|
963
1105
|
}
|
|
964
|
-
const main = async (
|
|
1106
|
+
const main = async (spinnerObject) => {
|
|
965
1107
|
const cmsConfig = await getCMSConfig();
|
|
966
1108
|
const schemaGenerationEnabled = cmsConfig.schemaGeneration.drizzle.enabled;
|
|
967
1109
|
const schemaOutDir = cmsConfig.schemaGeneration.drizzle.outDir;
|
|
@@ -973,7 +1115,7 @@ const main = async (s) => {
|
|
|
973
1115
|
*/
|
|
974
1116
|
if (schemaGenerationEnabled) {
|
|
975
1117
|
console.log(chalk.white(`Generating Drizzle schema...`));
|
|
976
|
-
|
|
1118
|
+
spinnerObject.start();
|
|
977
1119
|
}
|
|
978
1120
|
else {
|
|
979
1121
|
console.log(chalk.yellow(`Schema generation is disabled. Skipping Drizzle schema generation.`));
|
|
@@ -987,8 +1129,7 @@ const main = async (s) => {
|
|
|
987
1129
|
const externalSelectDbTables = new Set();
|
|
988
1130
|
const localesTableAssetMetadata = new Map();
|
|
989
1131
|
sections = await SectionFactory.getSectionsSilently();
|
|
990
|
-
|
|
991
|
-
console.log(chalk.gray(sections.map((s) => s.name).join(', ')));
|
|
1132
|
+
note(sections.length === 0 ? chalk.dim('(none)') : sections.map((s) => chalk.gray(`• ${s.name}`)).join('\n'), `Sections to insert (${sections.length})`);
|
|
992
1133
|
/**
|
|
993
1134
|
* Let's see if the table `__nextjs_cms_tables` exists in the database.
|
|
994
1135
|
* If it doesn't, we'll create it using the same schema as `NextJsCmsTablesTable`.
|
|
@@ -997,9 +1138,11 @@ const main = async (s) => {
|
|
|
997
1138
|
CREATE TABLE IF NOT EXISTS __nextjs_cms_tables (
|
|
998
1139
|
name VARCHAR(100) NOT NULL PRIMARY KEY,
|
|
999
1140
|
section VARCHAR(200),
|
|
1141
|
+
config LONGTEXT,
|
|
1000
1142
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
1001
1143
|
)
|
|
1002
1144
|
`);
|
|
1145
|
+
await ensureTableRegistryConfigColumn();
|
|
1003
1146
|
/**
|
|
1004
1147
|
* Persistent key/value store for CMS-level state that must survive config removal.
|
|
1005
1148
|
*/
|
|
@@ -1017,6 +1160,7 @@ const main = async (s) => {
|
|
|
1017
1160
|
existingTables = await db
|
|
1018
1161
|
.select({
|
|
1019
1162
|
name: NextJsCmsTablesTable.tableName,
|
|
1163
|
+
config: NextJsCmsTablesTable.config,
|
|
1020
1164
|
})
|
|
1021
1165
|
.from(NextJsCmsTablesTable);
|
|
1022
1166
|
/**
|
|
@@ -1113,9 +1257,11 @@ const main = async (s) => {
|
|
|
1113
1257
|
for (const field of s.fields) {
|
|
1114
1258
|
if (isExternalSelectDbField(field) && !externalSelectDbTables.has(field.db.table)) {
|
|
1115
1259
|
externalSelectDbTables.add(field.db.table);
|
|
1116
|
-
const
|
|
1117
|
-
const
|
|
1118
|
-
const
|
|
1260
|
+
const identifierType = field.db.identifierType ?? 'text';
|
|
1261
|
+
const labelType = field.db.labelType ?? 'text';
|
|
1262
|
+
const identifierFieldConfig = buildSelectDbFieldConfig(field.db.identifier, 'Option Id', identifierType);
|
|
1263
|
+
const typedLabelFieldConfig = buildSelectDbFieldConfig(field.db.label, 'Option Label', labelType);
|
|
1264
|
+
const selectDbFieldConfigs = [identifierFieldConfig, typedLabelFieldConfig];
|
|
1119
1265
|
if (field.db.orderBy &&
|
|
1120
1266
|
field.db.orderBy !== field.db.identifier &&
|
|
1121
1267
|
field.db.orderBy !== field.db.label) {
|
|
@@ -1128,6 +1274,14 @@ const main = async (s) => {
|
|
|
1128
1274
|
sectionType: 'selectDb',
|
|
1129
1275
|
identifier: identifierFieldConfig,
|
|
1130
1276
|
primaryKey: [identifierFieldConfig],
|
|
1277
|
+
selectDb: {
|
|
1278
|
+
identifier: field.db.identifier,
|
|
1279
|
+
identifierType,
|
|
1280
|
+
identifierTypeExplicit: field.db.identifierType !== undefined,
|
|
1281
|
+
label: field.db.label,
|
|
1282
|
+
labelType,
|
|
1283
|
+
labelTypeExplicit: field.db.labelType !== undefined,
|
|
1284
|
+
},
|
|
1131
1285
|
});
|
|
1132
1286
|
const selectDbSchema = generateDrizzleSchema({
|
|
1133
1287
|
name: field.db.table,
|
|
@@ -1138,7 +1292,7 @@ const main = async (s) => {
|
|
|
1138
1292
|
selectDbSchema.drizzleImports.forEach((type) => drizzleImports.add(type));
|
|
1139
1293
|
}
|
|
1140
1294
|
if (field.destinationDb) {
|
|
1141
|
-
|
|
1295
|
+
log.info(`Destination DB found for input: ${chalk.cyan(field.name)} with table: ${chalk.cyan(field.destinationDb.table)}`);
|
|
1142
1296
|
const parentIdentifierType = s.db.identifier.type;
|
|
1143
1297
|
const referenceIdFieldConfig = parentIdentifierType === 'number'
|
|
1144
1298
|
? numberField({
|
|
@@ -1385,7 +1539,7 @@ const main = async (s) => {
|
|
|
1385
1539
|
* Reject the run before schema writes if any section configuration is unsafe.
|
|
1386
1540
|
*/
|
|
1387
1541
|
if (configurationErrors.length > 0) {
|
|
1388
|
-
|
|
1542
|
+
spinnerObject.stop(`update-sections aborted: ${configurationErrors.length} configuration conflict(s) detected.\n` +
|
|
1389
1543
|
configurationErrors.map((e) => ` - ${e}`).join('\n') +
|
|
1390
1544
|
`\n\nThe suffix '${RESERVED_TABLE_SUFFIX}' and the column name '${RESERVED_COLUMN_NAME}' are reserved for the localization system. Section-managed tables must be referenced with the 'section' prop. Please update cms.config.ts and re-run.`);
|
|
1391
1545
|
throw new Error('update-sections: configuration conflicts detected');
|
|
@@ -1408,10 +1562,10 @@ const main = async (s) => {
|
|
|
1408
1562
|
"} from 'drizzle-orm/mysql-core'\n\n" +
|
|
1409
1563
|
drizzleTableSchemas.join('\n');
|
|
1410
1564
|
fs.writeFileSync(schemaFilePath, schemaFileContent, 'utf8');
|
|
1411
|
-
|
|
1565
|
+
spinnerObject.stop();
|
|
1412
1566
|
}
|
|
1413
1567
|
console.log(chalk.white('Finding tables to create, update or remove...'));
|
|
1414
|
-
|
|
1568
|
+
spinnerObject.start();
|
|
1415
1569
|
/**
|
|
1416
1570
|
* Filter out the tables that already exist in the database
|
|
1417
1571
|
*/
|
|
@@ -1420,23 +1574,24 @@ const main = async (s) => {
|
|
|
1420
1574
|
* Let's find out tables that need to be updated.
|
|
1421
1575
|
* If a table exists in both the desired tables and the existing tables, we should mark it for update.
|
|
1422
1576
|
*/
|
|
1423
|
-
const tablesToUpdate = desiredTables
|
|
1577
|
+
const tablesToUpdate = desiredTables
|
|
1578
|
+
.filter((table) => existingTables.some((existingTable) => existingTable.name === table.name))
|
|
1579
|
+
.map((table) => {
|
|
1580
|
+
const existingTable = existingTables.find((existingTable) => existingTable.name === table.name);
|
|
1581
|
+
return { ...table, config: existingTable?.config };
|
|
1582
|
+
});
|
|
1424
1583
|
/**
|
|
1425
1584
|
* Let's find out tables to remove as well.
|
|
1426
1585
|
* If a table exists in the database but not in the desired tables, we should mark it for removal.
|
|
1427
1586
|
*/
|
|
1428
1587
|
let tablesToRemove = existingTables.filter((existingTable) => !desiredTables.some((table) => table.name === existingTable.name));
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
console.log('Tables to create:');
|
|
1437
|
-
console.log(chalk.gray(tablesToCreate.map((table) => table.name).join(', ')));
|
|
1438
|
-
console.log('Tables to remove:');
|
|
1439
|
-
console.log(chalk.gray(tablesToRemove.map((table) => table.name).join(', ')));
|
|
1588
|
+
spinnerObject.stop();
|
|
1589
|
+
const formatTableList = (tables, color) => tables.length === 0 ? chalk.dim('(none)') : tables.map((table) => color(`• ${table.name}`)).join('\n');
|
|
1590
|
+
note(formatTableList(desiredTables, chalk.gray), `Desired tables (${desiredTables.length})`);
|
|
1591
|
+
note(formatTableList(existingTables, chalk.gray), `Existing tables (${existingTables.length})`);
|
|
1592
|
+
note(formatTableList(tablesToUpdate, chalk.yellow), `Tables to update (${tablesToUpdate.length})`);
|
|
1593
|
+
note(formatTableList(tablesToCreate, chalk.green), `Tables to create (${tablesToCreate.length})`);
|
|
1594
|
+
note(formatTableList(tablesToRemove, chalk.red), `Tables to remove (${tablesToRemove.length})`);
|
|
1440
1595
|
console.log(`\n`);
|
|
1441
1596
|
intro(chalk.inverse(' update-sections '));
|
|
1442
1597
|
const localizationCurrentlyEnabled = cmsConfig.localization?.enabled === true;
|
|
@@ -1473,12 +1628,12 @@ const main = async (s) => {
|
|
|
1473
1628
|
const storedPendingDisableLocalizationState = storedEnabledLocalizationState?.transition?.type === 'disable' ? storedEnabledLocalizationState : null;
|
|
1474
1629
|
if (storedEnabledLocalizationState &&
|
|
1475
1630
|
storedEnabledLocalizationState.defaultLocale !== configLocalizationState.defaultLocale) {
|
|
1476
|
-
|
|
1631
|
+
spinnerObject.stop(`Aborting. Changing localization defaultLocale is not supported (stored='${storedEnabledLocalizationState.defaultLocale}', config='${configLocalizationState.defaultLocale}'). Revert cms.config.ts and re-run update-sections.`);
|
|
1477
1632
|
return;
|
|
1478
1633
|
}
|
|
1479
1634
|
if (storedPendingEnableLocalizationState &&
|
|
1480
1635
|
storedPendingEnableLocalizationState.defaultLocale !== configLocalizationState.defaultLocale) {
|
|
1481
|
-
|
|
1636
|
+
spinnerObject.stop(`Aborting. Changing localization defaultLocale while enable is pending is not supported (stored='${storedPendingEnableLocalizationState.defaultLocale}', config='${configLocalizationState.defaultLocale}'). Revert cms.config.ts and re-run update-sections.`);
|
|
1482
1637
|
return;
|
|
1483
1638
|
}
|
|
1484
1639
|
/**
|
|
@@ -1509,7 +1664,7 @@ const main = async (s) => {
|
|
|
1509
1664
|
: ` - No existing tables need a locale backfill; update-sections will verify localized tables after sync.`;
|
|
1510
1665
|
const isEnableRetry = storedPendingEnableLocalizationState?.transition.status === 'pending';
|
|
1511
1666
|
const isDisableRecovery = storedPendingDisableLocalizationState?.transition?.status === 'pending';
|
|
1512
|
-
|
|
1667
|
+
spinnerObject.stop();
|
|
1513
1668
|
const confirm = await select({
|
|
1514
1669
|
message: (isDisableRecovery
|
|
1515
1670
|
? `A previous localization disable attempt did not complete, but cms.config.ts has localization enabled again. update-sections will repair localization artifacts and clear the pending disable state after verification.\n\n`
|
|
@@ -1527,7 +1682,7 @@ const main = async (s) => {
|
|
|
1527
1682
|
initialValue: 'no',
|
|
1528
1683
|
});
|
|
1529
1684
|
if (confirm !== 'yes') {
|
|
1530
|
-
|
|
1685
|
+
spinnerObject.stop('Aborted. Reconfigure cms.config.ts and re-run update-sections.');
|
|
1531
1686
|
return;
|
|
1532
1687
|
}
|
|
1533
1688
|
localizationTransition = 'enable';
|
|
@@ -1539,7 +1694,7 @@ const main = async (s) => {
|
|
|
1539
1694
|
...(isEnableRetry || isDisableRecovery ? { lastAttemptAt: now } : {}),
|
|
1540
1695
|
});
|
|
1541
1696
|
await writeStoredLocalizationState(enablePendingState);
|
|
1542
|
-
|
|
1697
|
+
spinnerObject.start();
|
|
1543
1698
|
}
|
|
1544
1699
|
await ensureEditorPhotosLocaleColumn(configLocalizationState.defaultLocale, editorPhotosColumns);
|
|
1545
1700
|
}
|
|
@@ -1564,7 +1719,7 @@ const main = async (s) => {
|
|
|
1564
1719
|
affected.push({ name: existing.name, type: 'localized_table' });
|
|
1565
1720
|
}
|
|
1566
1721
|
}
|
|
1567
|
-
|
|
1722
|
+
spinnerObject.stop();
|
|
1568
1723
|
const isDisableRetry = storedEnabledLocalizationState.transition?.status === 'pending';
|
|
1569
1724
|
const confirm = await select({
|
|
1570
1725
|
message: (isDisableRetry
|
|
@@ -1586,7 +1741,7 @@ const main = async (s) => {
|
|
|
1586
1741
|
initialValue: 'no',
|
|
1587
1742
|
});
|
|
1588
1743
|
if (confirm !== 'yes') {
|
|
1589
|
-
|
|
1744
|
+
spinnerObject.stop('Aborted. Reconfigure cms.config.ts and re-run update-sections.');
|
|
1590
1745
|
return;
|
|
1591
1746
|
}
|
|
1592
1747
|
localizationTransition = 'disable';
|
|
@@ -1626,7 +1781,7 @@ const main = async (s) => {
|
|
|
1626
1781
|
else {
|
|
1627
1782
|
log.info(chalk.gray(` - 'editor_photos' already delocalized; resuming junction/_locales cleanup.`));
|
|
1628
1783
|
}
|
|
1629
|
-
|
|
1784
|
+
spinnerObject.start();
|
|
1630
1785
|
}
|
|
1631
1786
|
const defaultLocaleCode = localizationCurrentlyEnabled
|
|
1632
1787
|
? (cmsConfig.localization?.defaultLocale ?? storedEnabledLocalizationState?.defaultLocale ?? 'en')
|
|
@@ -1640,7 +1795,7 @@ const main = async (s) => {
|
|
|
1640
1795
|
* Loop through the tables to update
|
|
1641
1796
|
*/
|
|
1642
1797
|
for (const table of tablesToUpdate) {
|
|
1643
|
-
await updateTable(table,
|
|
1798
|
+
await updateTable(table, spinnerObject, ctx);
|
|
1644
1799
|
}
|
|
1645
1800
|
}
|
|
1646
1801
|
/**
|
|
@@ -1652,23 +1807,26 @@ const main = async (s) => {
|
|
|
1652
1807
|
* Loop through the tables to create
|
|
1653
1808
|
*/
|
|
1654
1809
|
for (const table of tablesToCreate) {
|
|
1655
|
-
const
|
|
1656
|
-
if (
|
|
1657
|
-
|
|
1658
|
-
const
|
|
1659
|
-
message:
|
|
1810
|
+
const existingTableStructure = await MysqlTableChecker.getExistingTableStructure(table.name);
|
|
1811
|
+
if (existingTableStructure) {
|
|
1812
|
+
spinnerObject.stop();
|
|
1813
|
+
const handleExistingTable = await select({
|
|
1814
|
+
message: buildExistingTablePromptMessage(table, existingTableStructure),
|
|
1660
1815
|
options: [
|
|
1661
|
-
{ value: '
|
|
1662
|
-
{
|
|
1816
|
+
{ value: 'configure', label: 'Configure table (Recommended)' },
|
|
1817
|
+
{
|
|
1818
|
+
value: 'ask_later',
|
|
1819
|
+
label: 'Ask later and skip for now (Not recommended and may produce runtime errors!)',
|
|
1820
|
+
},
|
|
1663
1821
|
],
|
|
1664
|
-
initialValue: '
|
|
1822
|
+
initialValue: 'configure',
|
|
1665
1823
|
});
|
|
1666
|
-
if (
|
|
1824
|
+
if (handleExistingTable === 'configure') {
|
|
1667
1825
|
await ensureTableRegistryEntry(table.name, table.sectionName);
|
|
1668
|
-
await updateTable(table,
|
|
1826
|
+
await updateTable(table, spinnerObject, ctx);
|
|
1669
1827
|
}
|
|
1670
1828
|
else {
|
|
1671
|
-
console.log(chalk.yellow(`Skipping existing table '${table.name}'.`));
|
|
1829
|
+
console.log(chalk.yellow(`Skipping existing table '${table.name}'. You will be asked again later.`));
|
|
1672
1830
|
}
|
|
1673
1831
|
continue;
|
|
1674
1832
|
}
|
|
@@ -1677,7 +1835,7 @@ const main = async (s) => {
|
|
|
1677
1835
|
* If there are, ask the user if they want to create the new table or rename a removed table
|
|
1678
1836
|
*/
|
|
1679
1837
|
if (tablesToRemove.length > 0) {
|
|
1680
|
-
|
|
1838
|
+
spinnerObject.stop();
|
|
1681
1839
|
const opType = await select({
|
|
1682
1840
|
message: `Is table '${table.name}' for section '${table.sectionName}' a new table?`,
|
|
1683
1841
|
options: [
|
|
@@ -1685,7 +1843,7 @@ const main = async (s) => {
|
|
|
1685
1843
|
{ value: 'rename', label: 'Rename existing table' },
|
|
1686
1844
|
],
|
|
1687
1845
|
});
|
|
1688
|
-
|
|
1846
|
+
spinnerObject.start();
|
|
1689
1847
|
switch (opType) {
|
|
1690
1848
|
case 'new': {
|
|
1691
1849
|
console.log(chalk.blueBright(`Creating table '${table.name}' for section '${table.sectionName}'`));
|
|
@@ -1694,7 +1852,7 @@ const main = async (s) => {
|
|
|
1694
1852
|
break;
|
|
1695
1853
|
}
|
|
1696
1854
|
case 'rename': {
|
|
1697
|
-
|
|
1855
|
+
spinnerObject.stop();
|
|
1698
1856
|
const tableToRename = await select({
|
|
1699
1857
|
message: `Select the table to rename to '${table.name}'`,
|
|
1700
1858
|
options: tablesToRemove.map((table) => {
|
|
@@ -1704,7 +1862,7 @@ const main = async (s) => {
|
|
|
1704
1862
|
if (tableToRename && typeof tableToRename === 'string') {
|
|
1705
1863
|
console.log(`Renaming table '${tableToRename}' to '${table.name}'`);
|
|
1706
1864
|
await renameTable(tableToRename, table.name);
|
|
1707
|
-
await updateTable(table,
|
|
1865
|
+
await updateTable(table, spinnerObject, ctx);
|
|
1708
1866
|
/**
|
|
1709
1867
|
* Remove the table from the tablesToRemove array
|
|
1710
1868
|
*/
|
|
@@ -1750,7 +1908,7 @@ const main = async (s) => {
|
|
|
1750
1908
|
opType = autoDropLocales;
|
|
1751
1909
|
}
|
|
1752
1910
|
else {
|
|
1753
|
-
|
|
1911
|
+
spinnerObject.stop();
|
|
1754
1912
|
opType = await select({
|
|
1755
1913
|
message: `You are about to remove table '${table.name}'. Proceed?`,
|
|
1756
1914
|
options: [
|
|
@@ -1760,7 +1918,7 @@ const main = async (s) => {
|
|
|
1760
1918
|
],
|
|
1761
1919
|
initialValue: 'later',
|
|
1762
1920
|
});
|
|
1763
|
-
|
|
1921
|
+
spinnerObject.start();
|
|
1764
1922
|
}
|
|
1765
1923
|
switch (opType) {
|
|
1766
1924
|
case 'yes':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-generator.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/schema-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAoB7D,MAAM,WAAW,YAAY;IACzB,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IACzC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CAC7C;AAED,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,CAAA;IAChD,OAAO,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,CAAA;CACpD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CA8BtE;AAED,wBAAgB,qBAAqB,CACjC,KAAK,EAAE;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,UAAU,CAAC,EAAE,WAAW,CAAA;IACxB,mBAAmB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACxC,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACxD,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACzD,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC9D,EACD,YAAY,EAAE,YAAY,GAC3B;IACC,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,EAAE,CAAA;CAC3B,
|
|
1
|
+
{"version":3,"file":"schema-generator.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/schema-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAoB7D,MAAM,WAAW,YAAY;IACzB,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IACzC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CAC7C;AAED,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,CAAA;IAChD,OAAO,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,CAAA;CACpD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CA8BtE;AAED,wBAAgB,qBAAqB,CACjC,KAAK,EAAE;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,UAAU,CAAC,EAAE,WAAW,CAAA;IACxB,mBAAmB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACxC,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACxD,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACzD,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC9D,EACD,YAAY,EAAE,YAAY,GAC3B;IACC,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,EAAE,CAAA;CAC3B,CA+MA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IAC/C,eAAe,EAAE,MAAM,CAAA;IACvB,gBAAgB,EAAE,WAAW,CAAA;IAC7B,eAAe,EAAE,WAAW,EAAE,CAAA;IAC9B,YAAY,EAAE,YAAY,CAAA;CAC7B,GAAG;IACA,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,EAAE,CAAA;CAC3B,CA+CA"}
|
|
@@ -152,13 +152,16 @@ export function generateDrizzleSchema(table, caseStyleFns) {
|
|
|
152
152
|
}
|
|
153
153
|
break;
|
|
154
154
|
case 'map':
|
|
155
|
-
case 'tags':
|
|
156
155
|
case 'photo':
|
|
157
156
|
case 'video':
|
|
158
157
|
case 'document':
|
|
159
158
|
columnType = `varchar('${input.name}', { length: 255 })`; // TEXT type
|
|
160
159
|
drizzleColumnType = 'varchar';
|
|
161
160
|
break;
|
|
161
|
+
case 'tags':
|
|
162
|
+
columnType = `longtext('${input.name}')`;
|
|
163
|
+
drizzleColumnType = 'longtext';
|
|
164
|
+
break;
|
|
162
165
|
default:
|
|
163
166
|
// TODO: Should I throw an error instead?
|
|
164
167
|
columnType = `varchar('${input.name}', { length: 255 })`;
|