@ask-ell/back-end 0.1.0 → 0.2.1

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/jest.config.ts CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable */
2
1
  import { readFileSync } from 'fs';
3
2
 
4
3
  // Reading the SWC compilation config for the spec files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ask-ell/back-end",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -20,6 +20,7 @@
20
20
  "helmet": "*",
21
21
  "keyv": "*",
22
22
  "pg": "*",
23
- "tslib": "*"
23
+ "tslib": "*",
24
+ "rxjs": "*"
24
25
  }
25
26
  }
package/project.json CHANGED
@@ -8,22 +8,19 @@
8
8
  "build": {
9
9
  "executor": "@nx/js:tsc",
10
10
  "dependsOn": ["^build"],
11
- "outputs": [
12
- "{options.outputPath}"
13
- ],
11
+ "outputs": ["{options.outputPath}"],
14
12
  "options": {
15
13
  "outputPath": "dist/packages/back-end",
16
14
  "main": "packages/back-end/src/index.ts",
17
15
  "tsConfig": "packages/back-end/tsconfig.lib.json",
18
- "assets": [
19
- "packages/back-end/*.md"
20
- ]
16
+ "assets": ["packages/back-end/*.md"]
21
17
  }
22
18
  },
23
19
  "test": {
24
20
  "options": {
25
21
  "passWithNoTests": true
26
22
  }
27
- }
23
+ },
24
+ "format": {}
28
25
  }
29
26
  }
@@ -1,5 +1,5 @@
1
- import { INestApplication } from "@nestjs/common";
1
+ import { INestApplication } from '@nestjs/common';
2
2
 
3
3
  export interface IAddOn<T> {
4
- apply(application: INestApplication<T>): void;
5
- }
4
+ apply(application: INestApplication<T>): void;
5
+ }
@@ -1,36 +1,31 @@
1
- import { INestApplication } from "@nestjs/common";
2
-
3
- import { IAddOn } from "./add-on.interface";
4
- import { SecurityAddOn } from "./implementations/security/security.add-on";
5
- import { AddOnWrapperParams } from "./types";
6
- import { SwaggerAddon } from "./implementations/swagger/swagger.add-on";
7
- import { SentryAddon } from "./implementations/sentry/sentry.add-on";
1
+ import { INestApplication } from '@nestjs/common';
8
2
 
3
+ import { IAddOn } from './add-on.interface';
4
+ import { SecurityAddOn } from './implementations/security/security.add-on';
5
+ import { AddOnWrapperParams } from './types';
6
+ import { SwaggerAddon } from './implementations/swagger/swagger.add-on';
7
+ import { SentryAddon } from './implementations/sentry/sentry.add-on';
9
8
 
10
9
  export class AddOnWrapper<T> implements IAddOn<T> {
11
- private implementations: IAddOn<T>[] = [
12
- new SecurityAddOn()
13
- ];
10
+ private implementations: IAddOn<T>[] = [new SecurityAddOn()];
14
11
 
15
- constructor(
16
- private params: AddOnWrapperParams
17
- ) {
18
- this.appendImplementations();
19
- }
12
+ constructor(private params: AddOnWrapperParams) {
13
+ this.appendImplementations();
14
+ }
20
15
 
21
- apply(application: INestApplication<T>): void {
22
- this.implementations.forEach((addOn: IAddOn<T>): void => {
23
- addOn.apply(application);
24
- });
25
- }
16
+ apply(application: INestApplication<T>): void {
17
+ this.implementations.forEach((addOn: IAddOn<T>): void => {
18
+ addOn.apply(application);
19
+ });
20
+ }
26
21
 
27
- private appendImplementations(): void {
28
- const { sentry, swagger } = this.params;
29
- if(swagger){
30
- this.implementations.push(new SwaggerAddon(swagger));
31
- }
32
- if(sentry){
33
- this.implementations.push(new SentryAddon(sentry));
34
- }
22
+ private appendImplementations(): void {
23
+ const { sentry, swagger } = this.params;
24
+ if (swagger) {
25
+ this.implementations.push(new SwaggerAddon(swagger));
26
+ }
27
+ if (sentry) {
28
+ this.implementations.push(new SentryAddon(sentry));
35
29
  }
36
- }
30
+ }
31
+ }
@@ -1,8 +1,7 @@
1
- import { INestApplication, ValidationPipe } from "@nestjs/common";
1
+ import { INestApplication, ValidationPipe } from '@nestjs/common';
2
2
  import helmet from 'helmet';
3
3
 
4
- import { IAddOn } from "../../add-on.interface";
5
-
4
+ import { IAddOn } from '../../add-on.interface';
6
5
 
