diff --git a/package-lock.json b/package-lock.json index a2cf6f2..0ee4bf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "monux-cli", - "version": "2.2.1", + "version": "2.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "monux-cli", - "version": "2.2.1", + "version": "2.2.4", "license": "MIT", "dependencies": { "chalk": "^4.1.2", diff --git a/package.json b/package.json index 82a3277..ef4fb24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "monux-cli", - "version": "2.2.3", + "version": "2.2.4", "license": "MIT", "main": "index.js", "engines": { diff --git a/src/commands/add/add-loopback/add-loopback.command.ts b/src/commands/add/add-loopback/add-loopback.command.ts index 875dab3..42b0c47 100644 --- a/src/commands/add/add-loopback/add-loopback.command.ts +++ b/src/commands/add/add-loopback/add-loopback.command.ts @@ -1,5 +1,6 @@ +import { loopbackViteContent } from './loopback-vite.content'; import { loopbackWebpackContent } from './loopback-webpack.content'; -import { APPS_DIRECTORY_NAME, PROD_DOCKER_COMPOSE_FILE_NAME, DOCKER_FILE_NAME, ENVIRONMENT_MODEL_TS_FILE_NAME, GIT_IGNORE_FILE_NAME, TS_CONFIG_FILE_NAME, WEBPACK_CONFIG, BASE_TS_CONFIG_FILE_NAME } from '../../../constants'; +import { APPS_DIRECTORY_NAME, PROD_DOCKER_COMPOSE_FILE_NAME, DOCKER_FILE_NAME, ENVIRONMENT_MODEL_TS_FILE_NAME, GIT_IGNORE_FILE_NAME, TS_CONFIG_FILE_NAME, WEBPACK_CONFIG, BASE_TS_CONFIG_FILE_NAME, VITE_CONFIG } from '../../../constants'; import { DbType, DbUtilities } from '../../../db'; import { DockerUtilities } from '../../../docker'; import { FsUtilities, QuestionsFor } from '../../../encapsulation'; @@ -93,7 +94,7 @@ export class AddLoopbackCommand extends BaseAddCommand await Promise.all([ this.setupTsConfig(config.name), - this.updateApplicationTs(root), + this.updateApplicationTs(root, databaseName), this.updateIndexTs(root, config.port), this.updateOpenApiSpec(root, config.port), EslintUtilities.setupProjectEslint(root, true, TS_CONFIG_FILE_NAME), @@ -112,6 +113,7 @@ export class AddLoopbackCommand extends BaseAddCommand config.subDomain ), this.updateDockerFile(root, config) + // this.setupVite(root, config.name) // this.setupWebpack(root, config.name) TODO: enable ]); @@ -119,7 +121,6 @@ export class AddLoopbackCommand extends BaseAddCommand scripts: { start: 'npm run start:watch', 'start:watch': 'tsc-watch --target es2017 --outDir ./dist --onSuccess \"node .\"' - // 'build:webpack': 'webpack' TODO: enable } }); await NpmUtilities.install(config.name, [NpmPackage.TSC_WATCH], true); @@ -132,24 +133,24 @@ export class AddLoopbackCommand extends BaseAddCommand await EnvUtilities.buildEnvironmentFileForApp(app, false, 'dev.docker-compose.yaml', getPath('.')); } + private async setupVite(root: Path, projectName: string): Promise { + await FsUtilities.createFile(getPath(root, VITE_CONFIG), loopbackViteContent); + await NpmUtilities.install(projectName, [NpmPackage.VITE, NpmPackage.VITE_TS_CONFIG_PATHS], true); + await NpmUtilities.updatePackageJson(projectName, { + scripts: { + 'build:vite': 'vite build' + } + }); + } + private async setupWebpack(root: Path, projectName: string): Promise { await FsUtilities.createFile(getPath(root, WEBPACK_CONFIG), loopbackWebpackContent); - await NpmUtilities.install( - projectName, - [ - NpmPackage.WEBPACK, - NpmPackage.WEBPACK_CLI, - NpmPackage.TS_LOADER, - NpmPackage.WEBPACK_NODE_EXTERNALS, - NpmPackage.TSCONFIG_PATH_WEBPACK_PLUGIN, - NpmPackage.FORK_TS_CHECKER_WEBPACK_PLUGIN - ], - true - ); - await NpmUtilities.install(projectName, [ - NpmPackage.CLDRJS, - NpmPackage.CLDR_DATA - ]); + await NpmUtilities.install(projectName, [NpmPackage.WEBPACK, NpmPackage.WEBPACK_CLI], true); + await NpmUtilities.updatePackageJson(projectName, { + scripts: { + 'build:webpack': 'webpack' + } + }); } private async updateDockerFile(root: string, config: AddLoopbackConfiguration): Promise { @@ -181,7 +182,10 @@ export class AddLoopbackCommand extends BaseAddCommand await FsUtilities.replaceInFile( indexPath, ' await app.boot();', - ' await app.boot();\n await app.migrateSchema({ existingSchema: \'alter\' });' + [ + ' await app.boot();', + ' await app.migrateSchema({ existingSchema: \'alter\' });' + ].join('\n') ); await FsUtilities.replaceInFile(indexPath, 'env.PORT', 'env[\'PORT\']'); await FsUtilities.replaceInFile(indexPath, 'env.HOST', 'env[\'HOST\']'); @@ -196,13 +200,18 @@ export class AddLoopbackCommand extends BaseAddCommand await FsUtilities.replaceInFile(openApiPath, '?? 3000', `?? ${port}`); } - private async updateApplicationTs(root: string): Promise { + private async updateApplicationTs(root: string, dbName: string): Promise { const applicationPath: Path = getPath(root, 'src', 'application.ts'); await FsUtilities.replaceInFile( applicationPath, 'BootMixin(RestApplication)', 'BootMixin(ServiceMixin(RepositoryMixin(RestApplication)))' ); + await TsUtilities.addToConstructorBody( + applicationPath, + // eslint-disable-next-line stylistic/max-len + `this.dataSource(${toPascalCase(dbName)}DataSource, 'db'); // "db" is the key under wich the datasource is registered in teh external components` + ); await TsUtilities.addImportStatements( applicationPath, [ diff --git a/src/commands/add/add-loopback/loopback-vite.content.ts b/src/commands/add/add-loopback/loopback-vite.content.ts new file mode 100644 index 0000000..1ad7e2e --- /dev/null +++ b/src/commands/add/add-loopback/loopback-vite.content.ts @@ -0,0 +1,22 @@ + +// eslint-disable-next-line jsdoc/require-jsdoc +export const loopbackViteContent: string = `import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [tsconfigPaths()], + build: { + ssr: 'src/index.ts', + target: 'node20', + outDir: 'dist', + emptyOutDir: true, + rollupOptions: { + output: { + format: 'cjs', + entryFileNames: '[name].js', + chunkFileNames: '[name].js', + inlineDynamicImports: true + } + } + } +});`; \ No newline at end of file diff --git a/src/commands/init/init.command.ts b/src/commands/init/init.command.ts index fbb3dd5..ce65093 100644 --- a/src/commands/init/init.command.ts +++ b/src/commands/init/init.command.ts @@ -83,7 +83,6 @@ export class InitCommand extends BaseCommand { ENV_FILE_NAME, ENVIRONMENT_TS_FILE_NAME, ROBOTS_FILE_NAME, - '**/init/**.sh', '**/init/**.sql', 'letsencrypt', '# compiled output', diff --git a/src/constants.ts b/src/constants.ts index 317f884..41b6e38 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -141,6 +141,11 @@ export const NEST_CLI_FILE_NAME: string = 'nest-cli.json'; */ export const WEBPACK_CONFIG: string = 'webpack.config.js'; +/** + * The name of the vite config file. + */ +export const VITE_CONFIG: string = 'vite.config.ts'; + /** * The message to notify the user of the help command. */ diff --git a/src/db/db-utilities.test.ts b/src/db/db-utilities.test.ts index 6558e46..72e3ff7 100644 --- a/src/db/db-utilities.test.ts +++ b/src/db/db-utilities.test.ts @@ -166,18 +166,30 @@ describe('DbUtilities', () => { await DbUtilities.createInitFiles('dev.docker-compose.yaml', getPath('.')); - const initShContent: string[] = await FsUtilities.readFileLines(getPath(mockConstants.PROJECT_DIR, DATABASES_DIRECTORY_NAME, 'postgres-db', 'init', '0.sh')); + const postgresInitContent: string[] = await FsUtilities.readFileLines(getPath(mockConstants.PROJECT_DIR, DATABASES_DIRECTORY_NAME, 'postgres-db', 'init', '0.sql')); const postgresPassword: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.dbPassword(POSTGRES_SERVICE_NAME, POSTGRES_DATABASE_NAME), 'dev.docker-compose.yaml', getPath('.')); - const initSqlContent: string[] = await FsUtilities.readFileLines(getPath(mockConstants.PROJECT_DIR, DATABASES_DIRECTORY_NAME, 'maria-db', 'init', '0.sql')); + const mariadbInitContent: string[] = await FsUtilities.readFileLines(getPath(mockConstants.PROJECT_DIR, DATABASES_DIRECTORY_NAME, 'maria-db', 'init', '0.sql')); const mariadbPassword: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.dbPassword(MARIADB_SERVICE_NAME, MARIADB_DATABASE_NAME), 'dev.docker-compose.yaml', getPath('.')); - expect(initShContent).toEqual([ - '#!/bin/bash', - 'psql -tc "SELECT 1 FROM pg_database WHERE datname = \'test2\'" | grep -q 1 || psql -c "CREATE DATABASE test2"', - `psql -tc "SELECT 1 FROM pg_roles WHERE rolname = 'test2_user'" | grep -q 1 || psql -c "CREATE USER test2_user WITH PASSWORD '${postgresPassword}'"`, - 'psql -c "GRANT ALL PRIVILEGES ON DATABASE test2 TO test2_user"' + expect(postgresInitContent).toEqual([ + '-- 1) Create DB if missing', + 'SELECT format(\'CREATE DATABASE %I\', \'test2\')', + ' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = \'test2\')\\gexec', + '', + '-- 2) Create user if missing', + 'SELECT format(', + ' \'CREATE USER %I WITH PASSWORD %L\',', + ' \'test2_user\',', + ` '${postgresPassword}'`, + ')', + ' WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = \'test2_user\')\\gexec', + '', + '-- 3) Grant privileges', + 'GRANT ALL PRIVILEGES ON DATABASE "test2" TO "test2_user";', + '\\connect test2', + 'GRANT ALL PRIVILEGES ON SCHEMA public TO test2_user;' ]); - expect(initSqlContent).toEqual([ + expect(mariadbInitContent).toEqual([ 'CREATE DATABASE IF NOT EXISTS `test`;', '', `CREATE USER IF NOT EXISTS 'test_user'@'%' IDENTIFIED BY '${mariadbPassword}';`, diff --git a/src/db/db.utilities.ts b/src/db/db.utilities.ts index 9adc53e..16908ef 100644 --- a/src/db/db.utilities.ts +++ b/src/db/db.utilities.ts @@ -70,18 +70,15 @@ export abstract class DbUtilities { for (const db of dbs) { const configs: DbInitConfig[] = await this.getInitConfigsForDb(db.name, rootDir); for (let i: number = 0; i < configs.length; i++) { - const initFileSh: Path = getPath(rootDir, DATABASES_DIRECTORY_NAME, toKebabCase(db.name), 'init', `${i}.sh`); const initFileSql: Path = getPath(rootDir, DATABASES_DIRECTORY_NAME, toKebabCase(db.name), 'init', `${i}.sql`); - await FsUtilities.rm(initFileSh); await FsUtilities.rm(initFileSql); - await this.createInitFile(configs[i], initFileSh, initFileSql, fileName, rootDir); + await this.createInitFile(configs[i], initFileSql, fileName, rootDir); } } } private static async createInitFile( config: DbInitConfig, - initFileSh: Path, initFileSql: Path, fileName: DockerComposeFileName, rootDir: string @@ -93,14 +90,24 @@ export abstract class DbUtilities { switch (config.type) { case DbType.POSTGRES: { await FsUtilities.createFile( - initFileSh, + initFileSql, [ - '#!/bin/bash', - // eslint-disable-next-line stylistic/max-len - `psql -tc "SELECT 1 FROM pg_database WHERE datname = '${dbName}'" | grep -q 1 || psql -c "CREATE DATABASE ${dbName}"`, - // eslint-disable-next-line stylistic/max-len - `psql -tc "SELECT 1 FROM pg_roles WHERE rolname = '${dbUser}'" | grep -q 1 || psql -c "CREATE USER ${dbUser} WITH PASSWORD '${dbPassword}'"`, - `psql -c "GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${dbUser}"` + '-- 1) Create DB if missing', + `SELECT format('CREATE DATABASE %I', '${dbName}')`, + ` WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${dbName}')\\gexec`, + '', + '-- 2) Create user if missing', + 'SELECT format(', + ' \'CREATE USER %I WITH PASSWORD %L\',', + ` '${dbUser}',`, + ` '${dbPassword}'`, + ')', + ` WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${dbUser}')\\gexec`, + '', + '-- 3) Grant privileges', + `GRANT ALL PRIVILEGES ON DATABASE "${dbName}" TO "${dbUser}";`, + `\\connect ${dbName}`, + `GRANT ALL PRIVILEGES ON SCHEMA public TO ${dbUser};` ] ); break; diff --git a/src/npm/npm-package.enum.ts b/src/npm/npm-package.enum.ts index 777cdfd..0ec5eba 100644 --- a/src/npm/npm-package.enum.ts +++ b/src/npm/npm-package.enum.ts @@ -37,6 +37,7 @@ export enum NpmPackage { TYPEORM = 'typeorm', PG = 'pg', MYSQL_2 = 'mysql2', + // TODO: clean up after loopback4 bundling WEBPACK = 'webpack', WEBPACK_CLI = 'webpack-cli', TS_LOADER = 'ts-loader', @@ -44,5 +45,7 @@ export enum NpmPackage { TSCONFIG_PATH_WEBPACK_PLUGIN = 'tsconfig-paths-webpack-plugin', FORK_TS_CHECKER_WEBPACK_PLUGIN = 'fork-ts-checker-webpack-plugin', CLDRJS = 'cldrjs', - CLDR_DATA = 'cldr-data' + CLDR_DATA = 'cldr-data', + VITE = 'vite', + VITE_TS_CONFIG_PATHS = 'vite-tsconfig-paths' } \ No newline at end of file