From f572a9d21fed4519981a57fddb8efda377966beb Mon Sep 17 00:00:00 2001 From: Tim Fabian Date: Fri, 1 Aug 2025 00:38:45 +0200 Subject: [PATCH] added zibri project type --- cspell.words.txt | 3 +- package-lock.json | 4 +- package.json | 2 +- .../add/add-zibri/add-zibri-command.test.ts | 37 ++ .../add/add-zibri/add-zibri.command.ts | 406 ++++++++++++++++++ src/commands/add/add-zibri/index.ts | 1 + src/commands/add/add.command.ts | 5 + src/commands/add/models/add-type.enum.ts | 1 + src/encapsulation/chalk.utilities.ts | 4 +- src/env/env.utilities.ts | 11 +- src/loopback/loopback.utilities.ts | 5 +- src/zibri/index.ts | 1 + src/zibri/zibri-utilities.test.ts | 21 + src/zibri/zibri.utilities.ts | 51 +++ 14 files changed, 543 insertions(+), 9 deletions(-) create mode 100644 src/commands/add/add-zibri/add-zibri-command.test.ts create mode 100644 src/commands/add/add-zibri/add-zibri.command.ts create mode 100644 src/commands/add/add-zibri/index.ts create mode 100644 src/zibri/index.ts create mode 100644 src/zibri/zibri-utilities.test.ts create mode 100644 src/zibri/zibri.utilities.ts diff --git a/cspell.words.txt b/cspell.words.txt index c77a8eb..ddcd138 100644 --- a/cspell.words.txt +++ b/cspell.words.txt @@ -40,4 +40,5 @@ cldr cldrjs htpasswd basicauth -usersfile \ No newline at end of file +usersfile +zibri \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0ee4bf7..a5a8698 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "monux-cli", - "version": "2.2.4", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "monux-cli", - "version": "2.2.4", + "version": "2.4.0", "license": "MIT", "dependencies": { "chalk": "^4.1.2", diff --git a/package.json b/package.json index 7687b56..6690fb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "monux-cli", - "version": "2.3.0", + "version": "2.4.0", "license": "MIT", "main": "index.js", "engines": { diff --git a/src/commands/add/add-zibri/add-zibri-command.test.ts b/src/commands/add/add-zibri/add-zibri-command.test.ts new file mode 100644 index 0000000..dd1cace --- /dev/null +++ b/src/commands/add/add-zibri/add-zibri-command.test.ts @@ -0,0 +1,37 @@ +import { afterEach, beforeEach, describe, expect, jest, test } from '@jest/globals'; + +import { FileMockUtilities, getMockConstants, MAX_ADD_TIME, MockConstants, inquireMock } from '../../../__testing__'; +import { DbType } from '../../../db'; +import { InquirerUtilities } from '../../../encapsulation'; +import { AddConfiguration, AddType } from '../models'; +import { AddZibriCommand } from './add-zibri.command'; + +const mockConstants: MockConstants = getMockConstants('add-zibri-command'); + +describe('AddZibriCommand', () => { + beforeEach(async () => { + await FileMockUtilities.setup(mockConstants); + InquirerUtilities['inquire'] = jest.fn(inquireMock({ + 'sub domain': 'api', + port: 3000, + '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', + 'Database compose service': 'NEW', + 'Compose service name': 'db', + 'Database name': 'sandbox', + 'database type': DbType.POSTGRES + })); + }); + + test('should run and add a new database', async () => { + const baseConfig: AddConfiguration = { name: 'api', type: AddType.ZIBRI }; + const command: AddZibriCommand = new AddZibriCommand(baseConfig); + await command.run(); + expect(true).toBe(true); + }, MAX_ADD_TIME); + + afterEach(() => { + jest.restoreAllMocks(); + }); +}); \ No newline at end of file diff --git a/src/commands/add/add-zibri/add-zibri.command.ts b/src/commands/add/add-zibri/add-zibri.command.ts new file mode 100644 index 0000000..a52b454 --- /dev/null +++ b/src/commands/add/add-zibri/add-zibri.command.ts @@ -0,0 +1,406 @@ +import { APPS_DIRECTORY_NAME, BASE_TS_CONFIG_FILE_NAME, DOCKER_FILE_NAME, ENVIRONMENT_MODEL_TS_FILE_NAME, PROD_DOCKER_COMPOSE_FILE_NAME } from '../../../constants'; +import { DbType, DbUtilities } from '../../../db'; +import { DockerUtilities } from '../../../docker'; +import { FsUtilities, QuestionsFor } from '../../../encapsulation'; +import { DefaultEnvKeys, EnvUtilities } from '../../../env'; +import { EslintUtilities } from '../../../eslint'; +import { TsConfigUtilities } from '../../../tsconfig'; +import { OmitStrict } from '../../../types'; +import { generatePlaceholderPassword, getPath, Path, toKebabCase, toPascalCase } from '../../../utilities'; +import { WorkspaceProject, WorkspaceUtilities } from '../../../workspace'; +import { ZibriUtilities } from '../../../zibri'; +import { AddConfiguration, BaseAddCommand } from '../models'; + +/** + * Configuration for adding a new zibri api. + */ +type AddZibriConfiguration = AddConfiguration & { + /** + * The name of the frontend where the reset password functionality is implemented. + */ + frontendName: string, + /** + * The port that should be used by the application. + * @default 3000 + */ + port: number, + /** + * The sub domain that this service should be reached under. + * If nothing is provided, Monux assumes that the service should be reached under the root domain + * and under the www sub domain. + */ + subDomain?: string, + /** + * The email for the default root user. + */ + defaultUserEmail: string, + /** + * The password for the default root user. + */ + defaultUserPassword: string +}; + +/** + * Command that handles adding a zibri api to the monorepo. + */ +export class AddZibriCommand extends BaseAddCommand { + + protected override configQuestions: QuestionsFor> = { + port: { + type: 'number', + message: 'port', + required: true, + default: 3000 + }, + subDomain: { + type: 'input', + message: 'sub domain', + required: false + }, + defaultUserEmail: { + type: 'input', + message: 'Email of the default user', + required: true, + default: async () => (await FsUtilities.readFile(getPath(PROD_DOCKER_COMPOSE_FILE_NAME))) + .split('.acme.email=')[1] + .split('\n')[0] + }, + defaultUserPassword: { + type: 'input', + message: 'Password of the default user', + required: true, + validate: (v) => v.length >= 12 ? true : 'Password must be at least 12 characters strong' + }, + frontendName: { + type: 'input', + message: 'Name of the frontend where the reset password ui is implemented', + required: true + } + }; + + override async run(): Promise { + const config: AddZibriConfiguration = await this.getConfig(); + const { dbServiceName, databaseName } = await DbUtilities.configureDb(config.name, DbType.POSTGRES, getPath('.')); + const root: Path = await this.createProject(config); + await EnvUtilities.setupProjectEnvironment(root, false); + await this.createZibriDatasource(dbServiceName, databaseName, DbType.POSTGRES, root, config.name); + await this.updateIndexTs(root, config); + await this.setupAuthVariables(root, config); + + await Promise.all([ + EslintUtilities.setupProjectEslint(root, true, 'tsconfig.json'), + this.setupTsConfig(config.name), + DockerUtilities.addServiceToCompose( + { + name: config.name, + build: { + dockerfile: `./${root}/${DOCKER_FILE_NAME}`, + context: '.' + }, + volumes: [`/${config.name}`] + }, + 3000, + config.port, + true, + false, + config.subDomain + ), + this.createDockerfile(root, config) + ]); + + const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name, getPath('.')); + await EnvUtilities.buildEnvironmentFileForApp(app, false, 'dev.docker-compose.yaml', getPath('.')); + } + + private async createProject(config: AddZibriConfiguration): Promise { + // eslint-disable-next-line no-console + console.log('Creates the base app'); + await ZibriUtilities.runCommand( + getPath(APPS_DIRECTORY_NAME), + `new ${config.name}`, + {} as never + ); + const newProject: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name, getPath('.')); + return newProject.path; + } + + private async createZibriDatasource( + dbServiceName: string, + databaseName: string, + dbType: DbType.POSTGRES, + root: Path, + projectName: string + ): Promise { + const datasourcesPath: Path = getPath( + root, + 'src', + 'data-sources' + ); + const newDbPath: Path = getPath(datasourcesPath, toKebabCase(databaseName)); + await FsUtilities.rm(newDbPath); + await FsUtilities.rm(getPath(datasourcesPath, 'db')); + await FsUtilities.createFile( + getPath(newDbPath, `${toKebabCase(databaseName)}.data-source.ts`), + [ + // eslint-disable-next-line stylistic/max-len + 'import { BaseDataSource, BaseEntity, DataSource, Newable, DataSourceOptions, MigrationEntity, MailingList, MailingListSubscriber, MailingListSubscriptionConfirmationToken, JwtRefreshToken, JwtCredentials, Log, PasswordResetToken } from \'zibri\';', + '', + 'import { environment } from \'../../environment/environment\';', + 'import { User } from \'../../models\';', + '', + '@DataSource()', + `export class ${toPascalCase(databaseName)}DataSource extends BaseDataSource {`, + ' options: DataSourceOptions = {', + ` type: '${dbType}',`, + ` host: environment.${DefaultEnvKeys.dbHost(dbServiceName)},`, + ' port: 5432,', + ` username: environment.${DefaultEnvKeys.dbUser(dbServiceName, databaseName)},`, + ` password: environment.${DefaultEnvKeys.dbPassword(dbServiceName, databaseName)},`, + ` database: environment.${DefaultEnvKeys.dbName(dbServiceName, databaseName)},`, + ' synchronize: true', + ' };', + ' entities: Newable[] = [', + ' MigrationEntity,', + ' User,', + ' JwtRefreshToken,', + ' JwtCredentials,', + ' PasswordResetToken,', + ' MailingList,', + ' MailingListSubscriber,', + ' MailingListSubscriptionConfirmationToken,', + ' Log', + ' ];', + '}' + ] + ); + await FsUtilities.createFile(getPath(newDbPath, 'migrations', 'index.ts'), ''); + await FsUtilities.createFile(getPath(newDbPath, 'index.ts'), `export * from './${toKebabCase(databaseName)}.data-source';`); + await FsUtilities.rm(getPath(datasourcesPath, 'index.ts')); + await FsUtilities.createFile(getPath(datasourcesPath, 'index.ts'), `export * from './${toKebabCase(databaseName)}';`); + + await FsUtilities.replaceInFile( + getPath(root, 'src', 'index.ts'), + 'import { DbDataSource } from \'./data-sources\';', + `import { ${toPascalCase(databaseName)}DataSource } from './data-sources';` + ); + await FsUtilities.replaceInFile( + getPath(root, 'src', 'index.ts'), + 'dataSources: [DbDataSource]', + `dataSources: [${toPascalCase(databaseName)}DataSource]` + ); + + const environmentModel: Path = getPath(root, 'src', 'environment', ENVIRONMENT_MODEL_TS_FILE_NAME); + + await EnvUtilities.addProjectVariableKey( + projectName, + environmentModel, + DefaultEnvKeys.dbPassword(dbServiceName, databaseName), + true, + getPath('.') + ); + await EnvUtilities.addProjectVariableKey( + projectName, + environmentModel, + DefaultEnvKeys.dbUser(dbServiceName, databaseName), + true, + getPath('.') + ); + await EnvUtilities.addProjectVariableKey(projectName, + environmentModel, + DefaultEnvKeys.dbName(dbServiceName, databaseName), + true, + getPath('.')); + await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DefaultEnvKeys.dbHost(dbServiceName), true, getPath('.')); + } + + private async updateIndexTs(root: Path, config: AddZibriConfiguration): Promise { + const indexTs: Path = getPath(root, 'src', 'index.ts'); + await FsUtilities.replaceInFile( + indexTs, + 'baseUrl: \'http://localhost:3000\'', + `baseUrl: environment.${DefaultEnvKeys.baseUrl(config.name)}` + ); + await FsUtilities.replaceInFile( + indexTs, + '\'JWT_ACCESS_TOKEN_SECRET\'', + `environment.${DefaultEnvKeys.ACCESS_TOKEN_SECRET}` + ); + await FsUtilities.replaceInFile( + indexTs, + '\'JWT_REFRESH_TOKEN_SECRET\'', + `environment.${DefaultEnvKeys.REFRESH_TOKEN_SECRET}` + ); + await FsUtilities.replaceInFile( + indexTs, + '\'http://localhost:4200/confirm-password-reset\'', + `\`\${environment.${DefaultEnvKeys.baseUrl(config.frontendName)}}/confirm-password-reset\`` + ); + await FsUtilities.replaceInFile( + indexTs, + 'defaultSender: \'\'', + `defaultSender: environment.${DefaultEnvKeys.WEBSERVER_MAIL_USER}` + ); + await FsUtilities.replaceInFile( + indexTs, + 'host: \'\'', + `host: environment.${DefaultEnvKeys.WEBSERVER_MAIL_HOST}` + ); + await FsUtilities.replaceInFile( + indexTs, + 'port: 0', + `port: environment.${DefaultEnvKeys.WEBSERVER_MAIL_PORT}` + ); + await FsUtilities.replaceInFile( + indexTs, + 'user: \'\'', + `user: environment.${DefaultEnvKeys.WEBSERVER_MAIL_USER}` + ); + await FsUtilities.replaceInFile( + indexTs, + 'pass: \'\'', + `pass: environment.${DefaultEnvKeys.WEBSERVER_MAIL_PASSWORD}` + ); + await FsUtilities.replaceInFile( + indexTs, + 'import { version } from \'../package.json\';', + [ + 'import { version } from \'../package.json\';', + 'import { environment } from \'./environment/environment\';' + ].join('\n') + ); + + const createDefaultDataTs: Path = getPath(root, 'src', 'create-default-data.function.ts'); + await FsUtilities.replaceInFile( + createDefaultDataTs, + '\'password\'', + `environment.${DefaultEnvKeys.defaultUserPassword(config.name)}` + ); + await FsUtilities.replaceAllInFile( + createDefaultDataTs, + '\'zibri@zibri.de\'', + `environment.${DefaultEnvKeys.defaultUserEmail(config.name)}` + ); + } + + private async setupTsConfig(projectName: string): Promise { + // eslint-disable-next-line no-console + console.log('sets up tsconfig'); + await TsConfigUtilities.updateTsConfig( + projectName, + { + extends: `../../${BASE_TS_CONFIG_FILE_NAME}`, + compilerOptions: { + emitDecoratorMetadata: undefined, + experimentalDecorators: undefined, + forceConsistentCasingInFileNames: undefined, + allowSyntheticDefaultImports: undefined, + sourceMap: undefined, + skipLibCheck: undefined, + noImplicitAny: undefined, + noFallthroughCasesInSwitch: undefined + } + } + ); + } + + private async createDockerfile(root: string, config: AddZibriConfiguration): Promise { + await FsUtilities.createFile( + getPath(root, DOCKER_FILE_NAME), + [ + 'FROM node:20 AS build', + '# Set to a non-root built-in user `node`', + 'USER node', + 'RUN mkdir -p /home/node/root', + 'COPY --chown=node . /home/node/root', + 'WORKDIR /home/node/root', + 'RUN npm install', + `RUN npm run build --workspace=${APPS_DIRECTORY_NAME}/${config.name} --omit=dev`, + '', + 'FROM node:20', + 'WORKDIR /usr/app', + `COPY --from=build /home/node/root/${APPS_DIRECTORY_NAME}/${config.name}/dist ./`, + 'CMD node bundle' + ] + ); + } + + private async setupAuthVariables( + root: string, + config: AddZibriConfiguration + ): Promise { + await EnvUtilities.addStaticVariable({ + key: DefaultEnvKeys.defaultUserEmail(config.name), + required: true, + type: 'string', + value: config.defaultUserEmail + }, false); + await EnvUtilities.addStaticVariable({ + key: DefaultEnvKeys.defaultUserPassword(config.name), + required: true, + type: 'string', + value: config.defaultUserPassword + }, false); + await EnvUtilities.addStaticVariable( + { key: DefaultEnvKeys.ACCESS_TOKEN_SECRET, required: true, type: 'string', value: generatePlaceholderPassword() }, + false + ); + await EnvUtilities.addStaticVariable( + { key: DefaultEnvKeys.REFRESH_TOKEN_SECRET, required: true, type: 'string', value: generatePlaceholderPassword() }, + false + ); + await EnvUtilities.addStaticVariable( + { key: DefaultEnvKeys.WEBSERVER_MAIL_USER, required: true, type: 'string', value: undefined }, + false + ); + await EnvUtilities.addStaticVariable( + { key: DefaultEnvKeys.WEBSERVER_MAIL_PASSWORD, required: true, type: 'string', value: undefined }, + false + ); + await EnvUtilities.addStaticVariable( + { key: DefaultEnvKeys.WEBSERVER_MAIL_HOST, required: true, type: 'string', value: undefined }, + false + ); + await EnvUtilities.addStaticVariable( + { key: DefaultEnvKeys.WEBSERVER_MAIL_PORT, required: true, type: 'number', value: undefined }, + false + ); + + const environmentModel: Path = getPath(root, 'src', 'environment', ENVIRONMENT_MODEL_TS_FILE_NAME); + await EnvUtilities.addProjectVariableKey( + config.name, + environmentModel, + DefaultEnvKeys.defaultUserEmail(config.name), + true, + getPath('.') + ); + await EnvUtilities.addProjectVariableKey( + config.name, + environmentModel, + DefaultEnvKeys.defaultUserPassword(config.name), + true, + getPath('.') + ); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.ACCESS_TOKEN_SECRET, true, getPath('.')); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.REFRESH_TOKEN_SECRET, true, getPath('.')); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.WEBSERVER_MAIL_USER, true, getPath('.')); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.WEBSERVER_MAIL_PASSWORD, true, getPath('.')); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.WEBSERVER_MAIL_HOST, true, getPath('.')); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.WEBSERVER_MAIL_PORT, true, getPath('.')); + await EnvUtilities.addProjectVariableKey( + config.name, + environmentModel, + DefaultEnvKeys.baseUrl(config.frontendName), + false, + getPath('.') + ); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.baseUrl(config.name), false, getPath('.')); + await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.ENV, false, getPath('.')); + await EnvUtilities.addProjectVariableKey( + config.name, + environmentModel, + DefaultEnvKeys.domain(config.frontendName), + false, + getPath('.') + ); + } +} \ No newline at end of file diff --git a/src/commands/add/add-zibri/index.ts b/src/commands/add/add-zibri/index.ts new file mode 100644 index 0000000..f7a889c --- /dev/null +++ b/src/commands/add/add-zibri/index.ts @@ -0,0 +1 @@ +export * from './add-zibri.command'; \ No newline at end of file diff --git a/src/commands/add/add.command.ts b/src/commands/add/add.command.ts index a782aea..3e61e61 100644 --- a/src/commands/add/add.command.ts +++ b/src/commands/add/add.command.ts @@ -8,6 +8,7 @@ import { AddConfiguration, addConfigurationQuestions, AddType } from './models'; import { InquirerUtilities } from '../../encapsulation'; import { BaseCommand } from '../base-command.model'; import { AddNestCommand } from './add-nest'; +import { AddZibriCommand } from './add-zibri'; /** * Adds a new project to the current monorepo. @@ -37,6 +38,10 @@ export class AddCommand extends BaseCommand { await new AddNestCommand(config).run(); return; } + case AddType.ZIBRI: { + await new AddZibriCommand(config).run(); + return; + } case AddType.TS_LIBRARY: { await new AddTsLibraryCommand(config).run(); return; diff --git a/src/commands/add/models/add-type.enum.ts b/src/commands/add/models/add-type.enum.ts index 804d6f5..f09f240 100644 --- a/src/commands/add/models/add-type.enum.ts +++ b/src/commands/add/models/add-type.enum.ts @@ -8,6 +8,7 @@ export enum AddType { ANGULAR_LIBRARY = 'angular-library', LOOPBACK = 'loopback', NEST = 'nest', + ZIBRI = 'zibri', TS_LIBRARY = 'ts-library', WORDPRESS = 'wordpress' } \ No newline at end of file diff --git a/src/encapsulation/chalk.utilities.ts b/src/encapsulation/chalk.utilities.ts index abe1b97..3b6142c 100644 --- a/src/encapsulation/chalk.utilities.ts +++ b/src/encapsulation/chalk.utilities.ts @@ -36,8 +36,8 @@ export abstract class ChalkUtilities { } /** - * Used to log errors in red. - * @param value - The value that should be logged as an error. + * Used to log success in green. + * @param value - The value that should be logged as a success message. * @returns The string to log. */ static success(...value: string[]): string { diff --git a/src/env/env.utilities.ts b/src/env/env.utilities.ts index f495856..5a0ce93 100644 --- a/src/env/env.utilities.ts +++ b/src/env/env.utilities.ts @@ -196,7 +196,13 @@ export abstract class EnvUtilities { } private static stringifyEnvKeyValue(v: EnvVariable): string { - const q: string = v.type === 'string' ? '\'' : ''; + let q: string = ''; + if ( + v.type === 'string' + || ['dev', 'local', 'stage', 'prod'].includes(v.value as string) + ) { + q = '\''; + } return `\n\t${v.key}: ${q}${v.value}${q}`; } @@ -206,6 +212,9 @@ export abstract class EnvUtilities { fileName: DockerComposeFileName, rootDir: string ): Promise { + if (variableKeys != undefined && !variableKeys.length) { + return []; + } const keys: VariableKeys = await this.splitVariableKeys(variableKeys, failOnMissingVariable, rootDir); const staticVariables: EnvVariable[] = await this.getStaticEnvVariables(keys.static, failOnMissingVariable, rootDir); const calculatedVariables: EnvVariable[] = await this.getCalculatedEnvVariables( diff --git a/src/loopback/loopback.utilities.ts b/src/loopback/loopback.utilities.ts index d073638..97fc7d7 100644 --- a/src/loopback/loopback.utilities.ts +++ b/src/loopback/loopback.utilities.ts @@ -159,7 +159,7 @@ type LoopbackCliOptions = */ export abstract class LoopbackUtilities { - private static readonly CLI_VERSION: number = 6; + private static readonly CLI_VERSION: number = 7; /** * Runs an loopback cli command inside the provided directory. @@ -533,7 +533,8 @@ export abstract class LoopbackUtilities { * @param dbName - The name of the database used by the api. */ static async setupLogging(root: string, name: string, dbName: string): Promise { - await NpmUtilities.install(name, [NpmPackage.LBX_PERSISTENCE_LOGGER, NpmPackage.LOOPBACK_CRON]); + await NpmUtilities.install(name, [NpmPackage.LBX_PERSISTENCE_LOGGER]); + await NpmUtilities.install(name, [NpmPackage.LOOPBACK_CRON]); const applicationTs: Path = getPath(root, 'src', 'application.ts'); await TsUtilities.addImportStatements( diff --git a/src/zibri/index.ts b/src/zibri/index.ts new file mode 100644 index 0000000..1962c45 --- /dev/null +++ b/src/zibri/index.ts @@ -0,0 +1 @@ +export * from './zibri.utilities'; \ No newline at end of file diff --git a/src/zibri/zibri-utilities.test.ts b/src/zibri/zibri-utilities.test.ts new file mode 100644 index 0000000..75bf183 --- /dev/null +++ b/src/zibri/zibri-utilities.test.ts @@ -0,0 +1,21 @@ +import { beforeEach, describe, expect, test } from '@jest/globals'; + +import { FileMockUtilities, getMockConstants, MockConstants } from '../__testing__'; +import { FsUtilities } from '../encapsulation'; +import { getPath } from '../utilities'; +import { ZibriUtilities } from './zibri.utilities'; + +const mockConstants: MockConstants = getMockConstants('zibri-utilities'); + +describe('ZibriUtilities', () => { + beforeEach(async () => { + await FileMockUtilities.setup(mockConstants); + }); + + test('run new command', async () => { + await ZibriUtilities.runCommand(mockConstants.APPS_DIR, 'new api', {} as never); + + const dirExists: boolean = await FsUtilities.exists(getPath(mockConstants.APPS_DIR, 'api')); + expect(dirExists).toBe(true); + }, 50000); +}); \ No newline at end of file diff --git a/src/zibri/zibri.utilities.ts b/src/zibri/zibri.utilities.ts new file mode 100644 index 0000000..4a5ec7c --- /dev/null +++ b/src/zibri/zibri.utilities.ts @@ -0,0 +1,51 @@ +import { CPUtilities } from '../encapsulation'; +import { optionsToCliString, Path } from '../utilities'; + +/** + * The `zi new {}` command. + */ +type CliNew = `new ${string}`; + +/** + * All possible zibri cli commands. + */ +type ZibriCliCommands = CliNew; + +/** + * Cli Options for running zi new. + */ +type NewOptions = never; { + // /** + // * Whether or not npm install should be skipped. + // */ + // '--skip-install': true, + // /** + // * Whether or not git initialization should be skipped. + // */ + // '--skip-git': true +} + +/** + * Possible zibri cli options, narrowed down based on the provided command. + */ +type ZibriCliOptions = + T extends CliNew ? NewOptions + : never; + +/** + * Utilities for zibri specific code generation/manipulation. + */ +export abstract class ZibriUtilities { + + private static readonly CLI_VERSION: string = '1.6.4'; + + /** + * Runs a zibri cli command inside the provided directory. + * @param directory - The directory to run the command inside. + * @param command - The command to run. + * @param options - Options for running the command. + */ + static async runCommand(directory: Path, command: ZibriCliCommands, options: ZibriCliOptions): Promise { + await CPUtilities.exec(`cd ${directory} && npx zibri-cli@${this.CLI_VERSION} ${command} ${optionsToCliString(options)}`); + } +} \ No newline at end of file