7
6
  export class SecurityAddOn<T> implements IAddOn<T> {
8
7
  apply(application: INestApplication<T>): void {
@@ -11,7 +10,7 @@ export class SecurityAddOn<T> implements IAddOn<T> {
11
10
  application.useGlobalPipes(
12
11
  new ValidationPipe({
13
12
  transform: true,
14
- whitelist: false
13
+ whitelist: false,
15
14
  })
16
15
  );
17
16
  }
@@ -1,8 +1,8 @@
1
- import { IEntity } from "@ask-ell/core";
2
-
3
- import { ISentryAddOnState } from "./sentry.add-on.state.interface";
4
- import { IAddOn } from "../../add-on.interface";
1
+ import { IEntity } from '@ask-ell/core';
5
2
 
3
+ import { ISentryAddOnState } from './sentry.add-on.state.interface';
4
+ import { IAddOn } from '../../add-on.interface';
6
5
 
7
6
  export interface ISentryAddOn<T>
8
- extends IEntity<ISentryAddOnState>, IAddOn<T> { }
7
+ extends IEntity<ISentryAddOnState>,
8
+ IAddOn<T> {}
@@ -2,4 +2,4 @@ export interface ISentryAddOnState {
2
2
  publicKey: string;
3
3
  host: string;
4
4
  projectId: string;
5
- }
5
+ }
@@ -1,15 +1,14 @@
1
- import { Entity, type IResult, ok } from "@ask-ell/core";
2
- import { NestSentryConfiguration } from "@ask-ell/nest"
3
-
4
- import { ISentryAddOnState } from "./sentry.add-on.state.interface";
5
- import { ISentryAddOn } from "./sentry.add-on.interface";
1
+ import { Entity, type IResult, ok } from '@ask-ell/core';
2
+ import { NestSentryConfiguration } from '@ask-ell/nest';
6
3
 
4
+ import { ISentryAddOnState } from './sentry.add-on.state.interface';
5
+ import { ISentryAddOn } from './sentry.add-on.interface';
7
6
 
8
7
  export class SentryAddon<T>
9
8
  extends Entity<ISentryAddOnState>
10
9
  implements ISentryAddOn<T>
11
10
  {
12
- checkStateValidity(_newState: ISentryAddOnState): IResult<void> {
11
+ checkStateValidity(): IResult<void> {
13
12
  return ok();
14
13
  }
15
14
 
@@ -17,9 +16,9 @@ export class SentryAddon<T>
17
16
  const { host, projectId, publicKey } = this.getSnapshot();
18
17
 
19
18
  new NestSentryConfiguration({
20
- publicKey,
21
- host,
22
- projectId
19
+ publicKey,
20
+ host,
21
+ projectId,
23
22
  }).init();
24
23
  }
25
- }
24
+ }
@@ -1,7 +1,8 @@
1
- import { IEntity } from "@ask-ell/core";
1
+ import { IEntity } from '@ask-ell/core';
2
2
 
3
- import { ISwaggerAddOnState } from "./swagger.add-on.state.interface";
4
- import { IAddOn } from "../../add-on.interface";
3
+ import { ISwaggerAddOnState } from './swagger.add-on.state.interface';
4
+ import { IAddOn } from '../../add-on.interface';
5
5
 
6
6
  export interface ISwaggerAddOn<T>
7
- extends IEntity<ISwaggerAddOnState>, IAddOn<T> { }
7
+ extends IEntity<ISwaggerAddOnState>,
8
+ IAddOn<T> {}
@@ -1,6 +1,7 @@
1
- import { AuthMethod } from "./types";
1
+ import { AuthMethod } from './types';
2
2
 
3
3
  export interface ISwaggerAddOnState {
4
4
  title: string;
5
5
  auths?: AuthMethod[];
6
- }
6
+ enabledInProduction?: boolean;
7
+ }
@@ -1,28 +1,42 @@
1
- import { Entity, type IResult, ok } from "@ask-ell/core";
2
- import { type INestApplication } from "@nestjs/common";
1
+ import {
2
+ Entity,
3
+ ILogger,
4
+ type IResult,
5
+ isOnDevelopmentMode,
6
+ ok,
7
+ } from '@ask-ell/core';
8
+ import { NestLogger } from '@ask-ell/nest';
9
+ import { type INestApplication } from '@nestjs/common';
3
10
  import {
4
11
  DocumentBuilder,
5
12
  type OpenAPIObject,
6
13
  SwaggerModule,
7
- } from "@nestjs/swagger";
8
-
9
- import { ISwaggerAddOnState } from "./swagger.add-on.state.interface";
10
- import { ISwaggerAddOn } from "./swagger.add-on.interface";
11
- import { AuthMethod } from "./types";
14
+ } from '@nestjs/swagger';
12
15
 
16
+ import { ISwaggerAddOnState } from './swagger.add-on.state.interface';
17
+ import { ISwaggerAddOn } from './swagger.add-on.interface';
18
+ import { AuthMethod } from './types';
13
19
 
14
20
  export class SwaggerAddon<T>
15
21
  extends Entity<ISwaggerAddOnState>
16
22
  implements ISwaggerAddOn<T>
