diff --git a/jest.config.mjs b/jest.config.mjs index 33da4c3..81de852 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -9,7 +9,7 @@ const config = { bail: true, modulePathIgnorePatterns: ['tmp'], // coverage - collectCoverage: true, // TODO: enable + collectCoverage: true, coverageDirectory: '/__testing__/coverage', coveragePathIgnorePatterns: [ '/node_modules/', diff --git a/package-lock.json b/package-lock.json index 83aea6b..bc74b68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "monux-cli", - "version": "2.0.9", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "monux-cli", - "version": "2.0.9", + "version": "2.1.0", "license": "MIT", "dependencies": { "chalk": "^4.1.2", diff --git a/package.json b/package.json index 0a2771a..35f55b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "monux-cli", - "version": "2.0.9", + "version": "2.1.0", "license": "MIT", "main": "index.js", "engines": { diff --git a/src/angular/angular.utilities.ts b/src/angular/angular.utilities.ts index ec154e6..71f917a 100644 --- a/src/angular/angular.utilities.ts +++ b/src/angular/angular.utilities.ts @@ -809,7 +809,7 @@ export abstract class AngularUtilities { { defaultImport: false, element: 'OfflineService', path: './services/offline.service' } ] ); - // TODO: enable OfflineRequestInterceptor + // TODO: enable OfflineRequestInterceptor. Need to fix parsing of provideServiceWorker first. // await this.addProvider( // root, // { provide: 'HTTP_INTERCEPTORS', useClass: 'OfflineRequestInterceptor' as any, multi: true }, diff --git a/src/commands/add/add-angular/add-angular.command.ts b/src/commands/add/add-angular/add-angular.command.ts index a55e8b8..74a766f 100644 --- a/src/commands/add/add-angular/add-angular.command.ts +++ b/src/commands/add/add-angular/add-angular.command.ts @@ -192,8 +192,10 @@ export class AddAngularCommand extends AddCommand { const newProject: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name); await FsUtilities.updateFile(getPath(newProject.path, 'src', 'app', 'app.component.html'), '', 'replace'); await AngularUtilities.addProvider(newProject.path, 'provideHttpClient(withInterceptorsFromDi(), withFetch())', [ + // eslint-disable-next-line sonar/no-duplicate-string { defaultImport: false, element: 'provideHttpClient', path: '@angular/common/http' }, - { defaultImport: false, element: 'withInterceptorsFromDi', path: '@angular/common/http' } + { defaultImport: false, element: 'withInterceptorsFromDi', path: '@angular/common/http' }, + { defaultImport: false, element: 'withFetch', path: '@angular/common/http' } ]); return newProject.path; } diff --git a/src/commands/add/add-loopback/add-loopback-command.test.ts b/src/commands/add/add-loopback/add-loopback-command.test.ts index 16ea9b6..e2d377a 100644 --- a/src/commands/add/add-loopback/add-loopback-command.test.ts +++ b/src/commands/add/add-loopback/add-loopback-command.test.ts @@ -15,7 +15,7 @@ describe('AddLoopbackCommand', () => { 'Email of the default user': 'test@test.com', 'Password of the default user': 'stringstring', 'Name of the frontend where the reset password ui is implemented': 'admin', - 'Compose service': 'NEW', + 'Database compose service': 'NEW', 'Compose service name': 'db', 'Database name': 'sandbox', 'database type': DbType.POSTGRES diff --git a/src/commands/add/add-loopback/add-loopback.command.ts b/src/commands/add/add-loopback/add-loopback.command.ts index 4a322af..e7c8cf7 100644 --- a/src/commands/add/add-loopback/add-loopback.command.ts +++ b/src/commands/add/add-loopback/add-loopback.command.ts @@ -2,7 +2,7 @@ import { APPS_DIRECTORY_NAME, PROD_DOCKER_COMPOSE_FILE_NAME, DOCKER_FILE_NAME, E import { DbUtilities } from '../../../db'; import { DockerUtilities } from '../../../docker'; import { FsUtilities, QuestionsFor } from '../../../encapsulation'; -import { DefaultEnvKeys, EnvironmentVariableKey, EnvUtilities } from '../../../env'; +import { DefaultEnvKeys, EnvUtilities } from '../../../env'; import { EslintUtilities } from '../../../eslint'; import { LbDatabaseConfig, LoopbackUtilities } from '../../../loopback'; import { NpmPackage, NpmUtilities } from '../../../npm'; @@ -179,11 +179,6 @@ export class AddLoopbackCommand extends AddCommand { await NpmUtilities.install(projectName, [NpmPackage.LOOPBACK_CONNECTOR_POSTGRES]); await LoopbackUtilities.runCommand(root, `datasource ${databaseName}`, { '--config': lbDatabaseConfig, '--yes': true }); - const PASSWORD_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbPassword(dbServiceName, databaseName); - const USER_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbUser(dbServiceName, databaseName); - const DATABASE_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbName(dbServiceName, databaseName); - const HOST_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbHost(dbServiceName); - const dataSourcePath: string = getPath(root, 'src', 'datasources', `${toKebabCase(databaseName)}.datasource.ts`); await TsUtilities.addImportStatements( dataSourcePath, @@ -197,11 +192,11 @@ export class AddLoopbackCommand extends AddCommand { [ 'const config = {', ' url: \'\',', - ` host: environment.${HOST_ENV_VARIABLE},`, + ` host: environment.${DefaultEnvKeys.dbHost(dbServiceName)},`, ' port: 5432,', - ` user: environment.${USER_ENV_VARIABLE},`, - ` password: environment.${PASSWORD_ENV_VARIABLE},`, - ` database: environment.${DATABASE_ENV_VARIABLE},` + ` user: environment.${DefaultEnvKeys.dbUser(dbServiceName, databaseName)},`, + ` password: environment.${DefaultEnvKeys.dbPassword(dbServiceName, databaseName)},`, + ` database: environment.${DefaultEnvKeys.dbName(dbServiceName, databaseName)},` ].join('\n') ); await FsUtilities.replaceInFile( @@ -222,10 +217,15 @@ export class AddLoopbackCommand extends AddCommand { ); const environmentModel: string = getPath(root, 'src', 'environment', ENVIRONMENT_MODEL_TS_FILE_NAME); - await EnvUtilities.addProjectVariableKey(projectName, environmentModel, PASSWORD_ENV_VARIABLE, true); - await EnvUtilities.addProjectVariableKey(projectName, environmentModel, USER_ENV_VARIABLE, true); - await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DATABASE_ENV_VARIABLE, true); - await EnvUtilities.addProjectVariableKey(projectName, environmentModel, HOST_ENV_VARIABLE, true); + await EnvUtilities.addProjectVariableKey( + projectName, + environmentModel, + DefaultEnvKeys.dbPassword(dbServiceName, databaseName), + true + ); + await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DefaultEnvKeys.dbUser(dbServiceName, databaseName), true); + await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DefaultEnvKeys.dbName(dbServiceName, databaseName), true); + await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DefaultEnvKeys.dbHost(dbServiceName), true); } private async createProject(config: AddLoopbackConfiguration): Promise { diff --git a/src/commands/add/add-wordpress/add-wordpress.command.ts b/src/commands/add/add-wordpress/add-wordpress.command.ts index 4c99d8a..a14fcf6 100644 --- a/src/commands/add/add-wordpress/add-wordpress.command.ts +++ b/src/commands/add/add-wordpress/add-wordpress.command.ts @@ -2,9 +2,9 @@ import { DEV_DOCKER_COMPOSE_FILE_NAME } from '../../../constants'; import { DbType, DbUtilities } from '../../../db'; import { ComposeService, DockerUtilities } from '../../../docker'; import { QuestionsFor } from '../../../encapsulation'; -import { DefaultEnvKeys, EnvUtilities } from '../../../env'; +import { DefaultEnvKeys } from '../../../env'; import { OmitStrict } from '../../../types'; -import { toKebabCase, toSnakeCase } from '../../../utilities'; +import { toKebabCase } from '../../../utilities'; import { AddCommand, AddConfiguration } from '../models'; /** @@ -31,22 +31,16 @@ export class AddWordpressCommand extends AddCommand { override async run(): Promise { const config: AddWordpressConfiguration = await this.getConfig(); - // TODO: make calculated variables based on subDomain and port. - await EnvUtilities.addStaticVariable({ - key: DefaultEnvKeys.domain(config.name), - required: true, - type: 'string', - value: 'localhost' - }); - await EnvUtilities.addStaticVariable({ key: DefaultEnvKeys.baseUrl(config.name), required: true, type: 'string', value: 'http://localhost' }); - const { dbServiceName } = await DbUtilities.configureDb(config.name, DbType.MARIADB); - await this.createProject(config, dbServiceName); + const { dbServiceName, databaseName } = await DbUtilities.configureDb(config.name, DbType.MARIADB); + await this.createProject(config, dbServiceName, databaseName); } - private async createProject(config: AddWordpressConfiguration, dbServiceName: string, version: string = '6.1'): Promise { - const DB_PASSWORD_ENV_VARIABLE: string = `${toSnakeCase(config.name)}_db_password`; - const DB_USER_ENV_VARIABLE: string = `${toSnakeCase(config.name)}_db_user`; - const DB_NAME_ENV_VARIABLE: string = `${toSnakeCase(config.name)}_database`; + private async createProject( + config: AddWordpressConfiguration, + dbServiceName: string, + databaseName: string, + version: string = '6.1' + ): Promise { const serviceDefinition: ComposeService = { name: config.name, image: `wordpress:${version}`, @@ -63,15 +57,15 @@ export class AddWordpressCommand extends AddCommand { }, { key: 'WORDPRESS_DB_USER', - value: `\${${DB_USER_ENV_VARIABLE}}` + value: `\${${DefaultEnvKeys.dbUser(dbServiceName, databaseName)}}` }, { key: 'WORDPRESS_DB_PASSWORD', - value: `\${${DB_PASSWORD_ENV_VARIABLE}}` + value: `\${${DefaultEnvKeys.dbPassword(dbServiceName, databaseName)}}` }, { key: 'WORDPRESS_DB_NAME', - value: `\${${DB_NAME_ENV_VARIABLE}}` + value: `\${${DefaultEnvKeys.dbName(dbServiceName, databaseName)}}` } ] }; diff --git a/src/db/db.utilities.ts b/src/db/db.utilities.ts index fc71bd6..4ea8cd7 100644 --- a/src/db/db.utilities.ts +++ b/src/db/db.utilities.ts @@ -1,6 +1,6 @@ import { Dirent } from 'fs'; -import { DATABASES_DIRECTORY_NAME, DEV_DOCKER_COMPOSE_FILE_NAME, DockerComposeFileName } from '../constants'; +import { DATABASES_DIRECTORY_NAME, DEV_DOCKER_COMPOSE_FILE_NAME, DockerComposeFileName, GLOBAL_ENVIRONMENT_MODEL_FILE_NAME } from '../constants'; import { ComposeService, DockerUtilities } from '../docker'; import { FsUtilities, InquirerUtilities, JsonUtilities, QuestionsFor } from '../encapsulation'; import { DefaultEnvKeys, EnvironmentVariableKey, EnvUtilities } from '../env'; @@ -132,7 +132,7 @@ export abstract class DbUtilities { const baseDbQuestions: QuestionsFor = { dbServiceName: { type: 'select', - message: 'Compose service', + message: 'Database compose service', choices: ['NEW', ...(await this.getAvailableDatabases()).map(db => db.name)], default: 'NEW' }, @@ -230,12 +230,6 @@ export abstract class DbUtilities { } private static async createMariaDbDatabase(dbServiceName: string, databaseName: string): Promise { - const PASSWORD_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbPassword(dbServiceName, databaseName); - const USER_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbUser(dbServiceName, databaseName); - const DATABASE_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbName(dbServiceName, databaseName); - const HOST_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbHost(dbServiceName); - const ROOT_PASSWORD_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbRootPassword(dbServiceName); - const user: string = `${toSnakeCase(databaseName)}_user`; const password: string = generatePlaceholderPassword(); const rootPassword: string = generatePlaceholderPassword(); @@ -256,7 +250,7 @@ export abstract class DbUtilities { environment: [ { key: 'MARIADB_ROOT_PASSWORD', - value: `\${${ROOT_PASSWORD_ENV_VARIABLE}}` + value: `\${${DefaultEnvKeys.dbRootPassword(dbServiceName)}}` } ] }; @@ -274,45 +268,57 @@ export abstract class DbUtilities { ); await DockerUtilities.addVolumeToCompose(`${toKebabCase(dbServiceName)}-data`, DEV_DOCKER_COMPOSE_FILE_NAME); await EnvUtilities.addStaticVariable({ - key: PASSWORD_ENV_VARIABLE, + key: DefaultEnvKeys.dbPassword(dbServiceName, databaseName), value: password, required: true, type: 'string' }); await EnvUtilities.addStaticVariable({ - key: USER_ENV_VARIABLE, + key: DefaultEnvKeys.dbUser(dbServiceName, databaseName), value: user, required: true, type: 'string' }); await EnvUtilities.addStaticVariable({ - key: DATABASE_ENV_VARIABLE, + key: DefaultEnvKeys.dbName(dbServiceName, databaseName), value: databaseName, required: true, type: 'string' }); - // TODO: make calculated variable either "localhost" for dev or "serviceName" for local/prod. await EnvUtilities.addStaticVariable({ - key: HOST_ENV_VARIABLE, - value: 'localhost', + key: DefaultEnvKeys.dbRootPassword(dbServiceName), + value: rootPassword, required: true, type: 'string' }); - await EnvUtilities.addStaticVariable({ - key: ROOT_PASSWORD_ENV_VARIABLE, - value: rootPassword, + + await EnvUtilities.addCalculatedVariable({ + key: DefaultEnvKeys.dbHost(dbServiceName), required: true, - type: 'string' + type: 'string', + value: (env, fileName) => { + switch (fileName) { + case 'dev.docker-compose.yaml': { + return 'localhost'; + } + case 'docker-compose.yaml': + case 'local.docker-compose.yaml': { + return 'DB_SERVICE_NAME_PLACEHOLDER'; + } + } + + } }); + + const environmentModelFilePath: string = getPath(GLOBAL_ENVIRONMENT_MODEL_FILE_NAME); + await FsUtilities.replaceInFile( + environmentModelFilePath, + 'DB_SERVICE_NAME_PLACEHOLDER', + dbServiceName + ); } private static async createPostgresDatabase(dbServiceName: string, databaseName: string): Promise { - const PASSWORD_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbPassword(dbServiceName, databaseName); - const USER_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbUser(dbServiceName, databaseName); - const DATABASE_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbName(dbServiceName, databaseName); - const HOST_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbHost(dbServiceName); - const ROOT_PASSWORD_ENV_VARIABLE: EnvironmentVariableKey = DefaultEnvKeys.dbRootPassword(dbServiceName); - const user: string = `${toSnakeCase(databaseName)}_user`; const password: string = generatePlaceholderPassword(); const rootPassword: string = generatePlaceholderPassword(); @@ -333,7 +339,7 @@ export abstract class DbUtilities { environment: [ { key: 'POSTGRES_PASSWORD', - value: `\${${ROOT_PASSWORD_ENV_VARIABLE}}` + value: `\${${DefaultEnvKeys.dbRootPassword(dbServiceName)}}` } ] }; @@ -351,36 +357,53 @@ export abstract class DbUtilities { ); await DockerUtilities.addVolumeToCompose(`${toKebabCase(dbServiceName)}-data`, DEV_DOCKER_COMPOSE_FILE_NAME); await EnvUtilities.addStaticVariable({ - key: PASSWORD_ENV_VARIABLE, + key: DefaultEnvKeys.dbPassword(dbServiceName, databaseName), value: password, required: true, type: 'string' }); await EnvUtilities.addStaticVariable({ - key: USER_ENV_VARIABLE, + key: DefaultEnvKeys.dbUser(dbServiceName, databaseName), value: user, required: true, type: 'string' }); await EnvUtilities.addStaticVariable({ - key: DATABASE_ENV_VARIABLE, + key: DefaultEnvKeys.dbName(dbServiceName, databaseName), value: databaseName, required: true, type: 'string' }); - // TODO: make calculated variable either "localhost" for dev or "serviceName" for local/prod. await EnvUtilities.addStaticVariable({ - key: HOST_ENV_VARIABLE, - value: 'localhost', + key: DefaultEnvKeys.dbRootPassword(dbServiceName), + value: rootPassword, required: true, type: 'string' }); - await EnvUtilities.addStaticVariable({ - key: ROOT_PASSWORD_ENV_VARIABLE, - value: rootPassword, + + await EnvUtilities.addCalculatedVariable({ + key: DefaultEnvKeys.dbHost(dbServiceName), required: true, - type: 'string' + type: 'string', + value: (env, fileName) => { + switch (fileName) { + case 'dev.docker-compose.yaml': { + return 'localhost'; + } + case 'docker-compose.yaml': + case 'local.docker-compose.yaml': { + return 'DB_SERVICE_NAME_PLACEHOLDER'; + } + } + + } }); + const environmentModelFilePath: string = getPath(GLOBAL_ENVIRONMENT_MODEL_FILE_NAME); + await FsUtilities.replaceInFile( + environmentModelFilePath, + 'DB_SERVICE_NAME_PLACEHOLDER', + dbServiceName + ); } private static isDatabaseService(service: ComposeService): boolean { diff --git a/src/loopback/loopback.utilities.ts b/src/loopback/loopback.utilities.ts index e10bd68..c535147 100644 --- a/src/loopback/loopback.utilities.ts +++ b/src/loopback/loopback.utilities.ts @@ -2,7 +2,7 @@ import { AddLoopbackConfiguration } from '../commands/add/add-loopback'; import { ENVIRONMENT_MODEL_TS_FILE_NAME } from '../constants'; import { CPUtilities, FsUtilities } from '../encapsulation'; -import { DefaultEnvKeys, EnvironmentVariableKey, EnvUtilities } from '../env'; +import { DefaultEnvKeys, EnvUtilities } from '../env'; import { TsUtilities } from '../ts'; import { generatePlaceholderPassword, getPath, optionsToCliString, toKebabCase, toPascalCase } from '../utilities'; import { LbDatabaseConfig } from './lb-database-config.model'; @@ -335,11 +335,18 @@ export abstract class LoopbackUtilities { root: string, config: AddLoopbackConfiguration ): Promise { - const emailEnvKey: EnvironmentVariableKey = DefaultEnvKeys.defaultUserEmail(config.name); - const passwordEnvKey: EnvironmentVariableKey = DefaultEnvKeys.defaultUserPassword(config.name); - - await EnvUtilities.addStaticVariable({ key: emailEnvKey, required: true, type: 'string', value: config.defaultUserEmail }); - await EnvUtilities.addStaticVariable({ key: passwordEnvKey, required: true, type: 'string', value: config.defaultUserPassword }); + await EnvUtilities.addStaticVariable({ + key: DefaultEnvKeys.defaultUserEmail(config.name), + required: true, + type: 'string', + value: config.defaultUserEmail + }); + await EnvUtilities.addStaticVariable({ + key: DefaultEnvKeys.defaultUserPassword(config.name), + required: true, + type: 'string', + value: config.defaultUserPassword + }); await EnvUtilities.addStaticVariable( { key: DefaultEnvKeys.ACCESS_TOKEN_SECRET, required: true, type: 'string', value: generatePlaceholderPassword() } ); @@ -360,8 +367,8 @@ export abstract class LoopbackUtilities { ); const environmentModel: string = getPath(root, 'src', 'environment', ENVIRONMENT_MODEL_TS_FILE_NAME); - await EnvUtilities.addProjectVariableKey(config.name, environmentModel, emailEnvKey, true); - await EnvUtilities.addProjectVariableKey(config.name, environmentModel, passwordEnvKey, true); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.defaultUserEmail(config.name), true); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.defaultUserPassword(config.name), true); await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'access_token_secret', true); await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'refresh_token_secret', true); await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_user', true); @@ -390,20 +397,19 @@ export abstract class LoopbackUtilities { ); await FsUtilities.replaceInFile(indexTs, 'return app', 'await createDefaultData(app);\n return app'); - const emailEnvKey: EnvironmentVariableKey = DefaultEnvKeys.defaultUserEmail(config.name); - const passwordEnvKey: EnvironmentVariableKey = DefaultEnvKeys.defaultUserPassword(config.name); await FsUtilities.updateFile(indexTs, [ '', `async function createDefaultData(app: ${toPascalCase(config.name)}Application): Promise {`, ' const adminController: AdminController = await app.get(\'controllers.AdminController\');', // eslint-disable-next-line stylistic/max-len ' const baseUserRepository: BaseUserRepository = await app.get>(\'repositories.BaseUserRepository\');', - ` if (!await baseUserRepository.findOne({ where: { email: environment.${emailEnvKey} } })) {`, + // eslint-disable-next-line stylistic/max-len + ` if (!await baseUserRepository.findOne({ where: { email: environment.${DefaultEnvKeys.defaultUserEmail(config.name)} } })) {`, // eslint-disable-next-line stylistic/max-len ' const newAdmin: Omit = {', ' name: \'Root\',', - ` password: environment.${passwordEnvKey},`, - ` email: environment.${emailEnvKey}`, + ` password: environment.${DefaultEnvKeys.defaultUserPassword(config.name)},`, + ` email: environment.${DefaultEnvKeys.defaultUserEmail(config.name)}`, ' };', ' await adminController.create(newAdmin);', ' }',