diff --git a/apps/sample/package.json b/apps/sample/package.json index c725b6da3..d38edd1a1 100644 --- a/apps/sample/package.json +++ b/apps/sample/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbo", "build": "next build", "start": "next start", "lint": "next lint" diff --git a/package.json b/package.json index 269b18bfc..74e8ac3ab 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "jest": "^29.7.0", "lint-staged": "^15.2.0", "prettier": "^3.1.1", + "rimraf": "^5.0.5", "turbo": "^1.11.2", "typescript": "^5.3.3", "zx": "^7.2.3" diff --git a/packages/config/eslint/index.js b/packages/config/eslint/index.js index fcba23510..31ed47591 100644 --- a/packages/config/eslint/index.js +++ b/packages/config/eslint/index.js @@ -20,6 +20,7 @@ module.exports = { ".*.js", "node_modules/", "dist/", + "lib/", ], overrides: [ { @@ -48,7 +49,7 @@ module.exports = { }, }, { - files: ["**/scripts/*.mjs"], + files: ["**/*.mjs"], env: { es6: true, node: true, diff --git a/packages/config/typescript/sdk.json b/packages/config/typescript/sdk.json index 31bc1f432..0ae0a817d 100644 --- a/packages/config/typescript/sdk.json +++ b/packages/config/typescript/sdk.json @@ -26,8 +26,8 @@ "experimentalDecorators": true, "stripInternal": true, "emitDecoratorMetadata": true, - "moduleResolution": "NodeNext", - "module": "NodeNext" + "moduleResolution": "bundler", + "module": "esnext" }, "exclude": ["node_modules"] } diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts index e96a4dbad..4ca93d482 100644 --- a/packages/core/jest.config.ts +++ b/packages/core/jest.config.ts @@ -3,11 +3,15 @@ import type { JestConfigWithTsJest } from "@ledgerhq/jest-config-dsdk"; const config: JestConfigWithTsJest = { preset: "@ledgerhq/jest-config-dsdk", setupFiles: ["/jest.setup.ts"], + testPathIgnorePatterns: ["/lib/"], collectCoverageFrom: [ // TODO: remove internal when the rest of the files are setup "src/internal/**/*.ts", "!src/**/*.stub.ts", ], + moduleNameMapper: { + "^@internal/(.*)$": "/src/internal/$1", + }, }; export default config; diff --git a/packages/core/package.json b/packages/core/package.json index d41b3a187..cde19d97a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -8,23 +8,27 @@ "./lib" ], "scripts": { - "build": "rm -rf lib && tsc", - "dev": "tsc --watch", + "build": "rimraf lib && tsc -p tsconfig.prod.json && tsc-alias -p tsconfig.prod.json", + "dev": "tsc && (concurrently \"tsc -w -p tsconfig.prod.json\" \"tsc-alias -w -p tsconfig.prod.json\")", "lint": "eslint --cache --ext .ts \"src\"", "lint:fix": "eslint --cache --fix --ext .ts \"src\"", - "test:unit": "jest --coverage src", - "module:init": "zx scripts/add-module.mjs", - "run:index": "ts-node src/index.ts" + "test": "jest src", + "test:watch": "pnpm test -- --watch", + "test:coverage": "pnpm test -- --coverage", + "module:init": "zx scripts/add-module.mjs" }, "dependencies": { "inversify": "^6.0.2", "inversify-logger-middleware": "^3.1.0", + "purify-ts": "^2.0.3", "reflect-metadata": "^0.2.1" }, "devDependencies": { "@ledgerhq/eslint-config-dsdk": "workspace:*", "@ledgerhq/jest-config-dsdk": "workspace:*", "@ledgerhq/tsconfig-dsdk": "workspace:*", - "ts-node": "^10.9.2" + "concurrently": "^8.2.2", + "ts-node": "^10.9.2", + "tsc-alias": "^1.8.8" } } diff --git a/packages/core/src/di.ts b/packages/core/src/di.ts index 4df17acf9..6fd46ab40 100644 --- a/packages/core/src/di.ts +++ b/packages/core/src/di.ts @@ -12,7 +12,10 @@ export const makeContainer = ({ }: Partial = {}) => { const container = new Container(); container.applyMiddleware(logger); - container.load(configModuleFactory({ mock })); + container.load( + configModuleFactory({ mock }) + // modules go here + ); return container; }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index da0b16c14..c0bb10b1a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,8 +1,7 @@ import "reflect-metadata"; - +import { types } from "@internal/config/di/configTypes"; +import { GetSdkVersionUseCase } from "@internal/config/usecase/GetSdkVersionUseCase"; import { makeContainer } from "./di"; -import { types } from "./internal/config/di/configTypes"; -import { GetSdkVersionUseCase } from "./internal/config/usecase/GetSdkVersionUseCase"; export * from "./api"; export * from "./transport"; diff --git a/packages/core/src/internal/config/data/ConfigDataSource.ts b/packages/core/src/internal/config/data/ConfigDataSource.ts index 1faf0389a..7e63f66fc 100644 --- a/packages/core/src/internal/config/data/ConfigDataSource.ts +++ b/packages/core/src/internal/config/data/ConfigDataSource.ts @@ -1,11 +1,16 @@ -import { Config } from "../model/Config"; +import { Either } from "purify-ts"; +import { + LocalConfigFailure, + RemoteConfigFailure, +} from "@internal/config/di/configTypes"; +import { Config } from "@internal/config/model/Config"; // Describe the different data sources interfaces our application could have export interface LocalConfigDataSource { - getConfig(): Config; + getConfig(): Either; } export interface RemoteConfigDataSource { - getConfig(): Promise; + getConfig(): Promise>; } diff --git a/packages/core/src/internal/config/data/Dto.ts b/packages/core/src/internal/config/data/Dto.ts index 8869cbe7d..328b5e045 100644 --- a/packages/core/src/internal/config/data/Dto.ts +++ b/packages/core/src/internal/config/data/Dto.ts @@ -1,6 +1,6 @@ // Types used by the data sources (here an example for the remote data source) // They will usually be the response of an API call that will need to be parsed -// into an object used by the application. (in our example: DTOConfig => Config) +// into an object used by the application. (in our example: ConfigDto => Config) export type ConfigDto = { version: string; diff --git a/packages/core/src/internal/config/data/LocalConfigDataSource.stub.ts b/packages/core/src/internal/config/data/LocalConfigDataSource.stub.ts index 51659fc48..a8e888b98 100644 --- a/packages/core/src/internal/config/data/LocalConfigDataSource.stub.ts +++ b/packages/core/src/internal/config/data/LocalConfigDataSource.stub.ts @@ -1,5 +1,6 @@ import { injectable } from "inversify"; -import { Config } from "../model/Config"; +import { Either } from "purify-ts"; +import { Config } from "@internal/config/model/Config"; import { LocalConfigDataSource } from "./ConfigDataSource"; /** @@ -10,10 +11,10 @@ import { LocalConfigDataSource } from "./ConfigDataSource"; @injectable() export class StubLocalConfigDataSource implements LocalConfigDataSource { - getConfig(): Config { - return { + getConfig(): Either { + return Either.of({ name: "DeviceSDK", version: "0.0.0-mock.1", - }; + }); } } diff --git a/packages/core/src/internal/config/data/LocalConfigDataSource.test.ts b/packages/core/src/internal/config/data/LocalConfigDataSource.test.ts index 59485444f..779befa29 100644 --- a/packages/core/src/internal/config/data/LocalConfigDataSource.test.ts +++ b/packages/core/src/internal/config/data/LocalConfigDataSource.test.ts @@ -1,43 +1,73 @@ -import fs from "fs"; +import { Either, Left } from "purify-ts"; +import { JSONParseError, ReadFileError } from "@internal/config/di/configTypes"; import { LocalConfigDataSource } from "./ConfigDataSource"; -import { FileLocalConfigDataSource } from "./LocalConfigDataSource"; -import { StubLocalConfigDataSource } from "./LocalConfigDataSource.stub"; +import * as LocalConfig from "./LocalConfigDataSource"; -const readFileSyncSpy = jest.spyOn(fs, "readFileSync"); +const { FileLocalConfigDataSource } = LocalConfig; + +const readFileSyncSpy = jest.spyOn(LocalConfig, "stubFsReadFile"); +const jsonParse = jest.spyOn(JSON, "parse"); let datasource: LocalConfigDataSource; describe("LocalConfigDataSource", () => { describe("FileLocalConfigDataSource", () => { beforeEach(() => { readFileSyncSpy.mockClear(); + jsonParse.mockClear(); datasource = new FileLocalConfigDataSource(); + }); + + afterAll(() => { + readFileSyncSpy.mockRestore(); + jsonParse.mockRestore(); + }); + + it("should return an Either", () => { readFileSyncSpy.mockReturnValue( JSON.stringify({ name: "DeviceSDK", version: "0.0.0-spied.1" }) ); - }); - it("should return the config", () => { - expect(datasource.getConfig()).toEqual({ + jsonParse.mockReturnValue({ name: "DeviceSDK", version: "0.0.0-spied.1", }); + + expect(datasource.getConfig()).toStrictEqual( + Either.of({ + name: "DeviceSDK", + version: "0.0.0-spied.1", + }) + ); }); - }); - describe("StubLocalConfigDataSource", () => { - beforeEach(() => { - datasource = new StubLocalConfigDataSource(); + it("should return an Either if readFileSync throws", () => { + const err = new Error("readFileSync error"); + readFileSyncSpy.mockImplementation(() => { + throw err; + }); + + expect(datasource.getConfig()).toEqual(Left(new ReadFileError(err))); }); - it("should return the config", () => { - expect(datasource.getConfig()).toEqual({ - name: "DeviceSDK", - version: "0.0.0-mock.1", + it("should return an Either if JSON.parse throws", () => { + const err = new Error("JSON.parse error"); + readFileSyncSpy.mockReturnValue( + JSON.stringify({ name: "DeviceSDK", version: "0.0.0-spied.1" }) + ); + + jsonParse.mockImplementation(() => { + throw err; }); + + expect(datasource.getConfig()).toEqual(Left(new JSONParseError(err))); }); }); - afterAll(() => { - readFileSyncSpy.mockClear(); + describe("stubFsReadFile", () => { + it("should return a stringified version of the version object", () => { + expect(LocalConfig.stubFsReadFile()).toEqual( + JSON.stringify({ name: "DeviceSDK", version: "0.0.0-local.1" }) + ); + }); }); }); diff --git a/packages/core/src/internal/config/data/LocalConfigDataSource.ts b/packages/core/src/internal/config/data/LocalConfigDataSource.ts index 89726302d..05b91bca1 100644 --- a/packages/core/src/internal/config/data/LocalConfigDataSource.ts +++ b/packages/core/src/internal/config/data/LocalConfigDataSource.ts @@ -1,8 +1,19 @@ -import fs from "fs"; import { injectable } from "inversify"; -import { Config } from "../model/Config"; +import { Either } from "purify-ts"; +import { + ReadFileError, + JSONParseError, + LocalConfigFailure, +} from "@internal/config/di/configTypes"; +import { Config } from "@internal/config/model/Config"; import { LocalConfigDataSource } from "./ConfigDataSource"; -import path from "path"; + +const version = { + name: "DeviceSDK", + version: "0.0.0-local.1", +}; + +export const stubFsReadFile = () => JSON.stringify(version); /** * @@ -12,11 +23,19 @@ import path from "path"; */ @injectable() export class FileLocalConfigDataSource implements LocalConfigDataSource { - getConfig(): Config { - const version = fs.readFileSync( - path.join(__dirname, "version.json"), - "utf-8" - ); - return JSON.parse(version) as Config; + getConfig(): Either { + return Either.encase(() => stubFsReadFile()) + .mapLeft((error) => { + console.log("readFileSync error"); + return new ReadFileError(error); + }) + .chain((str) => { + return Either.encase(() => JSON.parse(str) as Config).mapLeft( + (error) => { + console.log("JSON.parse error"); + return new JSONParseError(error); + } + ); + }); } } diff --git a/packages/core/src/internal/config/data/RemoteConfigDataSource.stub.ts b/packages/core/src/internal/config/data/RemoteConfigDataSource.stub.ts index 448b9f3c6..6dd24a477 100644 --- a/packages/core/src/internal/config/data/RemoteConfigDataSource.stub.ts +++ b/packages/core/src/internal/config/data/RemoteConfigDataSource.stub.ts @@ -1,6 +1,7 @@ import { injectable } from "inversify"; +import { Either } from "purify-ts"; +import { Config } from "@internal/config/model/Config"; import { RemoteConfigDataSource } from "./ConfigDataSource"; -import { Config } from "../model/Config"; /** * class RemoteRestConfigDataSource @@ -8,12 +9,14 @@ import { Config } from "../model/Config"; */ @injectable() export class StubRemoteConfigDataSource implements RemoteConfigDataSource { - async getConfig(): Promise { + async getConfig(): Promise> { return new Promise((res) => - res({ - name: "DeviceSDK", - version: "0.0.0-fake.2", - }) + res( + Either.of({ + name: "DeviceSDK", + version: "0.0.0-fake.2", + }) + ) ); } } diff --git a/packages/core/src/internal/config/data/RemoteConfigDataSource.test.ts b/packages/core/src/internal/config/data/RemoteConfigDataSource.test.ts index fb70ae21a..a3b413187 100644 --- a/packages/core/src/internal/config/data/RemoteConfigDataSource.test.ts +++ b/packages/core/src/internal/config/data/RemoteConfigDataSource.test.ts @@ -1,30 +1,152 @@ +import { Either, Left } from "purify-ts"; +import { + ApiCallError, + JSONParseError, + ParseResponseError, +} from "@internal/config/di/configTypes"; import { RemoteConfigDataSource } from "./ConfigDataSource"; import { RestRemoteConfigDataSource } from "./RemoteConfigDataSource"; -import { StubRemoteConfigDataSource } from "./RemoteConfigDataSource.stub"; let datasource: RemoteConfigDataSource; + +// Necessary to use `any` on the prototype to be able to spy on private methods +/* eslint-disable @typescript-eslint/no-explicit-any */ +const callApiSpy = jest.spyOn( + RestRemoteConfigDataSource.prototype as any, + "_callApi" +); +const parseResponseSpy = jest.spyOn( + RestRemoteConfigDataSource.prototype as any, + "_parseResponse" +); +/* eslint-enable @typescript-eslint/no-explicit-any */ + describe("RemoteRestConfigDataSource", () => { describe("RestRemoteConfigDataSource", () => { - beforeAll(() => { + beforeEach(() => { + callApiSpy.mockClear(); + parseResponseSpy.mockClear(); datasource = new RestRemoteConfigDataSource(); }); - it("should return the config", async () => { - expect(await datasource.getConfig()).toStrictEqual({ - name: "DeviceSDK", - version: "0.0.0-fake.1", + it("should return an Either", async () => { + callApiSpy.mockResolvedValue( + Either.of({ + ok: true, + json: () => + Promise.resolve( + Either.of({ name: "DeviceSDK", version: "0.0.0-fake.1" }) + ), + }) + ); + + parseResponseSpy.mockReturnValue( + Either.of({ + name: "DeviceSDK", + version: "0.0.0-fake.1", + }) + ); + + expect(await datasource.getConfig()).toStrictEqual( + Either.of({ + name: "DeviceSDK", + version: "0.0.0-fake.1", + }) + ); + }); + + it("should return an Either if _callApi throws", async () => { + const err = new Error("_callApi error"); + callApiSpy.mockResolvedValue(Left(err)); + + expect(await datasource.getConfig()).toStrictEqual( + Left(new ApiCallError(err)) + ); + }); + + it("should return an Either if _callApi returns a non-ok response", async () => { + callApiSpy.mockResolvedValue( + Either.of({ + ok: false, + json: () => + Promise.resolve( + Either.of({ name: "DeviceSDK", version: "0.0.0-fake.1" }) + ), + }) + ); + + expect(await datasource.getConfig()).toStrictEqual( + Left(new ApiCallError(new Error("response not ok"))) + ); + }); + + it("should return an Either if deserializing json fails", async () => { + const err = new Error("deserializing json failure"); + callApiSpy.mockResolvedValue( + Either.of({ + ok: true, + json: () => Promise.resolve(Left(err)), + }) + ); + + expect(await datasource.getConfig()).toStrictEqual( + Left(new JSONParseError()) + ); + }); + + it("should return an Either if _parseResponse throws", async () => { + callApiSpy.mockResolvedValue( + Either.of({ + ok: true, + json: () => + Promise.resolve( + Either.of({ name: "DeviceSDK", version: "0.0.0-fake.1" }) + ), + }) + ); + + parseResponseSpy.mockImplementation(() => { + return Left(new ParseResponseError()); }); + + expect(await datasource.getConfig()).toStrictEqual( + Left(new ParseResponseError()) + ); }); - }); - describe("MockRemoteConfigDataSource", () => { - beforeAll(() => { - datasource = new StubRemoteConfigDataSource(); + + it("should return an Either if `name` is missing in Dto", async () => { + parseResponseSpy.mockRestore(); + callApiSpy.mockResolvedValue( + Either.of({ + ok: true, + json: () => + Promise.resolve( + Either.of({ + version: "0.0.0-fake.1", + yolo: "yolo", + }) + ), + }) + ); + + expect(await datasource.getConfig()).toStrictEqual( + Left(new ParseResponseError()) + ); }); - it("should return the config", async () => { - expect(await datasource.getConfig()).toStrictEqual({ - name: "DeviceSDK", - version: "0.0.0-fake.2", + describe("without private methods spy", () => { + beforeEach(() => { + callApiSpy.mockRestore(); + parseResponseSpy.mockRestore(); + }); + + it("should return an Either", async () => { + expect(await datasource.getConfig()).toStrictEqual( + Either.of({ + name: "DeviceSDK", + version: "0.0.0-fake.1", + }) + ); }); }); }); diff --git a/packages/core/src/internal/config/data/RemoteConfigDataSource.ts b/packages/core/src/internal/config/data/RemoteConfigDataSource.ts index 06072aa99..25bacd431 100644 --- a/packages/core/src/internal/config/data/RemoteConfigDataSource.ts +++ b/packages/core/src/internal/config/data/RemoteConfigDataSource.ts @@ -1,7 +1,14 @@ import { injectable } from "inversify"; +import { Either, Left } from "purify-ts"; +import { Config } from "@internal/config/model/Config"; +import { + ApiCallError, + JSONParseError, + ParseResponseError, + RemoteConfigFailure, +} from "@internal/config/di/configTypes"; import { RemoteConfigDataSource } from "./ConfigDataSource"; import { ConfigDto } from "./Dto"; -import { Config } from "../model/Config"; /** * class RemoteRestConfigDataSource @@ -9,31 +16,65 @@ import { Config } from "../model/Config"; */ @injectable() export class RestRemoteConfigDataSource implements RemoteConfigDataSource { - async getConfig() { - // Fake API call - const v = await new Promise<{ json: () => Promise }>( - (resolve) => { - resolve({ - json: async () => - new Promise((res) => - res({ - name: "DeviceSDK", - version: "0.0.0-fake.1", - yolo: "yolo", - }) - ), - }); - } - ); - const json = await v.json(); - const config = this._parseResponse(json); - return config; + async getConfig(): Promise> { + const call = await this._callApi(); + if (call.isLeft()) { + console.error("ApiCallError"); + return Left(new ApiCallError(call.extract())); + } + + if (!call.extract().ok) { + console.error("ApiCallError"); + return Left(new ApiCallError(new Error("response not ok"))); + } + + const json = await call.extract().json(); + if (json.isLeft()) { + console.error("JSONParseError"); + return Left(new JSONParseError()); + } + + return json + .chain((dto) => this._parseResponse(dto)) + .map((config) => config); } // Parser for the Dto // parserResponse: ConfigDto => Config - private _parseResponse(dto: ConfigDto): Config { + private _parseResponse(dto: ConfigDto): Either { const { name, version } = dto; - return { name, version }; + if (!name || !version) { + console.log("missing stuff"); + return Left(new ParseResponseError()); + } + return Either.of({ name, version }); + } + + private _callApi(): Promise< + Either< + never, + { + ok: boolean; + json: () => Promise>; + } + > + > { + return new Promise((res) => { + res( + Either.of({ + ok: true, + json: async () => + new Promise((r) => { + r( + Either.of({ + name: "DeviceSDK", + version: "0.0.0-fake.1", + yolo: "yolo", + }) + ); + }), + }) + ); + }); } } diff --git a/packages/core/src/internal/config/data/version.json b/packages/core/src/internal/config/data/version.json deleted file mode 100644 index 2fde7da1a..000000000 --- a/packages/core/src/internal/config/data/version.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "DeviceSDK", - "version": "0.0.0-local.1" -} diff --git a/packages/core/src/internal/config/di/configModule.test.ts b/packages/core/src/internal/config/di/configModule.test.ts index f825ebde6..5150f0403 100644 --- a/packages/core/src/internal/config/di/configModule.test.ts +++ b/packages/core/src/internal/config/di/configModule.test.ts @@ -1,10 +1,10 @@ import { Container } from "inversify"; +import { FileLocalConfigDataSource } from "@internal/config/data/LocalConfigDataSource"; +import { StubLocalConfigDataSource } from "@internal/config/data/LocalConfigDataSource.stub"; +import { RestRemoteConfigDataSource } from "@internal/config/data/RemoteConfigDataSource"; +import { StubRemoteConfigDataSource } from "@internal/config/data/RemoteConfigDataSource.stub"; import configModuleFactory from "./configModule"; import { types } from "./configTypes"; -import { FileLocalConfigDataSource } from "../data/LocalConfigDataSource"; -import { StubLocalConfigDataSource } from "../data/LocalConfigDataSource.stub"; -import { RestRemoteConfigDataSource } from "../data/RemoteConfigDataSource"; -import { StubRemoteConfigDataSource } from "../data/RemoteConfigDataSource.stub"; describe("configModuleFactory", () => { describe("Default", () => { @@ -31,7 +31,7 @@ describe("configModuleFactory", () => { describe("Mocked", () => { let container: Container; let mod: ReturnType; - beforeAll(() => { + beforeEach(() => { mod = configModuleFactory({ mock: true }); container = new Container(); container.load(mod); diff --git a/packages/core/src/internal/config/di/configModule.ts b/packages/core/src/internal/config/di/configModule.ts index 09ee5fb0f..bc0b11d7c 100644 --- a/packages/core/src/internal/config/di/configModule.ts +++ b/packages/core/src/internal/config/di/configModule.ts @@ -1,10 +1,10 @@ import { ContainerModule } from "inversify"; -import { FileLocalConfigDataSource } from "../data/LocalConfigDataSource"; -import { StubLocalConfigDataSource } from "../data/LocalConfigDataSource.stub"; -import { RestRemoteConfigDataSource } from "../data/RemoteConfigDataSource"; -import { StubRemoteConfigDataSource } from "../data/RemoteConfigDataSource.stub"; -import { DefaultConfigService } from "../service/DefaultConfigService"; -import { GetSdkVersionUseCase } from "../usecase/GetSdkVersionUseCase"; +import { FileLocalConfigDataSource } from "@internal/config/data/LocalConfigDataSource"; +import { StubLocalConfigDataSource } from "@internal/config/data/LocalConfigDataSource.stub"; +import { RestRemoteConfigDataSource } from "@internal/config/data/RemoteConfigDataSource"; +import { StubRemoteConfigDataSource } from "@internal/config/data/RemoteConfigDataSource.stub"; +import { DefaultConfigService } from "@internal/config/service/DefaultConfigService"; +import { GetSdkVersionUseCase } from "@internal/config/usecase/GetSdkVersionUseCase"; import { types } from "./configTypes"; // This module is used to configure the dependency injection container diff --git a/packages/core/src/internal/config/di/configTypes.ts b/packages/core/src/internal/config/di/configTypes.ts index a0de0a651..c91af94fa 100644 --- a/packages/core/src/internal/config/di/configTypes.ts +++ b/packages/core/src/internal/config/di/configTypes.ts @@ -4,3 +4,42 @@ export const types = { ConfigService: Symbol.for("ConfigService"), GetSdkVersionUseCase: Symbol.for("GetSdkVersionUseCase"), }; + +export class ApiCallError { + readonly _tag = "ApiCallError"; + originalError?: Error; + constructor(readonly err?: Error) { + this.originalError = err; + } +} + +export class ParseResponseError { + readonly _tag = "ParseResponseError"; + originalError?: Error; + constructor(readonly err?: Error) { + this.originalError = err; + } +} + +export class JSONParseError { + readonly _tag = "JSONParseError"; + originalError?: Error; + constructor(readonly err?: Error) { + this.originalError = err; + } +} + +export type RemoteConfigFailure = + | ApiCallError + | ParseResponseError + | JSONParseError; + +export class ReadFileError { + readonly _tag = "ReadFileError"; + originalError?: Error; + constructor(readonly err?: Error) { + this.originalError = err; + } +} + +export type LocalConfigFailure = JSONParseError | ReadFileError; diff --git a/packages/core/src/internal/config/service/ConfigService.ts b/packages/core/src/internal/config/service/ConfigService.ts index d58355bb9..ca7a9cc9b 100644 --- a/packages/core/src/internal/config/service/ConfigService.ts +++ b/packages/core/src/internal/config/service/ConfigService.ts @@ -1,4 +1,4 @@ -import { Config } from "../model/Config"; +import { Config } from "@internal/config/model/Config"; export interface ConfigService { getSdkConfig(): Promise; diff --git a/packages/core/src/internal/config/service/DefaultConfigService.test.ts b/packages/core/src/internal/config/service/DefaultConfigService.test.ts index e7628c35f..29b2b80d4 100644 --- a/packages/core/src/internal/config/service/DefaultConfigService.test.ts +++ b/packages/core/src/internal/config/service/DefaultConfigService.test.ts @@ -1,5 +1,7 @@ +import { Either, Left } from "purify-ts"; import { ConfigService } from "./ConfigService"; import { DefaultConfigService } from "./DefaultConfigService"; +import { JSONParseError } from "@internal/config/di/configTypes"; const localDataSource = { getConfig: jest.fn(), @@ -20,10 +22,12 @@ describe("DefaultConfigService", () => { describe("when the local config is available", () => { it("should return the `local` version", async () => { - localDataSource.getConfig.mockReturnValue({ - name: "DeviceSDK", - version: "1.0.0-local", - }); + localDataSource.getConfig.mockReturnValue( + Either.of({ + name: "DeviceSDK", + version: "1.0.0-local", + }) + ); expect(await service.getSdkConfig()).toStrictEqual({ name: "DeviceSDK", @@ -32,17 +36,31 @@ describe("DefaultConfigService", () => { }); }); - describe("when the local config is not available", () => { + describe("when the local config is not available, use remote", () => { it("should return the `remote` version", async () => { - localDataSource.getConfig.mockReturnValue(""); - remoteDataSource.getConfig.mockResolvedValue({ + localDataSource.getConfig.mockReturnValue(Left(new JSONParseError())); + remoteDataSource.getConfig.mockResolvedValue( + Either.of({ + name: "DeviceSDK", + version: "1.0.0-remote", + }) + ); + + expect(await service.getSdkConfig()).toStrictEqual({ name: "DeviceSDK", version: "1.0.0-remote", }); + }); + }); + + describe("when the local remote config are not available", () => { + it("should return the `default` version", async () => { + localDataSource.getConfig.mockReturnValue(Left(new JSONParseError())); + remoteDataSource.getConfig.mockResolvedValue(Left(new JSONParseError())); expect(await service.getSdkConfig()).toStrictEqual({ - name: "DeviceSDK", - version: "1.0.0-remote", + name: "DeadSdk", + version: "0.0.0-dead.1", }); }); }); diff --git a/packages/core/src/internal/config/service/DefaultConfigService.ts b/packages/core/src/internal/config/service/DefaultConfigService.ts index 59256fbfd..2d4582ec7 100644 --- a/packages/core/src/internal/config/service/DefaultConfigService.ts +++ b/packages/core/src/internal/config/service/DefaultConfigService.ts @@ -1,11 +1,11 @@ import { inject, injectable } from "inversify"; -import { ConfigService } from "./ConfigService"; -import { types } from "../di/configTypes"; +import { types } from "@internal/config/di/configTypes"; import type { LocalConfigDataSource, RemoteConfigDataSource, -} from "../data/ConfigDataSource"; -import { Config } from "../model/Config"; +} from "@internal/config/data/ConfigDataSource"; +import { Config } from "@internal/config/model/Config"; +import { ConfigService } from "./ConfigService"; @injectable() export class DefaultConfigService implements ConfigService { @@ -20,11 +20,25 @@ export class DefaultConfigService implements ConfigService { } async getSdkConfig(): Promise { - const localConfig = this._local.getConfig(); - if (localConfig?.version) { - return this._local.getConfig(); + // Returns an Either + const localConfig = this._local.getConfig().ifLeft((err) => { + console.error("Local config not available"); + console.error(err); + }); + + if (localConfig.isRight()) { + return localConfig.extract(); } - return this._remote.getConfig().then((config) => config); + return this._remote.getConfig().then((config) => { + return config + .mapLeft((err) => { + // Here we handle the error and return a default value + console.error("Remote config not available"); + console.error(err); + return { name: "DeadSdk", version: "0.0.0-dead.1" }; + }) + .extract(); + }); } } diff --git a/packages/core/src/internal/config/usecase/GetSdkVersionUseCase.test.ts b/packages/core/src/internal/config/usecase/GetSdkVersionUseCase.test.ts index a6cfe70a5..d16f097ac 100644 --- a/packages/core/src/internal/config/usecase/GetSdkVersionUseCase.test.ts +++ b/packages/core/src/internal/config/usecase/GetSdkVersionUseCase.test.ts @@ -16,8 +16,8 @@ describe("GetSdkVersionUseCase", () => { it("should return the sdk version", async () => { getSdkConfigMock.mockResolvedValue({ name: "DeviceSDK", - version: "1.0.0-mock.1", + version: "1.0.0", }); - expect(await usecase.getSdkVersion()).toBe("1.0.0-mock.1"); + expect(await usecase.getSdkVersion()).toBe("1.0.0"); }); }); diff --git a/packages/core/src/internal/config/usecase/GetSdkVersionUseCase.ts b/packages/core/src/internal/config/usecase/GetSdkVersionUseCase.ts index adff29256..1f28edd65 100644 --- a/packages/core/src/internal/config/usecase/GetSdkVersionUseCase.ts +++ b/packages/core/src/internal/config/usecase/GetSdkVersionUseCase.ts @@ -1,6 +1,6 @@ import { inject, injectable } from "inversify"; -import type { ConfigService } from "../service/ConfigService"; -import { types } from "../di/configTypes"; +import type { ConfigService } from "@internal/config/service/ConfigService"; +import { types } from "@internal/config/di/configTypes"; /** * class GetSDKVersionUseCase diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index af298fa8e..8ffe96945 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "@ledgerhq/tsconfig-dsdk/sdk", "compilerOptions": { - "outDir": "./lib" + "outDir": "./lib", + "baseUrl": ".", + "paths": { + "@internal/*": ["src/internal/*"] + } }, - "include": ["src/**/*", "jest.config.ts", "jest.setup.ts"] + "include": ["src", "jest.*.ts"] } diff --git a/packages/core/tsconfig.prod.json b/packages/core/tsconfig.prod.json new file mode 100644 index 000000000..457b19611 --- /dev/null +++ b/packages/core/tsconfig.prod.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/**/*.test.ts", "jest.*.ts"] +} diff --git a/packages/signer/package.json b/packages/signer/package.json index 539dbe798..5d22ed8b9 100644 --- a/packages/signer/package.json +++ b/packages/signer/package.json @@ -8,7 +8,7 @@ "./lib" ], "scripts": { - "build": "rm -rf lib && tsc", + "build": "rimraf lib && tsc", "dev": "tsc --watch", "lint": "eslint --cache --ext .ts \"src\"", "lint:fix": "eslint --cache --fix --ext .ts \"src\"", diff --git a/packages/trusted-apps/package.json b/packages/trusted-apps/package.json index e6a05f3b8..af535ef61 100644 --- a/packages/trusted-apps/package.json +++ b/packages/trusted-apps/package.json @@ -8,7 +8,7 @@ "./lib" ], "scripts": { - "build": "rm -rf lib && tsc", + "build": "rimraf lib && tsc", "lint": "eslint --cache --ext .ts \"src\"", "lint:fix": "eslint --cache --fix --ext .ts \"src\"", "dev": "tsc --watch", diff --git a/packages/ui/package.json b/packages/ui/package.json index c14e0385f..df50fed1c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -8,7 +8,7 @@ "./lib" ], "scripts": { - "build": "rm -rf lib && tsc", + "build": "rimraf lib && tsc", "dev": "tsc --watch", "lint": "eslint --cache --ext .ts \"src\"", "lint:fix": "eslint --cache --fix --ext .ts \"src\"", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2d988118..a9bd7a6ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: prettier: specifier: ^3.1.1 version: 3.1.1 + rimraf: + specifier: ^5.0.5 + version: 5.0.5 turbo: specifier: ^1.11.2 version: 1.11.2 @@ -120,6 +123,9 @@ importers: inversify-logger-middleware: specifier: ^3.1.0 version: 3.1.0 + purify-ts: + specifier: ^2.0.3 + version: 2.0.3 reflect-metadata: specifier: ^0.2.1 version: 0.2.1 @@ -133,9 +139,15 @@ importers: '@ledgerhq/tsconfig-dsdk': specifier: workspace:* version: link:../config/typescript + concurrently: + specifier: ^8.2.2 + version: 8.2.2 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.10.6)(typescript@5.3.3) + tsc-alias: + specifier: ^1.8.8 + version: 1.8.8 packages/signer: dependencies: @@ -606,6 +618,18 @@ packages: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -999,6 +1023,13 @@ packages: fastq: 1.15.0 dev: true + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + /@pkgr/utils@2.4.2: resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1135,7 +1166,6 @@ packages: /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} @@ -1789,6 +1819,12 @@ packages: concat-map: 0.0.1 dev: true + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -2009,10 +2045,31 @@ packages: engines: {node: '>= 6'} dev: true + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.1 + shell-quote: 1.8.1 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + dev: true + /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2067,6 +2124,13 @@ packages: engines: {node: '>= 12'} dev: true + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.23.6 + dev: true + /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -2216,6 +2280,10 @@ packages: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: true + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /electron-to-chromium@1.4.614: resolution: {integrity: sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ==} @@ -3042,6 +3110,14 @@ packages: is-callable: 1.2.7 dev: true + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: true + /formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -3180,6 +3256,18 @@ packages: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} dev: false + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 7.0.4 + path-scurry: 1.10.1 + dev: true + /glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} dependencies: @@ -3717,6 +3805,15 @@ packages: set-function-name: 2.0.1 dev: true + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + /jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4340,6 +4437,11 @@ packages: dependencies: js-tokens: 4.0.0 + /lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} + engines: {node: 14 || >=16.14} + dev: true + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -4411,10 +4513,22 @@ packages: brace-expansion: 1.1.11 dev: true + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -4422,6 +4536,11 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true + /mylas@2.1.13: + resolution: {integrity: sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==} + engines: {node: '>=12.0.0'} + dev: true + /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: @@ -4721,6 +4840,14 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.2.0 + minipass: 7.0.4 + dev: true + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4763,6 +4890,13 @@ packages: find-up: 4.1.0 dev: true + /plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} + dependencies: + queue-lit: 1.5.2 + dev: true + /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -4913,6 +5047,17 @@ packages: resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} dev: true + /purify-ts@2.0.3: + resolution: {integrity: sha512-RiPOlX4L+eggnbEdwGV34t7iRSPK5d37nKPZXSu8G5mTUhxbEjPpThRFuEV4GL/T6zEJQ+ZeiuNoBk61VJvszg==} + dependencies: + '@types/json-schema': 7.0.15 + dev: false + + /queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -5095,6 +5240,14 @@ packages: glob: 7.2.3 dev: true + /rimraf@5.0.5: + resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 10.3.10 + dev: true + /run-applescript@5.0.0: resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} engines: {node: '>=12'} @@ -5108,6 +5261,12 @@ packages: queue-microtask: 1.2.3 dev: true + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + dependencies: + tslib: 2.6.2 + dev: true + /safe-array-concat@1.0.1: resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} engines: {node: '>=0.4'} @@ -5180,6 +5339,10 @@ packages: engines: {node: '>=8'} dev: true + /shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + dev: true + /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -5260,6 +5423,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + dev: true + /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: @@ -5332,6 +5499,15 @@ packages: strip-ansi: 6.0.1 dev: true + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + /string-width@7.0.0: resolution: {integrity: sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==} engines: {node: '>=18'} @@ -5584,6 +5760,11 @@ packages: is-number: 7.0.0 dev: true + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true + /ts-api-utils@1.0.3(typescript@5.3.3): resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} engines: {node: '>=16.13.0'} @@ -5662,6 +5843,18 @@ packages: yn: 3.1.1 dev: true + /tsc-alias@1.8.8: + resolution: {integrity: sha512-OYUOd2wl0H858NvABWr/BoSKNERw3N9GTi3rHPK8Iv4O1UyUXIrTTOAZNHsjlVpXFOhpJBVARI1s+rzwLivN3Q==} + hasBin: true + dependencies: + chokidar: 3.5.3 + commander: 9.5.0 + globby: 11.1.0 + mylas: 2.1.13 + normalize-path: 3.0.0 + plimit-lit: 1.6.1 + dev: true + /tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} dependencies: @@ -5989,6 +6182,15 @@ packages: strip-ansi: 6.0.1 dev: true + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + /wrap-ansi@9.0.0: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'}