Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](http://keepachangelog.com/).

## Version 4.0.0 BREAKING-CHANGE t.b.d

### Changed

- Cloud storage SDKs (`@aws-sdk/client-s3`, `@aws-sdk/lib-storage`, `@azure/storage-blob`, `@google-cloud/storage`) are now optional peer dependencies. Install only the SDK(s) for the provider you use (e.g. `npm install @aws-sdk/client-s3 @aws-sdk/lib-storage` for AWS S3). A clear error message with the exact install command is shown if a required SDK is missing at runtime.

## Version 3.12.2

### Fixed
Expand Down
47 changes: 40 additions & 7 deletions lib/mtx/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -619,11 +619,22 @@ cds.on("listening", async () => {
* @param {string} tenant - Tenant ID
*/
const _cleanupAWSS3Objects = async (creds, tenant) => {
const {
S3Client,
paginateListObjectsV2,
DeleteObjectsCommand,
} = require("@aws-sdk/client-s3")
let S3Client, paginateListObjectsV2, DeleteObjectsCommand
try {
;({
S3Client,
paginateListObjectsV2,
DeleteObjectsCommand,
} = require("@aws-sdk/client-s3"))
} catch (e) {
if (e.code === "MODULE_NOT_FOUND")
throw new Error(
'Cleanup of AWS S3 objects requires "@aws-sdk/client-s3" to be installed.\n' +
"Please run: npm install @aws-sdk/client-s3",
{ cause: e },
)
throw e
}
const client = new S3Client({
region: creds.region,
credentials: {
Expand Down Expand Up @@ -682,7 +693,18 @@ const _cleanupAWSS3Objects = async (creds, tenant) => {
* @param {string} tenant - Tenant ID
*/
const _cleanupAzureBlobObjects = async (creds, tenant) => {
const { BlobServiceClient } = require("@azure/storage-blob")
let BlobServiceClient
try {
;({ BlobServiceClient } = require("@azure/storage-blob"))
} catch (e) {
if (e.code === "MODULE_NOT_FOUND")
throw new Error(
'Cleanup of Azure Blob objects requires "@azure/storage-blob" to be installed.\n' +
"Please run: npm install @azure/storage-blob",
{ cause: e },
)
throw e
}
const blobServiceClient = new BlobServiceClient(
`${creds.container_uri}?${creds.sas_token}`,
)
Expand Down Expand Up @@ -721,7 +743,18 @@ const _cleanupAzureBlobObjects = async (creds, tenant) => {
* @param {string} tenant - Tenant ID
*/
const _cleanupGoogleCloudObjects = async (creds, tenant) => {
const { Storage } = require("@google-cloud/storage")
let Storage
try {
;({ Storage } = require("@google-cloud/storage"))
} catch (e) {
if (e.code === "MODULE_NOT_FOUND")
throw new Error(
'Cleanup of Google Cloud Storage objects requires "@google-cloud/storage" to be installed.\n' +
"Please run: npm install @google-cloud/storage",
{ cause: e },
)
throw e
}
const storageClient = new Storage({
projectId: creds.project_id,
credentials: creds.service_account,
Expand Down
24 changes: 21 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
"prepare": "husky"
},
"dependencies": {
"axios": "^1.13.5"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.993.0",
"@aws-sdk/lib-storage": "^3.993.0",
"@azure/storage-blob": "^12.31.0",
"@google-cloud/storage": "^7.19.0",
"axios": "^1.13.5"
},
"devDependencies": {
"@cap-js/cds-test": ">=0",
"@cap-js/cds-types": "^0.16.0",
"@cap-js/hana": "^2.7.0",
Expand All @@ -39,8 +39,26 @@
"husky": "^9.1.7"
},
"peerDependencies": {
"@aws-sdk/client-s3": "^3",
"@aws-sdk/lib-storage": "^3",
"@azure/storage-blob": "^12",
"@google-cloud/storage": "^7",
"@sap/cds": ">=8"
},
"peerDependenciesMeta": {
"@aws-sdk/client-s3": {
"optional": true
},
"@aws-sdk/lib-storage": {
"optional": true
},
"@azure/storage-blob": {
"optional": true
},
"@google-cloud/storage": {
"optional": true
}
},
"engines": {
"node": ">=18.0.0"
},
Expand Down
24 changes: 20 additions & 4 deletions srv/attachments/aws-s3.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
const {
S3Client,
let S3Client,
GetObjectCommand,
DeleteObjectCommand,
HeadObjectCommand,
CopyObjectCommand,
} = require("@aws-sdk/client-s3")
const { Upload } = require("@aws-sdk/lib-storage")
Upload
try {
;({
S3Client,
GetObjectCommand,
DeleteObjectCommand,
HeadObjectCommand,
CopyObjectCommand,
} = require("@aws-sdk/client-s3"))
;({ Upload } = require("@aws-sdk/lib-storage"))
} catch (e) {
if (e.code === "MODULE_NOT_FOUND")
throw new Error(
'The AWS S3 storage provider requires "@aws-sdk/client-s3" and "@aws-sdk/lib-storage" to be installed.\n' +
"Please run: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
{ cause: e },
)
throw e
}
const cds = require("@sap/cds")
const LOG = cds.log("attachments")
const utils = require("../../lib/helper")
Expand Down
13 changes: 12 additions & 1 deletion srv/attachments/azure-blob-storage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
const { BlobServiceClient } = require("@azure/storage-blob")
let BlobServiceClient
try {
;({ BlobServiceClient } = require("@azure/storage-blob"))
} catch (e) {
if (e.code === "MODULE_NOT_FOUND")
throw new Error(
'The Azure Blob Storage provider requires "@azure/storage-blob" to be installed.\n' +
"Please run: npm install @azure/storage-blob",
{ cause: e },
)
throw e
}
const { AbortController } = require("abort-controller")
const cds = require("@sap/cds")
const LOG = cds.log("attachments")
Expand Down
13 changes: 12 additions & 1 deletion srv/attachments/gcp.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
const { Storage } = require("@google-cloud/storage")
let Storage
try {
;({ Storage } = require("@google-cloud/storage"))
} catch (e) {
if (e.code === "MODULE_NOT_FOUND")
throw new Error(
'The Google Cloud Platform storage provider requires "@google-cloud/storage" to be installed.\n' +
"Please run: npm install @google-cloud/storage",
{ cause: e },
)
throw e
}
const cds = require("@sap/cds")
const LOG = cds.log("attachments")
const utils = require("../../lib/helper")
Expand Down
75 changes: 75 additions & 0 deletions tests/unit/optionalDeps.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
describe("optional peer dependency errors", () => {
const makeNotFound = (pkg) => {
const err = new Error(`Cannot find module '${pkg}'`)
err.code = "MODULE_NOT_FOUND"
return err
}

test("aws-s3 throws helpful error when @aws-sdk/client-s3 is missing", () => {
jest.isolateModules(() => {
jest.doMock("@aws-sdk/client-s3", () => {
throw makeNotFound("@aws-sdk/client-s3")
})
expect(() => require("../../srv/attachments/aws-s3")).toThrow(
"npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
)
})
Comment thread
stefanrudi marked this conversation as resolved.
})

test("aws-s3 throws helpful error when @aws-sdk/lib-storage is missing", () => {
jest.isolateModules(() => {
jest.doMock("@aws-sdk/lib-storage", () => {
throw makeNotFound("@aws-sdk/lib-storage")
})
expect(() => require("../../srv/attachments/aws-s3")).toThrow(
"npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
)
})
})
Comment thread
stefanrudi marked this conversation as resolved.

test("azure-blob-storage throws helpful error when @azure/storage-blob is missing", () => {
jest.isolateModules(() => {
jest.doMock("@azure/storage-blob", () => {
throw makeNotFound("@azure/storage-blob")
})
expect(() => require("../../srv/attachments/azure-blob-storage")).toThrow(
"npm install @azure/storage-blob",
)
})
})
Comment thread
stefanrudi marked this conversation as resolved.

test("gcp throws helpful error when @google-cloud/storage is missing", () => {
jest.isolateModules(() => {
jest.doMock("@google-cloud/storage", () => {
throw makeNotFound("@google-cloud/storage")
})
expect(() => require("../../srv/attachments/gcp")).toThrow(
"npm install @google-cloud/storage",
)
})
})

test("aws-s3 loads successfully when SDKs are present", () => {
jest.isolateModules(() => {
jest.dontMock("@aws-sdk/client-s3")
jest.dontMock("@aws-sdk/lib-storage")
expect(() => require("../../srv/attachments/aws-s3")).not.toThrow()
})
})

test("azure-blob-storage loads successfully when SDK is present", () => {
jest.isolateModules(() => {
jest.dontMock("@azure/storage-blob")
expect(() =>
require("../../srv/attachments/azure-blob-storage"),
).not.toThrow()
})
})

test("gcp loads successfully when SDK is present", () => {
jest.isolateModules(() => {
jest.dontMock("@google-cloud/storage")
expect(() => require("../../srv/attachments/gcp")).not.toThrow()
})
})
})
Loading