nextjs-cms-kit 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/dist/commands/db-config.d.ts +3 -0
- package/dist/commands/db-config.d.ts.map +1 -0
- package/dist/commands/db-config.js +20 -0
- package/dist/commands/fix-master-admin.d.ts +3 -0
- package/dist/commands/fix-master-admin.d.ts.map +1 -0
- package/dist/commands/fix-master-admin.js +19 -0
- package/dist/commands/set-master-admin.d.ts +3 -0
- package/dist/commands/set-master-admin.d.ts.map +1 -0
- package/dist/commands/set-master-admin.js +29 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +61 -0
- package/dist/commands/update-sections.d.ts +3 -0
- package/dist/commands/update-sections.d.ts.map +1 -0
- package/dist/commands/update-sections.js +33 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/lib/actions.d.ts +7 -0
- package/dist/lib/actions.d.ts.map +1 -0
- package/dist/lib/actions.js +62 -0
- package/dist/lib/add-table-keys.d.ts +32 -0
- package/dist/lib/add-table-keys.d.ts.map +1 -0
- package/dist/lib/add-table-keys.js +168 -0
- package/dist/lib/db-config-setup.d.ts +4 -0
- package/dist/lib/db-config-setup.d.ts.map +1 -0
- package/dist/lib/db-config-setup.js +216 -0
- package/dist/lib/fix-master-admin.d.ts +2 -0
- package/dist/lib/fix-master-admin.d.ts.map +1 -0
- package/dist/lib/fix-master-admin.js +7 -0
- package/dist/lib/reload-env.d.ts +16 -0
- package/dist/lib/reload-env.d.ts.map +1 -0
- package/dist/lib/reload-env.js +42 -0
- package/dist/lib/schema-generator.d.ts +10 -0
- package/dist/lib/schema-generator.d.ts.map +1 -0
- package/dist/lib/schema-generator.js +168 -0
- package/dist/lib/set-master-admin.d.ts +2 -0
- package/dist/lib/set-master-admin.d.ts.map +1 -0
- package/dist/lib/set-master-admin.js +53 -0
- package/dist/lib/update-sections.d.ts +2 -0
- package/dist/lib/update-sections.d.ts.map +1 -0
- package/dist/lib/update-sections.js +898 -0
- package/package.json +55 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { db } from 'nextjs-cms/db/client';
|
|
3
|
+
export async function addTableKeys(table, existingKeys) {
|
|
4
|
+
// console.log(chalk.gray(` - Checking table keys`))
|
|
5
|
+
/**
|
|
6
|
+
* Number of SQL errors
|
|
7
|
+
*/
|
|
8
|
+
let sqlErrors = 0;
|
|
9
|
+
/**
|
|
10
|
+
* Add the primary key, but first, check if the primary key exists or has changed
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
13
|
+
if (table.primaryKey) {
|
|
14
|
+
if (existingKeys) {
|
|
15
|
+
const { primaryKeys } = existingKeys;
|
|
16
|
+
/**
|
|
17
|
+
* Check if the existing primary key matches the new primary key
|
|
18
|
+
*/
|
|
19
|
+
const primaryKeyChanged = primaryKeys.length !== table.primaryKey.length ||
|
|
20
|
+
!table.primaryKey.every((key) => primaryKeys.includes(key.name));
|
|
21
|
+
/**
|
|
22
|
+
* If primary key doesn't exist or has changed, update it
|
|
23
|
+
*/
|
|
24
|
+
if (primaryKeys.length === 0 || primaryKeyChanged) {
|
|
25
|
+
/**
|
|
26
|
+
* Drop existing primary key if it exists
|
|
27
|
+
*/
|
|
28
|
+
if (primaryKeys.length > 0) {
|
|
29
|
+
console.log(chalk.gray(` - Dropping existing primary key from '${table.name}: [${primaryKeys.join(', ')}]`));
|
|
30
|
+
try {
|
|
31
|
+
await db.execute(`ALTER TABLE \`${table.name}\` DROP PRIMARY KEY`);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
sqlErrors++;
|
|
35
|
+
console.error(chalk.red(` - Error dropping primary key for table '${table.name}':`, error));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Add the new primary key
|
|
40
|
+
*/
|
|
41
|
+
console.log(chalk.gray(` - Adding primary key to '${table.name}': [${table.primaryKey.map((key) => key.name).join(', ')}]`));
|
|
42
|
+
try {
|
|
43
|
+
const primaryKeyColumns = table.primaryKey.map((column) => `\`${column.name}\``).join(', ');
|
|
44
|
+
await db.execute(`ALTER TABLE \`${table.name}\` ADD PRIMARY KEY (${primaryKeyColumns})`);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
sqlErrors++;
|
|
48
|
+
console.error(chalk.red(` - Error updating primary key for table '${table.name}':`, error));
|
|
49
|
+
/**
|
|
50
|
+
* Reverting to the old primary key
|
|
51
|
+
*/
|
|
52
|
+
console.log(chalk.gray(` - Re-adding old primary key to '${table.name}': [${primaryKeys.map((key) => key).join(', ')}]`));
|
|
53
|
+
try {
|
|
54
|
+
const primaryKeyColumns = primaryKeys.map((column) => `\`${column}\``).join(', ');
|
|
55
|
+
await db.execute(`ALTER TABLE \`${table.name}\` ADD PRIMARY KEY (${primaryKeyColumns})`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
sqlErrors++;
|
|
59
|
+
console.error(chalk.red(` - Error re-adding primary key for table '${table.name}':`, error));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// TODO: What is this?
|
|
66
|
+
sqlErrors++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Modify unique keys
|
|
71
|
+
*/
|
|
72
|
+
if (table.unique) {
|
|
73
|
+
const existingUniqueKeys = existingKeys?.uniqueKeys ?? [];
|
|
74
|
+
for (const requestedUnique of table.unique) {
|
|
75
|
+
/**
|
|
76
|
+
* Check if the unique key already exists
|
|
77
|
+
*/
|
|
78
|
+
const matchingUnique = existingUniqueKeys.find((u) => {
|
|
79
|
+
return (requestedUnique.columns.length === u.columns.length &&
|
|
80
|
+
requestedUnique.columns.every((col) => u.columns.includes(col.name)));
|
|
81
|
+
});
|
|
82
|
+
const uniqueColumns = requestedUnique.columns.map((col) => `\`${col.name}\``).join(', ');
|
|
83
|
+
/**
|
|
84
|
+
* If key doesn't exist, add it
|
|
85
|
+
*/
|
|
86
|
+
if (!matchingUnique) {
|
|
87
|
+
console.log(chalk.gray(` - Adding unique key with columns [${requestedUnique.columns.map((col) => col.name).join(', ')}] to '${table.name}'`));
|
|
88
|
+
await db.execute(`ALTER TABLE \`${table.name}\` ADD UNIQUE ${requestedUnique.name ?? ''}(${uniqueColumns})`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Drop any existing unique keys that are not in the new set (based on columns)
|
|
93
|
+
*/
|
|
94
|
+
for (const existingUnique of existingUniqueKeys) {
|
|
95
|
+
if (!table.unique.find((unique) => {
|
|
96
|
+
return (unique.columns.length === existingUnique.columns.length &&
|
|
97
|
+
unique.columns.every((col) => existingUnique.columns.includes(col.name)));
|
|
98
|
+
})) {
|
|
99
|
+
console.log(chalk.gray(` - Dropping unique key with columns [${existingUnique.columns.join(', ')}] from '${table.name}'`));
|
|
100
|
+
await db.execute(`ALTER TABLE \`${table.name}\` DROP INDEX \`${existingUnique.name}\``);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Modify indexes
|
|
106
|
+
*/
|
|
107
|
+
if (table.index) {
|
|
108
|
+
const existingIndexes = existingKeys?.indexKeys ?? [];
|
|
109
|
+
for (const index of table.index) {
|
|
110
|
+
const matchingIndex = existingIndexes.find((i) => {
|
|
111
|
+
return (index.columns.length === i.columns.length &&
|
|
112
|
+
index.columns.every((col) => i.columns.includes(col.name)));
|
|
113
|
+
});
|
|
114
|
+
const indexColumns = index.columns.map((col) => `\`${col.name}\``).join(', ');
|
|
115
|
+
if (!matchingIndex) {
|
|
116
|
+
// Add index
|
|
117
|
+
console.log(chalk.gray(` - Adding index with columns [${index.columns.map((col) => col.name).join(', ')}] to '${table.name}'`));
|
|
118
|
+
await db.execute(`ALTER TABLE \`${table.name}\` ADD INDEX ${index.name ?? ''}(${indexColumns})`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Drop any existing indexes that are not in the new set (based on columns)
|
|
123
|
+
*/
|
|
124
|
+
for (const existingIndex of existingIndexes) {
|
|
125
|
+
if (!table.index.find((index) => {
|
|
126
|
+
return (index.columns.length === existingIndex.columns.length &&
|
|
127
|
+
index.columns.every((col) => existingIndex.columns.includes(col.name)));
|
|
128
|
+
})) {
|
|
129
|
+
console.log(chalk.gray(` - Dropping index with columns [${existingIndex.columns.join(', ')}] from '${table.name}'`));
|
|
130
|
+
await db.execute(`ALTER TABLE \`${table.name}\` DROP INDEX \`${existingIndex.name}\``);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Modify fulltext indexes
|
|
136
|
+
*/
|
|
137
|
+
if (table.fulltext) {
|
|
138
|
+
const existingIndexes = existingKeys?.fullTextKeys ?? [];
|
|
139
|
+
for (const requestedFullTextIndex of table.fulltext) {
|
|
140
|
+
const matchingFulltext = existingIndexes.find((i) => {
|
|
141
|
+
return (requestedFullTextIndex.columns.length === i.columns.length &&
|
|
142
|
+
requestedFullTextIndex.columns.every((col) => i.columns.includes(col.name)));
|
|
143
|
+
});
|
|
144
|
+
const fulltextColumns = requestedFullTextIndex.columns.map((col) => `\`${col.name}\``).join(', ');
|
|
145
|
+
/**
|
|
146
|
+
* If fulltext index doesn't exist, add it
|
|
147
|
+
*/
|
|
148
|
+
if (!matchingFulltext) {
|
|
149
|
+
// Add fulltext index
|
|
150
|
+
console.log(chalk.gray(` - Adding fulltext index with columns [${requestedFullTextIndex.columns.map((col) => col.name).join(', ')}] to '${table.name}'`));
|
|
151
|
+
await db.execute(`ALTER TABLE \`${table.name}\` ADD FULLTEXT ${requestedFullTextIndex.name ?? ''}(${fulltextColumns})`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Drop any existing fulltext indexes that are not in the new set (based on columns)
|
|
156
|
+
*/
|
|
157
|
+
for (const existingFulltext of existingIndexes) {
|
|
158
|
+
if (!table.fulltext.find((fulltext) => {
|
|
159
|
+
return (fulltext.columns.length === existingFulltext.columns.length &&
|
|
160
|
+
fulltext.columns.every((col) => existingFulltext.columns.includes(col.name)));
|
|
161
|
+
})) {
|
|
162
|
+
console.log(chalk.gray(` - Dropping fulltext index with columns [${existingFulltext.columns.join(', ')}] from '${table.name}'`));
|
|
163
|
+
await db.execute(`ALTER TABLE \`${table.name}\` DROP INDEX \`${existingFulltext.name}\``);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return sqlErrors;
|
|
168
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-config-setup.d.ts","sourceRoot":"","sources":["../../src/lib/db-config-setup.ts"],"names":[],"mappings":"AAkCA,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,iBAqN5D"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { createConnection } from 'mysql2/promise';
|
|
2
|
+
import { intro, isCancel, outro, text, spinner, note } from '@clack/prompts';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { cwd } from 'process';
|
|
6
|
+
import { reloadAndUpdateEnvironmentVariables } from './reload-env';
|
|
7
|
+
/**
|
|
8
|
+
* Removes specific environment variables from the .env file content
|
|
9
|
+
* Removes both the generated comment block and the individual variable lines
|
|
10
|
+
*/
|
|
11
|
+
function removeEnvVariables(content, variables) {
|
|
12
|
+
let result = content;
|
|
13
|
+
// Remove the generated block comment if it exists (including the markers)
|
|
14
|
+
const blockRegex = /\n*####\s*\n#\s*generated by the init script\s*\n####\s*\n?/g;
|
|
15
|
+
result = result.replace(blockRegex, '\n');
|
|
16
|
+
// Remove individual variable lines (entire lines including newline)
|
|
17
|
+
variables.forEach((variable) => {
|
|
18
|
+
result = result.replace(new RegExp(`^${variable}=.*$\\n?`, 'gm'), '');
|
|
19
|
+
});
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validates if a string is a valid port number
|
|
24
|
+
*/
|
|
25
|
+
function isValidPort(value) {
|
|
26
|
+
const port = parseInt(value, 10);
|
|
27
|
+
return !isNaN(port) && port > 0 && port <= 65535;
|
|
28
|
+
}
|
|
29
|
+
export async function dbConfigSetup(options) {
|
|
30
|
+
const { dev = false } = options;
|
|
31
|
+
intro(`Welcome to LZCMS! Let's set up the database...`);
|
|
32
|
+
/**
|
|
33
|
+
* Step 1: Ask for connection credentials
|
|
34
|
+
*/
|
|
35
|
+
note('Please provide your MySQL/MariaDB server credentials', 'Database Connection');
|
|
36
|
+
const dbHost = await text({
|
|
37
|
+
message: 'Database host:',
|
|
38
|
+
placeholder: 'localhost',
|
|
39
|
+
defaultValue: 'localhost',
|
|
40
|
+
validate(value) {
|
|
41
|
+
if (value && value.length < 2)
|
|
42
|
+
return `Database host must be at least 2 characters`;
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
if (isCancel(dbHost)) {
|
|
46
|
+
outro(`LZCMS init cancelled by user.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const dbPort = await text({
|
|
50
|
+
message: 'Database port:',
|
|
51
|
+
placeholder: '3306',
|
|
52
|
+
defaultValue: '3306',
|
|
53
|
+
validate(value) {
|
|
54
|
+
if (value && !isValidPort(value)) {
|
|
55
|
+
return `Database port must be a valid number between 1 and 65535`;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
if (isCancel(dbPort)) {
|
|
60
|
+
outro(`LZCMS init cancelled by user.`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const dbUser = await text({
|
|
64
|
+
message: 'Database username:',
|
|
65
|
+
validate(value) {
|
|
66
|
+
if (value.length < 1)
|
|
67
|
+
return `Database username is required`;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
if (isCancel(dbUser)) {
|
|
71
|
+
outro(`LZCMS init cancelled by user.`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const dbPassword = await text({
|
|
75
|
+
message: 'Database password:',
|
|
76
|
+
validate(value) {
|
|
77
|
+
if (value.length < 1)
|
|
78
|
+
return `Database password is required`;
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
if (isCancel(dbPassword)) {
|
|
82
|
+
outro(`LZCMS init cancelled by user.`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Step 2: Test the database connection
|
|
87
|
+
*/
|
|
88
|
+
const s = spinner();
|
|
89
|
+
let migrationClient;
|
|
90
|
+
try {
|
|
91
|
+
s.start('Testing database connection...');
|
|
92
|
+
/**
|
|
93
|
+
* It's recommended to not use a pool connection for migrations, that's why we're creating a new connection here.
|
|
94
|
+
*/
|
|
95
|
+
migrationClient = await createConnection({
|
|
96
|
+
host: String(dbHost),
|
|
97
|
+
user: String(dbUser),
|
|
98
|
+
password: String(dbPassword),
|
|
99
|
+
database: '',
|
|
100
|
+
port: parseInt(String(dbPort), 10),
|
|
101
|
+
});
|
|
102
|
+
s.stop('✓ Connected successfully!');
|
|
103
|
+
/**
|
|
104
|
+
* Step 3: Now that connection is successful, ask for database name
|
|
105
|
+
*/
|
|
106
|
+
note('If the database does not exist, it will be created', 'Database Name');
|
|
107
|
+
const dbName = await text({
|
|
108
|
+
message: 'Database name:',
|
|
109
|
+
validate(value) {
|
|
110
|
+
if (value.length < 2)
|
|
111
|
+
return `Database name must be at least 2 characters`;
|
|
112
|
+
// Validate database name format (alphanumeric, underscore, hyphen)
|
|
113
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
|
|
114
|
+
return `Database name can only contain letters, numbers, underscores, and hyphens`;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
if (isCancel(dbName)) {
|
|
119
|
+
outro(`LZCMS init cancelled by user.`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Step 4: Create the database
|
|
124
|
+
*/
|
|
125
|
+
s.start('Creating database...');
|
|
126
|
+
/**
|
|
127
|
+
* Let's create the database using parameterized query to prevent SQL injection
|
|
128
|
+
*/
|
|
129
|
+
await migrationClient.query('CREATE DATABASE IF NOT EXISTS ??', [dbName]);
|
|
130
|
+
s.stop('✓ Database created successfully!');
|
|
131
|
+
/**
|
|
132
|
+
* Let's save the database credentials in the .env file
|
|
133
|
+
*/
|
|
134
|
+
const envPath = path.resolve(cwd(), dev ? '.env.development' : '.env.production');
|
|
135
|
+
/**
|
|
136
|
+
* If the .env file does not exist, we'll create it
|
|
137
|
+
*/
|
|
138
|
+
if (!fs.existsSync(envPath)) {
|
|
139
|
+
fs.writeFileSync(envPath, '');
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Read current .env content and remove existing database credentials
|
|
143
|
+
*/
|
|
144
|
+
let currentEnv = fs.readFileSync(envPath, 'utf8');
|
|
145
|
+
currentEnv = removeEnvVariables(currentEnv, ['DB_HOST', 'DB_PORT', 'DB_NAME', 'DB_USER', 'DB_PASSWORD']);
|
|
146
|
+
/**
|
|
147
|
+
* Prepare new environment variables block
|
|
148
|
+
*/
|
|
149
|
+
const newEnvBlock = `
|
|
150
|
+
|
|
151
|
+
####
|
|
152
|
+
# generated by the init script
|
|
153
|
+
####
|
|
154
|
+
DB_HOST=${dbHost}
|
|
155
|
+
DB_PORT=${dbPort}
|
|
156
|
+
DB_NAME=${dbName}
|
|
157
|
+
DB_USER=${dbUser}
|
|
158
|
+
DB_PASSWORD='${dbPassword}'
|
|
159
|
+
`;
|
|
160
|
+
/**
|
|
161
|
+
* Append the new credentials to the .env file
|
|
162
|
+
*/
|
|
163
|
+
let finalEnv = currentEnv + newEnvBlock;
|
|
164
|
+
// Trim multiple consecutive empty lines to a maximum of two empty lines
|
|
165
|
+
finalEnv = finalEnv.replace(/(\n\s*\n){3,}/g, '\n\n');
|
|
166
|
+
// Trim leading whitespace
|
|
167
|
+
finalEnv = finalEnv.trim() + '\n';
|
|
168
|
+
fs.writeFileSync(envPath, finalEnv);
|
|
169
|
+
// Reload environment variables after writing to .env file
|
|
170
|
+
const dbVariables = {
|
|
171
|
+
DB_HOST: String(dbHost),
|
|
172
|
+
DB_PORT: String(dbPort),
|
|
173
|
+
DB_NAME: String(dbName),
|
|
174
|
+
DB_USER: String(dbUser),
|
|
175
|
+
DB_PASSWORD: String(dbPassword),
|
|
176
|
+
};
|
|
177
|
+
const reloadSuccess = reloadAndUpdateEnvironmentVariables(dbVariables, dev ? '.env.development' : '.env.production');
|
|
178
|
+
if (reloadSuccess) {
|
|
179
|
+
console.log('✓ Environment variables reloaded successfully!');
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.warn('⚠️ Environment variables written to file but could not be reloaded in current process');
|
|
183
|
+
}
|
|
184
|
+
outro(`✓ Database setup completed successfully! Please run 'npm run lz:setup' to finish the setup.`);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
s.stop('✗ Database setup failed!');
|
|
188
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
189
|
+
const errorCode = error && typeof error === 'object' && 'code' in error ? error.code : null;
|
|
190
|
+
// Provide specific error messages based on error type
|
|
191
|
+
if (errorCode === 'ER_ACCESS_DENIED_ERROR' || errorCode === 'ECONNREFUSED') {
|
|
192
|
+
console.error('\n❌ Connection Error:', errorMessage);
|
|
193
|
+
console.error('\nPlease verify:');
|
|
194
|
+
console.error(' • Database host and port are correct');
|
|
195
|
+
console.error(' • Username and password are correct');
|
|
196
|
+
console.error(' • Database server is running');
|
|
197
|
+
console.error(' • User has sufficient privileges');
|
|
198
|
+
}
|
|
199
|
+
else if (errorCode === 'ER_BAD_DB_ERROR') {
|
|
200
|
+
console.error('\n❌ Database Error:', errorMessage);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.error('\n❌ Error:', errorMessage);
|
|
204
|
+
}
|
|
205
|
+
outro(`\nSetup failed. Please check the errors above and try again.`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
/**
|
|
210
|
+
* Always close the connection, even if an error occurred
|
|
211
|
+
*/
|
|
212
|
+
if (migrationClient) {
|
|
213
|
+
await migrationClient.end();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix-master-admin.d.ts","sourceRoot":"","sources":["../../src/lib/fix-master-admin.ts"],"names":[],"mappings":"AAGA,wBAAsB,cAAc,kBAInC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reloads environment variables from .env files
|
|
3
|
+
* This is useful after programmatically writing to .env files
|
|
4
|
+
*/
|
|
5
|
+
export declare function reloadEnvironmentVariables(envFile?: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Updates specific environment variables in process.env
|
|
8
|
+
* This provides immediate availability without requiring a full reload
|
|
9
|
+
*/
|
|
10
|
+
export declare function updateEnvironmentVariables(variables: Record<string, string>): void;
|
|
11
|
+
/**
|
|
12
|
+
* Reloads environment variables and updates specific ones in process.env
|
|
13
|
+
* This combines both approaches for maximum compatibility
|
|
14
|
+
*/
|
|
15
|
+
export declare function reloadAndUpdateEnvironmentVariables(variables?: Record<string, string>, envFile?: string): boolean;
|
|
16
|
+
//# sourceMappingURL=reload-env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reload-env.d.ts","sourceRoot":"","sources":["../../src/lib/reload-env.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAepE;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAIlF;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAQjH"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import { cwd } from 'process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Reloads environment variables from .env files
|
|
6
|
+
* This is useful after programmatically writing to .env files
|
|
7
|
+
*/
|
|
8
|
+
export function reloadEnvironmentVariables(envFile) {
|
|
9
|
+
try {
|
|
10
|
+
const envPath = envFile ? path.resolve(cwd(), envFile) : undefined;
|
|
11
|
+
const result = config({ path: envPath });
|
|
12
|
+
if (result.error) {
|
|
13
|
+
console.warn('⚠️ Error loading .env file:', result.error);
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.warn('⚠️ Could not reload environment variables:', error);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Updates specific environment variables in process.env
|
|
25
|
+
* This provides immediate availability without requiring a full reload
|
|
26
|
+
*/
|
|
27
|
+
export function updateEnvironmentVariables(variables) {
|
|
28
|
+
Object.entries(variables).forEach(([key, value]) => {
|
|
29
|
+
process.env[key] = value;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reloads environment variables and updates specific ones in process.env
|
|
34
|
+
* This combines both approaches for maximum compatibility
|
|
35
|
+
*/
|
|
36
|
+
export function reloadAndUpdateEnvironmentVariables(variables, envFile) {
|
|
37
|
+
const reloadSuccess = reloadEnvironmentVariables(envFile);
|
|
38
|
+
if (variables) {
|
|
39
|
+
updateEnvironmentVariables(variables);
|
|
40
|
+
}
|
|
41
|
+
return reloadSuccess;
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FieldConfig } from 'nextjs-cms/core/fields';
|
|
2
|
+
export declare function generateDrizzleSchema(table: {
|
|
3
|
+
name: string;
|
|
4
|
+
fields: FieldConfig[];
|
|
5
|
+
identifier: FieldConfig;
|
|
6
|
+
}): {
|
|
7
|
+
schema: string;
|
|
8
|
+
columnTypes: string[];
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=schema-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-generator.d.ts","sourceRoot":"","sources":["../../src/lib/schema-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAqBzD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,EAAE,CAAC;IAAC,UAAU,EAAE,WAAW,CAAA;CAAE,GAAG;IAC5G,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,EAAE,CAAA;CACxB,CAiKA"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { getCMSConfig } from 'nextjs-cms/core/config';
|
|
2
|
+
// Converts snake_case to camelCase
|
|
3
|
+
function toCamelCase(snake) {
|
|
4
|
+
return snake.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
5
|
+
}
|
|
6
|
+
// Converts snake_case to PascalCase
|
|
7
|
+
function toPascalCase(snake) {
|
|
8
|
+
return snake.toLowerCase().replace(/(^|_)([a-z])/g, (_, __, letter) => letter.toUpperCase());
|
|
9
|
+
}
|
|
10
|
+
// Converts any case (camelCase, PascalCase, etc.) to snake_case
|
|
11
|
+
function toSnakeCase(str) {
|
|
12
|
+
return str
|
|
13
|
+
.replace(/([A-Z])/g, '_$1') // Add underscore before capital letters
|
|
14
|
+
.replace(/^_/, '') // Remove leading underscore if any
|
|
15
|
+
.toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
export function generateDrizzleSchema(table) {
|
|
18
|
+
let tableCaseStyleFn;
|
|
19
|
+
let columnCaseStyleFn;
|
|
20
|
+
const lzConfig = getCMSConfig();
|
|
21
|
+
switch (lzConfig.db.schemaNamingConvention.tableCaseStyle) {
|
|
22
|
+
case 'snakeCase':
|
|
23
|
+
tableCaseStyleFn = toSnakeCase;
|
|
24
|
+
break;
|
|
25
|
+
case 'camelCase':
|
|
26
|
+
tableCaseStyleFn = toCamelCase;
|
|
27
|
+
break;
|
|
28
|
+
case 'pascalCase':
|
|
29
|
+
default:
|
|
30
|
+
tableCaseStyleFn = toPascalCase;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
switch (lzConfig.db.schemaNamingConvention.columnCaseStyle) {
|
|
34
|
+
case 'snakeCase':
|
|
35
|
+
columnCaseStyleFn = toSnakeCase;
|
|
36
|
+
break;
|
|
37
|
+
case 'pascalCase':
|
|
38
|
+
columnCaseStyleFn = toPascalCase;
|
|
39
|
+
break;
|
|
40
|
+
case 'camelCase':
|
|
41
|
+
default:
|
|
42
|
+
columnCaseStyleFn = toCamelCase;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* This function generates a Drizzle schema for a given table.
|
|
47
|
+
* It takes a table object with a name, inputs (fields), and an identifier field.
|
|
48
|
+
* It returns an object containing the generated schema as a string and an array of column types.
|
|
49
|
+
*/
|
|
50
|
+
const drizzleColumnTypes = [];
|
|
51
|
+
let schema = '';
|
|
52
|
+
const schemaName = tableCaseStyleFn(`${table.name}_table`);
|
|
53
|
+
schema += `export const ${schemaName} = mysqlTable('${table.name}', {\n`;
|
|
54
|
+
for (const input of table.fields) {
|
|
55
|
+
let columnType;
|
|
56
|
+
let drizzleColumnType;
|
|
57
|
+
// let columnOptions = ''
|
|
58
|
+
switch (input.type) {
|
|
59
|
+
case 'text':
|
|
60
|
+
columnType = `varchar('${input.name}', { length: ${input.maxLength ?? 255} })`;
|
|
61
|
+
drizzleColumnType = 'varchar';
|
|
62
|
+
break;
|
|
63
|
+
case 'textarea':
|
|
64
|
+
case 'rich_text':
|
|
65
|
+
columnType = `longtext('${input.name}')`;
|
|
66
|
+
drizzleColumnType = 'longtext';
|
|
67
|
+
break;
|
|
68
|
+
case 'select_multiple':
|
|
69
|
+
case 'password':
|
|
70
|
+
columnType = `varchar('${input.name}', { length: 255 })`;
|
|
71
|
+
drizzleColumnType = 'varchar';
|
|
72
|
+
break;
|
|
73
|
+
case 'select':
|
|
74
|
+
if (input.optionsType === 'static' && input.options) {
|
|
75
|
+
columnType = `mysqlEnum('${input.name}', [${input.options.map((option) => `'${option.value}'`).join(', ')}])`;
|
|
76
|
+
drizzleColumnType = 'mysqlEnum';
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
columnType = `varchar('${input.name}', { length: 255 })`;
|
|
80
|
+
drizzleColumnType = 'varchar';
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 'checkbox':
|
|
84
|
+
columnType = `boolean('${input.name}')`;
|
|
85
|
+
drizzleColumnType = 'boolean';
|
|
86
|
+
break;
|
|
87
|
+
case 'color':
|
|
88
|
+
columnType = `varchar('${input.name}', { length: 7 })`; // #RRGGBB
|
|
89
|
+
drizzleColumnType = 'varchar';
|
|
90
|
+
break;
|
|
91
|
+
case 'date':
|
|
92
|
+
switch (input.format) {
|
|
93
|
+
case 'timestamp':
|
|
94
|
+
columnType = `timestamp('${input.name}')`;
|
|
95
|
+
drizzleColumnType = 'timestamp';
|
|
96
|
+
break;
|
|
97
|
+
case 'date':
|
|
98
|
+
columnType = `date('${input.name}')`;
|
|
99
|
+
drizzleColumnType = 'date';
|
|
100
|
+
break;
|
|
101
|
+
case 'datetime':
|
|
102
|
+
columnType = `datetime('${input.name}')`;
|
|
103
|
+
drizzleColumnType = 'datetime';
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case 'number':
|
|
108
|
+
switch (input.format) {
|
|
109
|
+
case 'double':
|
|
110
|
+
columnType = `double('${input.name}')`;
|
|
111
|
+
drizzleColumnType = 'double';
|
|
112
|
+
break;
|
|
113
|
+
case 'float':
|
|
114
|
+
columnType = `float('${input.name}')`;
|
|
115
|
+
drizzleColumnType = 'float';
|
|
116
|
+
break;
|
|
117
|
+
case 'int':
|
|
118
|
+
default:
|
|
119
|
+
columnType = `int('${input.name}')`;
|
|
120
|
+
drizzleColumnType = 'int';
|
|
121
|
+
if (input.hasAutoIncrement) {
|
|
122
|
+
columnType += `.autoincrement()`;
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
case 'map':
|
|
128
|
+
case 'tags':
|
|
129
|
+
case 'photo':
|
|
130
|
+
case 'video':
|
|
131
|
+
case 'document':
|
|
132
|
+
columnType = `varchar('${input.name}', { length: 255 })`; // TEXT type
|
|
133
|
+
drizzleColumnType = 'varchar';
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
// @ts-expect-error name will be any at this point
|
|
137
|
+
// TODO: Should I throw an error instead?
|
|
138
|
+
columnType = `varchar('${input.name}', { length: 255 })`;
|
|
139
|
+
drizzleColumnType = 'varchar';
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
if (input.required) {
|
|
143
|
+
columnType += `.notNull()`;
|
|
144
|
+
}
|
|
145
|
+
if (input === table.identifier) {
|
|
146
|
+
columnType += `.primaryKey()`;
|
|
147
|
+
}
|
|
148
|
+
/*if (input.type === 'date') {
|
|
149
|
+
columnType += `.defaultNow()`
|
|
150
|
+
}*/
|
|
151
|
+
schema += ` ${columnCaseStyleFn(input.name)}: ${columnType},\n`;
|
|
152
|
+
/**
|
|
153
|
+
* First, let's check if the column type is already in the drizzleColumnTypes array
|
|
154
|
+
*/
|
|
155
|
+
if (drizzleColumnType && !drizzleColumnTypes.includes(drizzleColumnType)) {
|
|
156
|
+
/**
|
|
157
|
+
* If it's not, add it to the array
|
|
158
|
+
*/
|
|
159
|
+
drizzleColumnTypes.push(drizzleColumnType);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
schema = schema.slice(0, -2); // Remove the last comma and newline
|
|
163
|
+
schema += `\n});\n\n`;
|
|
164
|
+
return {
|
|
165
|
+
schema: schema,
|
|
166
|
+
columnTypes: drizzleColumnTypes,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set-master-admin.d.ts","sourceRoot":"","sources":["../../src/lib/set-master-admin.ts"],"names":[],"mappings":"AAGA,wBAAsB,cAAc,kBA2DnC"}
|