@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/wizard.js DELETED
@@ -1,599 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { program } from 'commander';
4
- import inquirer from 'inquirer';
5
- import fs from 'fs-extra';
6
- import fsCore from 'fs';
7
- import { execSync } from 'child_process';
8
- import path from 'path';
9
- import { fileURLToPath } from 'url';
10
- import cliProgress from 'cli-progress';
11
- import { gatherFileUploadSettings, insertFileUploadConfiguration } from './utils/fileUploadConfig.js';
12
- import packageJson from './package.json' assert { type: 'json' };
13
- import crypto from 'crypto';
14
-
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = path.dirname(__filename);
17
-
18
- const dependencyVersions = JSON.parse(
19
- fsCore.readFileSync(path.join(__dirname, 'utils', 'dependency-versions.json'), 'utf-8')
20
- );
21
-
22
- const phaseDurations = {
23
- nestjs: 20,
24
- appmodule: 5,
25
- installDependencies: 80,
26
- installCore: 120,
27
- prismaSetup: 10,
28
- setupConcluded: 5,
29
- end: 5
30
- };
31
-
32
- const calculateTotalETA = (includeBackoffice) => {
33
- let totalTime = phaseDurations.nestjs +
34
- phaseDurations.appmodule +
35
- phaseDurations.installDependencies +
36
- phaseDurations.installCore +
37
- phaseDurations.prismaSetup +
38
- phaseDurations.setupConcluded
39
- + phaseDurations.end;
40
- return totalTime;
41
- };
42
-
43
- const logo = `\x1b[36m
44
- _ _______ _______ ____ ____
45
- / \\ |_ __ \\|_ __ \\|_ _||_ _|
46
- / _ \\ | |__) | | |__) | \\ \\ / /
47
- / ___ \\ | ___/ | ___/ > \`' <
48
- _/ / \\ \\_ _| |_ _| |_ _/ /'\\ \\_
49
- |____| |____||_____| |_____| |____||____|
50
- \x1b[0m
51
- CLI Version: ${packageJson.version}
52
- `;
53
-
54
- console.log(logo);
55
- program.version(packageJson.version).description('NestJS Project Initialization Wizard');
56
- let progressBar;
57
- let remainingETA = 0;
58
- program
59
- .command('create')
60
- .description('Create a new NestJS project')
61
- .action(() => {
62
- console.log('Starting AppX Core...');
63
- promptUser();
64
- });
65
-
66
- program.command('generate')
67
- .description('Updates Prisma client and generates the GraphQL schema')
68
- .action(() => {
69
- // run file inside node_modules/appx-core/dist/config/generate-all.js
70
- const generatePath = path.join(process.cwd(), 'node_modules', '@appxdigital/appx-core', 'dist', 'config', 'generate-all.js');
71
- if (!fsCore.existsSync(generatePath)) {
72
- console.error('❌ Not inside a valid AppX Core project directory. Please run this command inside your project directory.');
73
- process.exit(1);
74
- }
75
- // Execute the script
76
- try {
77
- execSync(`node ${generatePath}`, { stdio: 'inherit' });
78
- console.log('✅ Prisma client and GraphQL schema generated successfully.');
79
- } catch (error) {
80
- console.error('❌ An error occurred while generating Prisma client and GraphQL schema:', error.message);
81
- process.exit(1);
82
- }
83
- });
84
-
85
- program.command('setup:fileupload')
86
- .description('Add file upload configuration to the project')
87
- .action(() => {
88
- // run file inside node_modules/appx-core/dist/config/setup-fileupload.js
89
- const generatePath = path.join(process.cwd(), 'node_modules', '@appxdigital/appx-core', 'dist', 'config', 'setup-fileupload.js');
90
- if (!fsCore.existsSync(generatePath)) {
91
- console.error('❌ Not inside a valid AppX Core project directory. Please run this command inside your project directory.');
92
- process.exit(1);
93
- }
94
- // Execute the script
95
- try {
96
- execSync(`node ${generatePath}`, { stdio: 'inherit' });
97
- console.log('✅ File upload configuration added successfully.');
98
- } catch (error) {
99
- console.error('❌ An error occurred while adding file upload configuration:', error.message);
100
- process.exit(1);
101
- }
102
- });
103
-
104
- async function promptUser() {
105
- let fileUploadConfigData = {};
106
- try {
107
- const answers = await inquirer.prompt([
108
- {
109
- type: 'input',
110
- name: 'projectName',
111
- message: 'Project name:',
112
- default: 'nestjs-project'
113
- },
114
- {
115
- type: 'list',
116
- name: 'dbProvider',
117
- message: 'Database provider:',
118
- choices: ['mysql', 'postgresql'],
119
- default: 'mysql'
120
- },
121
- {
122
- type: 'input',
123
- name: 'dbHost',
124
- message: 'Database Host:',
125
- default: '127.0.0.1'
126
- },
127
- {
128
- type: 'input',
129
- name: 'dbPort',
130
- message: 'Database Port:',
131
- default: (answers) => answers.dbProvider === 'mysql' ? '3306' : '5432'
132
- },
133
- {
134
- type: 'input',
135
- name: 'dbUser',
136
- message: 'Database User:',
137
- default: 'root'
138
- },
139
- {
140
- type: 'password',
141
- name: 'dbPassword',
142
- message: 'Database Password:',
143
- mask: '*'
144
- },
145
- {
146
- type: 'input',
147
- name: 'dbName',
148
- message: 'Database Name:',
149
- default: 'generic'
150
- },
151
- {
152
- type: 'confirm',
153
- name: 'showOutput',
154
- message: 'Show installation output?',
155
- default: false
156
- },
157
- {
158
- type: 'confirm',
159
- name: 'configureFileUpload',
160
- message: 'Configure file upload?',
161
- default: false
162
- }
163
- ]);
164
-
165
- if (answers.configureFileUpload) {
166
- fileUploadConfigData = await gatherFileUploadSettings();
167
- }
168
-
169
- remainingETA = calculateTotalETA(answers.backoffice);
170
- createProject(answers, fileUploadConfigData);
171
- } catch (error) {
172
- console.error('Error prompting user:', error);
173
- }
174
- }
175
-
176
- async function createProject(answers, fileUploadConfigData) {
177
- const { projectName, dbProvider, dbHost, dbPort, dbUser, dbPassword, dbName, showOutput, backoffice, configureFileUpload } = answers;
178
- console.log(`Creating project: ${projectName}`);
179
- const projectPath = `${process.cwd()}/${projectName}`;
180
- if (!showOutput) {
181
- const phases = backoffice ? 8 : 7;
182
- progressBar = new cliProgress.SingleBar(
183
- {
184
- format: `\x1b[36m{bar}\x1b[0m {percentage}% | ETA: {remainingETA}s | {value}/{total}`,
185
- barCompleteChar: '\u2588',
186
- barIncompleteChar: '\u2591'
187
- },
188
- cliProgress.Presets.shades_classic
189
- );
190
- progressBar.update(0, { remainingETA });
191
- progressBar.start(phases, 0);
192
- }
193
-
194
- try {
195
- fs.ensureDirSync(projectPath);
196
- fs.emptyDirSync(projectPath);
197
- process.chdir(projectPath);
198
-
199
- setupProjectStructure(projectPath, answers);
200
- incrementProgress(answers?.showOutput, "setupConcluded");
201
- const envContent = `
202
- ##DataBase Configurations##
203
- DB_HOST=${dbHost}
204
- DB_PORT=${dbPort}
205
- DB_USER=${dbUser}
206
- DB_PASSWORD=${dbPassword}
207
- DB_NAME=${dbName}
208
- DB_PROVIDER=${dbProvider}
209
- DB_URL="\${DB_PROVIDER}://\${DB_USER}:\${DB_PASSWORD}@\${DB_HOST}:\${DB_PORT}/\${DB_NAME}"
210
-
211
-
212
- ##Project configurations##
213
-
214
- #Port
215
- APP_PORT=3000
216
-
217
- #Default behaviour for use of transactions
218
- USE_TRANSACTION=true
219
-
220
- #Session secret
221
- SESSION_SECRET="${crypto.randomBytes(32).toString('hex')}"
222
-
223
- #Cookie name for the session token
224
- SESSION_COOKIE_NAME="APPXCORE"
225
-
226
- #Expiration time for the session token in seconds
227
- SESSION_TTL=86400
228
-
229
- # JWT
230
- JWT_SECRET="${crypto.randomBytes(32).toString('hex')}"
231
- JWT_REFRESH_SECRET="${crypto.randomBytes(32).toString('hex')}"
232
- `;
233
-
234
- fs.writeFileSync(`${projectPath}/.env`, envContent);
235
- if (fileUploadConfigData.provider) {
236
- await insertFileUploadConfiguration(fileUploadConfigData, projectPath);
237
- incrementProgress(showOutput, "fileUploadSetup");
238
- }
239
- updateGitignore(projectPath);
240
- configureProjectSettings(projectPath);
241
- executeCommand('npx prettier --write .', answers?.showOutput);
242
- initializeGitRepo(projectPath);
243
- incrementProgress(answers?.showOutput, "end");
244
- if (!showOutput) {
245
- progressBar.stop();
246
- }
247
- console.log('Project created successfully!');
248
- } catch (error) {
249
- console.error('❌ An error occurred during project creation:', error.message);
250
- if (fs.existsSync(projectPath)) {
251
- console.log('🧹 Cleaning up incomplete project files...');
252
- fs.removeSync(projectPath);
253
- }
254
- console.error('🚫 Project creation aborted due to the above error.');
255
- if (!showOutput && progressBar) {
256
- progressBar.stop();
257
- }
258
- process.exit(1);
259
- }
260
- }
261
-
262
- function setupProjectStructure(projectPath, answers) {
263
- ensureAndRunNestCli(projectPath, answers?.showOutput);
264
- incrementProgress(answers?.showOutput, "nestjs");
265
-
266
- // Copy main README.md
267
- const readmePath = path.join(__dirname, 'README.md');
268
- fs.copyFileSync(readmePath, `${projectPath}/README.md`);
269
-
270
- const bootstrapContent = getBootstrapContent();
271
- fs.writeFileSync(`${projectPath}/src/main.ts`, bootstrapContent);
272
-
273
- const tsConfigPath = path.join(projectPath, 'tsconfig.json');
274
- if (fs.existsSync(tsConfigPath)) {
275
- const tsConfig = fs.readJsonSync(tsConfigPath);
276
-
277
- if (!tsConfig.compilerOptions) {
278
- tsConfig.compilerOptions = {};
279
- }
280
- tsConfig.compilerOptions.target = 'ES2017';
281
- fs.writeJsonSync(tsConfigPath, tsConfig, { spaces: 2 });
282
- } else {
283
- console.warn('Could not find generated tsconfig.json to modify.');
284
- }
285
-
286
- const appModuleContent = getAppModuleTemplate();
287
- fs.writeFileSync(`${projectPath}/src/app.module.ts`, appModuleContent);
288
- incrementProgress(answers?.showOutput, "appmodule");
289
- installDependenciesFromManifest(answers?.showOutput);
290
- executeCommand('npm install --save-dev @types/multer @types/express cross-env', answers?.showOutput);
291
- incrementProgress(answers?.showOutput, "installDependencies");
292
-
293
- // const tgzPath = path.resolve('D:/Projects/core/appx-core', fs.readdirSync('D:/Projects/core/appx-core').find(f => f.startsWith('appx-core-package') && f.endsWith('.tgz')));
294
- // executeCommand(`npm install "${tgzPath}"`, answers?.showOutput);
295
- executeCommand('npm install @appxdigital/appx-core@latest', answers?.showOutput);
296
- incrementProgress(answers?.showOutput, "installCore");
297
-
298
- //Prisma setup
299
- const prismaVersion = dependencyVersions.devDependencies?.prisma;
300
- if (!prismaVersion) {
301
- console.error('Prisma version not found in dependency-versions.json devDependencies. Cannot run prisma init.');
302
- process.exit(1);
303
- }
304
- executeCommand(`npx prisma@${prismaVersion} init`, answers?.showOutput);
305
- customizePrismaSchema(projectPath, answers);
306
- incrementProgress(answers?.showOutput, "prismaSetup");
307
- createPrismaModule(projectPath);
308
- createPermissionsConfig(projectPath);
309
- setupAdminJS(projectPath);
310
- createAdminConfig(projectPath);
311
- addScriptsToPackageJson(projectPath);
312
- }
313
-
314
- function ensureAndRunNestCli(projectPath, showOutput = false) {
315
- const options = showOutput ? { stdio: 'inherit' } : { stdio: 'ignore' };
316
- const command = 'npx --yes @nestjs/cli new . --skip-install --package-manager npm';
317
- try {
318
- execSync('npx --no-install @nestjs/cli --version', options);
319
- } catch {
320
- try {
321
- execSync('npx @nestjs/cli --version', options);
322
- } catch (fetchError) {
323
- console.error('❌ Failed to fetch @nestjs/cli via npx. Please check your internet connection.');
324
- process.exit(1);
325
- }
326
- }
327
- try {
328
- execSync(command, { cwd: projectPath, ...options });
329
- } catch (creationError) {
330
- console.error(`❌ Failed to create the project: ${creationError.message}`);
331
- process.exit(1);
332
- }
333
- }
334
-
335
- function getBootstrapContent() {
336
- const bootstrapFilePath = path.join(__dirname, 'templates', 'bootstrap.template.js');
337
- return fs.readFileSync(bootstrapFilePath, 'utf8');
338
- }
339
-
340
- function getAppModuleTemplate() {
341
- const appModuleTemplatePath = path.join(__dirname, 'templates', 'app.module.template.js');
342
- return fs.readFileSync(appModuleTemplatePath, 'utf8');
343
- }
344
-
345
- function setupAdminJS(projectPath) {
346
- const backoffice = path.join(projectPath, 'src/backoffice');
347
- fs.ensureDirSync(backoffice);
348
-
349
- const backofficeComponents = path.join(backoffice, 'components');
350
- fs.ensureDirSync(backofficeComponents);
351
-
352
- const dashboardContent = getDashboardTemplate();
353
- fs.writeFileSync(path.join(backofficeComponents, 'dashboard.tsx'), dashboardContent);
354
- }
355
-
356
- function getComponentLoaderTemplate() {
357
- const template = path.join(__dirname, 'templates', 'adminjs', 'component-loader.template.js');
358
- return fs.readFileSync(template, 'utf8');
359
- }
360
-
361
- function getDashboardTemplate() {
362
- const template = path.join(__dirname, 'templates', 'adminjs', 'dashboard.template.js');
363
- return fs.readFileSync(template, 'utf8');
364
- }
365
-
366
- function getUtilsTemplate() {
367
- const template = path.join(__dirname, 'templates', 'adminjs', 'utils.template.js');
368
- return fs.readFileSync(template, 'utf8');
369
- }
370
-
371
- function getAdminTemplate() {
372
- const template = path.join(__dirname, 'templates', 'adminjs', 'admin.template.js');
373
- return fs.readFileSync(template, 'utf8');
374
- }
375
-
376
- function getAdminAppModuleTemplate() {
377
- const appModuleTemplatePath = path.join(__dirname, 'templates', 'adminjs', 'adminjs-app.module.template.js');
378
- return fs.readFileSync(appModuleTemplatePath, 'utf8');
379
- }
380
-
381
- function createPermissionsConfig(projectPath) {
382
- const configDir = path.join(projectPath, 'src/config');
383
- fs.ensureDirSync(configDir);
384
-
385
- const templatePath = path.join(__dirname, 'templates', 'permissions.config.template.js');
386
- const permissionsConfigContent = fs.readFileSync(templatePath, 'utf-8');
387
-
388
- fs.writeFileSync(path.join(configDir, 'permissions.config.ts'), permissionsConfigContent);
389
- }
390
-
391
- function createAdminConfig (projectPath) {
392
- const configDir = path.join(projectPath, 'src/config');
393
- fs.ensureDirSync(configDir);
394
-
395
- const templatePath = path.join(__dirname, 'templates', 'admin.config.template.js');
396
- const permissionsConfigContent = fs.readFileSync(templatePath, 'utf-8');
397
-
398
- fs.writeFileSync(path.join(configDir, 'admin.config.ts'), permissionsConfigContent);
399
- }
400
-
401
- function customizePrismaSchema(projectPath, answers) {
402
- const prismaDir = `${projectPath}/prisma`;
403
- const schemaFile = `${prismaDir}/schema.prisma`;
404
-
405
- fs.ensureDirSync(prismaDir);
406
-
407
- const schemaContent = `
408
- datasource db {
409
- provider = "${answers.dbProvider}"
410
- url = env("DB_URL")
411
- }
412
-
413
- generator client {
414
- provider = "prisma-client-js"
415
- }
416
-
417
- generator nestgraphql {
418
- provider = "node node_modules/prisma-nestjs-graphql"
419
- output = "../src/generated/"
420
- }
421
-
422
- model User {
423
- id Int @id @default(autoincrement())
424
- email String @unique
425
- name String?
426
- password String? /// @Role(none)
427
- role Role @default(GUEST)
428
- refreshTokens UserRefreshToken[]
429
- }
430
-
431
- model Session {
432
- id String @id
433
- sid String @unique
434
- data String @db.Text
435
- expiresAt DateTime
436
- userId Int?
437
- }
438
-
439
- model UserRefreshToken {
440
- id String @id @default(cuid())
441
- token String @unique @db.VarChar(255)
442
- userId Int
443
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
444
- expiresAt DateTime
445
- createdAt DateTime @default(now())
446
- revokedAt DateTime?
447
-
448
- @@index([userId])
449
- }
450
-
451
- enum Role {
452
- ADMIN
453
- GUEST
454
- }
455
- `;
456
-
457
- fs.writeFileSync(schemaFile, schemaContent);
458
- }
459
-
460
- function createPrismaModule(projectPath) {
461
- const prismaDir = path.join(projectPath, 'src/prisma');
462
- fs.ensureDirSync(prismaDir);
463
-
464
- const templatePath = path.join(__dirname, 'templates', 'prisma.module.template.js');
465
- const prismaModuleContent = fs.readFileSync(templatePath, 'utf-8');
466
-
467
- fs.writeFileSync(path.join(prismaDir, 'prisma.module.ts'), prismaModuleContent);
468
- }
469
-
470
- function addScriptsToPackageJson(projectPath) {
471
- const packageJsonPath = path.join(projectPath, 'package.json');
472
- const packageJson = fs.readJsonSync(packageJsonPath);
473
-
474
- packageJson.scripts = packageJson.scripts || {};
475
- packageJson.scripts["start:dev"] = "cross-env NODE_ENV=development nest start --watch";
476
- packageJson.scripts["start:prod"] = "cross-env NODE_ENV=production node dist/main";
477
- packageJson.scripts["db-pull"] = "npm run db-pull --prefix ./node_modules/appx-core";
478
-
479
- fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
480
- }
481
-
482
- function initializeGitRepo(projectPath) {
483
- try {
484
- execSync('git add .', { cwd: projectPath, stdio: 'ignore' });
485
- execSync('git commit -m "Project init"', { cwd: projectPath, stdio: 'ignore' });
486
- } catch (error) {
487
- console.error("Failed to initialize git repository or make the initial commit:", error);
488
- }
489
- }
490
-
491
-
492
- function configureProjectSettings(projectPath) {
493
- const prettierConfig = {
494
- singleQuote: true,
495
- trailingComma: 'all',
496
- endOfLine: 'lf',
497
- tabWidth: 2,
498
- semi: true,
499
- bracketSpacing: true,
500
- jsxBracketSameLine: true
501
- };
502
-
503
- fs.writeFileSync(
504
- path.join(projectPath, '.prettierrc'),
505
- JSON.stringify(prettierConfig, null, 2)
506
- );
507
-
508
- const eslintConfig = `module.exports = {
509
- parser: '@typescript-eslint/parser',
510
- parserOptions: {
511
- project: 'tsconfig.json',
512
- tsconfigRootDir: __dirname,
513
- sourceType: 'module',
514
- },
515
- plugins: ['@typescript-eslint/eslint-plugin'],
516
- extends: [
517
- 'plugin:@typescript-eslint/recommended',
518
- 'plugin:prettier/recommended',
519
- ],
520
- root: true,
521
- env: {
522
- node: true,
523
- jest: true,
524
- },
525
- ignorePatterns: ['.eslintrc.js'],
526
- rules: {
527
- '@typescript-eslint/interface-name-prefix': 'off',
528
- '@typescript-eslint/explicit-function-return-type': 'off',
529
- '@typescript-eslint/explicit-module-boundary-types': 'off',
530
- '@typescript-eslint/no-explicit-any': 'off',
531
- 'prettier/prettier': ['error', { 'endOfLine': 'lf' }],
532
- },
533
- };`;
534
-
535
- fs.writeFileSync(path.join(projectPath, '.eslintrc.js'), eslintConfig);
536
-
537
- const gitattributesContent = `
538
- * text=auto eol=lf
539
- `;
540
- fs.writeFileSync(path.join(projectPath, '.gitattributes'), gitattributesContent);
541
- }
542
-
543
- function updateGitignore(projectPath) {
544
- const gitignorePath = path.join(projectPath, '.gitignore');
545
- const envFilesToAdd = ['.env', '.env.production'];
546
- let gitignoreContent = '';
547
- if (fs.existsSync(gitignorePath)) {
548
- gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
549
- } else {
550
- console.log(`.gitignore file not found. Creating a new one.`);
551
- }
552
- const existingLines = gitignoreContent.split('\n');
553
- const updatedLines = new Set(existingLines);
554
- envFilesToAdd.forEach((file) => {
555
- if (!existingLines.includes(file)) {
556
- updatedLines.add(file);
557
- }
558
- });
559
- const finalContent = Array.from(updatedLines).join('\n').trim() + '\n';
560
- fs.writeFileSync(gitignorePath, finalContent, 'utf-8');
561
- }
562
-
563
-
564
- function incrementProgress(showOutput, phase) {
565
- if (!showOutput) {
566
- remainingETA -= phaseDurations[phase];
567
- progressBar.update(progressBar.value + phase === 'end' ? 0 : 1, { remainingETA });
568
- }
569
- }
570
-
571
- function executeCommand(command, showOutput = answers?.showOutput) {
572
- const options = showOutput ? { stdio: 'inherit' } : { stdio: 'ignore' };
573
- try {
574
- execSync(command, options);
575
- } catch (error) {
576
- console.error(`Failed to execute command: ${command}`, error);
577
- }
578
- }
579
-
580
- function installDependenciesFromManifest(showOutput = true) {
581
- const { dependencies, devDependencies } = dependencyVersions;
582
- const depList = Object.entries(dependencies)
583
- .map(([pkg, version]) => `${pkg}@${version}`)
584
- .join(' ');
585
-
586
- const devDepList = Object.entries(devDependencies)
587
- .map(([pkg, version]) => `${pkg}@${version}`)
588
- .join(' ');
589
-
590
- if (depList) {
591
- executeCommand(`npm install ${depList}`, showOutput);
592
- }
593
-
594
- if (devDepList) {
595
- executeCommand(`npm install -D ${devDepList}`, showOutput);
596
- }
597
- }
598
-
599
- program.parse(process.argv);