diff --git a/docs/assets/dtaas-logo-with-text.png b/docs/assets/dtaas-logo-with-text.png new file mode 100755 index 000000000..97c8783ed Binary files /dev/null and b/docs/assets/dtaas-logo-with-text.png differ diff --git a/servers/execution/runner/runner-api.yaml b/docs/developer/servers/execution/runner/runner-api.yaml similarity index 100% rename from servers/execution/runner/runner-api.yaml rename to docs/developer/servers/execution/runner/runner-api.yaml diff --git a/docs/redirect-page.html b/docs/redirect-page.html index 5309ef422..a11d206c0 100644 --- a/docs/redirect-page.html +++ b/docs/redirect-page.html @@ -45,7 +45,9 @@ - + diff --git a/docs/user/servers/execution/runner/README.md b/docs/user/servers/execution/runner/README.md new file mode 100644 index 000000000..b76c78218 --- /dev/null +++ b/docs/user/servers/execution/runner/README.md @@ -0,0 +1,205 @@ +# :runner: Runner + +A utility service to manage safe execution of remote commands. +User launches this from commandline and let the utility +manage the commands to be executed. + +The runner utility runs as a service and provides +REST API interface to safely execute remote commands. +Multiple runners can be active simultaneously on one computer. +The commands are sent via the REST API and are executed on the computer +with active runner. + +## :arrow_down: Install + +The package is available in Github +[packages registry](https://github.com/orgs/INTO-CPS-Association/packages). + +Set the registry and install the package with the following commands + +```bash +sudo npm config set @into-cps-association:registry https://npm.pkg.github.com +sudo npm install -g @into-cps-association/runner +``` + +The _npm install_ command asks for username and password. The username is +your Github username and the password is your Github +[personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens). +In order for the npm to download the package, your personal access token +needs to have _read:packages_ scope. + +## :gear: Configure + +The microservices requires config specified in YAML format. +The template configuration file is: + +```ini +port: 5000 +location: "lifecycle" #directory location of scripts +``` + +The file should be named as _runner.yaml_ and placed in the directory +in which the _runner_ microservice is run. + +## :pen: Create Commands + +The runner requires commands / scripts to be run. +These need to be placed in the `location` specified in +_runner.yaml_ file. The location must be relative to +the directory in which the **runner** microservice is being +run. + +## :rocket: Use + +```bash +runner # launch the digital twin runner +``` + +Once launched, the utility runs at the port specified in +_runner.yaml_ file. + +If launched on one computer, +you can access the same at `http://localhost:`. + +Access to the service on network is available at `http://:/`. + +### Application Programming Interface (API) + +Three REST API methods are active. The route paths and the responses given +for these two sources are: + +| REST API Route | HTTP Method | Return Value | Comment | +| :----------------------------- |:--------|:----------- | :------ | +| localhost:port | POST | Returns the execution status of command | Executes the command provided. Each invocation appends to _array_ of commands executed so far. | +| localhost:port | GET | Returns the execution status of the last command sent via POST request. | | +| localhost:port/history | GET | Returns the array of POST requests received so far. | | + +#### POST Request to / + +Executes a command. The command name given here must exist +in _location_ directory. + +=== "Valid HTTP Request" + + ``` http-request + POST / HTTP/1.1 + Host: foo.com + Content-Type: application/json + Content-Length: 388 + + { + "name": "" + } + ``` + +=== "HTTP Response - Valid Command" + + ``` http-response + Connection: close + Content-Length: 134 + Content-Type: application/json; charset=utf-8 + Date: Tue, 09 Apr 2024 08:51:11 GMT + Etag: W/"86-ja15r8P5HJu72JcROfBTV4sAn2I" + X-Powered-By: Express + + { + "status": "success" + } + ``` + +=== "HTTP Response - Inalid Command" + + ``` http-request + Connection: close + Content-Length: 28 + Content-Type: application/json; charset=utf-8 + Date: Tue, 09 Apr 2024 08:51:11 GMT + Etag: W/"86-ja15r8P5HJu72JcROfBTV4sAn2I" + X-Powered-By: Express + + { + "status": "invalid command" + } + ``` + +#### GET Request to / + +Shows the status of the command last executed. + +=== "Valid HTTP Request" + + ``` http-request + GET / HTTP/1.1 + Host: foo.com + Content-Type: application/json + Content-Length: 388 + + { + "name": "" + } + ``` + +=== "HTTP Response - Valid Command" + + ``` http-response + Connection: close + Content-Length: 134 + Content-Type: application/json; charset=utf-8 + Date: Tue, 09 Apr 2024 08:51:11 GMT + Etag: W/"86-ja15r8P5HJu72JcROfBTV4sAn2I" + X-Powered-By: Express + + { + "name": "", + "status": "valid", + "logs": { + "stdout": "", + "stderr": "" + } + } + ``` + +=== "HTTP Response - Inalid Command" + + ``` http-request + Connection: close + Content-Length: 70 + Content-Type: application/json; charset=utf-8 + Date: Tue, 09 Apr 2024 08:51:11 GMT + Etag: W/"86-ja15r8P5HJu72JcROfBTV4sAn2I" + X-Powered-By: Express + + { + "name": "` + +Please see [README](./README.md) for more information. diff --git a/servers/execution/runner/README.md b/servers/execution/runner/README.md index 497f7cda8..0d989a0a8 100644 --- a/servers/execution/runner/README.md +++ b/servers/execution/runner/README.md @@ -1,92 +1,150 @@ -# :runner: Digital Twin Runner +# :runner: Runner -A utility service to manage the -[lifecycle of one digital twin](../../../docs/user/digital-twins/lifecycle.md). -The lifecycle of a digital twin is made of multiple phases. -This digital twin runner utility -helps with the managing the execution of lifecycle phases. -This utility can be -launched in two scenarios: +A utility service to manage safe execution of remote commands. +User launches this from commandline and let the utility +manage the commands to be executed. -1. User launches this from commandline and let the utility - manage the lifecycle of one digital twin. -1. Execution infrastructure of Digital Twin as a Service (DTaaS) - launches this utility and instructs it to manage the lifecycle of - one digital twin. +The runner utility runs as a service and provides +REST API interface to safely execute remote commands. +Multiple runners can be active simultaneously on one computer. +The commands are sent via the REST API and are executed on the computer +with active runner. -The digital twin runner utility runs as a service and will provide -REST API interface to execute lifecycle scripts of a digital twin. -One digital twin runner is responsible for execution of a digital twin. +## :arrow_down: Install -## :hammer_and_wrench: Developer Commands +The package is available in Github +[packages registry](https://github.com/orgs/INTO-CPS-Association/packages). + +Set the registry and install the package with the following commands ```bash -yarn install # Install dependencies for the microservice -yarn syntax # Analyze code for errors and style issues -yarn graph # Generate dependency graphs in the code -yarn build # Compile ES6 to ES5 and copy JS files to build/ directory -yarn test # Run tests -yarn test:nocov # Run the tests but do not report coverage -yarn test:watchAll # Watch changes in test/ and run the tests -yarn start # Start the application -yarn clean # Deletes directories "build", "coverage", and "dist" +sudo npm config set @into-cps-association:registry https://npm.pkg.github.com +sudo npm install -g @into-cps-association/runner ``` -## :package: :ship: NPM package +The _npm install_ command asks for username and password. The username is +your Github username and the password is your Github +[personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens). +In order for the npm to download the package, your personal access token +needs to have _read:packages_ scope. + +## :gear: Configure -### Github Package Registry +The microservices requires config specified in YAML format. +The template configuration file is: -The Github actions workflow of -[lib microservice](../../../.github/workflows/runner.yml) publishes the **runner** -into -[packages](https://github.com/orgs/INTO-CPS-Association/packages?repo_name=DTaaS). +```ini +port: 5000 +location: "lifecycle" #directory location of scripts +``` -### Verdaccio - Local Package Registry +The file should be named as _runner.yaml_ and placed in the directory +in which the _runner_ microservice is run. -Use the instructions in -[publish npm package](../../../docs/developer/npm-packages.md) for help -with publishing **runner npm package**. +## :pen: Create Commands -Application of the advice given on that page for **runner** will require -running the following commands. +The runner requires commands / scripts to be run. +These need to be placed in the `location` specified in +_runner.yaml_ file. The location must be relative to +the directory in which the **runner** microservice is being +run. -### Publish +## :rocket: Use ```bash -yarn install -yarn build #the dist/ directory is needed for publishing step -yarn publish --no-git-tag-version #increments version and publishes to registry -yarn publish #increments version, publishes to registry and adds a git tag +runner # launch the digital twin runner ``` -### Unpublish +Once launched, the utility runs at the port specified in +_runner.yaml_ file. -```bash -npm unpublish --registry http://localhost:4873/ @into-cps-association/runner@0.0.2 +If launched on one computer, +you can access the same at `http://localhost:`. + +Access to the service on network is available at `http://:/`. + +### Application Programming Interface (API) + +Three REST API methods are active. The route paths and the responses given +for these two sources are: + +| REST API Route | HTTP Method | Return Value | Comment | +| :----------------------------- |:--------|:----------- | :------ | +| localhost:port | POST | Returns the execution status of command | Executes the command provided. Each invocation appends to _array_ of commands executed so far. | +| localhost:port | GET | Returns the execution status of the last command sent via POST request. | | +| localhost:port/history | GET | Returns the array of POST requests received so far. | | + +#### POST Request to / + +Executes a command. The command name given here must exist +in _location_ directory. + +```http +{ + "name": "" +} ``` -## :rocket: Access the service +If the command exists, a successful response will be -```bash -sudo npm install --registry http://localhost:4873 -g @dtaas/runner -runner # launch the digital twin runner +```http +{ + "status": "success" +} ``` -Once launched, the utility runs at `port 3000`. +If the does not exist, the response will be -If launched on one computer, -you can access the same at `http://localhost:3000`. +```http +{ + "status": "invalid command name" +} +``` -Access to the service on network is available at `http://:3000/`. +#### GET Request to / -Two REST API routes are active. The route paths and the responses given -for these two sources are: +Shows the status of the command last executed. If the execution +was successful, the status will be -| REST API Route | Return Value | Comment | -| :----------------------------- | :----------- | :------ | -| localhost:3000/phase | [ hello ] | Each invocation appends to _array_. | -| localhost:3000/lifecycle/phase | _true_ | Always returns _true_ | -| localhost:3000/phase | [ hello ] | array. | +```http +{ + "name": "", + "status": "valid", + "logs": { + "stdout": "", + "stderr": "" + } +} +``` + +If the execution is unsuccessful, the status will be + +```http +{ + "message": "Validation Failed", + "error": "Bad Request", + "statusCode": 400 +} +``` + +#### GET Request to /history + +Returns the array of POST requests received so far. +Both valid and invalid commands are recorded in the history. + +```http +[ + { + "name": "valid command" + }, + { + "name": "valid command" + }, + { + "name": "invalid command" + } +] +``` ## :balance_scale: License diff --git a/servers/execution/runner/package.json b/servers/execution/runner/package.json index b3df2fb7d..8854838d4 100644 --- a/servers/execution/runner/package.json +++ b/servers/execution/runner/package.json @@ -1,6 +1,6 @@ { "name": "@into-cps-association/runner", - "version": "0.0.3", + "version": "0.1.0", "description": "DT Runner", "main": "dist/src/runner.js", "repository": "https://github.com/into-cps-association/DTaaS.git", @@ -14,7 +14,10 @@ "format": "prettier --ignore-path ../.gitignore --write \"**/*.{ts,tsx,css,scss}\"", "start": "npx cross-env NODE_OPTIONS='--es-module-specifier-resolution=node --experimental-specifier-resolution=node' NODE_NO_WARNINGS=1 node dist/src/main.js", "syntax": "npx eslint . --fix", + "pretest": "npx shx cp test/config/runner.yaml runner.yaml", + "posttest": "npx rimraf runner.yaml", "test": "npx cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest --coverage=true", + "test:e2e": "npx cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest --coverage=true --verbose=true test/app.e2e.spec.ts", "test:nocov": "npx cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest --coverage=false", "test:watchAll": "npx cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest --coverage=true --watchAll", "graph": "npx madge --image src.svg src && npx madge --image test.svg test" @@ -26,8 +29,7 @@ "runner": "./dist/src/main.js" }, "files": [ - "dist/", - "script/" + "dist/" ], "prettier": { "singleQuote": true @@ -69,9 +71,11 @@ "@nestjs/platform-express": "^10.3.7", "cross-env": "^7.0.3", "execa": "^8.0.1", + "express": "^4.18.3", "js-yaml": "^4.1.0", "reflect-metadata": "^0.2.1", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "zod": "^3.22.4" }, "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610" } diff --git a/servers/execution/runner/src/app.controller.ts b/servers/execution/runner/src/app.controller.ts index 44cb14e36..345e2858c 100644 --- a/servers/execution/runner/src/app.controller.ts +++ b/servers/execution/runner/src/app.controller.ts @@ -1,35 +1,55 @@ -import { Controller, Get } from '@nestjs/common'; -// import { AppService } from './app.service'; -import Queue from './queue.service.js'; -import { Phase } from './interfaces/lifecycle.interface.js'; -import ExecaCMDRunner from './execaCMDRunner.js'; +import { + Body, + Controller, + Get, + HttpStatus, + Post, + Res, + UsePipes, +} from '@nestjs/common'; +import { Response } from 'express'; +import { PhaseStatus } from './interfaces/lifecycle.interface.js'; import LifeCycleManager from './lifecycleManager.service.js'; +import { UpdatePhaseDto, updatePhaseSchema } from './dto/phase.dto.js'; +import ZodValidationPipe from './validation.pipe.js'; @Controller() export default class AppController { // eslint-disable-next-line no-useless-constructor - constructor( - private readonly lifecycle: LifeCycleManager, - private readonly queueService: Queue, - ) {} // eslint-disable-line no-empty-function + constructor(private readonly lifecycle: LifeCycleManager) {} // eslint-disable-line no-empty-function - @Get('phase') - getHello(): string[] { - const phase: Phase = { - name: 'hello', - status: 'valid', - task: new ExecaCMDRunner(''), - }; - this.queueService.enqueue(phase); - return this.queueService.phaseHistory(); + @Get('history') + getHello(): Array { + return this.lifecycle.checkHistory(); } - @Get('lifecycle/phase') - async changePhase(): Promise { + @Post() + @UsePipes(new ZodValidationPipe(updatePhaseSchema)) + async changePhase( + @Body() updatePhaseDto: UpdatePhaseDto, + @Res({ passthrough: true }) res: Response, + ): Promise { let success = false; + [success] = await this.lifecycle.changePhase(updatePhaseDto.name); + if (success) { + res.status(HttpStatus.OK).send({ + status: 'success', + }); + } else { + res.status(HttpStatus.BAD_REQUEST).send({ + status: 'invalid command', + }); + } + } - [success] = await this.lifecycle.changePhase('date'); - - return success; + @Get() + async reportPhase(): Promise { + return this.lifecycle.checkPhase(); + } + /* + @Get('lifecycle/phase') + async reportPhase(): Promise { + return this.lifecycle.checkPhase(); } + */ } diff --git a/servers/execution/runner/src/config/configuration.ts b/servers/execution/runner/src/config/configuration.ts index ff07ca0a1..ceb550833 100644 --- a/servers/execution/runner/src/config/configuration.ts +++ b/servers/execution/runner/src/config/configuration.ts @@ -5,7 +5,8 @@ import Config from './Config.interface.js'; const YAML_CONFIG_FILENAME = 'runner.yaml'; -export default () => - yaml.load( +export default function readConfig(): Config { + return yaml.load( readFileSync(join(process.cwd(), YAML_CONFIG_FILENAME), 'utf8'), - ) as Record; + ) as Config; +} diff --git a/servers/execution/runner/src/dto/phase.dto.ts b/servers/execution/runner/src/dto/phase.dto.ts new file mode 100644 index 000000000..1277be1d7 --- /dev/null +++ b/servers/execution/runner/src/dto/phase.dto.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const updatePhaseSchema = z + .object({ + name: z.string(), + }) + .required(); + +export type UpdatePhaseDto = z.infer; diff --git a/servers/execution/runner/src/interfaces/lifecycle.interface.ts b/servers/execution/runner/src/interfaces/lifecycle.interface.ts index 15eedd33c..ab4ad1063 100644 --- a/servers/execution/runner/src/interfaces/lifecycle.interface.ts +++ b/servers/execution/runner/src/interfaces/lifecycle.interface.ts @@ -1,3 +1,4 @@ +import { UpdatePhaseDto } from 'src/dto/phase.dto.js'; import CMDRunner from './CMDRunner.interface.js'; type Phase = { @@ -6,10 +7,19 @@ type Phase = { task: CMDRunner; }; +type PhaseStatus = { + name: string; + status: string; + logs: { + stdout: string | undefined; + stderr: string | undefined; + }; +}; + interface DTLifeCycle { changePhase(name: string): Promise<[boolean, Map]>; - checkHistory(): Array; - checkPhase(): Phase | undefined; + checkHistory(): Array; + checkPhase(): PhaseStatus; } -export { Phase, DTLifeCycle }; +export { Phase, PhaseStatus, DTLifeCycle }; diff --git a/servers/execution/runner/src/lifecycleManager.service.ts b/servers/execution/runner/src/lifecycleManager.service.ts index 1a0369799..d1c16f397 100644 --- a/servers/execution/runner/src/lifecycleManager.service.ts +++ b/servers/execution/runner/src/lifecycleManager.service.ts @@ -1,6 +1,16 @@ -import { Phase, DTLifeCycle } from './interfaces/lifecycle.interface.js'; +import { join } from 'path'; +import { + Phase, + DTLifeCycle, + PhaseStatus, +} from './interfaces/lifecycle.interface.js'; import ExecaCMDRunner from './execaCMDRunner.js'; import Queue from './queue.service.js'; +import readConfig from './config/configuration.js'; +import Config from './config/Config.interface.js'; +import { UpdatePhaseDto } from './dto/phase.dto.js'; + +const config: Config = readConfig(); export default class LifeCycleManager implements DTLifeCycle { private phaseQueue: Queue = new Queue(); @@ -15,7 +25,7 @@ export default class LifeCycleManager implements DTLifeCycle { let success: boolean = false; - phase.task = new ExecaCMDRunner(name); + phase.task = new ExecaCMDRunner(join(process.cwd(), config.location, name)); this.phaseQueue.enqueue(phase); await phase.task.run().then((value) => { success = value; @@ -24,11 +34,37 @@ export default class LifeCycleManager implements DTLifeCycle { return [success, phase.task.checkLogs()]; } - checkPhase(): Phase | undefined { - return this.phaseQueue.activePhase(); + checkPhase(): PhaseStatus { + let phaseStatus: PhaseStatus; + const logs: Map = new Map(); + const phase: Phase | undefined = this.phaseQueue.activePhase(); + + logs.set('stdout', ''); + logs.set('stderr', ''); + if (phase === undefined) { + phaseStatus = { + name: 'none', + status: 'invalid', + logs: { + stdout: '', + stderr: '', + }, + }; + } else { + phaseStatus = { + name: phase.name, + status: phase.status, + logs: { + stdout: phase.task.checkLogs().get('stdout'), + stderr: phase.task.checkLogs().get('stderr'), + }, + }; + // console.log(phase.task.checkLogs()); + } + return phaseStatus; } - checkHistory(): Array { + checkHistory(): Array { return this.phaseQueue.phaseHistory(); } } diff --git a/servers/execution/runner/src/main.ts b/servers/execution/runner/src/main.ts index e62f297dc..3573a0d4a 100644 --- a/servers/execution/runner/src/main.ts +++ b/servers/execution/runner/src/main.ts @@ -2,6 +2,8 @@ import { NestFactory } from '@nestjs/core'; import AppModule from './app.module.js'; +import Config from './config/Config.interface.js'; +import readConfig from './config/configuration.js'; /* The js file extension in import is a limitation of typescript. @@ -9,8 +11,10 @@ See: https://stackoverflow.com/questions/62619058/appending-js-extension-on-rela https://github.com/microsoft/TypeScript/issues/16577 */ +const config: Config = readConfig(); + async function bootstrap() { const app = await NestFactory.create(AppModule); - await app.listen(3000); + await app.listen(config.port); } bootstrap(); diff --git a/servers/execution/runner/src/queue.service.ts b/servers/execution/runner/src/queue.service.ts index 58c1e776a..bed3447a6 100644 --- a/servers/execution/runner/src/queue.service.ts +++ b/servers/execution/runner/src/queue.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import { Phase } from './interfaces/lifecycle.interface.js'; +import { UpdatePhaseDto } from './dto/phase.dto.js'; @Injectable() export default class Queue { @@ -10,8 +11,10 @@ export default class Queue { return true; } - phaseHistory(): Array { - return this.queue.map((phase) => phase.name); + phaseHistory(): Array { + const updatePhaseDto: Array = []; + this.queue.map((phase) => updatePhaseDto.push({ name: phase.name })); + return updatePhaseDto; } activePhase(): Phase | undefined { diff --git a/servers/execution/runner/src/runner.ts b/servers/execution/runner/src/runner.ts deleted file mode 100644 index 49e83e787..000000000 --- a/servers/execution/runner/src/runner.ts +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env -S NODE_OPTIONS="--es-module-specifier-resolution=node --experimental-modules --experimental-specifier-resolution=node" NODE_NO_WARNINGS=1 node - -/* eslint-disable no-console */ - -import ExecaCMDRunner from './execaCMDRunner.js'; -import CMDRunner from './interfaces/CMDRunner.interface.js'; -import LifeCycleManager from './lifecycleManager.service.js'; -import { DTLifeCycle, Phase } from './interfaces/lifecycle.interface.js'; - -const command: CMDRunner = new ExecaCMDRunner('date'); - -command.run().then((value) => { - console.log('Check the functionality of ExecaCMDRunner:\n'); - console.log(`The return status of date command is: ${value}`); - const logs: Map = command.checkLogs(); - console.log(`The output of date command is: ${logs.get('stdout')}`); -}); - -// Check the functionality of LifeCycleManager -const dt: DTLifeCycle = new LifeCycleManager(); - -dt.changePhase('whoami').then(([status, logs]) => { - console.log('LifeCycleManager First command: whoami'); - console.log(`command execution status: ${status}`); - console.log(`command logs: ${logs.get('stdout')}`); -}); -console.log(`Past phases after first command: ${dt.checkHistory()}`); -let phase: Phase | undefined = dt.checkPhase(); - -const sleep = (ms: number) => - new Promise((r) => { - setTimeout(r, ms); - }); -await sleep(1000); - -if (phase !== undefined) { - console.log(`${phase.name} is ${phase.status}`); - if (phase.status === 'valid') { - const tasklogs = phase.task.checkLogs(); - console.log(`Logs for ${phase.name} : ${tasklogs.get('stdout')}`); - } -} - -dt.changePhase('ls').then(([status, logs]) => { - console.log('LifeCycleManager Second command: ls'); - console.log(`command execution status: ${status}`); - console.log(`command logs: ${logs.get('stdout')}`); -}); -console.log(dt.checkHistory()); -console.log(`Past phases after second command: ${dt.checkHistory()}`); -await sleep(1000); - -phase = dt.checkPhase(); - -if (phase !== undefined) { - console.log(`${phase.name} is ${phase.status}`); -} diff --git a/servers/execution/runner/src/validation.pipe.ts b/servers/execution/runner/src/validation.pipe.ts new file mode 100644 index 000000000..6be89669f --- /dev/null +++ b/servers/execution/runner/src/validation.pipe.ts @@ -0,0 +1,17 @@ +import { PipeTransform, BadRequestException } from '@nestjs/common'; +import { ZodSchema } from 'zod'; +import { UpdatePhaseDto } from './dto/phase.dto'; + +export default class ZodValidationPipe implements PipeTransform { + // eslint-disable-next-line no-empty-function, no-useless-constructor + constructor(private schema: ZodSchema) {} + + transform(value: UpdatePhaseDto) { + try { + const parsedValue = this.schema.parse(value); + return parsedValue; + } catch (error) { + throw new BadRequestException('Validation Failed'); + } + } +} diff --git a/servers/execution/runner/test/app.e2e.spec.ts b/servers/execution/runner/test/app.e2e.spec.ts index ead58fa37..8d3a426f7 100644 --- a/servers/execution/runner/test/app.e2e.spec.ts +++ b/servers/execution/runner/test/app.e2e.spec.ts @@ -5,6 +5,28 @@ import AppModule from 'src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; + type CommandRequest = { + name: string; + }; + type CommandResponse = { + status: string; + }; + let body: CommandRequest = { + name: 'create', + }; + + function postRequest( + route: string, HttpStatus: number, + res: CommandResponse, + ) { + return supertest(app.getHttpServer()) + .post(route) + .send(body) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect(HttpStatus) + .expect(res); + } beforeAll(async () => { const moduleFixture = await Test.createTestingModule({ @@ -19,15 +41,22 @@ describe('AppController (e2e)', () => { await app.close(); }); - it('/phase (GET)', () => - supertest(app.getHttpServer()) - .get('/phase') - .expect(200) - .expect('["hello"]')); - - it('/lifecycle/phase (GET)', () => - supertest(app.getHttpServer()) - .get('/lifecycle/phase') - .expect(200) - .expect('true')); + it('/ (GET) get execution status with no prior command executions', () => { + return supertest(app.getHttpServer()).get('/').expect(200); + }); + + it('/ (POST) execute a valid command', () => { + return postRequest('/', 200, { + status: 'success', + }) + }); + + it('/ (POST) execute an invalid command', () => { + body = { + name: 'configure', + }; + return postRequest('/', 400, { + status: 'invalid command', + }); + }); }); diff --git a/servers/execution/runner/test/config/runner.yaml b/servers/execution/runner/test/config/runner.yaml new file mode 100644 index 000000000..046b28ed7 --- /dev/null +++ b/servers/execution/runner/test/config/runner.yaml @@ -0,0 +1,2 @@ +port: 5000 +location: "test/data/lifecycle" #directory location of scripts diff --git a/servers/execution/runner/test/data/lifecycle/create b/servers/execution/runner/test/data/lifecycle/create new file mode 100755 index 000000000..984b8d8b0 --- /dev/null +++ b/servers/execution/runner/test/data/lifecycle/create @@ -0,0 +1,4 @@ +#!/bin/bash +date +whoami +printf "dynamic updates to scripts" diff --git a/servers/execution/runner/test/integration/lifecycleManager.service.spec.ts b/servers/execution/runner/test/integration/lifecycleManager.service.spec.ts index 9bebec2a8..4d1695d5e 100644 --- a/servers/execution/runner/test/integration/lifecycleManager.service.spec.ts +++ b/servers/execution/runner/test/integration/lifecycleManager.service.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals'; import LifeCycleManager from 'src/lifecycleManager.service'; -import { DTLifeCycle, Phase } from 'src/interfaces/lifecycle.interface'; -import ExecaCMDRunner from 'src/execaCMDRunner'; +import { DTLifeCycle, PhaseStatus } from 'src/interfaces/lifecycle.interface'; +import { UpdatePhaseDto } from 'src/dto/phase.dto'; describe('Check LifecycleManager', () => { it('Should create object', async () => { @@ -18,7 +18,7 @@ describe('Check LifecycleManager', () => { let status: boolean = false; let logs: Map = new Map(); - [status, logs] = await dt.changePhase('whoami'); + [status, logs] = await dt.changePhase('create'); expect(status).toBe(true); expect(logs.get('stdout')).toEqual(expect.any(String)); @@ -34,29 +34,37 @@ describe('Check LifecycleManager', () => { expect(status).toBe(false); }); - it('Should return undefined if there has been no changePhase calls', async () => { - const dt: DTLifeCycle = new LifeCycleManager(); - let phase: Phase | undefined = { - name: 'hello', - status: 'valid', - task: new ExecaCMDRunner(''), + it('Should return correct phase status if there has been no changePhase calls', async () => { + const expPhaseStatus = { + name: 'none', + status: 'invalid', + logs: { + stdout: '', + stderr: '', + }, }; + const dt: DTLifeCycle = new LifeCycleManager(); - phase = dt.checkPhase(); - - expect(phase).toBe(undefined); + const phaseStatus: PhaseStatus = dt.checkPhase(); + expect(phaseStatus).toEqual(expPhaseStatus); }); it('Should hold correct phase history', async () => { const dt: DTLifeCycle = new LifeCycleManager(); const status: boolean[] = []; - const pastPhases: Array = ['date', 'whoami']; + const pastPhases: Array = [ + { + name: 'date', + }, + { + name: 'whoami', + }, + ]; - dt.changePhase(pastPhases[0]).then(([value]) => { - status.push(value); - }); - dt.changePhase(pastPhases[1]).then(([value]) => { - status.push(value); + pastPhases.map(async (phase) => { + await dt.changePhase(phase.name).then(([value]) => { + status.push(value); + }); }); const pastPhasesActual = dt.checkHistory(); diff --git a/servers/execution/runner/test/unit/validation.pipe.spec.ts b/servers/execution/runner/test/unit/validation.pipe.spec.ts new file mode 100644 index 000000000..b33c3eae4 --- /dev/null +++ b/servers/execution/runner/test/unit/validation.pipe.spec.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from '@jest/globals'; +import { ZodError } from 'zod'; +import ZodValidationPipe from 'src/validation.pipe'; +import { UpdatePhaseDto, updatePhaseSchema } from 'src/dto/phase.dto'; + +describe('Check UpdatePhaseDto validation pipe', () => { + it('Validation successful for correct json request', async () => { + const updatePhaseDto: UpdatePhaseDto = { + name: 'create', + }; + const updatePhaseValidator: ZodValidationPipe = new ZodValidationPipe( + updatePhaseSchema, + ); + + expect(updatePhaseValidator.transform(updatePhaseDto).name).toBe('create'); + }); + + it('zod schema validator works correctly', async () => { + const incorrectRequest = { + name: 10, + }; + expect(() => updatePhaseSchema.parse(incorrectRequest)).toThrow(ZodError); + }); +}); diff --git a/servers/execution/runner/yarn.lock b/servers/execution/runner/yarn.lock index 76c010888..fe7f32058 100644 --- a/servers/execution/runner/yarn.lock +++ b/servers/execution/runner/yarn.lock @@ -350,7 +350,7 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": version "4.10.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== @@ -1093,7 +1093,7 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== -"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8": +"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -1144,7 +1144,7 @@ dependencies: "@types/node" "*" -"@types/semver@^7.5.0": +"@types/semver@^7.5.0", "@types/semver@^7.5.8": version "7.5.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== @@ -1201,31 +1201,31 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^7.2.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz#1dc52fe48454d5b54be2d5f089680452f1628a5a" - integrity sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ== - dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "7.5.0" - "@typescript-eslint/type-utils" "7.5.0" - "@typescript-eslint/utils" "7.5.0" - "@typescript-eslint/visitor-keys" "7.5.0" + version "7.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz#1f5df5cda490a0bcb6fbdd3382e19f1241024242" + integrity sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.6.0" + "@typescript-eslint/type-utils" "7.6.0" + "@typescript-eslint/utils" "7.6.0" + "@typescript-eslint/visitor-keys" "7.6.0" debug "^4.3.4" graphemer "^1.4.0" - ignore "^5.2.4" + ignore "^5.3.1" natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" + semver "^7.6.0" + ts-api-utils "^1.3.0" "@typescript-eslint/parser@^7.2.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.5.0.tgz#1eeff36309ac2253c905dd4a88b4b71b72a358ed" - integrity sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ== - dependencies: - "@typescript-eslint/scope-manager" "7.5.0" - "@typescript-eslint/types" "7.5.0" - "@typescript-eslint/typescript-estree" "7.5.0" - "@typescript-eslint/visitor-keys" "7.5.0" + version "7.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.6.0.tgz#0aca5de3045d68b36e88903d15addaf13d040a95" + integrity sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg== + dependencies: + "@typescript-eslint/scope-manager" "7.6.0" + "@typescript-eslint/types" "7.6.0" + "@typescript-eslint/typescript-estree" "7.6.0" + "@typescript-eslint/visitor-keys" "7.6.0" debug "^4.3.4" "@typescript-eslint/scope-manager@6.21.0": @@ -1236,33 +1236,33 @@ "@typescript-eslint/types" "6.21.0" "@typescript-eslint/visitor-keys" "6.21.0" -"@typescript-eslint/scope-manager@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz#70f0a7361430ab1043a5f97386da2a0d8b2f4d56" - integrity sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA== +"@typescript-eslint/scope-manager@7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz#1e9972f654210bd7500b31feadb61a233f5b5e9d" + integrity sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w== dependencies: - "@typescript-eslint/types" "7.5.0" - "@typescript-eslint/visitor-keys" "7.5.0" + "@typescript-eslint/types" "7.6.0" + "@typescript-eslint/visitor-keys" "7.6.0" -"@typescript-eslint/type-utils@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz#a8faa403232da3a3901655387c7082111f692cf9" - integrity sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw== +"@typescript-eslint/type-utils@7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz#644f75075f379827d25fe0713e252ccd4e4a428c" + integrity sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw== dependencies: - "@typescript-eslint/typescript-estree" "7.5.0" - "@typescript-eslint/utils" "7.5.0" + "@typescript-eslint/typescript-estree" "7.6.0" + "@typescript-eslint/utils" "7.6.0" debug "^4.3.4" - ts-api-utils "^1.0.1" + ts-api-utils "^1.3.0" "@typescript-eslint/types@6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== -"@typescript-eslint/types@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.5.0.tgz#0a284bcdef3cb850ec9fd57992df9f29d6bde1bc" - integrity sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg== +"@typescript-eslint/types@7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.6.0.tgz#53dba7c30c87e5f10a731054266dd905f1fbae38" + integrity sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ== "@typescript-eslint/typescript-estree@6.21.0": version "6.21.0" @@ -1278,32 +1278,32 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/typescript-estree@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz#aa5031c511874420f6b5edd90f8e4021525ee776" - integrity sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ== +"@typescript-eslint/typescript-estree@7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz#112a3775563799fd3f011890ac8322f80830ac17" + integrity sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw== dependencies: - "@typescript-eslint/types" "7.5.0" - "@typescript-eslint/visitor-keys" "7.5.0" + "@typescript-eslint/types" "7.6.0" + "@typescript-eslint/visitor-keys" "7.6.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" - minimatch "9.0.3" - semver "^7.5.4" - ts-api-utils "^1.0.1" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.5.0.tgz#bbd963647fbbe9ffea033f42c0fb7e89bb19c858" - integrity sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw== +"@typescript-eslint/utils@7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.6.0.tgz#e400d782280b6f724c8a1204269d984c79202282" + integrity sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "7.5.0" - "@typescript-eslint/types" "7.5.0" - "@typescript-eslint/typescript-estree" "7.5.0" - semver "^7.5.4" + "@types/json-schema" "^7.0.15" + "@types/semver" "^7.5.8" + "@typescript-eslint/scope-manager" "7.6.0" + "@typescript-eslint/types" "7.6.0" + "@typescript-eslint/typescript-estree" "7.6.0" + semver "^7.6.0" "@typescript-eslint/utils@^6.0.0": version "6.21.0" @@ -1326,13 +1326,13 @@ "@typescript-eslint/types" "6.21.0" eslint-visitor-keys "^3.4.1" -"@typescript-eslint/visitor-keys@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz#8abcac66f93ef20b093e87a400c2d21e3a6d55ee" - integrity sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA== +"@typescript-eslint/visitor-keys@7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz#d1ce13145844379021e1f9bd102c1d78946f4e76" + integrity sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw== dependencies: - "@typescript-eslint/types" "7.5.0" - eslint-visitor-keys "^3.4.1" + "@typescript-eslint/types" "7.6.0" + eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": version "1.2.0" @@ -2425,9 +2425,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.668: - version "1.4.729" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.729.tgz#8477d21e2a50993781950885b2731d92ad532c00" - integrity sha512-bx7+5Saea/qu14kmPTDHQxkp2UnziG3iajUQu3BxFvCOnpAJdDbMV4rSl+EqFDkkpNNVUFlR1kDfpL59xfy1HA== + version "1.4.730" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.730.tgz#5e382c83085b50b9c63cb08692e8fcd875c1b9eb" + integrity sha512-oJRPo82XEqtQAobHpJIR3zW5YO3sSRRkPz2an4yxi1UvqhsGm54vR/wzTFV74a3soDOJ8CKW7ajOOX5ESzddwg== emittery@^0.13.1: version "0.13.1" @@ -2855,7 +2855,7 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -express@4.19.2: +express@4.19.2, express@^4.18.3: version "4.19.2" resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== @@ -3455,7 +3455,7 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.2.0, ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -4501,7 +4501,7 @@ minimatch@^8.0.2: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.1, minimatch@^9.0.3: +minimatch@^9.0.1, minimatch@^9.0.3, minimatch@^9.0.4: version "9.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== @@ -5310,7 +5310,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: +semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== @@ -5513,7 +5513,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5573,7 +5582,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -5792,7 +5808,7 @@ trim-repeated@^2.0.0: dependencies: escape-string-regexp "^5.0.0" -ts-api-utils@^1.0.1: +ts-api-utils@^1.0.1, ts-api-utils@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== @@ -6161,7 +6177,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -6179,6 +6195,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -6248,3 +6273,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.22.4: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
Digital Twin as a Service (DTaaS) + DTaaS logo +
Documentation