From d4fbe5cc03a73453a270623be578cb78ac82b939 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Fri, 23 Feb 2024 10:52:27 +0100 Subject: [PATCH] feat: publish LwM2M shadows for hello.nrfcloud.com/map --- cdk/BackendLambdas.d.ts | 1 + cdk/backend.ts | 1 + cdk/resources/LambdaLogGroup.ts | 18 + .../hello.nrfcloud.com/PublicLwM2MShadows.ts | 87 +++ cdk/stacks/BackendStack.ts | 11 + lambda/onMessage.ts | 61 +-- lambda/publishLwM2MShadowsToJSON.ts | 29 + lwm2m/fetchLwM2MShadows.ts | 62 +++ package-lock.json | 503 +++++++++++++++++- package.json | 1 + 10 files changed, 709 insertions(+), 65 deletions(-) create mode 100644 cdk/resources/LambdaLogGroup.ts create mode 100644 cdk/resources/hello.nrfcloud.com/PublicLwM2MShadows.ts create mode 100644 lambda/publishLwM2MShadowsToJSON.ts create mode 100644 lwm2m/fetchLwM2MShadows.ts diff --git a/cdk/BackendLambdas.d.ts b/cdk/BackendLambdas.d.ts index 8358a75..1f89b06 100644 --- a/cdk/BackendLambdas.d.ts +++ b/cdk/BackendLambdas.d.ts @@ -13,4 +13,5 @@ type BackendLambdas = { parseSinkMessages: PackedLambda nrplusGatewayScan: PackedLambda updatesToLwM2M: PackedLambda + publishLwM2MShadowsToJSON: PackedLambda } diff --git a/cdk/backend.ts b/cdk/backend.ts index ef499c5..16e3594 100644 --- a/cdk/backend.ts +++ b/cdk/backend.ts @@ -52,6 +52,7 @@ new BackendApp({ parseSinkMessages: await pack('parseSinkMessages'), nrplusGatewayScan: await pack('nrplusGatewayScan'), updatesToLwM2M: await pack('updatesToLwM2M'), + publishLwM2MShadowsToJSON: await pack('publishLwM2MShadowsToJSON'), }, layer: await packLayer({ id: 'baseLayer', diff --git a/cdk/resources/LambdaLogGroup.ts b/cdk/resources/LambdaLogGroup.ts new file mode 100644 index 0000000..2dfb265 --- /dev/null +++ b/cdk/resources/LambdaLogGroup.ts @@ -0,0 +1,18 @@ +import { Construct } from 'constructs' +import { aws_logs as Logs, Names, Stack } from 'aws-cdk-lib' + +export class LambdaLogGroup extends Construct { + public readonly logGroup: Logs.LogGroup + constructor( + parent: Construct, + id: string, + retention = Logs.RetentionDays.ONE_DAY, + ) { + super(parent, id) + this.logGroup = new Logs.LogGroup(this, 'logGroup', { + retention, + logGroupName: `/${Stack.of(this).stackName}/fn/${id}-${Names.uniqueId(this)}`, + logGroupClass: Logs.LogGroupClass.STANDARD, // INFREQUENT_ACCESS does not support custom metrics + }) + } +} diff --git a/cdk/resources/hello.nrfcloud.com/PublicLwM2MShadows.ts b/cdk/resources/hello.nrfcloud.com/PublicLwM2MShadows.ts new file mode 100644 index 0000000..c5f3765 --- /dev/null +++ b/cdk/resources/hello.nrfcloud.com/PublicLwM2MShadows.ts @@ -0,0 +1,87 @@ +import { + Duration, + aws_iam as IAM, + aws_lambda as Lambda, + RemovalPolicy, + aws_s3 as S3, + aws_events_targets as EventTargets, + aws_events as Events, +} from 'aws-cdk-lib' +import { Construct } from 'constructs' +import type { PackedLambda } from '../../backend.js' +import { LambdaLogGroup } from '../LambdaLogGroup.js' + +/** + * Publish a JSON of all LwM2M shadows so https://hello.nrfcloud.com/map can show them. + */ +export class PublicLwM2MShadows extends Construct { + public readonly bucket: S3.Bucket + constructor( + parent: Construct, + { + baseLayer, + lambdaSources, + }: { + baseLayer: Lambda.ILayerVersion + lambdaSources: { + publishLwM2MShadowsToJSON: PackedLambda + } + }, + ) { + super(parent, 'PublicLwM2MShadows') + + this.bucket = new S3.Bucket(this, 'bucket', { + autoDeleteObjects: true, + removalPolicy: RemovalPolicy.DESTROY, + publicReadAccess: true, + websiteIndexDocument: 'index.html', + blockPublicAccess: { + blockPublicAcls: false, + ignorePublicAcls: false, + restrictPublicBuckets: false, + blockPublicPolicy: false, + }, + objectOwnership: S3.ObjectOwnership.OBJECT_WRITER, + cors: [ + { + allowedOrigins: ['https://hello.nrfcloud.com', 'http://localhost:*'], + allowedMethods: [S3.HttpMethods.GET], + }, + ], + }) + + const fn = new Lambda.Function(this, 'fn', { + handler: lambdaSources.publishLwM2MShadowsToJSON.handler, + architecture: Lambda.Architecture.ARM_64, + runtime: Lambda.Runtime.NODEJS_20_X, + timeout: Duration.minutes(1), + memorySize: 1792, + code: Lambda.Code.fromAsset( + lambdaSources.publishLwM2MShadowsToJSON.lambdaZipFile, + ), + description: + 'Provides the LwM2M shadow of the devices to https://hello.nrfcloud.com/map', + layers: [baseLayer], + environment: { + VERSION: this.node.tryGetContext('version'), + NODE_NO_WARNINGS: '1', + BUCKET: this.bucket.bucketName, + }, + ...new LambdaLogGroup(this, 'devicesFnLogs'), + initialPolicy: [ + new IAM.PolicyStatement({ + actions: ['iot:SearchIndex', 'iot:DescribeThing'], + resources: ['*'], + }), + ], + }) + + this.bucket.grantWrite(fn) + + const rule = new Events.Rule(this, 'rule', { + description: `Rule to schedule publishLwM2MShadowsToJSON lambda invocations`, + schedule: Events.Schedule.rate(Duration.minutes(1)), + }) + rule.addTarget(new EventTargets.LambdaFunction(fn)) + } +} diff --git a/cdk/stacks/BackendStack.ts b/cdk/stacks/BackendStack.ts index 512fd81..8ef7eb2 100644 --- a/cdk/stacks/BackendStack.ts +++ b/cdk/stacks/BackendStack.ts @@ -18,6 +18,7 @@ import { Wirepas5GMeshGateway } from '../resources/Wirepas5GMeshGateway.js' import { STACK_NAME } from './stackName.js' import { NRPlusGateway } from '../resources/NRPlusGateway.js' import { LwM2M } from '../resources/LwM2M.js' +import { PublicLwM2MShadows } from '../resources/hello.nrfcloud.com/PublicLwM2MShadows.js' export class BackendStack extends Stack { public constructor( @@ -105,6 +106,11 @@ export class BackendStack extends Stack { const wirepasGateway = new Wirepas5GMeshGateway(this) + const lwm2mPublicShadows = new PublicLwM2MShadows(this, { + lambdaSources, + baseLayer, + }) + // Outputs new CfnOutput(this, 'WebSocketURI', { exportName: `${this.stackName}:WebSocketURI`, @@ -141,6 +147,11 @@ export class BackendStack extends Stack { value: wirepasGateway.accessKey.attrSecretAccessKey, exportName: `${this.stackName}:wirepasGatewayUserSecretAccessKey`, }) + + new CfnOutput(this, 'publicLwM2MShadowsBucketURL', { + value: `https://${lwm2mPublicShadows.bucket.bucketDomainName}/`, + exportName: `${this.stackName}:publicLwM2MShadowsBucketURL`, + }) } } diff --git a/lambda/onMessage.ts b/lambda/onMessage.ts index ba89b39..9fb4f15 100644 --- a/lambda/onMessage.ts +++ b/lambda/onMessage.ts @@ -10,20 +10,14 @@ import { IoTDataPlaneClient, PublishCommand, } from '@aws-sdk/client-iot-data-plane' -import { - DescribeThingCommand, - IoTClient, - SearchIndexCommand, -} from '@aws-sdk/client-iot' +import { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot' import { UpdateThingShadowCommand, type UpdateThingShadowCommandInput, } from '@aws-sdk/client-iot-data-plane' import { ApiGatewayManagementApi } from '@aws-sdk/client-apigatewaymanagementapi' import { sendEvent } from './notifyClients.js' -import type { LwM2MObjectInstance } from '@hello.nrfcloud.com/proto-lwm2m' -import { shadowToObjects } from '../lwm2m/shadowToObjects.js' -import { getDeviceInfo } from './withDeviceAlias.js' +import { fetchLwM2MShadows } from '../lwm2m/fetchLwM2MShadows.js' const { TableName, websocketManagementAPIURL } = fromEnv({ TableName: 'CONNECTIONS_TABLE_NAME', @@ -77,7 +71,7 @@ const apiGwManagementClient = new ApiGatewayManagementApi({ const send = sendEvent(apiGwManagementClient) -const deviceInfo = getDeviceInfo(iot) +const fetchLwM2M = fetchLwM2MShadows(iot) export const handler = async ( event: APIGatewayProxyWebsocketEventV2, @@ -129,54 +123,7 @@ export const handler = async ( if (message.data === 'LWM2M-shadows') { // Publish LwM2M shadows - const { things } = await iot.send( - new SearchIndexCommand({ - // Find all things which have an LwM2M shadow - queryString: 'shadow.name.lwm2m.hasDelta:*', - }), - ) - const shadows = ( - await Promise.all<{ - deviceId: string - alias?: string - objects: LwM2MObjectInstance[] - }>( - (things ?? []).map(async ({ thingName, shadow }) => { - const alias = (await deviceInfo(thingName as string)).alias - const reported = JSON.parse(shadow ?? '{}').name.lwm2m.reported - if (reported === undefined) - return { - deviceId: thingName as string, - alias, - objects: [], - } - - try { - return { - deviceId: thingName as string, - alias, - objects: shadowToObjects(reported), - } - } catch (err) { - console.error(`Failed to convert shadow for thing ${thingName}`) - console.log( - JSON.stringify({ - thingName, - shadow: { - reported, - }, - }), - ) - console.error(err) - return { - deviceId: thingName as string, - alias, - objects: [], - } - } - }), - ) - ).filter(({ objects }) => objects.length > 0) + const shadows = await fetchLwM2M() console.log( JSON.stringify({ diff --git a/lambda/publishLwM2MShadowsToJSON.ts b/lambda/publishLwM2MShadowsToJSON.ts new file mode 100644 index 0000000..82cf3cc --- /dev/null +++ b/lambda/publishLwM2MShadowsToJSON.ts @@ -0,0 +1,29 @@ +import { IoTClient } from '@aws-sdk/client-iot' +import { fetchLwM2MShadows } from '../lwm2m/fetchLwM2MShadows.js' +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3' +import { fromEnv } from '@nordicsemiconductor/from-env' + +const iot = new IoTClient({}) +const fetchShadows = fetchLwM2MShadows(iot) +const s3 = new S3Client({}) +const { bucket } = fromEnv({ bucket: 'BUCKET' })(process.env) + +export const handler = async (): Promise => { + await s3.send( + new PutObjectCommand({ + Bucket: bucket, + Key: 'lwm2m-shadows.json', + ContentType: 'application/json', + CacheControl: 'public, max-age=60', + Body: JSON.stringify({ + '@context': 'https://github.com/hello-nrfcloud/proto/map/devices', + devices: (await fetchShadows()).map(({ deviceId, objects }) => ({ + '@context': 'https://github.com/hello-nrfcloud/proto/map/device', + id: deviceId, + model: 'world.thingy.rocks', + state: objects, + })), + }), + }), + ) +} diff --git a/lwm2m/fetchLwM2MShadows.ts b/lwm2m/fetchLwM2MShadows.ts new file mode 100644 index 0000000..fce5c60 --- /dev/null +++ b/lwm2m/fetchLwM2MShadows.ts @@ -0,0 +1,62 @@ +import { IoTClient, SearchIndexCommand } from '@aws-sdk/client-iot' +import type { LwM2MObjectInstance } from '@hello.nrfcloud.com/proto-lwm2m' +import { shadowToObjects } from './shadowToObjects.js' +import { getDeviceInfo } from '../lambda/withDeviceAlias.js' + +type LwM2MShadow = { + deviceId: string + alias?: string + objects: LwM2MObjectInstance[] +} + +export const fetchLwM2MShadows = ( + iot: IoTClient, +): (() => Promise) => { + const deviceInfo = getDeviceInfo(iot) + return async () => { + const { things } = await iot.send( + new SearchIndexCommand({ + // Find all things which have an LwM2M shadow + queryString: 'shadow.name.lwm2m.hasDelta:*', + }), + ) + return ( + await Promise.all( + (things ?? []).map(async ({ thingName, shadow }) => { + const alias = (await deviceInfo(thingName as string)).alias + const reported = JSON.parse(shadow ?? '{}').name.lwm2m.reported + if (reported === undefined) + return { + deviceId: thingName as string, + alias, + objects: [], + } + + try { + return { + deviceId: thingName as string, + alias, + objects: shadowToObjects(reported), + } + } catch (err) { + console.error(`Failed to convert shadow for thing ${thingName}`) + console.log( + JSON.stringify({ + thingName, + shadow: { + reported, + }, + }), + ) + console.error(err) + return { + deviceId: thingName as string, + alias, + objects: [], + } + } + }), + ) + ).filter(({ objects }) => objects.length > 0) + } +} diff --git a/package-lock.json b/package-lock.json index 6a7bf4b..da75689 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0-development", "license": "BSD-3-Clause", "dependencies": { + "@aws-sdk/client-s3": "3.515.0", "@hello.nrfcloud.com/proto-lwm2m": "2.4.6", "@nordicsemiconductor/from-env": "3.0.1", "@nordicsemiconductor/timestream-helpers": "6.0.2", @@ -110,6 +111,31 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/@aws-crypto/crc32c": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", + "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/crc32c/node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/crc32c/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@aws-crypto/ie11-detection": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", @@ -125,6 +151,51 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "peer": true }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", + "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", + "dependencies": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@aws-crypto/sha256-browser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", @@ -775,6 +846,171 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.515.0.tgz", + "integrity": "sha512-K527n83hrMUdosxOYTzL63wtlJtmN5SUJZnGY1sUR6UyOrnOr9lS6t3AB6BgHqLFRFZJqSqmhflv2cOD7P1UPg==", + "dependencies": { + "@aws-crypto/sha1-browser": "3.0.0", + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-bucket-endpoint": "3.515.0", + "@aws-sdk/middleware-expect-continue": "3.515.0", + "@aws-sdk/middleware-flexible-checksums": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-location-constraint": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-sdk-s3": "3.515.0", + "@aws-sdk/middleware-signing": "3.515.0", + "@aws-sdk/middleware-ssec": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/signature-v4-multi-region": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", + "@aws-sdk/xml-builder": "3.496.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.2", + "@smithy/eventstream-serde-browser": "^2.1.1", + "@smithy/eventstream-serde-config-resolver": "^2.1.1", + "@smithy/eventstream-serde-node": "^2.1.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-blob-browser": "^2.1.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/hash-stream-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/md5-js": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-stream": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "@smithy/util-waiter": "^2.1.1", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "dependencies": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/sha256-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/client-s3/node_modules/fast-xml-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/@aws-sdk/client-ssm": { "version": "3.515.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.515.0.tgz", @@ -2437,6 +2673,23 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.515.0.tgz", + "integrity": "sha512-Vm423j3udFrhKPaKiXtie+6aF05efjX8lhAu5VOruIvbam7olvdWNdkH7sGWlz1ko3CVa7PwOYjGHiOOhxpEOA==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-arn-parser": "3.495.0", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "@smithy/util-config-provider": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/middleware-endpoint-discovery": { "version": "3.515.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.515.0.tgz", @@ -2465,6 +2718,38 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.515.0.tgz", + "integrity": "sha512-TWCXulivab4reOMx/vxa/IwnPX78fLwI9NUoAxjsqB6W9qjmSnPD43BSVeGvbbl/YNmgk7XfMbZb6IgxW7RyzA==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.515.0.tgz", + "integrity": "sha512-ydGjnqNeYlJaAkmQeQnS4pZRAAvzefdm8c234Qh0Fg55xRwHTNLp7uYsdfkTjrdAlj6YIO3Zr6vK6VJ6MGCwug==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@aws-crypto/crc32c": "3.0.0", + "@aws-sdk/types": "3.515.0", + "@smithy/is-array-buffer": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/middleware-host-header": { "version": "3.515.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.515.0.tgz", @@ -2479,6 +2764,19 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.515.0.tgz", + "integrity": "sha512-ORFC5oijjTJsHhUXy9o52/vl5Irf6e83bE/8tBp+sVVx81+E8zTTWZbysoa41c0B5Ycd0H3wCWutvjdXT16ydQ==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.515.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.515.0.tgz", @@ -2506,6 +2804,55 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.515.0.tgz", + "integrity": "sha512-vB8JwiTEAqm1UT9xfugnCgl0H0dtBLUQQK99JwQEWjHPZmQ3HQuVkykmJRY3X0hzKMEgqXodz0hZOvf3Hq1mvQ==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-arn-parser": "3.495.0", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/signature-v4": "^2.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/util-config-provider": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-signing": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.515.0.tgz", + "integrity": "sha512-SdjCyQCL702I07KhCiBFcoh6+NYtnruHJQIzWwMpBteuYHnCHW1k9uZ6pqacsS+Y6qpAKfTVNpQx2zP2s6QoHA==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/signature-v4": "^2.1.1", + "@smithy/types": "^2.9.1", + "@smithy/util-middleware": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.515.0.tgz", + "integrity": "sha512-0qLjKiorosVBzzaV/o7MEyS9xqLLu02qGbP564Z/FZY74JUQEpBNedgveMUbb6lqr85RnOuwZ0GZ0cBRfH2brQ==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.515.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.515.0.tgz", @@ -2537,6 +2884,22 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.515.0.tgz", + "integrity": "sha512-5lrCn4DSE0zL41k0L6moqcdExZhWdAnV0/oMEagrISzQYoia+aNTEeyVD3xqJhRbEW4gCj3Uoyis6c8muf7b9g==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/signature-v4": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/token-providers": { "version": "3.515.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.515.0.tgz", @@ -2565,6 +2928,17 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.495.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.495.0.tgz", + "integrity": "sha512-hwdA3XAippSEUxs7jpznwD63YYFR+LtQvlEcebPTgWR9oQgG9TfS+39PUfbnEeje1ICuOrN3lrFqFbmP9uzbMg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/util-base64": { "version": "3.208.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz", @@ -2742,6 +3116,18 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.496.0.tgz", + "integrity": "sha512-GvEjh537IIeOw1ZkZuB37sV12u+ipS5Z1dwjEC/HAvhl5ac23ULtTr1/n+U1gLNN+BAKSWjKiQ2ksj8DiUzeyw==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -3477,6 +3863,23 @@ "node": ">=14.0.0" } }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.1.1.tgz", + "integrity": "sha512-NjNFCKxC4jVvn+lUr3Yo4/PmUJj3tbyqH6GNHueyTGS5Q27vlEJ1MkNhUDV8QGxJI7Bodnc2pD18lU2zRfhHlQ==", + "dependencies": { + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.1.1.tgz", + "integrity": "sha512-zNW+43dltfNMUrBEYLMWgI8lQr0uhtTcUyxkgC9EP4j17WREzgSFMPUFVrVV6Rc2+QtWERYjb4tzZnQGa7R9fQ==", + "dependencies": { + "@smithy/util-base64": "^2.1.1", + "tslib": "^2.5.0" + } + }, "node_modules/@smithy/config-resolver": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.1.1.tgz", @@ -3526,16 +3929,67 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.1.1.tgz", - "integrity": "sha512-E8KYBxBIuU4c+zrpR22VsVrOPoEDzk35bQR3E+xm4k6Pa6JqzkDOdMyf9Atac5GPNKHJBdVaQ4JtjdWX2rl/nw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.1.2.tgz", + "integrity": "sha512-2PHrVRixITHSOj3bxfZmY93apGf8/DFiyhRh9W0ukfi07cvlhlRonZ0fjgcqryJjUZ5vYHqqmfIE/Qe1HM9mlw==", "dependencies": { "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^2.9.1", + "@smithy/types": "^2.10.0", "@smithy/util-hex-encoding": "^2.1.1", "tslib": "^2.5.0" } }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.1.2.tgz", + "integrity": "sha512-2N11IFHvOmKuwK6hLVkqM8ge8oiQsFkflr4h07LToxo3rX+njkx/5eRn6RVcyNmpbdbxYYt0s0Pf8u+yhHmOKg==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^2.1.2", + "@smithy/types": "^2.10.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.1.2.tgz", + "integrity": "sha512-nD/+k3mK+lMMwf2AItl7uWma+edHLqiE6LyIYXYnIBlCJcIQnA/vTHjHFoSJFCfG30sBJnU/7u4X5j/mbs9uKg==", + "dependencies": { + "@smithy/types": "^2.10.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.1.2.tgz", + "integrity": "sha512-zNE6DhbwDEWTKl4mELkrdgXBGC7UsFg1LDkTwizSOFB/gd7G7la083wb0JgU+xPt+TYKK0AuUlOM0rUZSJzqeA==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^2.1.2", + "@smithy/types": "^2.10.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.1.2.tgz", + "integrity": "sha512-Upd/zy+dNvvIDPU1HGhW9ivNjvJQ0W4UkkQOzr5Mo0hz2lqnZAyOuit4TK2JAEg/oo+V1gUY4XywDc7zNbCF0g==", + "dependencies": { + "@smithy/eventstream-codec": "^2.1.2", + "@smithy/types": "^2.10.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@smithy/fetch-http-handler": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.4.1.tgz", @@ -3548,6 +4002,17 @@ "tslib": "^2.5.0" } }, + "node_modules/@smithy/hash-blob-browser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-2.1.2.tgz", + "integrity": "sha512-f8QHgOVSXeYsc4BLKWdfXRowKa2g9byAkAX5c7Ku89bi9uBquWLEVmKlYXFBlkX562Fkmp2YSeciv+zZuOrIOQ==", + "dependencies": { + "@smithy/chunked-blob-reader": "^2.1.1", + "@smithy/chunked-blob-reader-native": "^2.1.1", + "@smithy/types": "^2.10.0", + "tslib": "^2.5.0" + } + }, "node_modules/@smithy/hash-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.1.1.tgz", @@ -3562,6 +4027,19 @@ "node": ">=14.0.0" } }, + "node_modules/@smithy/hash-stream-node": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-2.1.2.tgz", + "integrity": "sha512-UB6xo+KN3axrLO+MfnWb8mtdeep4vjGUcjYCVFdk9h+OqUb7JYWZZLRcupRPZx28cNBCBEUtc9wVZDI71JDdQA==", + "dependencies": { + "@smithy/types": "^2.10.0", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@smithy/invalid-dependency": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.1.1.tgz", @@ -3582,6 +4060,16 @@ "node": ">=14.0.0" } }, + "node_modules/@smithy/md5-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.1.2.tgz", + "integrity": "sha512-C/FWR5ooyDNDfc1Opx3n0QFO5p4G0gldIbk2VU9mPGnZVTjzXcWM5jUQp33My5UK305tKYpG5/kZdQSNVh+tLw==", + "dependencies": { + "@smithy/types": "^2.10.0", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + } + }, "node_modules/@smithy/middleware-content-length": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.1.1.tgz", @@ -3791,9 +4279,9 @@ } }, "node_modules/@smithy/types": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.9.1.tgz", - "integrity": "sha512-vjXlKNXyprDYDuJ7UW5iobdmyDm6g8dDG+BFUncAg/3XJaN45Gy5RWWWUVgrzIK7S4R1KWgIX5LeJcfvSI24bw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.10.0.tgz", + "integrity": "sha512-QYXQmpIebS8/jYXgyJjCanKZbI4Rr8tBVGBAIdDhA35f025TVjJNW69FJ0TGiDqt+lIGo037YIswq2t2Y1AYZQ==", "dependencies": { "tslib": "^2.5.0" }, @@ -3991,7 +4479,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.1.1.tgz", "integrity": "sha512-kYy6BLJJNif+uqNENtJqWdXcpqo1LS+nj1AfXcDhOpqpSHJSAkVySLyZV9fkmuVO21lzGoxjvd1imGGJHph/IA==", - "dev": true, "dependencies": { "@smithy/abort-controller": "^2.1.1", "@smithy/types": "^2.9.1", diff --git a/package.json b/package.json index f1b52a4..f420a92 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ }, "prettier": "@bifravst/prettier-config", "dependencies": { + "@aws-sdk/client-s3": "3.515.0", "@hello.nrfcloud.com/proto-lwm2m": "2.4.6", "@nordicsemiconductor/from-env": "3.0.1", "@nordicsemiconductor/timestream-helpers": "6.0.2",