17
23
  {
18
- checkStateValidity(_newState: ISwaggerAddOnState): IResult<void> {
24
+ private logger: ILogger = NestLogger.fromClass(SwaggerAddon);
25
+
26
+ checkStateValidity(): IResult<void> {
19
27
  return ok();
20
28
  }
21
29
 
22
30
  apply(application: INestApplication<T>): void {
23
- const { title, auths } = this.getSnapshot();
31
+ const { title, auths, enabledInProduction } = this.getSnapshot();
24
32
 
25
- const documentBuilder: DocumentBuilder = new DocumentBuilder().setTitle(title)
33
+ if (!enabledInProduction && !isOnDevelopmentMode()) {
34
+ return;
35
+ }
36
+
37
+ const documentBuilder: DocumentBuilder = new DocumentBuilder().setTitle(
38
+ title
39
+ );
26
40
 
27
41
  auths?.forEach((auth: AuthMethod): void => {
28
42
  if (auth === 'bearer') {
@@ -30,14 +44,16 @@ export class SwaggerAddon<T>
30
44
  }
31
45
  });
32
46
 
33
- const documentBuild: Omit<OpenAPIObject, "paths"> = documentBuilder.build();
47
+ const documentBuild: Omit<OpenAPIObject, 'paths'> = documentBuilder.build();
34
48
 
35
49
  const documentFactory = (): OpenAPIObject =>
36
50
  SwaggerModule.createDocument(application, documentBuild);
37
51
 
38
- SwaggerModule.setup("/swagger", application, documentFactory, {
52
+ SwaggerModule.setup('/swagger', application, documentFactory, {
39
53
  customSiteTitle: title,
40
- jsonDocumentUrl: "/swagger/json",
54
+ jsonDocumentUrl: '/swagger/json',
41
55
  });
56
+
57
+ this.logger.log('Swagger initialized');
42
58
  }
43
- }
59
+ }
@@ -1 +1 @@
1
- export type AuthMethod = 'bearer' | 'cookie';
1
+ export type AuthMethod = 'bearer' | 'cookie';
@@ -1 +1,2 @@
1
- export * from './add-on.wrapper'
1
+ export * from './add-on.wrapper';
2
+ export * from './types';
@@ -1,7 +1,7 @@
1
- import { ISentryAddOnState } from "./implementations/sentry/sentry.add-on.state.interface";
2
- import { ISwaggerAddOnState } from "./implementations/swagger/swagger.add-on.state.interface";
1
+ import { ISentryAddOnState } from './implementations/sentry/sentry.add-on.state.interface';
2
+ import { ISwaggerAddOnState } from './implementations/swagger/swagger.add-on.state.interface';
3
3
 
4
4
  export type AddOnWrapperParams = Partial<{
5
- swagger: ISwaggerAddOnState;
6
- sentry: ISentryAddOnState;
7
- }>;
5
+ swagger: ISwaggerAddOnState;
6
+ sentry: ISentryAddOnState;
7
+ }>;
@@ -1,15 +1,18 @@
1
- import { DynamicModule, Provider } from "@nestjs/common";
1
+ import { DynamicModule, Provider, Module, Global } from '@nestjs/common';
2
2
 
3
- import { RequirementModule } from "./requirement.module";
3
+ import { RequirementModule } from './requirement.module';
4
4
 
5
+ @Global()
6
+ @Module({
7
+ imports: [RequirementModule],
8
+ })
5
9
  export class ApplicationBase {
6
- static withProviders(providers: Provider[]): DynamicModule {
7
- const providersAndExports: Provider[] = [...providers];
8
- return {
9
- module: this,
10
- imports: [RequirementModule],
11
- providers: providersAndExports,
12
- exports: [...providersAndExports]
13
- }
14
- }
15
- }
10
+ static withProviders(providers: Provider[]): DynamicModule {
11
+ const providersAndExports: Provider[] = [...providers];
12
+ return {
13
+ module: this,
14
+ providers: providersAndExports,
15
+ exports: [...providersAndExports],
16
+ };
17
+ }
18
+ }
@@ -0,0 +1,23 @@
1
+ import { ConsoleLogger, DynamicModule, INestApplication } from '@nestjs/common';
2
+ import { NestFactory } from '@nestjs/core';
3
+ import { IServerProvider } from '@ask-ell/node';
4
+
5
+ import { AddOnWrapper, AddOnWrapperParams } from './add-ons';
6
+
7
+ type NestApplicationFactoryParams = {
8
+ params: AddOnWrapperParams;
9
+ module: DynamicModule;
10
+ };
11
+
12
+ export async function createNestApplication({
13
+ params,
14
+ module,
15
+ }: NestApplicationFactoryParams): Promise<IServerProvider> {
16
+ const application: INestApplication = await NestFactory.create(module, {
17
+ logger: new ConsoleLogger({
18
+ json: true,
19
+ }),
20
+ });
21
+ new AddOnWrapper(params).apply(application);
22
+ return application;
23
+ }
@@ -0,0 +1,25 @@
1
+ import { Entity, IResult, ok } from '@ask-ell/core';
2
+ import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
3
+ import { catchError, Observable, throwError } from 'rxjs';
4
+
5
+ type ErrorInterceptorProps = {
6
+ errorConvertor: (error: unknown) => unknown;
7
+ };
8
+
9
+ export class ErrorInterceptor
10
+ extends Entity<ErrorInterceptorProps>
11
+ implements NestInterceptor
12
+ {
13
+ override checkStateValidity(): IResult {
14
+ return ok();
15
+ }
16
+
17
+ intercept(_: ExecutionContext, next: CallHandler): Observable<unknown> {
18
+ const { errorConvertor } = this.getSnapshot();
19
+ return next.handle().pipe(
20
+ catchError((error: unknown): Observable<never> => {
21
+ return throwError((): unknown => errorConvertor(error));
22
+ })
23
+ );
24
+ }
25
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @module @ask-ell/back-end
3
3
  */
4
- export * from './add-ons';
5
4
  export * from './scripts';
5
+ export * from './application.base';
6
+ export * from './application.factory';
7
+ export * from './error.interceptor';
6
8
  export * from './keyv.store.adapter.factory';
7
- export * from "./application.base";
@@ -1,37 +1,43 @@
1
- import { Injectable } from "@nestjs/common";
2
- import { KeyvStoreAdapter } from "keyv";
1
+ import { Injectable } from '@nestjs/common';
2
+ import { KeyvStoreAdapter } from 'keyv';
3
3
  import KeyvSqlite from '@keyv/sqlite';
4
4
  import KeyvPostgres from '@keyv/postgres';
5
- import { ILogger, MaybeUndefined } from "@ask-ell/core";
5
+ import { ILogger, MaybeUndefined } from '@ask-ell/core';
6
6
  import { NestLogger } from '@ask-ell/nest';
7
- import { join } from "node:path";
8
-
9
- import { createTmpFolder, TMP_FOLDER_PATH } from "./utils";
7
+ import { join } from 'node:path';
10
8
 
9
+ import { createTmpFolder, TMP_FOLDER_PATH } from './utils';
11
10
 
12
11
  type CreateKeyvStoreAdapterOptions = {
13
- postgresUri: MaybeUndefined<string>;
14
- }
12
+ postgresUri: MaybeUndefined<string>;
13
+ };
15
14
 
