create-fullstack-boilerplate 2.2.2 ā 3.0.0
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/index.js +24 -11
- package/lib/addDB.js +153 -21
- package/lib/inquirePrompts/extraDBPrompts.js +72 -0
- package/lib/inquirePrompts/mainPrompts.js +46 -0
- package/lib/inquirePrompts/routePrompts.js +37 -0
- package/lib/inquirePrompts/tableAttributePrompts.js +148 -0
- package/lib/inquirePrompts/tablePrompts.js +31 -0
- package/lib/prompts.js +13 -289
- package/lib/setupExtraDB.js +12 -75
- package/lib/tableHandlers/createStatementGenerator.js +90 -0
- package/lib/tableHandlers/dbTableAddition.js +98 -0
- package/lib/tableHandlers/handleTableCreation.js +77 -0
- package/package.json +1 -1
- package/template/Frontend/index.html +2 -2
- package/template/Frontend/public/load.webp +0 -0
- package/template/Frontend/src/components/Loader.jsx +1 -1
- package/template/Frontend/src/pages/Login.jsx +1 -1
- package/template/Frontend/public/PMDLogo.png +0 -0
- package/template/Frontend/public/tabicon.png +0 -0
package/index.js
CHANGED
|
@@ -13,12 +13,11 @@
|
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// ---- Your existing create-project code ----
|
|
17
16
|
const path = require("path");
|
|
18
17
|
const copyProject = require('./lib/copyProject');
|
|
19
|
-
const { runInstall, installDBDriver, normalizePath } = require("./lib/utils");
|
|
18
|
+
const { runInstall, installDBDriver, normalizePath } = require("./lib/utils");
|
|
20
19
|
const setupMainDB = require("./lib/setupMainDB");
|
|
21
|
-
const setupExtraDB = require("./lib/setupExtraDB");
|
|
20
|
+
const { setupExtraDB, handleTableCreation } = require("./lib/setupExtraDB");
|
|
22
21
|
const testDBConnection = require("./lib/testDBConnection");
|
|
23
22
|
const { mainPrompts, extraDBPrompts } = require("./lib/prompts");
|
|
24
23
|
|
|
@@ -29,7 +28,7 @@
|
|
|
29
28
|
let templateDir;
|
|
30
29
|
|
|
31
30
|
// Try __dirname first (works for local/dev)
|
|
32
|
-
const localTemplate = normalizePath(path.resolve(__dirname, "template"));
|
|
31
|
+
const localTemplate = normalizePath(path.resolve(__dirname, "template"));
|
|
33
32
|
console.log("š Checking local template:", localTemplate);
|
|
34
33
|
|
|
35
34
|
if (fs.existsSync(localTemplate)) {
|
|
@@ -39,7 +38,7 @@
|
|
|
39
38
|
// Fallback for npx (temp folder)
|
|
40
39
|
try {
|
|
41
40
|
const pkg = require.resolve("create-fullstack-boilerplate/package.json");
|
|
42
|
-
templateDir = normalizePath(path.resolve(path.dirname(pkg), "template"));
|
|
41
|
+
templateDir = normalizePath(path.resolve(path.dirname(pkg), "template"));
|
|
43
42
|
console.log("ā
Found npm template:", templateDir);
|
|
44
43
|
} catch (err) {
|
|
45
44
|
console.error("ā Could not resolve package:", err.message);
|
|
@@ -68,7 +67,7 @@
|
|
|
68
67
|
console.log("\nšļø Setting Up Full Stack Project...\n");
|
|
69
68
|
|
|
70
69
|
const answers = await mainPrompts();
|
|
71
|
-
const targetDir = normalizePath(path.join(process.cwd(), answers.projectName));
|
|
70
|
+
const targetDir = normalizePath(path.join(process.cwd(), answers.projectName));
|
|
72
71
|
|
|
73
72
|
console.log("Target directory:", targetDir);
|
|
74
73
|
|
|
@@ -84,23 +83,37 @@
|
|
|
84
83
|
|
|
85
84
|
await setupMainDB(targetDir, answers.dbDialect);
|
|
86
85
|
console.log("ā”ļø Installing backend dependencies...");
|
|
87
|
-
await runInstall(normalizePath(path.join(targetDir, "Backend")));
|
|
86
|
+
await runInstall(normalizePath(path.join(targetDir, "Backend")));
|
|
88
87
|
|
|
89
88
|
console.log("ā”ļø Installing frontend dependencies...");
|
|
90
|
-
await runInstall(normalizePath(path.join(targetDir, "Frontend")));
|
|
89
|
+
await runInstall(normalizePath(path.join(targetDir, "Frontend")));
|
|
91
90
|
|
|
92
91
|
console.log("ā”ļø Installing Your Db Dialect: ", answers.dbDialect);
|
|
93
|
-
await installDBDriver(normalizePath(path.join(targetDir, "Backend")), answers.dbDialect);
|
|
94
|
-
|
|
92
|
+
await installDBDriver(normalizePath(path.join(targetDir, "Backend")), answers.dbDialect);
|
|
95
93
|
|
|
96
94
|
if (answers.addExtraDB) {
|
|
97
95
|
const extraDB = await extraDBPrompts();
|
|
98
96
|
console.log(`\nš Testing connection for ${extraDB.dbKey}...`);
|
|
99
|
-
await installDBDriver(normalizePath(path.join(targetDir, "Backend")), extraDB.dialect);
|
|
97
|
+
await installDBDriver(normalizePath(path.join(targetDir, "Backend")), extraDB.dialect);
|
|
100
98
|
const ok = await testDBConnection(targetDir, extraDB);
|
|
101
99
|
|
|
102
100
|
if (ok) {
|
|
101
|
+
// Setup the database configuration and first table
|
|
103
102
|
await setupExtraDB(targetDir, extraDB);
|
|
103
|
+
|
|
104
|
+
const firstTable = {
|
|
105
|
+
tableName: extraDB.tableName,
|
|
106
|
+
attributes: extraDB.attributes
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
await handleTableCreation(
|
|
110
|
+
targetDir,
|
|
111
|
+
extraDB.dbKey,
|
|
112
|
+
firstTable,
|
|
113
|
+
extraDB.dialect,
|
|
114
|
+
true // isNewDB
|
|
115
|
+
);
|
|
116
|
+
|
|
104
117
|
console.log(`ā
Extra DB '${extraDB.dbKey}' successfully integrated.\n`);
|
|
105
118
|
} else {
|
|
106
119
|
console.log(`ā ļø Connection failed. Skipping extra DB setup.\n`);
|
package/lib/addDB.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const { extraDBPrompts } = require("./prompts");
|
|
3
|
-
const testDBConnection = require("./testDBConnection");
|
|
4
|
-
const setupExtraDB = require("./setupExtraDB");
|
|
5
2
|
const fs = require("fs-extra");
|
|
6
|
-
|
|
3
|
+
const inquirer = require("inquirer");
|
|
4
|
+
const { tablePrompts } = require("./prompts");
|
|
5
|
+
const testDBConnection = require("./testDBConnection");
|
|
6
|
+
const { setupExtraDB, handleTableCreation } = require("./setupExtraDB");
|
|
7
|
+
const { installDBDriver } = require("./utils");
|
|
7
8
|
|
|
8
9
|
module.exports = async function addDB() {
|
|
9
10
|
console.log(`\nā Add New Database to Existing Project\n`);
|
|
@@ -31,28 +32,134 @@ module.exports = async function addDB() {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
try {
|
|
34
|
-
//
|
|
35
|
-
const extraDB = await extraDBPrompts();
|
|
36
|
-
|
|
37
|
-
// Check if DB with same key already exists
|
|
35
|
+
// Get list of existing databases
|
|
38
36
|
const dbConfigsPath = path.join(dbDir, "dbConfigs.js");
|
|
37
|
+
let existingDBs = [];
|
|
38
|
+
|
|
39
39
|
if (await fs.pathExists(dbConfigsPath)) {
|
|
40
40
|
const dbConfigsContent = await fs.readFile(dbConfigsPath, "utf8");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return;
|
|
41
|
+
const keyMatches = dbConfigsContent.match(/key:\s*"([^"]+)"/g);
|
|
42
|
+
if (keyMatches) {
|
|
43
|
+
existingDBs = keyMatches.map(match => match.match(/"([^"]+)"/)[1]);
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.log(
|
|
52
|
-
console.log(
|
|
47
|
+
// Show existing databases if any
|
|
48
|
+
if (existingDBs.length > 0) {
|
|
49
|
+
console.log(`\nš Existing databases in your project:`);
|
|
50
|
+
existingDBs.forEach(db => console.log(` - ${db}`));
|
|
51
|
+
console.log('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Ask what user wants to do
|
|
55
|
+
const { action } = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: 'list',
|
|
58
|
+
name: 'action',
|
|
59
|
+
message: 'What would you like to do?',
|
|
60
|
+
choices: [
|
|
61
|
+
{ name: 'Add a new database', value: 'new' },
|
|
62
|
+
...(existingDBs.length > 0 ? [{ name: 'Add tables to an existing database', value: 'existing' }] : [])
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
// If user wants to add to existing DB
|
|
68
|
+
if (action === 'existing') {
|
|
69
|
+
const { dbKey } = await inquirer.prompt([
|
|
70
|
+
{
|
|
71
|
+
type: 'list',
|
|
72
|
+
name: 'dbKey',
|
|
73
|
+
message: 'Select database to add tables to:',
|
|
74
|
+
choices: existingDBs
|
|
75
|
+
}
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
console.log(`\nā
Adding tables to '${dbKey}'...\n`);
|
|
79
|
+
|
|
80
|
+
await handleTableCreation(
|
|
81
|
+
targetDir,
|
|
82
|
+
dbKey,
|
|
83
|
+
null, // no first table
|
|
84
|
+
'mysql', // default dialect
|
|
85
|
+
false // isNewDB = false
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
console.log(`ā
Successfully updated ${dbKey}.\n`);
|
|
89
|
+
console.log(`š Next steps:`);
|
|
90
|
+
console.log(` 1. Use the CREATE statements above to create tables in your database`);
|
|
91
|
+
console.log(` 2. Import and use in your routes: const { ${dbKey} } = require('./Models');\n`);
|
|
92
|
+
|
|
53
93
|
return;
|
|
54
94
|
}
|
|
55
95
|
|
|
96
|
+
// New DB flow
|
|
97
|
+
console.log(`\nš Setting up a new database...\n`);
|
|
98
|
+
|
|
99
|
+
// Ask for DB identifier first
|
|
100
|
+
const { dbKey } = await inquirer.prompt([
|
|
101
|
+
{
|
|
102
|
+
name: 'dbKey',
|
|
103
|
+
message: 'DB Identifier (e.g., REPORTING_DB, myDB, test-db):',
|
|
104
|
+
validate: (input) => {
|
|
105
|
+
if (!input || input.trim().length === 0) {
|
|
106
|
+
return 'Cannot be empty';
|
|
107
|
+
}
|
|
108
|
+
if (/[\s@#$%^&*()+={}[\];:'",.<>?/\\|`~]/.test(input)) {
|
|
109
|
+
return 'Special characters and spaces not allowed';
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
// Collect DB credentials
|
|
117
|
+
const dbCredentials = await inquirer.prompt([
|
|
118
|
+
{
|
|
119
|
+
name: 'database',
|
|
120
|
+
message: 'DB Name:',
|
|
121
|
+
validate: (input) => input.trim().length > 0 ? true : 'Cannot be empty'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'username',
|
|
125
|
+
message: 'DB Username:',
|
|
126
|
+
validate: (input) => input.trim().length > 0 ? true : 'Cannot be empty'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'password',
|
|
130
|
+
message: 'DB Password:'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'host',
|
|
134
|
+
message: 'DB Host:',
|
|
135
|
+
default: 'localhost'
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'port',
|
|
139
|
+
message: 'DB Port:',
|
|
140
|
+
default: '3306',
|
|
141
|
+
validate: (input) => {
|
|
142
|
+
const port = parseInt(input);
|
|
143
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
144
|
+
return 'Please enter a valid port number (1-65535)';
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: 'list',
|
|
151
|
+
name: 'dialect',
|
|
152
|
+
message: 'DB Dialect:',
|
|
153
|
+
choices: ['mysql', 'mariadb', 'postgres']
|
|
154
|
+
}
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
const extraDB = {
|
|
158
|
+
dbKey: dbKey,
|
|
159
|
+
...dbCredentials
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Test connection
|
|
56
163
|
console.log(`\nš Testing connection for ${extraDB.dbKey}...\n`);
|
|
57
164
|
const ok = await testDBConnection(targetDir, extraDB);
|
|
58
165
|
|
|
@@ -61,11 +168,36 @@ module.exports = async function addDB() {
|
|
|
61
168
|
return;
|
|
62
169
|
}
|
|
63
170
|
|
|
171
|
+
// Install DB driver
|
|
172
|
+
await installDBDriver(path.join(targetDir, "Backend"), extraDB.dialect);
|
|
173
|
+
|
|
174
|
+
// Collect first table information
|
|
175
|
+
console.log(`\nš Now let's create the first table for ${extraDB.dbKey}...\n`);
|
|
176
|
+
const firstTableConfig = await tablePrompts();
|
|
177
|
+
|
|
178
|
+
// Setup database with first table
|
|
179
|
+
extraDB.tableName = firstTableConfig.tableName;
|
|
180
|
+
extraDB.attributes = firstTableConfig.attributes;
|
|
64
181
|
await setupExtraDB(targetDir, extraDB);
|
|
65
|
-
|
|
182
|
+
|
|
183
|
+
// Handle additional tables
|
|
184
|
+
const firstTable = {
|
|
185
|
+
tableName: firstTableConfig.tableName,
|
|
186
|
+
attributes: firstTableConfig.attributes
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
await handleTableCreation(
|
|
190
|
+
targetDir,
|
|
191
|
+
extraDB.dbKey,
|
|
192
|
+
firstTable,
|
|
193
|
+
extraDB.dialect,
|
|
194
|
+
true // isNewDB
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
console.log(`ā
Successfully added ${extraDB.dbKey} to project.\n`);
|
|
66
198
|
console.log(`š Next steps:`);
|
|
67
|
-
console.log(` 1.
|
|
68
|
-
console.log(` 2.
|
|
199
|
+
console.log(` 1. Use the CREATE statements above to create tables in your database`);
|
|
200
|
+
console.log(` 2. Check backend/.env for database credentials`);
|
|
69
201
|
console.log(` 3. Import and use in your routes: const { ${extraDB.dbKey} } = require('./Models');\n`);
|
|
70
202
|
|
|
71
203
|
} catch (err) {
|
|
@@ -74,4 +206,4 @@ module.exports = async function addDB() {
|
|
|
74
206
|
console.log(`\nStack trace:`, err.stack);
|
|
75
207
|
}
|
|
76
208
|
}
|
|
77
|
-
};
|
|
209
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const inquirer = require("inquirer");
|
|
2
|
+
const collectTableAttributes = require('./tableAttributePrompts')
|
|
3
|
+
|
|
4
|
+
const extraDBPrompts = async () => {
|
|
5
|
+
const basicInfo = await inquirer.prompt([
|
|
6
|
+
{
|
|
7
|
+
name: 'dbKey',
|
|
8
|
+
message: 'DB Identifier (e.g., REPORTING_DB, myDB, test-db):',
|
|
9
|
+
validate: (input) => {
|
|
10
|
+
if (!input || input.trim().length === 0) {
|
|
11
|
+
return 'Cannot be empty';
|
|
12
|
+
}
|
|
13
|
+
if (/[\s@#$%^&*()+={}[\];:'",.<>?/\\|`~]/.test(input)) {
|
|
14
|
+
return 'Special characters and spaces not allowed';
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{ name: 'database', message: 'DB Name:', validate: (input) => input.trim().length > 0 ? true : 'Cannot be empty' },
|
|
20
|
+
{ name: 'username', message: 'DB Username:', validate: (input) => input.trim().length > 0 ? true : 'Cannot be empty' },
|
|
21
|
+
{ name: 'password', message: 'DB Password:' },
|
|
22
|
+
{ name: 'host', message: 'DB Host:', default: 'localhost' },
|
|
23
|
+
{
|
|
24
|
+
name: 'port',
|
|
25
|
+
message: 'DB Port:',
|
|
26
|
+
default: '3306',
|
|
27
|
+
validate: (input) => {
|
|
28
|
+
const port = parseInt(input);
|
|
29
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
30
|
+
return 'Please enter a valid port number (1-65535)';
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'list',
|
|
37
|
+
name: 'dialect',
|
|
38
|
+
message: 'DB Dialect:',
|
|
39
|
+
choices: ['mysql', 'mariadb', 'postgres']
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'tableName',
|
|
43
|
+
message: 'Model / Table Name:',
|
|
44
|
+
default: 'sample_table',
|
|
45
|
+
validate: (input) => {
|
|
46
|
+
if (!input || input.trim().length === 0) {
|
|
47
|
+
return 'Cannot be empty';
|
|
48
|
+
}
|
|
49
|
+
if (/\s/.test(input)) {
|
|
50
|
+
return 'Spaces not allowed - use underscores instead';
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'defineAttributes',
|
|
59
|
+
message: 'Do you want to define table attributes now?',
|
|
60
|
+
default: false
|
|
61
|
+
}
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// If user wants to define attributes, collect them
|
|
65
|
+
if (basicInfo.defineAttributes) {
|
|
66
|
+
basicInfo.attributes = await collectTableAttributes(basicInfo.dialect);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return basicInfo;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
module.exports = extraDBPrompts;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const inquirer = require("inquirer");
|
|
2
|
+
|
|
3
|
+
const mainPrompts = () => {
|
|
4
|
+
return inquirer.prompt([
|
|
5
|
+
{
|
|
6
|
+
name: 'projectName',
|
|
7
|
+
message: 'Project name',
|
|
8
|
+
default: 'my-fullstack-app',
|
|
9
|
+
validate: (input) => {
|
|
10
|
+
if (!input || input.trim().length === 0) {
|
|
11
|
+
return 'Project name cannot be empty';
|
|
12
|
+
}
|
|
13
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
|
14
|
+
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: 'list',
|
|
21
|
+
name: 'dbDialect',
|
|
22
|
+
message: 'Main DB Dialect:',
|
|
23
|
+
choices: ['mysql', 'mariadb', 'postgres']
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'confirm',
|
|
27
|
+
name: 'addExtraDB',
|
|
28
|
+
message: 'Add an extra database connection?',
|
|
29
|
+
default: false
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'confirm',
|
|
33
|
+
name: 'initGit',
|
|
34
|
+
message: 'Initialize Git repository?',
|
|
35
|
+
default: false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'remoteRepo',
|
|
39
|
+
message: 'Enter remote Git repository URL:',
|
|
40
|
+
when: (answers) => answers.initGit === true,
|
|
41
|
+
validate: (input) => input.length > 0 ? true : "This field cannot be empty."
|
|
42
|
+
}
|
|
43
|
+
]);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports = mainPrompts;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const inquirer = require("inquirer");
|
|
2
|
+
|
|
3
|
+
const routePrompts = () => {
|
|
4
|
+
return inquirer.prompt([
|
|
5
|
+
{
|
|
6
|
+
name: 'routeName',
|
|
7
|
+
message: 'Route name (e.g., users, products):',
|
|
8
|
+
validate: (input) => {
|
|
9
|
+
if (!input || input.trim().length === 0) {
|
|
10
|
+
return 'Route name cannot be empty';
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'routePath',
|
|
17
|
+
message: 'Route path (e.g., /users):',
|
|
18
|
+
default: (answers) => `/${answers.routeName}`,
|
|
19
|
+
validate: (input) => {
|
|
20
|
+
if (!input || input.trim().length === 0) {
|
|
21
|
+
return 'Route path cannot be empty';
|
|
22
|
+
}
|
|
23
|
+
if (!input.startsWith('/')) {
|
|
24
|
+
return 'Route path should start with /';
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'confirm',
|
|
31
|
+
name: 'needsAuth',
|
|
32
|
+
message: 'Does this route require authentication?',
|
|
33
|
+
default: true
|
|
34
|
+
}
|
|
35
|
+
]);
|
|
36
|
+
};
|
|
37
|
+
module.exports = routePrompts;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const inquirer = require("inquirer");
|
|
2
|
+
const { getDataTypeChoices, needsLength } = require('../dataTypes');
|
|
3
|
+
|
|
4
|
+
const collectTableAttributes = async (dialect) => {
|
|
5
|
+
const attributes = [];
|
|
6
|
+
|
|
7
|
+
console.log('\nš Define table attributes. Start with the primary key.\n');
|
|
8
|
+
|
|
9
|
+
// Primary key first
|
|
10
|
+
const pkAnswers = await inquirer.prompt([
|
|
11
|
+
{
|
|
12
|
+
name: 'name',
|
|
13
|
+
message: 'Primary key column name:',
|
|
14
|
+
default: 'id',
|
|
15
|
+
validate: (input) => {
|
|
16
|
+
if (!input || input.trim().length === 0) return 'Column name cannot be empty';
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'list',
|
|
22
|
+
name: 'type',
|
|
23
|
+
message: 'Primary key data type:',
|
|
24
|
+
choices: ['INTEGER', 'BIGINT', 'UUID', 'STRING'],
|
|
25
|
+
default: 'INTEGER'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'confirm',
|
|
29
|
+
name: 'autoIncrement',
|
|
30
|
+
message: 'Auto-increment?',
|
|
31
|
+
default: true,
|
|
32
|
+
when: (answers) => answers.type === 'INTEGER' || answers.type === 'BIGINT'
|
|
33
|
+
}
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
attributes.push({
|
|
37
|
+
name: pkAnswers.name,
|
|
38
|
+
type: pkAnswers.type,
|
|
39
|
+
primaryKey: true,
|
|
40
|
+
autoIncrement: pkAnswers.autoIncrement || false,
|
|
41
|
+
allowNull: false
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log('\nā
Primary key added. Now add other attributes.\n');
|
|
45
|
+
|
|
46
|
+
// Collect other attributes
|
|
47
|
+
let addMore = true;
|
|
48
|
+
while (addMore) {
|
|
49
|
+
const attrAnswers = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
name: 'name',
|
|
52
|
+
message: 'Column name:',
|
|
53
|
+
validate: (input) => {
|
|
54
|
+
if (!input || input.trim().length === 0) {
|
|
55
|
+
return 'Cannot be empty';
|
|
56
|
+
}
|
|
57
|
+
if (/\s/.test(input)) {
|
|
58
|
+
return 'Spaces not allowed - use underscores instead';
|
|
59
|
+
}
|
|
60
|
+
if (attributes.find(a => a.name === input)) return 'Column name already exists';
|
|
61
|
+
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
type: 'list',
|
|
68
|
+
name: 'type',
|
|
69
|
+
message: 'Data type:',
|
|
70
|
+
choices: getDataTypeChoices(dialect)
|
|
71
|
+
}
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
// Ask for length if needed
|
|
75
|
+
if (needsLength(dialect, attrAnswers.type)) {
|
|
76
|
+
const lengthAnswer = await inquirer.prompt([
|
|
77
|
+
{
|
|
78
|
+
name: 'length',
|
|
79
|
+
message: `Length/Precision for ${attrAnswers.type}:`,
|
|
80
|
+
default: attrAnswers.type === 'STRING' ? '255' : '10,2',
|
|
81
|
+
validate: (input) => {
|
|
82
|
+
if (!input || input.trim().length === 0) return 'Length cannot be empty';
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]);
|
|
87
|
+
attrAnswers.length = lengthAnswer.length;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Ask for constraints
|
|
91
|
+
const constraints = await inquirer.prompt([
|
|
92
|
+
{
|
|
93
|
+
type: 'confirm',
|
|
94
|
+
name: 'allowNull',
|
|
95
|
+
message: 'Allow NULL values?',
|
|
96
|
+
default: true
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: 'confirm',
|
|
100
|
+
name: 'unique',
|
|
101
|
+
message: 'Should this be unique?',
|
|
102
|
+
default: false
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'confirm',
|
|
106
|
+
name: 'hasDefault',
|
|
107
|
+
message: 'Set a default value?',
|
|
108
|
+
default: false
|
|
109
|
+
}
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
if (constraints.hasDefault) {
|
|
113
|
+
const defaultAnswer = await inquirer.prompt([
|
|
114
|
+
{
|
|
115
|
+
name: 'defaultValue',
|
|
116
|
+
message: 'Default value:',
|
|
117
|
+
validate: (input) => input !== undefined && input !== '' ? true : 'Provide a default value'
|
|
118
|
+
}
|
|
119
|
+
]);
|
|
120
|
+
attrAnswers.defaultValue = defaultAnswer.defaultValue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
attributes.push({
|
|
124
|
+
name: attrAnswers.name,
|
|
125
|
+
type: attrAnswers.type,
|
|
126
|
+
length: attrAnswers.length,
|
|
127
|
+
allowNull: constraints.allowNull,
|
|
128
|
+
unique: constraints.unique,
|
|
129
|
+
defaultValue: attrAnswers.defaultValue
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const continueAnswer = await inquirer.prompt([
|
|
133
|
+
{
|
|
134
|
+
type: 'confirm',
|
|
135
|
+
name: 'addMore',
|
|
136
|
+
message: 'Add another attribute?',
|
|
137
|
+
default: true
|
|
138
|
+
}
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
addMore = continueAnswer.addMore;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`\nā
Added ${attributes.length} attributes to the model.\n`);
|
|
145
|
+
return attributes;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = collectTableAttributes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const inquirer = require("inquirer");
|
|
2
|
+
const collectTableAttributes = require('./tableAttributePrompts')
|
|
3
|
+
|
|
4
|
+
const tablePrompts = async () => {
|
|
5
|
+
const tableInfo = await inquirer.prompt([
|
|
6
|
+
{
|
|
7
|
+
name: 'tableName',
|
|
8
|
+
message: 'Model / Table Name:',
|
|
9
|
+
validate: (input) => {
|
|
10
|
+
if (!input || input.trim().length === 0) {
|
|
11
|
+
return 'Table name cannot be empty';
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
type: 'confirm',
|
|
18
|
+
name: 'defineAttributes',
|
|
19
|
+
message: 'Do you want to define table attributes now?',
|
|
20
|
+
default: true
|
|
21
|
+
}
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
if (tableInfo.defineAttributes) {
|
|
25
|
+
tableInfo.attributes = await collectTableAttributes('mysql'); // You can pass dialect if needed
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return tableInfo;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
module.exports = tablePrompts
|
package/lib/prompts.js
CHANGED
|
@@ -1,289 +1,13 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
|
15
|
-
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
type: 'list',
|
|
22
|
-
name: 'dbDialect',
|
|
23
|
-
message: 'Main DB Dialect:',
|
|
24
|
-
choices: ['mysql', 'mariadb', 'postgres']
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
type: 'confirm',
|
|
28
|
-
name: 'addExtraDB',
|
|
29
|
-
message: 'Add an extra database connection?',
|
|
30
|
-
default: false
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
type: 'confirm',
|
|
34
|
-
name: 'initGit',
|
|
35
|
-
message: 'Initialize Git repository?',
|
|
36
|
-
default: false
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: 'remoteRepo',
|
|
40
|
-
message: 'Enter remote Git repository URL:',
|
|
41
|
-
when: (answers) => answers.initGit === true,
|
|
42
|
-
validate: (input) => input.length > 0 ? true : "This field cannot be empty."
|
|
43
|
-
}
|
|
44
|
-
]);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
module.exports.extraDBPrompts = async () => {
|
|
48
|
-
const basicInfo = await inquirer.prompt([
|
|
49
|
-
{
|
|
50
|
-
name: 'dbKey',
|
|
51
|
-
message: 'DB Identifier (e.g., REPORTING_DB):',
|
|
52
|
-
validate: (input) => {
|
|
53
|
-
if (!input || input.trim().length === 0) {
|
|
54
|
-
return 'DB identifier cannot be empty';
|
|
55
|
-
}
|
|
56
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(input)) {
|
|
57
|
-
return 'DB identifier should be UPPERCASE with underscores (e.g., MY_DB)';
|
|
58
|
-
}
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
{ name: 'database', message: 'DB Name:', validate: (input) => input.trim().length > 0 ? true : 'Cannot be empty' },
|
|
63
|
-
{ name: 'username', message: 'DB Username:', validate: (input) => input.trim().length > 0 ? true : 'Cannot be empty' },
|
|
64
|
-
{ name: 'password', message: 'DB Password:' },
|
|
65
|
-
{ name: 'host', message: 'DB Host:', default: 'localhost' },
|
|
66
|
-
{
|
|
67
|
-
name: 'port',
|
|
68
|
-
message: 'DB Port:',
|
|
69
|
-
default: '3306',
|
|
70
|
-
validate: (input) => {
|
|
71
|
-
const port = parseInt(input);
|
|
72
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
73
|
-
return 'Please enter a valid port number (1-65535)';
|
|
74
|
-
}
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
type: 'list',
|
|
80
|
-
name: 'dialect',
|
|
81
|
-
message: 'DB Dialect:',
|
|
82
|
-
choices: ['mysql', 'mariadb', 'postgres']
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: 'tableName',
|
|
86
|
-
message: 'Model / Table Name:',
|
|
87
|
-
default: 'sample_table',
|
|
88
|
-
validate: (input) => {
|
|
89
|
-
if (!input || input.trim().length === 0) {
|
|
90
|
-
return 'Table name cannot be empty';
|
|
91
|
-
}
|
|
92
|
-
if (!/^[a-z][a-z0-9_]*$/.test(input)) {
|
|
93
|
-
return 'Table name should be lowercase with underscores (e.g., user_profiles)';
|
|
94
|
-
}
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
type: 'confirm',
|
|
100
|
-
name: 'defineAttributes',
|
|
101
|
-
message: 'Do you want to define table attributes now?',
|
|
102
|
-
default: false
|
|
103
|
-
}
|
|
104
|
-
]);
|
|
105
|
-
|
|
106
|
-
// If user wants to define attributes, collect them
|
|
107
|
-
if (basicInfo.defineAttributes) {
|
|
108
|
-
basicInfo.attributes = await collectTableAttributes(basicInfo.dialect);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return basicInfo;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
async function collectTableAttributes(dialect) {
|
|
115
|
-
const attributes = [];
|
|
116
|
-
|
|
117
|
-
console.log('\nš Define table attributes. Start with the primary key.\n');
|
|
118
|
-
|
|
119
|
-
// Primary key first
|
|
120
|
-
const pkAnswers = await inquirer.prompt([
|
|
121
|
-
{
|
|
122
|
-
name: 'name',
|
|
123
|
-
message: 'Primary key column name:',
|
|
124
|
-
default: 'id',
|
|
125
|
-
validate: (input) => {
|
|
126
|
-
if (!input || input.trim().length === 0) return 'Column name cannot be empty';
|
|
127
|
-
if (!/^[a-z][a-z0-9_]*$/.test(input)) return 'Use lowercase with underscores';
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
type: 'list',
|
|
133
|
-
name: 'type',
|
|
134
|
-
message: 'Primary key data type:',
|
|
135
|
-
choices: ['INTEGER', 'BIGINT', 'UUID', 'STRING'],
|
|
136
|
-
default: 'INTEGER'
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
type: 'confirm',
|
|
140
|
-
name: 'autoIncrement',
|
|
141
|
-
message: 'Auto-increment?',
|
|
142
|
-
default: true,
|
|
143
|
-
when: (answers) => answers.type === 'INTEGER' || answers.type === 'BIGINT'
|
|
144
|
-
}
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
attributes.push({
|
|
148
|
-
name: pkAnswers.name,
|
|
149
|
-
type: pkAnswers.type,
|
|
150
|
-
primaryKey: true,
|
|
151
|
-
autoIncrement: pkAnswers.autoIncrement || false,
|
|
152
|
-
allowNull: false
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
console.log('\nā
Primary key added. Now add other attributes.\n');
|
|
156
|
-
|
|
157
|
-
// Collect other attributes
|
|
158
|
-
let addMore = true;
|
|
159
|
-
while (addMore) {
|
|
160
|
-
const attrAnswers = await inquirer.prompt([
|
|
161
|
-
{
|
|
162
|
-
name: 'name',
|
|
163
|
-
message: 'Column name:',
|
|
164
|
-
validate: (input) => {
|
|
165
|
-
if (!input || input.trim().length === 0) return 'Column name cannot be empty';
|
|
166
|
-
if (!/^[a-z][a-z0-9_]*$/.test(input)) return 'Use lowercase with underscores';
|
|
167
|
-
if (attributes.find(a => a.name === input)) return 'Column name already exists';
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
type: 'list',
|
|
173
|
-
name: 'type',
|
|
174
|
-
message: 'Data type:',
|
|
175
|
-
choices: getDataTypeChoices(dialect)
|
|
176
|
-
}
|
|
177
|
-
]);
|
|
178
|
-
|
|
179
|
-
// Ask for length if needed
|
|
180
|
-
if (needsLength(dialect, attrAnswers.type)) {
|
|
181
|
-
const lengthAnswer = await inquirer.prompt([
|
|
182
|
-
{
|
|
183
|
-
name: 'length',
|
|
184
|
-
message: `Length/Precision for ${attrAnswers.type}:`,
|
|
185
|
-
default: attrAnswers.type === 'STRING' ? '255' : '10,2',
|
|
186
|
-
validate: (input) => {
|
|
187
|
-
if (!input || input.trim().length === 0) return 'Length cannot be empty';
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
]);
|
|
192
|
-
attrAnswers.length = lengthAnswer.length;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Ask for constraints
|
|
196
|
-
const constraints = await inquirer.prompt([
|
|
197
|
-
{
|
|
198
|
-
type: 'confirm',
|
|
199
|
-
name: 'allowNull',
|
|
200
|
-
message: 'Allow NULL values?',
|
|
201
|
-
default: true
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
type: 'confirm',
|
|
205
|
-
name: 'unique',
|
|
206
|
-
message: 'Should this be unique?',
|
|
207
|
-
default: false
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
type: 'confirm',
|
|
211
|
-
name: 'hasDefault',
|
|
212
|
-
message: 'Set a default value?',
|
|
213
|
-
default: false
|
|
214
|
-
}
|
|
215
|
-
]);
|
|
216
|
-
|
|
217
|
-
if (constraints.hasDefault) {
|
|
218
|
-
const defaultAnswer = await inquirer.prompt([
|
|
219
|
-
{
|
|
220
|
-
name: 'defaultValue',
|
|
221
|
-
message: 'Default value:',
|
|
222
|
-
validate: (input) => input !== undefined && input !== '' ? true : 'Provide a default value'
|
|
223
|
-
}
|
|
224
|
-
]);
|
|
225
|
-
attrAnswers.defaultValue = defaultAnswer.defaultValue;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
attributes.push({
|
|
229
|
-
name: attrAnswers.name,
|
|
230
|
-
type: attrAnswers.type,
|
|
231
|
-
length: attrAnswers.length,
|
|
232
|
-
allowNull: constraints.allowNull,
|
|
233
|
-
unique: constraints.unique,
|
|
234
|
-
defaultValue: attrAnswers.defaultValue
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const continueAnswer = await inquirer.prompt([
|
|
238
|
-
{
|
|
239
|
-
type: 'confirm',
|
|
240
|
-
name: 'addMore',
|
|
241
|
-
message: 'Add another attribute?',
|
|
242
|
-
default: true
|
|
243
|
-
}
|
|
244
|
-
]);
|
|
245
|
-
|
|
246
|
-
addMore = continueAnswer.addMore;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
console.log(`\nā
Added ${attributes.length} attributes to the model.\n`);
|
|
250
|
-
return attributes;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
module.exports.routePrompts = () => {
|
|
254
|
-
return inquirer.prompt([
|
|
255
|
-
{
|
|
256
|
-
name: 'routeName',
|
|
257
|
-
message: 'Route name (e.g., users, products):',
|
|
258
|
-
validate: (input) => {
|
|
259
|
-
if (!input || input.trim().length === 0) {
|
|
260
|
-
return 'Route name cannot be empty';
|
|
261
|
-
}
|
|
262
|
-
if (!/^[a-z][a-z0-9_]*$/.test(input)) {
|
|
263
|
-
return 'Route name should be lowercase with underscores (e.g., user_profiles)';
|
|
264
|
-
}
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
name: 'routePath',
|
|
270
|
-
message: 'Route path (e.g., /users):',
|
|
271
|
-
default: (answers) => `/${answers.routeName}`,
|
|
272
|
-
validate: (input) => {
|
|
273
|
-
if (!input || input.trim().length === 0) {
|
|
274
|
-
return 'Route path cannot be empty';
|
|
275
|
-
}
|
|
276
|
-
if (!input.startsWith('/')) {
|
|
277
|
-
return 'Route path should start with /';
|
|
278
|
-
}
|
|
279
|
-
return true;
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
type: 'confirm',
|
|
284
|
-
name: 'needsAuth',
|
|
285
|
-
message: 'Does this route require authentication?',
|
|
286
|
-
default: true
|
|
287
|
-
}
|
|
288
|
-
]);
|
|
289
|
-
};
|
|
1
|
+
const mainPrompts = require("./inquirePrompts/mainPrompts");
|
|
2
|
+
const extraDBPrompts = require("./inquirePrompts/extraDBPrompts");
|
|
3
|
+
const routePrompts = require("./inquirePrompts/routePrompts");
|
|
4
|
+
const tablePrompts = require("./inquirePrompts/tablePrompts");
|
|
5
|
+
const tableAttributePrompts = require("./inquirePrompts/tableAttributePrompts");
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
mainPrompts,
|
|
9
|
+
extraDBPrompts,
|
|
10
|
+
routePrompts,
|
|
11
|
+
tablePrompts,
|
|
12
|
+
tableAttributePrompts
|
|
13
|
+
};
|
package/lib/setupExtraDB.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const fs = require("fs-extra");
|
|
3
|
+
const { generateModelContent, addTableToDB } = require('./tableHandlers/dbTableAddition')
|
|
4
|
+
const handleTableCreation = require('./tableHandlers/handleTableCreation')
|
|
5
|
+
const generateCreateStatement = require('./tableHandlers/createStatementGenerator')
|
|
3
6
|
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
async function setupExtraDB(targetDir, dbConfig) {
|
|
6
9
|
const backendDir = path.join(targetDir, "Backend");
|
|
7
10
|
const modelsDir = path.join(backendDir, "Models");
|
|
8
11
|
const dbDir = path.join(backendDir, "DB");
|
|
@@ -96,77 +99,11 @@ $2`
|
|
|
96
99
|
console.log(`ā
Database '${dbConfig.dbKey}' configured successfully.`);
|
|
97
100
|
};
|
|
98
101
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (attr.length) {
|
|
108
|
-
attrDef += `(${attr.length})`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (attr.primaryKey) {
|
|
112
|
-
attrDef += `,\n primaryKey: true,\n autoIncrement: true`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (attr.allowNull === false) {
|
|
116
|
-
attrDef += `,\n allowNull: false`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (attr.unique) {
|
|
120
|
-
attrDef += `,\n unique: true`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (attr.defaultValue !== undefined) {
|
|
124
|
-
attrDef += `,\n defaultValue: ${JSON.stringify(attr.defaultValue)}`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
attrDef += `\n }`;
|
|
128
|
-
return attrDef;
|
|
129
|
-
}).join(',\n');
|
|
130
|
-
|
|
131
|
-
return `'use strict';
|
|
132
|
-
module.exports = (sequelize, DataTypes) => {
|
|
133
|
-
const ${modelName} = sequelize.define('${modelName}', {
|
|
134
|
-
${attributesStr}
|
|
135
|
-
}, {
|
|
136
|
-
tableName: '${dbConfig.tableName}',
|
|
137
|
-
timestamps: true
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
${modelName}.associate = function(models) {
|
|
141
|
-
// Define associations here
|
|
142
|
-
// Example: ${modelName}.belongsTo(models.OtherModel, { foreignKey: 'otherId' });
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
return ${modelName};
|
|
146
|
-
};
|
|
147
|
-
`;
|
|
148
|
-
} else {
|
|
149
|
-
// Simple model with just ID
|
|
150
|
-
return `'use strict';
|
|
151
|
-
module.exports = (sequelize, DataTypes) => {
|
|
152
|
-
const ${modelName} = sequelize.define('${modelName}', {
|
|
153
|
-
id: {
|
|
154
|
-
type: DataTypes.INTEGER,
|
|
155
|
-
primaryKey: true,
|
|
156
|
-
autoIncrement: true
|
|
157
|
-
}
|
|
158
|
-
}, {
|
|
159
|
-
tableName: '${dbConfig.tableName}',
|
|
160
|
-
timestamps: true
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
${modelName}.associate = function(models) {
|
|
164
|
-
// Define associations here
|
|
165
|
-
// Example: ${modelName}.belongsTo(models.OtherModel, { foreignKey: 'otherId' });
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
return ${modelName};
|
|
169
|
-
};
|
|
170
|
-
`;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
setupExtraDB,
|
|
105
|
+
addTableToDB,
|
|
106
|
+
handleTableCreation,
|
|
107
|
+
generateCreateStatement,
|
|
108
|
+
generateModelContent
|
|
109
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
function generateCreateStatement(tableName, attributes, dialect = 'mysql') {
|
|
2
|
+
const modelName = tableName.charAt(0).toUpperCase() + tableName.slice(1);
|
|
3
|
+
|
|
4
|
+
if (!attributes || attributes.length === 0) {
|
|
5
|
+
return `CREATE TABLE ${tableName} (
|
|
6
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
7
|
+
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
8
|
+
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
9
|
+
);`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const columns = attributes.map(attr => {
|
|
13
|
+
let colDef = ` ${attr.name} `;
|
|
14
|
+
|
|
15
|
+
// Map Sequelize types to SQL types
|
|
16
|
+
switch (attr.type) {
|
|
17
|
+
case 'INTEGER':
|
|
18
|
+
colDef += 'INT';
|
|
19
|
+
break;
|
|
20
|
+
case 'BIGINT':
|
|
21
|
+
colDef += 'BIGINT';
|
|
22
|
+
break;
|
|
23
|
+
case 'STRING':
|
|
24
|
+
colDef += `VARCHAR(${attr.length || 255})`;
|
|
25
|
+
break;
|
|
26
|
+
case 'TEXT':
|
|
27
|
+
colDef += 'TEXT';
|
|
28
|
+
break;
|
|
29
|
+
case 'BOOLEAN':
|
|
30
|
+
colDef += 'BOOLEAN';
|
|
31
|
+
break;
|
|
32
|
+
case 'DATE':
|
|
33
|
+
colDef += 'DATE';
|
|
34
|
+
break;
|
|
35
|
+
case 'DATEONLY':
|
|
36
|
+
colDef += 'DATE';
|
|
37
|
+
break;
|
|
38
|
+
case 'DECIMAL':
|
|
39
|
+
colDef += `DECIMAL(${attr.length || '10,2'})`;
|
|
40
|
+
break;
|
|
41
|
+
case 'FLOAT':
|
|
42
|
+
colDef += 'FLOAT';
|
|
43
|
+
break;
|
|
44
|
+
case 'DOUBLE':
|
|
45
|
+
colDef += 'DOUBLE';
|
|
46
|
+
break;
|
|
47
|
+
case 'UUID':
|
|
48
|
+
colDef += 'CHAR(36)';
|
|
49
|
+
break;
|
|
50
|
+
case 'JSON':
|
|
51
|
+
colDef += 'JSON';
|
|
52
|
+
break;
|
|
53
|
+
default:
|
|
54
|
+
colDef += attr.type;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (attr.primaryKey) {
|
|
58
|
+
colDef += ' PRIMARY KEY';
|
|
59
|
+
if (attr.autoIncrement) {
|
|
60
|
+
colDef += ' AUTO_INCREMENT';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (attr.allowNull === false && !attr.primaryKey) {
|
|
65
|
+
colDef += ' NOT NULL';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (attr.unique && !attr.primaryKey) {
|
|
69
|
+
colDef += ' UNIQUE';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (attr.defaultValue !== undefined) {
|
|
73
|
+
if (typeof attr.defaultValue === 'string') {
|
|
74
|
+
colDef += ` DEFAULT '${attr.defaultValue}'`;
|
|
75
|
+
} else {
|
|
76
|
+
colDef += ` DEFAULT ${attr.defaultValue}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return colDef;
|
|
81
|
+
}).join(',\n');
|
|
82
|
+
|
|
83
|
+
return `CREATE TABLE ${tableName} (
|
|
84
|
+
${columns},
|
|
85
|
+
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
86
|
+
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
87
|
+
);`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = generateCreateStatement;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs-extra");
|
|
3
|
+
|
|
4
|
+
function generateModelContent(dbConfig) {
|
|
5
|
+
const modelName = dbConfig.tableName.charAt(0).toUpperCase() + dbConfig.tableName.slice(1);
|
|
6
|
+
|
|
7
|
+
// Generate model content based on whether attributes were provided
|
|
8
|
+
if (dbConfig.attributes && dbConfig.attributes.length > 0) {
|
|
9
|
+
const attributesStr = dbConfig.attributes.map(attr => {
|
|
10
|
+
let attrDef = ` ${attr.name}: {\n type: DataTypes.${attr.type}`;
|
|
11
|
+
|
|
12
|
+
if (attr.length) {
|
|
13
|
+
attrDef += `(${attr.length})`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (attr.primaryKey) {
|
|
17
|
+
attrDef += `,\n primaryKey: true,\n autoIncrement: true`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (attr.allowNull === false) {
|
|
21
|
+
attrDef += `,\n allowNull: false`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (attr.unique) {
|
|
25
|
+
attrDef += `,\n unique: true`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (attr.defaultValue !== undefined) {
|
|
29
|
+
attrDef += `,\n defaultValue: ${JSON.stringify(attr.defaultValue)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
attrDef += `\n }`;
|
|
33
|
+
return attrDef;
|
|
34
|
+
}).join(',\n');
|
|
35
|
+
|
|
36
|
+
return `'use strict';
|
|
37
|
+
module.exports = (sequelize, DataTypes) => {
|
|
38
|
+
const ${modelName} = sequelize.define('${modelName}', {
|
|
39
|
+
${attributesStr}
|
|
40
|
+
}, {
|
|
41
|
+
tableName: '${dbConfig.tableName}',
|
|
42
|
+
timestamps: true
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
${modelName}.associate = function(models) {
|
|
46
|
+
// Define associations here
|
|
47
|
+
// Example: ${modelName}.belongsTo(models.OtherModel, { foreignKey: 'otherId' });
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return ${modelName};
|
|
51
|
+
};
|
|
52
|
+
`;
|
|
53
|
+
} else {
|
|
54
|
+
// Simple model with just ID
|
|
55
|
+
return `'use strict';
|
|
56
|
+
module.exports = (sequelize, DataTypes) => {
|
|
57
|
+
const ${modelName} = sequelize.define('${modelName}', {
|
|
58
|
+
id: {
|
|
59
|
+
type: DataTypes.INTEGER,
|
|
60
|
+
primaryKey: true,
|
|
61
|
+
autoIncrement: true
|
|
62
|
+
}
|
|
63
|
+
}, {
|
|
64
|
+
tableName: '${dbConfig.tableName}',
|
|
65
|
+
timestamps: true
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
${modelName}.associate = function(models) {
|
|
69
|
+
// Define associations here
|
|
70
|
+
// Example: ${modelName}.belongsTo(models.OtherModel, { foreignKey: 'otherId' });
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return ${modelName};
|
|
74
|
+
};
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// New function to add a table to an existing DB
|
|
80
|
+
async function addTableToDB(targetDir, dbKey, tableConfig) {
|
|
81
|
+
const backendDir = path.join(targetDir, "Backend");
|
|
82
|
+
const modelsDir = path.join(backendDir, "Models");
|
|
83
|
+
const dbModelsFolder = path.join(modelsDir, dbKey);
|
|
84
|
+
|
|
85
|
+
// Create the model file
|
|
86
|
+
const modelContent = generateModelContent(tableConfig);
|
|
87
|
+
const modelFileName = `${tableConfig.tableName}.js`;
|
|
88
|
+
const modelPath = path.join(dbModelsFolder, modelFileName);
|
|
89
|
+
|
|
90
|
+
await fs.writeFile(modelPath, modelContent);
|
|
91
|
+
console.log(`ā
Table model '${tableConfig.tableName}' created in ${dbKey}/`);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
addTableToDB,
|
|
97
|
+
generateModelContent
|
|
98
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const {tablePrompts} = require('../prompts')
|
|
2
|
+
const { addTableToDB } = require('./dbTableAddition')
|
|
3
|
+
const generateCreateStatement = require('./createStatementGenerator')
|
|
4
|
+
const inquirer = require("inquirer");
|
|
5
|
+
|
|
6
|
+
async function handleTableCreation(
|
|
7
|
+
targetDir,
|
|
8
|
+
dbKey,
|
|
9
|
+
firstTable = null,
|
|
10
|
+
dialect = 'mysql',
|
|
11
|
+
isNewDB = true
|
|
12
|
+
) {
|
|
13
|
+
const createdTables = [];
|
|
14
|
+
|
|
15
|
+
// If this is a new DB and we have a first table from initial setup
|
|
16
|
+
if (isNewDB && firstTable) {
|
|
17
|
+
createdTables.push(firstTable);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Ask if user wants to add more tables (or start adding if no firstTable)
|
|
21
|
+
let addMoreTables = true;
|
|
22
|
+
|
|
23
|
+
while (addMoreTables) {
|
|
24
|
+
// If we don't have a first table yet, or we're adding additional tables
|
|
25
|
+
if (!firstTable || createdTables.length > 0) {
|
|
26
|
+
// Ask if they want to add a table
|
|
27
|
+
const { wantToAdd } = await inquirer.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: "confirm",
|
|
30
|
+
name: "wantToAdd",
|
|
31
|
+
message: createdTables.length === 0
|
|
32
|
+
? "Do you want to create a table for this database?"
|
|
33
|
+
: "Do you want to add another table to this database?",
|
|
34
|
+
default: createdTables.length === 0 ? true : false
|
|
35
|
+
}
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
if (!wantToAdd) {
|
|
39
|
+
addMoreTables = false;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const tableConfig = await tablePrompts();
|
|
44
|
+
|
|
45
|
+
// For additional tables (not the first one), use addTableToDB
|
|
46
|
+
if (isNewDB && createdTables.length === 0) {
|
|
47
|
+
// First table in new DB - already created by setupExtraDB, just track it
|
|
48
|
+
createdTables[0] = tableConfig;
|
|
49
|
+
} else {
|
|
50
|
+
// Additional tables - need to create them
|
|
51
|
+
await addTableToDB(targetDir, dbKey, tableConfig);
|
|
52
|
+
createdTables.push(tableConfig);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// We already have the first table, just break to ask for more
|
|
56
|
+
addMoreTables = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Print CREATE statements for all tables
|
|
61
|
+
if (createdTables.length > 0) {
|
|
62
|
+
console.log(`\nš CREATE Statements for your tables:\n`);
|
|
63
|
+
console.log(`${'='.repeat(60)}\n`);
|
|
64
|
+
|
|
65
|
+
createdTables.forEach((table, index) => {
|
|
66
|
+
console.log(`-- Table ${index + 1}: ${table.tableName}`);
|
|
67
|
+
console.log(generateCreateStatement(table.tableName, table.attributes, dialect));
|
|
68
|
+
console.log('');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
console.log(`${'='.repeat(60)}\n`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return createdTables;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
module.exports = handleTableCreation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fullstack-boilerplate",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A powerful CLI tool to generate fully configured full-stack applications with React frontend and Express backend. Includes authentication middleware, database configuration, protected routes, route-based Axios instances, and encryption setup - all ready with a single command.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/pp.jpg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>
|
|
7
|
+
<title>Full Stack Boilerplate</title>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="root"></div>
|
|
Binary file
|
|
@@ -6,7 +6,7 @@ const SpinningLoader = ({ size = 10 }) => {
|
|
|
6
6
|
return (
|
|
7
7
|
<div className={`flex flex-col justify-center items-center gap-4 h-full w-full ${isDark ? 'bg-gray-900' : 'bg-white'}`}>
|
|
8
8
|
<img
|
|
9
|
-
src={'/
|
|
9
|
+
src={'/load.webp'}
|
|
10
10
|
alt="Loading..."
|
|
11
11
|
style={{ width: "auto", height: size }}
|
|
12
12
|
className="animate-pulse"
|
|
@@ -69,7 +69,7 @@ const Login = () => {
|
|
|
69
69
|
{/* Header */}
|
|
70
70
|
<div className="px-6 pt-6 pb-4 text-center">
|
|
71
71
|
<div className="relative mb-4">
|
|
72
|
-
<img src={'/
|
|
72
|
+
<img src={'/pp.jpg'} alt="PMD Logo" className='w-max h-20 rounded-full shadow-md shadow-gray-400 mx-auto' />
|
|
73
73
|
</div>
|
|
74
74
|
|
|
75
75
|
<h1 className="text-2xl font-articulatcf-demibold text-gray-800 mb-1">Welcome Back</h1>
|
|
Binary file
|
|
Binary file
|