@appxdigital/appx-core-cli 1.0.11 → 1.0.13
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/cli.js +578 -0
- package/package.json +5 -4
- package/scaffold/.env.template +33 -0
- package/scaffold/.eslintrc.js +26 -0
- package/scaffold/.gitattributes +2 -0
- package/scaffold/.prettierrc +9 -0
- package/scaffold/prisma/schema.prisma.template +47 -0
- package/scaffold/src/app.controller.ts +13 -0
- package/{templates/app.module.template.js → scaffold/src/app.module.ts} +8 -4
- package/scaffold/src/app.service.ts +8 -0
- package/{templates/adminjs/dashboard.template.js → scaffold/src/backoffice/components/dashboard.js} +0 -2
- package/{templates/permissions.config.template.js → scaffold/src/config/permissions.config.ts} +1 -4
- package/{templates/bootstrap.template.js → scaffold/src/main.ts} +9 -10
- package/{templates/prisma.module.template.js → scaffold/src/prisma/prisma.module.ts} +3 -3
- package/utils/fileUploadConfig.js +11 -11
- package/README.md +0 -159
- package/utils/dependency-versions.json +0 -28
- package/wizard.js +0 -599
- /package/{templates/admin.config.template.js → scaffold/src/config/admin.config.ts} +0 -0
package/cli.js
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Ensure node 20 at least
|
|
4
|
+
const [major] = process.versions.node.split('.').map(Number);
|
|
5
|
+
if (major < 20) {
|
|
6
|
+
console.error('❌ Node.js version 20 or higher is required to run this script.');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Dependencies
|
|
11
|
+
import {program} from 'commander';
|
|
12
|
+
import inquirer from 'inquirer';
|
|
13
|
+
import fs from 'fs-extra';
|
|
14
|
+
import fsCore from 'fs';
|
|
15
|
+
import {execSync} from 'child_process';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import {fileURLToPath} from 'url';
|
|
18
|
+
import cliProgress from 'cli-progress';
|
|
19
|
+
import crypto from 'crypto';
|
|
20
|
+
import pg from "pg";
|
|
21
|
+
import mysql from "mysql2/promise";
|
|
22
|
+
|
|
23
|
+
// Utils and data
|
|
24
|
+
import {gatherFileUploadSettings, insertFileUploadConfiguration} from './utils/fileUploadConfig.js';
|
|
25
|
+
import packageJson from './package.json' with {type: 'json'};
|
|
26
|
+
|
|
27
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
28
|
+
const __dirname = path.dirname(__filename);
|
|
29
|
+
|
|
30
|
+
const CORE_VERSION = '0.1.115';
|
|
31
|
+
|
|
32
|
+
const phaseDurations = {
|
|
33
|
+
installDependencies: 30,
|
|
34
|
+
setupProjectStructure: 5,
|
|
35
|
+
createPrismaMigration: 5,
|
|
36
|
+
generateCoreModels: 10,
|
|
37
|
+
gitInit: 1,
|
|
38
|
+
migrateDatabase: 5,
|
|
39
|
+
fileUploadSetup: 5
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const calculateTotalETA = () => {
|
|
43
|
+
return Object.values(phaseDurations).reduce((acc, curr) => acc + curr, 0);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const logo = `\x1b[36m
|
|
47
|
+
_ _______ _______ ____ ____
|
|
48
|
+
/ \\ |_ __ \\|_ __ \\|_ _||_ _|
|
|
49
|
+
/ _ \\ | |__) | | |__) | \\ \\ / /
|
|
50
|
+
/ ___ \\ | ___/ | ___/ > \`' <
|
|
51
|
+
_/ / \\ \\_ _| |_ _| |_ _/ /'\\ \\_
|
|
52
|
+
|____| |____||_____| |_____| |____||____|
|
|
53
|
+
\x1b[0m
|
|
54
|
+
CLI Version: ${packageJson.version}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
console.log(logo);
|
|
58
|
+
program.version(packageJson.version).description('AppX Core Project Initialization Wizard');
|
|
59
|
+
let progressBar;
|
|
60
|
+
let remainingETA = 0;
|
|
61
|
+
program
|
|
62
|
+
.command('create')
|
|
63
|
+
.description('Create a new AppX Core project')
|
|
64
|
+
.action(() => {
|
|
65
|
+
console.log('Starting AppX Core...');
|
|
66
|
+
promptUser();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function coreGenerate(showOutput) {
|
|
70
|
+
// run file inside node_modules/appx-core/dist/config/generate-all.js
|
|
71
|
+
const generatePath = path.join(process.cwd(), 'node_modules', '@appxdigital/appx-core', 'dist', 'config', 'generate-all.js');
|
|
72
|
+
if (!fsCore.existsSync(generatePath)) {
|
|
73
|
+
console.error('❌ Not inside a valid AppX Core project directory. Please run this command inside your project directory.');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
// Execute the script
|
|
77
|
+
try {
|
|
78
|
+
executeCommand(`node ${generatePath}`, showOutput);
|
|
79
|
+
console.log('✅ Prisma client and GraphQL schema generated successfully.');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('❌ An error occurred while generating Prisma client and GraphQL schema:', error.message);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
program.command('generate')
|
|
87
|
+
.description('Updates Prisma client and generates the GraphQL schema')
|
|
88
|
+
.action(() => {
|
|
89
|
+
coreGenerate(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
program.command('setup:fileupload')
|
|
93
|
+
.description('Add file upload configuration to the project')
|
|
94
|
+
.action(() => {
|
|
95
|
+
// run file inside node_modules/appx-core/dist/config/setup-fileupload.js
|
|
96
|
+
const generatePath = path.join(process.cwd(), 'node_modules', '@appxdigital/appx-core', 'dist', 'config', 'setup-fileupload.js');
|
|
97
|
+
if (!fsCore.existsSync(generatePath)) {
|
|
98
|
+
console.error('❌ Not inside a valid AppX Core project directory. Please run this command inside your project directory.');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
// Execute the script
|
|
102
|
+
try {
|
|
103
|
+
execSync(`node ${generatePath}`, {stdio: 'inherit'});
|
|
104
|
+
console.log('✅ File upload configuration added successfully.');
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('❌ An error occurred while adding file upload configuration:', error.message);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
async function checkDb(config) {
|
|
112
|
+
const {dbProvider, dbHost, dbPort, dbUser, dbPassword, dbName} = config;
|
|
113
|
+
|
|
114
|
+
if (dbProvider === "mysql") {
|
|
115
|
+
const conn = await mysql.createConnection({
|
|
116
|
+
host: dbHost,
|
|
117
|
+
port: Number(dbPort),
|
|
118
|
+
user: dbUser,
|
|
119
|
+
password: dbPassword,
|
|
120
|
+
// Important: don't set database here yet; we first check it exists
|
|
121
|
+
connectTimeout: 5000,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// 1) DB exists?
|
|
126
|
+
const [dbRows] = await conn.query(
|
|
127
|
+
"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?",
|
|
128
|
+
[dbName]
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (dbRows.length === 0) {
|
|
132
|
+
return {ok: false, reason: `Database "${dbName}" does not exist.`};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2) Any tables?
|
|
136
|
+
const [tableRows] = await conn.query(
|
|
137
|
+
"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? LIMIT 1",
|
|
138
|
+
[dbName]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (tableRows.length > 0) {
|
|
142
|
+
return {ok: false, reason: `Database "${dbName}" is not empty (has tables).`};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {ok: true};
|
|
146
|
+
} finally {
|
|
147
|
+
await conn.end();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (dbProvider === "postgresql") {
|
|
152
|
+
const client = new pg.Client({
|
|
153
|
+
host: dbHost,
|
|
154
|
+
port: Number(dbPort),
|
|
155
|
+
user: dbUser,
|
|
156
|
+
password: dbPassword,
|
|
157
|
+
database: "postgres", // connect to a known db first to check existence
|
|
158
|
+
connectionTimeoutMillis: 5000,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await client.connect();
|
|
162
|
+
try {
|
|
163
|
+
// 1) DB exists?
|
|
164
|
+
const dbRes = await client.query(
|
|
165
|
+
"SELECT 1 FROM pg_database WHERE datname = $1",
|
|
166
|
+
[dbName]
|
|
167
|
+
);
|
|
168
|
+
if (dbRes.rowCount === 0) {
|
|
169
|
+
return {ok: false, reason: `Database "${dbName}" does not exist.`};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 2) Connect to target db and check tables in public schema (and others if you want)
|
|
173
|
+
const client2 = new pg.Client({
|
|
174
|
+
host: dbHost,
|
|
175
|
+
port: Number(dbPort),
|
|
176
|
+
user: dbUser,
|
|
177
|
+
password: dbPassword,
|
|
178
|
+
database: dbName,
|
|
179
|
+
connectionTimeoutMillis: 5000,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await client2.connect();
|
|
183
|
+
try {
|
|
184
|
+
const tableRes = await client2.query(
|
|
185
|
+
`
|
|
186
|
+
SELECT 1
|
|
187
|
+
FROM information_schema.tables
|
|
188
|
+
WHERE table_type = 'BASE TABLE'
|
|
189
|
+
AND table_schema NOT IN ('pg_catalog', 'information_schema') LIMIT 1
|
|
190
|
+
`
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (tableRes.rowCount > 0) {
|
|
194
|
+
return {ok: false, reason: `Database "${dbName}" is not empty (has tables).`};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {ok: true};
|
|
198
|
+
} finally {
|
|
199
|
+
await client2.end();
|
|
200
|
+
}
|
|
201
|
+
} finally {
|
|
202
|
+
await client.end();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {ok: false, reason: "Unsupported dbProvider."};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function promptDbConfig() {
|
|
210
|
+
return inquirer.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: "list",
|
|
213
|
+
name: "dbProvider",
|
|
214
|
+
message: "Database provider:",
|
|
215
|
+
choices: ["mysql", "postgresql"],
|
|
216
|
+
default: "mysql",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: "input",
|
|
220
|
+
name: "dbHost",
|
|
221
|
+
message: "Database Host:",
|
|
222
|
+
default: "127.0.0.1",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
type: "input",
|
|
226
|
+
name: "dbPort",
|
|
227
|
+
message: "Database Port:",
|
|
228
|
+
default: (answers) => (answers.dbProvider === "mysql" ? "3306" : "5432"),
|
|
229
|
+
validate: (v) => (!Number.isNaN(Number(v)) ? true : "Port must be a number"),
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: "input",
|
|
233
|
+
name: "dbUser",
|
|
234
|
+
message: "Database User:",
|
|
235
|
+
default: "root",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
type: "password",
|
|
239
|
+
name: "dbPassword",
|
|
240
|
+
message: "Database Password:",
|
|
241
|
+
mask: "*",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
type: "input",
|
|
245
|
+
name: "dbName",
|
|
246
|
+
message: "Database Name:",
|
|
247
|
+
default: "generic",
|
|
248
|
+
},
|
|
249
|
+
]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function promptDbUntilValid() {
|
|
253
|
+
while (true) {
|
|
254
|
+
const dbConfig = await promptDbConfig();
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const result = await checkDb(dbConfig);
|
|
258
|
+
|
|
259
|
+
if (result.ok) {
|
|
260
|
+
return dbConfig;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
process.stdout.write(`❌ Database check failed: ${result.reason}\n`);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
process.stdout.write(`❌ Database check failed: ${err.message}\n`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const {retry} = await inquirer.prompt([
|
|
269
|
+
{
|
|
270
|
+
type: "confirm",
|
|
271
|
+
name: "retry",
|
|
272
|
+
message: "Re-enter all database settings?",
|
|
273
|
+
default: true,
|
|
274
|
+
},
|
|
275
|
+
]);
|
|
276
|
+
|
|
277
|
+
if (!retry) {
|
|
278
|
+
throw new Error("User aborted database configuration.");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function promptUser() {
|
|
284
|
+
let fileUploadConfigData = {};
|
|
285
|
+
try {
|
|
286
|
+
const baseAnswers = await inquirer.prompt([
|
|
287
|
+
{
|
|
288
|
+
type: "input",
|
|
289
|
+
name: "projectName",
|
|
290
|
+
message: "Project name:",
|
|
291
|
+
default: "nestjs-project",
|
|
292
|
+
},
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
const dbAnswers = await promptDbUntilValid();
|
|
296
|
+
|
|
297
|
+
const rest = await inquirer.prompt([
|
|
298
|
+
{
|
|
299
|
+
type: "confirm",
|
|
300
|
+
name: "showOutput",
|
|
301
|
+
message: "Show installation output?",
|
|
302
|
+
default: false,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
type: "confirm",
|
|
306
|
+
name: "configureFileUpload",
|
|
307
|
+
message: "Configure file upload?",
|
|
308
|
+
default: false,
|
|
309
|
+
},
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
const answers = {...baseAnswers, ...dbAnswers, ...rest};
|
|
313
|
+
|
|
314
|
+
if (answers.configureFileUpload) {
|
|
315
|
+
fileUploadConfigData = await gatherFileUploadSettings();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
remainingETA = calculateTotalETA();
|
|
319
|
+
createProject(answers, fileUploadConfigData);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error('Error prompting user:', error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function createProject(answers, fileUploadConfigData) {
|
|
326
|
+
const {projectName, showOutput, backoffice} = answers;
|
|
327
|
+
|
|
328
|
+
console.log(`Creating project: ${projectName}`);
|
|
329
|
+
|
|
330
|
+
// If current directory is empty, use it as project directory
|
|
331
|
+
let projectPath = process.cwd();
|
|
332
|
+
|
|
333
|
+
// Ignore DS_STORE files when checking if directory is empty
|
|
334
|
+
if (fs.readdirSync(projectPath).filter(f => f.toLowerCase() !== '.DS_STORE').length > 0) {
|
|
335
|
+
projectPath += `/${projectName.replace(/\s+/g, '_').toLowerCase()}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.log(`Project path: ${projectPath}`);
|
|
339
|
+
|
|
340
|
+
const project_config = {
|
|
341
|
+
DB_PROVIDER: answers.dbProvider === 'mysql' ? 'mysql' : 'postgresql',
|
|
342
|
+
DB_HOST: answers.dbHost,
|
|
343
|
+
DB_PORT: answers.dbPort,
|
|
344
|
+
DB_USER: answers.dbUser,
|
|
345
|
+
DB_PASSWORD: answers.dbPassword,
|
|
346
|
+
DB_NAME: answers.dbName,
|
|
347
|
+
SESSION_SECRET: crypto.randomBytes(32).toString('hex'),
|
|
348
|
+
SESSION_COOKIE_NAME: 'session_' + projectName.replace(/\s+/g, '_').toLowerCase(),
|
|
349
|
+
JWT_SECRET: crypto.randomBytes(32).toString('hex'),
|
|
350
|
+
JWT_REFRESH_SECRET: crypto.randomBytes(32).toString('hex'),
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!showOutput) {
|
|
354
|
+
const phases = backoffice ? 8 : 7;
|
|
355
|
+
progressBar = new cliProgress.SingleBar(
|
|
356
|
+
{
|
|
357
|
+
format: `\x1b[36m{bar}\x1b[0m {percentage}% | ETA: {remainingETA}s | {value}/{total}`,
|
|
358
|
+
barCompleteChar: '\u2588',
|
|
359
|
+
barIncompleteChar: '\u2591'
|
|
360
|
+
},
|
|
361
|
+
cliProgress.Presets.shades_classic
|
|
362
|
+
);
|
|
363
|
+
progressBar.update(0, {remainingETA});
|
|
364
|
+
progressBar.start(phases, 0);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
fs.ensureDirSync(projectPath);
|
|
369
|
+
|
|
370
|
+
// Directory must be empty
|
|
371
|
+
if (fs.readdirSync(projectPath).filter(f => f.toLowerCase() !== '.DS_STORE').length > 0) {
|
|
372
|
+
console.error('🚫 The target directory is not empty. Please choose an empty directory or remove existing files. Check for hidden files as well.');
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
process.chdir(projectPath);
|
|
377
|
+
|
|
378
|
+
// TODO test database connection here and exit if it fails
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
// Install dependencies
|
|
382
|
+
createPackageJson(projectPath, projectName);
|
|
383
|
+
executeCommand('npm --logevel=error install', answers.showOutput);
|
|
384
|
+
incrementProgress(answers?.showOutput, "installDependencies");
|
|
385
|
+
|
|
386
|
+
// Update PATH to include local node_modules/.bin
|
|
387
|
+
const localBinPath = path.join(projectPath, 'node_modules', '.bin');
|
|
388
|
+
process.env.PATH = `${localBinPath}${path.delimiter}${process.env.PATH}`;
|
|
389
|
+
|
|
390
|
+
// Create Core project structure
|
|
391
|
+
setupProjectStructure(projectPath, project_config);
|
|
392
|
+
|
|
393
|
+
// Create .gitignore
|
|
394
|
+
await createGitignore(projectPath);
|
|
395
|
+
|
|
396
|
+
// Create tsconfig.json and tsconfig.build.json
|
|
397
|
+
createTsConfig(projectPath);
|
|
398
|
+
|
|
399
|
+
incrementProgress(answers?.showOutput, "setupProjectStructure");
|
|
400
|
+
|
|
401
|
+
// Create prisma migration file
|
|
402
|
+
executeCommand(`prisma migrate dev --name init --create-only`, answers?.showOutput);
|
|
403
|
+
incrementProgress(answers?.showOutput, "createPrismaMigration");
|
|
404
|
+
|
|
405
|
+
// Generate Core Models
|
|
406
|
+
coreGenerate(answers?.showOutput);
|
|
407
|
+
incrementProgress(answers?.showOutput, "generateCoreModels");
|
|
408
|
+
|
|
409
|
+
// Format code
|
|
410
|
+
executeCommand('npx --yes prettier --write .', answers?.showOutput);
|
|
411
|
+
|
|
412
|
+
// Initialize git repo
|
|
413
|
+
initializeGitRepo(projectPath, answers?.showOutput);
|
|
414
|
+
incrementProgress(answers?.showOutput, "gitInit");
|
|
415
|
+
|
|
416
|
+
// Migrate database
|
|
417
|
+
executeCommand(`prisma migrate dev`, answers?.showOutput);
|
|
418
|
+
|
|
419
|
+
incrementProgress(answers?.showOutput, "migrateDatabase");
|
|
420
|
+
|
|
421
|
+
if (fileUploadConfigData.provider) {
|
|
422
|
+
await insertFileUploadConfiguration(fileUploadConfigData, projectPath);
|
|
423
|
+
incrementProgress(showOutput, "fileUploadSetup");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (!showOutput) {
|
|
427
|
+
progressBar.stop();
|
|
428
|
+
}
|
|
429
|
+
console.log('Project created successfully!');
|
|
430
|
+
|
|
431
|
+
} catch (error) {
|
|
432
|
+
console.error('❌ An error occurred during project creation:', error.message);
|
|
433
|
+
if (fs.existsSync(projectPath)) {
|
|
434
|
+
//console.log('🧹 Cleaning up incomplete project files...');
|
|
435
|
+
//fs.removeSync(projectPath);
|
|
436
|
+
}
|
|
437
|
+
console.error('🚫 Project creation aborted due to the above error.');
|
|
438
|
+
if (!showOutput && progressBar) {
|
|
439
|
+
progressBar.stop();
|
|
440
|
+
}
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function createPackageJson(projectPath, projectName) {
|
|
446
|
+
const packageJsonContent = {
|
|
447
|
+
name: projectName,
|
|
448
|
+
version: "0.0.1",
|
|
449
|
+
description: "",
|
|
450
|
+
author: "Powered by AppX Digital",
|
|
451
|
+
private: true,
|
|
452
|
+
license: "UNLICENSED",
|
|
453
|
+
scripts: {
|
|
454
|
+
"build": "nest build",
|
|
455
|
+
"start": "nest start",
|
|
456
|
+
"start:dev": "cross-env NODE_ENV=development nest start --watch",
|
|
457
|
+
"start:prod": "cross-env NODE_ENV=production node dist/main",
|
|
458
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
459
|
+
"db-pull": "npm run db-pull --prefix ./node_modules/appx-core"
|
|
460
|
+
},
|
|
461
|
+
dependencies: {
|
|
462
|
+
"@appxdigital/appx-core": CORE_VERSION,
|
|
463
|
+
},
|
|
464
|
+
devDependencies: {
|
|
465
|
+
"@nestjs/cli": "~11.0.0",
|
|
466
|
+
"cross-env": "~10.1.0",
|
|
467
|
+
},
|
|
468
|
+
engines: {
|
|
469
|
+
"node": ">=20.0.0"
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
fs.writeJsonSync(path.join(projectPath, 'package.json'), packageJsonContent, {spaces: 2});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function setupProjectStructure(projectPath, project_config) {
|
|
477
|
+
// Create scaffold files from scaffold directory recursively, replacing placeholders from *.template files
|
|
478
|
+
const scaffoldDir = path.join(__dirname, 'scaffold');
|
|
479
|
+
|
|
480
|
+
function copyAndReplaceTemplates(srcDir, destDir) {
|
|
481
|
+
fs.readdirSync(srcDir).forEach((item) => {
|
|
482
|
+
const srcPath = path.join(srcDir, item);
|
|
483
|
+
let destPath = path.join(destDir, item.replace('.template', ''));
|
|
484
|
+
const stats = fs.statSync(srcPath);
|
|
485
|
+
if (stats.isDirectory()) {
|
|
486
|
+
fs.ensureDirSync(destPath);
|
|
487
|
+
copyAndReplaceTemplates(srcPath, destPath);
|
|
488
|
+
} else {
|
|
489
|
+
let content = fs.readFileSync(srcPath, 'utf-8');
|
|
490
|
+
|
|
491
|
+
// Replace placeholders
|
|
492
|
+
for (const [key, value] of Object.entries(project_config)) {
|
|
493
|
+
const placeholder = `{{${key}}}`;
|
|
494
|
+
content = content.replace(new RegExp(placeholder, 'g'), value);
|
|
495
|
+
}
|
|
496
|
+
fs.writeFileSync(destPath, content, 'utf-8');
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
copyAndReplaceTemplates(scaffoldDir, projectPath);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function initializeGitRepo(projectPath, showOutput = false) {
|
|
505
|
+
// If git is installed in the system, initialize a git repository and make the initial commit
|
|
506
|
+
let installed = true;
|
|
507
|
+
try {
|
|
508
|
+
execSync('git --version', {stdio: 'ignore'});
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.log('Git is not installed. Skipping git initialization.');
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
executeCommand('git init', showOutput);
|
|
516
|
+
executeCommand('git add .', showOutput);
|
|
517
|
+
executeCommand('git commit -m "Project init"', showOutput);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
console.error("Failed to initialize git repository or make the initial commit:", error);
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function createGitignore(projectPath) {
|
|
525
|
+
const envFilesToAdd = ['.env', '.env.production'];
|
|
526
|
+
|
|
527
|
+
const {defaultGitIgnore} = await import(path.join(projectPath, 'node_modules', '@nestjs/cli/lib/configuration/defaults.js'));
|
|
528
|
+
const existingLines = defaultGitIgnore.split('\n');
|
|
529
|
+
const updatedLines = new Set(existingLines);
|
|
530
|
+
envFilesToAdd.forEach((file) => {
|
|
531
|
+
if (!existingLines.includes(file)) {
|
|
532
|
+
updatedLines.add(file);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
const finalContent = Array.from(updatedLines).join('\n').trim() + '\n';
|
|
536
|
+
|
|
537
|
+
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
538
|
+
fs.writeFileSync(gitignorePath, finalContent, 'utf-8');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function createTsConfig(projectPath) {
|
|
542
|
+
let tsConfig = fs.readFileSync(path.join(projectPath, 'node_modules/@nestjs/schematics/dist/lib/application/files/ts/tsconfig.json'), 'utf-8');
|
|
543
|
+
// Replace '<%= strict %>' with 'false'
|
|
544
|
+
tsConfig = tsConfig.replaceAll("<%= strict %>", 'false');
|
|
545
|
+
|
|
546
|
+
let tsConfigBuild = fs.readFileSync(path.join(projectPath, 'node_modules/@nestjs/schematics/dist/lib/application/files/ts/tsconfig.build.json'), 'utf-8');
|
|
547
|
+
|
|
548
|
+
// Ensure target is ES2017
|
|
549
|
+
tsConfig = JSON.parse(tsConfig);
|
|
550
|
+
if (!tsConfig.compilerOptions) {
|
|
551
|
+
tsConfig.compilerOptions = {};
|
|
552
|
+
}
|
|
553
|
+
tsConfig.compilerOptions.target = 'ES2017';
|
|
554
|
+
tsConfig = JSON.stringify(tsConfig, null, 2);
|
|
555
|
+
|
|
556
|
+
fs.writeFileSync(path.join(projectPath, 'tsconfig.json'), tsConfig);
|
|
557
|
+
fs.writeFileSync(path.join(projectPath, 'tsconfig.build.json'), tsConfigBuild);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
function incrementProgress(showOutput, phase) {
|
|
562
|
+
if (!showOutput) {
|
|
563
|
+
remainingETA -= phaseDurations[phase];
|
|
564
|
+
progressBar.update(progressBar.value + phase === 'end' ? 0 : 1, {remainingETA});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function executeCommand(command, showOutput = answers?.showOutput) {
|
|
569
|
+
const options = showOutput ? {stdio: 'inherit'} : {stdio: 'ignore'};
|
|
570
|
+
try {
|
|
571
|
+
execSync(command, options);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
console.error(`Failed to execute command: ${command}`, error);
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@appxdigital/appx-core-cli",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.13",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"npm:publish": "npm publish --access public",
|
|
@@ -11,16 +11,17 @@
|
|
|
11
11
|
"author": "",
|
|
12
12
|
"license": "ISC",
|
|
13
13
|
"bin": {
|
|
14
|
-
"appx-core": "./
|
|
14
|
+
"appx-core": "./cli.js"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@inquirer/prompts": "^7.0.0",
|
|
18
|
-
"@nestjs/cli": "^11.0.6",
|
|
19
18
|
"cli-progress": "^3.12.0",
|
|
20
19
|
"commander": "^12.1.0",
|
|
21
20
|
"fs-extra": "^11.2.0",
|
|
22
21
|
"handlebars": "^4.7.8",
|
|
23
|
-
"inquirer": "^12.0.0"
|
|
22
|
+
"inquirer": "^12.0.0",
|
|
23
|
+
"mysql2": "^3.16.1",
|
|
24
|
+
"pg": "^8.17.1"
|
|
24
25
|
},
|
|
25
26
|
"description": ""
|
|
26
27
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
##DataBase Configurations##
|
|
2
|
+
DB_HOST={{DB_HOST}}
|
|
3
|
+
DB_PORT={{DB_PORT}}
|
|
4
|
+
DB_USER={{DB_USER}}
|
|
5
|
+
DB_PASSWORD={{DB_PASSWORD}}
|
|
6
|
+
DB_NAME={{DB_NAME}}
|
|
7
|
+
DB_PROVIDER={{DB_PROVIDER}}
|
|
8
|
+
DB_URL="${DB_PROVIDER}://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
|
|
9
|
+
|
|
10
|
+
##Project configurations##
|
|
11
|
+
|
|
12
|
+
#Port
|
|
13
|
+
APP_PORT=3000
|
|
14
|
+
|
|
15
|
+
#Default behaviour for use of transactions
|
|
16
|
+
USE_TRANSACTION=true
|
|
17
|
+
|
|
18
|
+
#Session secret
|
|
19
|
+
SESSION_SECRET="{{SESSION_SECRET}}"
|
|
20
|
+
|
|
21
|
+
#Cookie name for the session token
|
|
22
|
+
SESSION_COOKIE_NAME="{{SESSION_COOKIE_NAME}}"
|
|
23
|
+
|
|
24
|
+
#Expiration time for the session token in seconds
|
|
25
|
+
SESSION_TTL=86400
|
|
26
|
+
|
|
27
|
+
# JWT expiration times
|
|
28
|
+
JWT_EXPIRES_IN=10d
|
|
29
|
+
JWT_REFRESH_EXPIRES_IN=1y
|
|
30
|
+
|
|
31
|
+
# JWT
|
|
32
|
+
JWT_SECRET="{{JWT_SECRET}}"
|
|
33
|
+
JWT_REFRESH_SECRET="{{JWT_REFRESH_SECRET}}"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
parser: '@typescript-eslint/parser',
|
|
3
|
+
parserOptions: {
|
|
4
|
+
project: 'tsconfig.json',
|
|
5
|
+
tsconfigRootDir: __dirname,
|
|
6
|
+
sourceType: 'module',
|
|
7
|
+
},
|
|
8
|
+
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
9
|
+
extends: [
|
|
10
|
+
'plugin:@typescript-eslint/recommended',
|
|
11
|
+
'plugin:prettier/recommended',
|
|
12
|
+
],
|
|
13
|
+
root: true,
|
|
14
|
+
env: {
|
|
15
|
+
node: true,
|
|
16
|
+
jest: true,
|
|
17
|
+
},
|
|
18
|
+
ignorePatterns: ['.eslintrc.js'],
|
|
19
|
+
rules: {
|
|
20
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
|
21
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
22
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
23
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
24
|
+
'prettier/prettier': ['error', {endOfLine: 'lf'}],
|
|
25
|
+
},
|
|
26
|
+
};
|