@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 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.11",
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": "./wizard.js"
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
+ };
@@ -0,0 +1,2 @@
1
+
2
+ * text=auto eol=lf
@@ -0,0 +1,9 @@
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "all",
4
+ "endOfLine": "lf",
5
+ "tabWidth": 2,
6
+ "semi": true,
7
+ "bracketSpacing": true,
8
+ "jsxBracketSameLine": true
9
+ }