From 740d005e3f0585e1ecb98f2b42025f8729d2c315 Mon Sep 17 00:00:00 2001 From: Nidhi Nandwani Date: Wed, 24 Jun 2026 05:59:44 +0000 Subject: [PATCH 1/3] feat(storage): support delete source objects on compose Updates compose file sample to support deleting source objects optionally. Fixes b/441557254 [Generated-by: AI] --- storage/composeFile.js | 72 +++++++++++++++++++++++++++++++ storage/system-test/files.test.js | 48 +++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 storage/composeFile.js diff --git a/storage/composeFile.js b/storage/composeFile.js new file mode 100644 index 0000000000..0121f07d21 --- /dev/null +++ b/storage/composeFile.js @@ -0,0 +1,72 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +/** + * This application demonstrates how to perform basic operations on bucket and + * file Access Control Lists with the Google Cloud Storage API. + * + * For more information, see the README.md under /storage and the documentation + * at https://cloud.google.com/storage/docs. + */ + +function main( + bucketName = 'my-bucket', + firstFileName = 'first.txt', + secondFileName = 'second.txt', + destinationFileName = 'destination.txt', + deleteSourceObjects = false +) { + // [START storage_compose_file] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const bucketName = 'your-unique-bucket-name'; + // const firstFileName = 'first.txt'; + // const secondFileName = 'second.txt'; + // const destinationFileName = 'destination.txt'; + // const deleteSourceObjects = false; + + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + async function composeFile() { + const bucket = storage.bucket(bucketName); + const sources = [firstFileName, secondFileName]; + + await bucket.combine(sources, destinationFileName); + + console.log( + `New composite file ${destinationFileName} was created by combining ${firstFileName} and ${secondFileName}` + ); + + if (String(deleteSourceObjects) === 'true') { + await Promise.all( + sources.map(async source => { + await bucket.file(source).delete(); + }) + ); + console.log(`Deleted source objects: ${firstFileName}, ${secondFileName}`); + } + } + + composeFile().catch(console.error); + // [END storage_compose_file] +} + +main(...process.argv.slice(2)); diff --git a/storage/system-test/files.test.js b/storage/system-test/files.test.js index a0147fa8a0..8d2ed77c14 100644 --- a/storage/system-test/files.test.js +++ b/storage/system-test/files.test.js @@ -146,6 +146,54 @@ describe('file', () => { await bucket.file(noContextFileName).delete(); }); }); + + describe('compose file', () => { + it('should combine multiple files into one new file and optionally delete source objects', async () => { + const firstFileName = 'first.txt'; + const secondFileName = 'second.txt'; + const destinationFileName = 'destination.txt'; + + // upload test.txt twice + await bucket.upload(filePath, {destination: firstFileName}); + await bucket.upload(filePath, {destination: secondFileName}); + + // Test with deleteSourceObjects = false + let output = execSync( + `node composeFile.js ${bucketName} ${firstFileName} ${secondFileName} ${destinationFileName} false` + ); + assert.include( + output, + `New composite file ${destinationFileName} was created by combining ${firstFileName} and ${secondFileName}` + ); + assert.notInclude(output, 'Deleted source objects'); + + let [destExists] = await bucket.file(destinationFileName).exists(); + assert.strictEqual(destExists, true); + let [firstExists] = await bucket.file(firstFileName).exists(); + assert.strictEqual(firstExists, true); + let [secondExists] = await bucket.file(secondFileName).exists(); + assert.strictEqual(secondExists, true); + + await bucket.file(destinationFileName).delete(); + + // Test with deleteSourceObjects = true + output = execSync( + `node composeFile.js ${bucketName} ${firstFileName} ${secondFileName} ${destinationFileName} true` + ); + assert.include( + output, + `New composite file ${destinationFileName} was created by combining ${firstFileName} and ${secondFileName}` + ); + assert.include(output, `Deleted source objects: ${firstFileName}, ${secondFileName}`); + + [destExists] = await bucket.file(destinationFileName).exists(); + assert.strictEqual(destExists, true); + [firstExists] = await bucket.file(firstFileName).exists(); + assert.strictEqual(firstExists, false); + [secondExists] = await bucket.file(secondFileName).exists(); + assert.strictEqual(secondExists, false); + }); + }); }); function generateName() { From 8e7c7c4b3595279bef456761044292c7a5a13edb Mon Sep 17 00:00:00 2001 From: Nidhi Nandwani Date: Wed, 24 Jun 2026 09:41:30 +0000 Subject: [PATCH 2/3] chore: fix prettier linting issues in composeFile sample --- storage/composeFile.js | 4 +++- storage/system-test/files.test.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/storage/composeFile.js b/storage/composeFile.js index 0121f07d21..5d1f0435c5 100644 --- a/storage/composeFile.js +++ b/storage/composeFile.js @@ -61,7 +61,9 @@ function main( await bucket.file(source).delete(); }) ); - console.log(`Deleted source objects: ${firstFileName}, ${secondFileName}`); + console.log( + `Deleted source objects: ${firstFileName}, ${secondFileName}` + ); } } diff --git a/storage/system-test/files.test.js b/storage/system-test/files.test.js index 8d2ed77c14..f3851c4b21 100644 --- a/storage/system-test/files.test.js +++ b/storage/system-test/files.test.js @@ -184,7 +184,10 @@ describe('file', () => { output, `New composite file ${destinationFileName} was created by combining ${firstFileName} and ${secondFileName}` ); - assert.include(output, `Deleted source objects: ${firstFileName}, ${secondFileName}`); + assert.include( + output, + `Deleted source objects: ${firstFileName}, ${secondFileName}` + ); [destExists] = await bucket.file(destinationFileName).exists(); assert.strictEqual(destExists, true); From ec55a0144b9b6ff121317f357ed741907f88ef18 Mon Sep 17 00:00:00 2001 From: Nidhi Nandwani Date: Wed, 24 Jun 2026 10:02:27 +0000 Subject: [PATCH 3/3] fix(storage): resolve PR comments for composeFile [Generated-by: AI] --- storage/composeFile.js | 10 ++++------ storage/system-test/files.test.js | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/storage/composeFile.js b/storage/composeFile.js index 5d1f0435c5..cf5a43c4b8 100644 --- a/storage/composeFile.js +++ b/storage/composeFile.js @@ -15,8 +15,8 @@ 'use strict'; /** - * This application demonstrates how to perform basic operations on bucket and - * file Access Control Lists with the Google Cloud Storage API. + * This application demonstrates how to compose/combine multiple files into a + * single destination file with the Google Cloud Storage API. * * For more information, see the README.md under /storage and the documentation * at https://cloud.google.com/storage/docs. @@ -52,7 +52,7 @@ function main( await bucket.combine(sources, destinationFileName); console.log( - `New composite file ${destinationFileName} was created by combining ${firstFileName} and ${secondFileName}` + `New composite file ${destinationFileName} was created by combining ${sources.join(', ')}` ); if (String(deleteSourceObjects) === 'true') { @@ -61,9 +61,7 @@ function main( await bucket.file(source).delete(); }) ); - console.log( - `Deleted source objects: ${firstFileName}, ${secondFileName}` - ); + console.log(`Deleted source objects: ${sources.join(', ')}`); } } diff --git a/storage/system-test/files.test.js b/storage/system-test/files.test.js index f3851c4b21..c5105c65aa 100644 --- a/storage/system-test/files.test.js +++ b/storage/system-test/files.test.js @@ -163,7 +163,7 @@ describe('file', () => { ); assert.include( output, - `New composite file ${destinationFileName} was created by combining ${firstFileName} and ${secondFileName}` + `New composite file ${destinationFileName} was created by combining ${firstFileName}, ${secondFileName}` ); assert.notInclude(output, 'Deleted source objects'); @@ -182,7 +182,7 @@ describe('file', () => { ); assert.include( output, - `New composite file ${destinationFileName} was created by combining ${firstFileName} and ${secondFileName}` + `New composite file ${destinationFileName} was created by combining ${firstFileName}, ${secondFileName}` ); assert.include( output,