16
15
  @Injectable()
17
16
  export class KeyvStoreAdapterFactory {
18
- private logger: ILogger = NestLogger.fromClass(KeyvStoreAdapterFactory);
17
+ private logger: ILogger = NestLogger.fromClass(KeyvStoreAdapterFactory);
19
18
 
20
- create(options: CreateKeyvStoreAdapterOptions): KeyvStoreAdapter {
21
- const adapter: KeyvStoreAdapter = this.getKeyvStoreAdapter(options);
22
- this.logger.log(`Connected to database with adapter "${adapter.constructor.name}"`);
23
- return adapter;
24
- }
19
+ create(options: CreateKeyvStoreAdapterOptions): KeyvStoreAdapter {
20
+ const adapter: KeyvStoreAdapter = this.getKeyvStoreAdapter(options);
21
+ this.logger.log(
22
+ `Connected to database with adapter "${adapter.constructor.name}"`
23
+ );
24
+ return adapter;
25
+ }
25
26
 
26
- private getKeyvStoreAdapter({ postgresUri }: CreateKeyvStoreAdapterOptions): KeyvStoreAdapter {
27
- if (!postgresUri) {
28
- createTmpFolder(this.logger);
29
- const localDatabaseFilePath: string = join(TMP_FOLDER_PATH, 'database.sqlite');
30
- return new KeyvSqlite(localDatabaseFilePath);
31
- }
32
-
33
- return new KeyvPostgres({
34
- uri: postgresUri,
35
- });
27
+ private getKeyvStoreAdapter({
28
+ postgresUri,
29
+ }: CreateKeyvStoreAdapterOptions): KeyvStoreAdapter {
30
+ if (!postgresUri) {
31
+ createTmpFolder(this.logger);
32
+ const localDatabaseFilePath: string = join(
33
+ TMP_FOLDER_PATH,
34
+ 'database.sqlite'
35
+ );
36
+ return new KeyvSqlite(localDatabaseFilePath);
36
37
  }
38
+
39
+ return new KeyvPostgres({
40
+ uri: postgresUri,
41
+ });
42
+ }
37
43
  }
@@ -1,57 +1,59 @@
1
- import { Global, Module } from "@nestjs/common";
2
- import { ConfigModule } from "@nestjs/config";
3
- import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core";
4
- import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
5
- import type { ILogger } from "@ask-ell/core";
6
- import { NestLogger, HealthModule, ResponseFormatInterceptor, HttpExceptionFilter } from "@ask-ell/nest";
1
+ import { Global, Module } from '@nestjs/common';
2
+ import { ConfigModule } from '@nestjs/config';
3
+ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
4
+ import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
5
+ import type { ILogger } from '@ask-ell/core';
6
+ import {
7
+ NestLogger,
8
+ HealthModule,
9
+ ResponseFormatInterceptor,
10
+ HttpExceptionFilter,
11
+ } from '@ask-ell/nest';
7
12
  import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup';
8
13
 
9
- import { KeyvStoreAdapterFactory } from "./keyv.store.adapter.factory";
10
- import { dockerSecretGetter, localSecretGetter } from "./secrets";
11
-
14
+ import { KeyvStoreAdapterFactory } from './keyv.store.adapter.factory';
15
+ import { dockerSecretGetter, localSecretGetter } from './secrets';
12
16
 
13
17
  const configModuleLogger: ILogger = NestLogger.fromClass(ConfigModule);
14
18
 
15
19
  @Global()
16
20
  @Module({
17
- imports: [
18
- HealthModule,
19
- SentryModule.forRoot(),
20
- ThrottlerModule.forRoot([{
21
- ttl: 60000,
22
- limit: 30,
23
- }]),
24
- ConfigModule.forRoot({
25
- isGlobal: true,
26
- load: [
27
- dockerSecretGetter(configModuleLogger),
28
- localSecretGetter(configModuleLogger)
29
- ]
30
- })
31
- ],
32
- providers: [
33
- KeyvStoreAdapterFactory,
34
- {
35
- provide: APP_GUARD,
36
- useClass: ThrottlerGuard
37
- },
38
- {
39
- provide: APP_INTERCEPTOR,
40
- useClass: ResponseFormatInterceptor
41
- },
42
- {
43
- provide: APP_FILTER,
44
- useClass: HttpExceptionFilter
45
- },
46
- {
47
- provide: APP_FILTER,
48
- useClass: SentryGlobalFilter
49
- }
50
- ],
51
- exports: [
52
- ConfigModule,
53
- SentryModule,
54
- KeyvStoreAdapterFactory
55
- ]
21
+ imports: [
22
+ HealthModule,
23
+ SentryModule.forRoot(),
24
+ ThrottlerModule.forRoot([
25
+ {
26
+ ttl: 60000,
27
+ limit: 30,
28
+ },
29
+ ]),
30
+ ConfigModule.forRoot({
31
+ isGlobal: true,
32
+ load: [
33
+ dockerSecretGetter(configModuleLogger),
34
+ localSecretGetter(configModuleLogger),
35
+ ],
36
+ }),
37
+ ],
38
+ providers: [
39
+ KeyvStoreAdapterFactory,
40
+ {
41
+ provide: APP_GUARD,
42
+ useClass: ThrottlerGuard,
43
+ },
44
+ {
45
+ provide: APP_INTERCEPTOR,
46
+ useClass: ResponseFormatInterceptor,
47
+ },
48
+ {
49
+ provide: APP_FILTER,
50
+ useClass: HttpExceptionFilter,
51
+ },
52
+ {
53
+ provide: APP_FILTER,
54
+ useClass: SentryGlobalFilter,
55
+ },
56
+ ],
57
+ exports: [ConfigModule, SentryModule, KeyvStoreAdapterFactory],
56
58
  })
57
- export class RequirementModule { }
59
+ export class RequirementModule {}
@@ -1 +1,2 @@
1
1
  export * from './move-keyv-data.script';
2
+ export * from './update-dependencies-versions.script';
@@ -1,67 +1,88 @@
1
1
  import { PercentageIncrementor } from '@ask-ell/core';
2
- import { Client, QueryResult } from 'pg';
3
2
  import { Script, ExecuteOptions } from '@ask-ell/node';
4
3
  import { NestLogger } from '@ask-ell/nest';
4
+ import { Client, QueryResult } from 'pg';
5
5
 
6
-
7
- type KeyvRowData = {
8
- key: string;
9
- value: any;
10
- }
6
+ type KeyvRowData<Value = unknown> = {
7
+ key: string;
8
+ value: Value;
9
+ };
11
10
 
12
11
  export class MoveKeyvDataScript extends Script {
13
- constructor() {
14
- super({
15
- arguments: [
16
- { name: 'origin', description: 'Origin Postgres database URI', required: true },
17
- { name: 'target', description: 'Target Postgres database URI', required: true }
18
- ],
19
- logger: NestLogger.fromClass(MoveKeyvDataScript)
20
- });
21
- }
22
-
23
- async execute({ logger, arguments: { origin, target }, prompts: { confirm } }: ExecuteOptions): Promise<void> {
24
- const originPostgresClient: Client = new Client(origin);
25
- const targetPostgresClient: Client = new Client(target);
12
+ constructor() {
13
+ super({
14
+ arguments: [
15
+ {
16
+ name: 'origin',
17
+ description: 'Origin Postgres database URI',
18
+ required: true,
19
+ },
20
+ {
21
+ name: 'target',
22
+ description: 'Target Postgres database URI',
23
+ required: true,
24
+ },
25
+ ],
26
+ logger: NestLogger.fromClass(MoveKeyvDataScript),
27
+ });
28
+ }
26
29
 
27
- logger.log('Reading databases...');
30
+ async execute({
31
+ logger,
32
+ arguments: { origin, target },
33
+ prompts: { confirm },
34
+ }: ExecuteOptions): Promise<void> {
35
+ const originPostgresClient: Client = new Client(origin);
36
+ const targetPostgresClient: Client = new Client(target);
28
37
 
29
- await Promise.all([
30
- originPostgresClient.connect(),
31
- targetPostgresClient.connect()
32
- ]);
38
+ logger.log('Reading databases...');
33
39
 
34
- try {
35
- const { rows }: QueryResult<KeyvRowData> = await originPostgresClient.query('SELECT * FROM keyv');
40
+ await Promise.all([
41
+ originPostgresClient.connect(),
42
+ targetPostgresClient.connect(),
43
+ ]);
36
44
 
37
- const rowsCount: number = rows.length;
38
- logger.log(`You're going to insert ${rowsCount} keyv entries from the original database to the target database.`);
45
+ try {
46
+ const { rows }: QueryResult<KeyvRowData> =
47
+ await originPostgresClient.query('SELECT * FROM keyv');
39
48
 
40
- const confirmed: boolean = await confirm('Are you sure you want to proceed?');
49
+ const rowsCount: number = rows.length;
50
+ logger.log(
51
+ `You're going to insert ${rowsCount} keyv entries from the original database to the target database.`
52
+ );
41
53
 
42
- if (confirmed) {
43
- logger.log('Inserting entries into the target database...');
44
- const incrementor: PercentageIncrementor = new PercentageIncrementor(rowsCount);
45
- incrementor.onIncrement(({ achivmentOnPercent }): void => {
46
- logger.log(`Progress: ${achivmentOnPercent}%`);
47
- });
54
+ const confirmed: boolean = await confirm(
55
+ 'Are you sure you want to proceed?'
56
+ );
48
57
 
49
- for (const { key, value } of rows) {
50
- await targetPostgresClient.query('INSERT INTO keyv (key, value) VALUES ($1, $2)', [key, value]);
51
- incrementor.increment();
52
- }
58
+ if (confirmed) {
59
+ logger.log('Inserting entries into the target database...');
60
+ const incrementor: PercentageIncrementor = new PercentageIncrementor(
61
+ rowsCount
62
+ );
63
+ incrementor.onIncrement(({ achivmentOnPercent }): void => {
64
+ logger.log(`Progress: ${achivmentOnPercent}%`);
65
+ });
53
66
 
54
- logger.log(`${rowsCount} entries inserted into the target database.`);
55
- logger.log('Migration completed successfully.');
56
- } else {
57
- logger.log('Migration cancelled.');
58
- }
59
- } finally {
60
- await Promise.all([
61
- originPostgresClient.end(),
62
- targetPostgresClient.end()
63
- ]);
64
- logger.log('Database connections closed.');
67
+ for (const { key, value } of rows) {
68
+ await targetPostgresClient.query(
69
+ 'INSERT INTO keyv (key, value) VALUES ($1, $2)',
70
+ [key, value]
71
+ );
72
+ incrementor.increment();
65
73
  }
74
+
75
+ logger.log(`${rowsCount} entries inserted into the target database.`);
76
+ logger.log('Migration completed successfully.');
77
+ } else {
78
+ logger.log('Migration cancelled.');
79
+ }
80
+ } finally {
81
+ await Promise.all([
82
+ originPostgresClient.end(),
83
+ targetPostgresClient.end(),
84
+ ]);
85
+ logger.log('Database connections closed.');
66
86
  }
87
+ }
67
88
  }
@@ -0,0 +1,201 @@
1
+ import {
2
+ MaybeUndefined,
3
+ StringDictionnary,
4
+ UnknownEnvironmentVariableError,
5
+ } from '@ask-ell/core';
6
+ import { NestLogger } from '@ask-ell/nest';
7
+ import { ExecuteOptions, Script } from '@ask-ell/node';
8
+ import { spawnSync } from 'node:child_process';
9
+ import { writeFileSync } from 'node:fs';
10
+ import { mkdir } from 'node:fs/promises';
11
+ import { join } from 'node:path';
12
+
13
+ const PROJECT_ROOT_PATH: MaybeUndefined<string> = process.env['PWD'];
14
+
15
+ function makeSureThatProjectRootPathIsDefined(
16
+ projectRootPath: MaybeUndefined<string>
17
+ ): asserts projectRootPath is string {
18
+ if (!projectRootPath) {
19
+ throw new UnknownEnvironmentVariableError('PWD');
20
+ }
21
+ }
22
+
23
+ makeSureThatProjectRootPathIsDefined(PROJECT_ROOT_PATH);
24
+
25
+ const NX_GRAPH_FILE_PATH: string = join(PROJECT_ROOT_PATH, 'tmp/graph.json');
26
+
27
+ type PackageContent = {
28
+ version: string;
29
+ dependencies: StringDictionnary;
30
+ devDependencies: StringDictionnary;
31
+ };
32
+
33
+ const ROOT_PACKAGE_FILE_PATH: string = join(PROJECT_ROOT_PATH, `package.json`);
34
+ const ROOT_PACKAGE_CONTENT: PackageContent = require(ROOT_PACKAGE_FILE_PATH);
35
+
36
+ function getOrganisationPackageFile(packageId: string): PackageContent {
37
+ makeSureThatProjectRootPathIsDefined(PROJECT_ROOT_PATH);
38
+ const packageFilePath = join(
39
+ PROJECT_ROOT_PATH,
40
+ `packages/${packageId}/package.json`
41
+ );
42
+ return require(packageFilePath);
43
+ }
44
+
45
+ type UpdateDependenciesVersionsScriptParams = {
46
+ organisationPackagePrefix: string;
47
+ blackListedPackages: string[];
48
+ };
49
+
50
+ type WorkspaceDependenciesMapElement = {
51
+ id: string;
52
+ dependencies: string[];
53
+ source: PackageContent;
54
+ };
55
+
56
+ type WorkspaceDependenciesMap = {
57
+ [id: string]: WorkspaceDependenciesMapElement;
58
+ };
59
+
60
+ export class UpdateDependenciesVersionsScript extends Script {
61
+ private organisationPackagePrefix: string;
62
+ private blackListedPackages: string[];
63
+ private workspaceDependenciesMap: WorkspaceDependenciesMap =
64
+ this.getWorkspaceDependenciesMap();
65
+
66
+ constructor({
67
+ organisationPackagePrefix,
68
+ blackListedPackages,
69
+ }: UpdateDependenciesVersionsScriptParams) {
70
+ super({
71
+ logger: NestLogger.fromClass(UpdateDependenciesVersionsScript),
72
+ arguments: [
73
+ {
74
+ name: 'targetedPackage',
75
+ description: 'Target package',
76
+ },
77
+ ],
78
+ });
79
+ this.organisationPackagePrefix = organisationPackagePrefix;
80
+ this.blackListedPackages = blackListedPackages;
81
+ }
82
+
83
+ async execute({
84
+ logger,
85
+ arguments: { targetedPackage },
86
+ }: ExecuteOptions): Promise<void> {
87
+ const workspaceDependenciesMapElements: WorkspaceDependenciesMapElement[] =
88
+ Object.values(this.workspaceDependenciesMap).filter(
89
+ ({ id }: WorkspaceDependenciesMapElement): boolean =>
90
+ this.isTargetedPackage(targetedPackage)(id)
91
+ );
92
+
93
+ for (const {
94
+ id,
95
+ dependencies,
96
+ source,
97
+ } of workspaceDependenciesMapElements) {
98
+ logger.log(`package.json file updating for project "${id}"...`);
99
+
100
+ if (!PROJECT_ROOT_PATH) {
101
+ throw new UnknownEnvironmentVariableError('PWD');
102
+ }
103
+ const packagePath: string = join(
104
+ PROJECT_ROOT_PATH,
105
+ `dist/packages/${id}`
106
+ );
107
+
108
+ await mkdir(packagePath, { recursive: true });
109
+ const packageFilePath: string = join(PROJECT_ROOT_PATH, 'package.json');
110
+
111
+ const packageFileContent: PackageContent = { ...source };
112
+
113
+ if (dependencies.length && !packageFileContent.dependencies) {
114
+ packageFileContent.dependencies = {};
115
+ }
116
+
117
+ dependencies.forEach((dependency: string): void => {
118
+ const versionTag = this.isAOrganisationPackage(dependency)
119
+ ? `^${
120
+ this.workspaceDependenciesMap[
121
+ dependency.replace(this.organisationPackagePrefix, '')
122
+ ].source.version
123
+ }`
124
+ : ROOT_PACKAGE_CONTENT.devDependencies[dependency];
125
+ packageFileContent.dependencies[dependency] = versionTag;
126
+ });
127
+
128
+ writeFileSync(
129
+ packageFilePath,
130
+ JSON.stringify(packageFileContent, null, 2) + '\n'
131
+ );
132
+
133
+ console.log(`package.json file updated for project "${id}"`);
134
+ }
135
+
136
+ console.log('Package files updated.');
137
+ }
138
+
139
+ getWorkspaceDependenciesMap(): WorkspaceDependenciesMap {
140
+ const workspaceDependenciesMap: WorkspaceDependenciesMap = {};
141
+
142
+ const nxGraphResult = spawnSync(
143
+ 'npx',
144
+ ['nx', 'graph', '--file', NX_GRAPH_FILE_PATH],
145
+ { stdio: 'inherit' }
146
+ );
147
+ if (nxGraphResult.status !== 0) {
148
+ throw new Error(`"nx graph" failed with status ${nxGraphResult.status}`);
149
+ }
150
+ const nxGraph = require(NX_GRAPH_FILE_PATH);
151
+
152
+ const packageIds = Object.entries(nxGraph.graph.dependencies).map(
153
+ ([packageId]) => packageId
154
+ );
155
+
156
+ for (const packageId of packageIds.filter(this.isNotBlackListed)) {
157
+ console.log(`Scanning dependencies for package ${packageId}...`);
158
+ const packageFileContent = getOrganisationPackageFile(packageId);
159
+ const dependencies = packageFileContent.dependencies
160
+ ? Object.entries(packageFileContent.dependencies).map(
161
+ ([dependency]) => dependency
162
+ )
163
+ : [];
164
+
165
+ workspaceDependenciesMap[packageId] = {
166
+ id: packageId,
167
+ dependencies: [],
168
+ source: packageFileContent,
169
+ };
170
+ if (packageFileContent.dependencies) {
171
+ for (const dependency of dependencies) {
172
+ workspaceDependenciesMap[packageId].dependencies.push(dependency);
173
+ }
174
+ }
175
+ }
176
+
177
+ console.log('Dependencies scan done.');
178
+
179
+ return workspaceDependenciesMap;
180
+ }
181
+
182
+ private isTargetedPackage =
183
+ (targetPackageId: MaybeUndefined<string>) => (packageId: string) => {
184
+ if (targetPackageId) {
185
+ return packageId === targetPackageId;
186
+ }
187
+ return true;
188
+ };
189
+
190
+ private isAOrganisationPackage(packageName: string): boolean {
191
+ return packageName.includes(this.organisationPackagePrefix);
192
+ }
193
+
194
+ private isBlackListed(packageId: string): boolean {
195
+ return this.blackListedPackages.includes(packageId);
196
+ }
197
+
198
+ private isNotBlackListed(packageId: string): boolean {
199
+ return !this.isBlackListed(packageId);
200
+ }
201
+ }
@@ -1,8 +1,9 @@
1
- import { FileSecretGetter, fileSecretGetter } from "./file.secret.getter";
2
-
1
+ import { FileSecretGetter, fileSecretGetter } from './file.secret.getter';
3
2
 
4
3
  const DOCKER_SECRETS_DIRECTORY = '/run/secrets';
5
4
 
6
- export const dockerSecretGetter: FileSecretGetter = fileSecretGetter
7
- (DOCKER_SECRETS_DIRECTORY)
8
- ('Application is not running inside Docker container. Docker secrets will be unavailable.')
5
+ export const dockerSecretGetter: FileSecretGetter = fileSecretGetter(
6
+ DOCKER_SECRETS_DIRECTORY
7
+ )(
8
+ 'Application is not running inside Docker container. Docker secrets will be unavailable.'
9
+ );
@@ -1,24 +1,27 @@
1
- import { Dirent, existsSync, readdirSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { ILogger } from "@ask-ell/core";
4
-
5
- import { SecretGetter } from "./types";
1
+ import { Dirent, existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { ILogger } from '@ask-ell/core';
6
4
 
5
+ import { SecretGetter } from './types';
7
6
 
8
7
  export type FileSecretGetter = (logger: ILogger) => SecretGetter;
9
8
 
10
- export const fileSecretGetter = (rootDirectoryPath: string) => (warningMessage: string): FileSecretGetter => (logger: ILogger): SecretGetter => (): object => {
9
+ export const fileSecretGetter =
10
+ (rootDirectoryPath: string) =>
11
+ (warningMessage: string): FileSecretGetter =>
12
+ (logger: ILogger): SecretGetter =>
13
+ (): object => {
11
14
  const isDirectoryExists: boolean = existsSync(rootDirectoryPath);
12
- if(!isDirectoryExists){
13
- logger.warn(warningMessage);
14
- return {};
15
+ if (!isDirectoryExists) {
16
+ logger.warn(warningMessage);
17
+ return {};
15
18
  }
16
19
 
17
20
  return readdirSync(rootDirectoryPath, { withFileTypes: true })
18
- .filter((entry: Dirent<string>): boolean => entry.isFile())
19
- .reduce((secrets: Record<string, string>, entry): object => {
20
- const filePath: string = join(rootDirectoryPath, entry.name);
21
- secrets[entry.name] = readFileSync(filePath, 'utf-8').trim();
22
- return secrets;
23
- }, {});
24
- }
21
+ .filter((entry: Dirent<string>): boolean => entry.isFile())
22
+ .reduce((secrets: Record<string, string>, entry): object => {
23
+ const filePath: string = join(rootDirectoryPath, entry.name);
24
+ secrets[entry.name] = readFileSync(filePath, 'utf-8').trim();
25
+ return secrets;
26
+ }, {});
27
+ };
@@ -1,2 +1,2 @@
1
- export * from './docker.secret.getter'
2
- export * from './local.secret.getter'
1
+ export * from './docker.secret.getter';
2
+ export * from './local.secret.getter';
@@ -1,8 +1,9 @@
1
- import { fileSecretGetter, FileSecretGetter } from "./file.secret.getter";
2
-
1
+ import { fileSecretGetter, FileSecretGetter } from './file.secret.getter';
3
2
 
4
3
  const LOCAL_SECRETS_DIRECTORY = '.secrets';
5
4
 
6
- export const localSecretGetter: FileSecretGetter = fileSecretGetter
7
- (LOCAL_SECRETS_DIRECTORY)
8
- ('Secrets directory not exists on this project. Local secrets will be unavailable.');
5
+ export const localSecretGetter: FileSecretGetter = fileSecretGetter(
6
+ LOCAL_SECRETS_DIRECTORY
7
+ )(
8
+ 'Secrets directory not exists on this project. Local secrets will be unavailable.'
9
+ );
package/src/utils/tmp.ts CHANGED
@@ -1,16 +1,15 @@
1
- import { ILogger } from "@ask-ell/core";
2
- import { existsSync, mkdirSync } from "node:fs";
3
- import { join } from "node:path";
4
-
1
+ import { ILogger } from '@ask-ell/core';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
5
4
 
6
5
  export const TMP_FOLDER_PATH: string = join(process.cwd(), 'tmp');
7
6
 
8
7
  export const createTmpFolder = (logger: ILogger): void => {
9
- const isTmpFolderExists: boolean = existsSync(TMP_FOLDER_PATH);
10
- if(isTmpFolderExists){
11
- logger.info(`"${TMP_FOLDER_PATH}" folder already exists.`);
12
- return;
13
- }
14
- mkdirSync(TMP_FOLDER_PATH);
15
- logger.info(`"${TMP_FOLDER_PATH}" folder created.`);
16
- }
8
+ const isTmpFolderExists: boolean = existsSync(TMP_FOLDER_PATH);
9
+ if (isTmpFolderExists) {
10
+ logger.info(`"${TMP_FOLDER_PATH}" folder already exists.`);
11
+ return;
12
+ }
13
+ mkdirSync(TMP_FOLDER_PATH);
14
+ logger.info(`"${TMP_FOLDER_PATH}" folder created.`);
15
+ };