s3-zip 3.3.0 → 3.3.1-a0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +29 -0
- package/.github/workflows/test.yml +29 -0
- package/README.md +23 -4
- package/aws_lambda.md +12 -9
- package/package.json +3 -2
- package/s3-zip.js +9 -1
- package/test/test-coverage-missing-lines.js +97 -0
- package/test/test-password-protected-duplicate.js +37 -0
- package/test/test-s3-v2-client-error.js +55 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
version:
|
|
9
|
+
description: 'Version to publish (leave empty to use package.json version)'
|
|
10
|
+
required: false
|
|
11
|
+
type: string
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
id-token: write
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
# Setup .npmrc file to publish to npm
|
|
22
|
+
- uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: '20.x'
|
|
25
|
+
registry-url: 'https://registry.npmjs.org'
|
|
26
|
+
- run: npm i
|
|
27
|
+
- run: npm publish --provenance --access public
|
|
28
|
+
env:
|
|
29
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [master]
|
|
6
|
+
types: [opened, synchronize, reopened]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [16, 18, 20]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: ${{ matrix.node-version }}
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: npm i
|
|
27
|
+
|
|
28
|
+
- name: Run tests
|
|
29
|
+
run: npm test
|
package/README.md
CHANGED
|
@@ -53,17 +53,19 @@ You can also pass a custom S3 client. For example if you want to zip files from
|
|
|
53
53
|
```javascript
|
|
54
54
|
const { S3Client } = require('@aws-sdk/client-s3')
|
|
55
55
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
s3ForcePathStyle: 'true',
|
|
56
|
+
const s3Client = new S3Client({
|
|
57
|
+
region: 'us-east-1',
|
|
59
58
|
endpoint: 'http://localhost:9000',
|
|
59
|
+
forcePathStyle: true
|
|
60
60
|
})
|
|
61
61
|
|
|
62
62
|
s3Zip
|
|
63
|
-
.archive({ s3:
|
|
63
|
+
.archive({ s3: s3Client, bucket: bucket }, folder, [file1, file2])
|
|
64
64
|
.pipe(output)
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
**Note:** When passing a custom S3 client, it must be an AWS SDK v3 client (from `@aws-sdk/client-s3`). AWS SDK v2 clients are not supported and will result in an error.
|
|
68
|
+
|
|
67
69
|
### Zip files with AWS Lambda
|
|
68
70
|
|
|
69
71
|
Example of s3-zip in combination with [AWS Lambda](aws_lambda.md).
|
|
@@ -194,6 +196,23 @@ If you would like a more fancy coverage report:
|
|
|
194
196
|
npm run coverage
|
|
195
197
|
```
|
|
196
198
|
|
|
199
|
+
## Publishing
|
|
200
|
+
|
|
201
|
+
This package is automatically published to NPM when a new release is created on GitHub. The publishing workflow:
|
|
202
|
+
|
|
203
|
+
1. Triggers on GitHub releases
|
|
204
|
+
2. Runs tests to ensure quality
|
|
205
|
+
3. Publishes to NPM using the `NPM_TOKEN` secret
|
|
206
|
+
|
|
207
|
+
### Setup for Maintainers
|
|
208
|
+
|
|
209
|
+
To enable automatic publishing, the repository requires an `NPM_TOKEN` secret to be configured in GitHub:
|
|
210
|
+
|
|
211
|
+
1. Generate an NPM access token with publish permissions
|
|
212
|
+
2. Add it as a repository secret named `NPM_TOKEN` in GitHub Settings > Secrets and variables > Actions
|
|
213
|
+
|
|
214
|
+
The workflow can also be triggered manually from the Actions tab for testing purposes.
|
|
215
|
+
|
|
197
216
|
|
|
198
217
|
|
|
199
218
|
|
package/aws_lambda.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const { Upload } = require("@aws-sdk/lib-storage");
|
|
8
8
|
const { S3 } = require("@aws-sdk/client-s3");
|
|
9
9
|
const s3Zip = require('s3-zip')
|
|
10
|
+
const {Readable} = require('stream')
|
|
10
11
|
|
|
11
12
|
exports.handler = function (event, context) {
|
|
12
13
|
console.log('event', event)
|
|
@@ -21,7 +22,8 @@ exports.handler = function (event, context) {
|
|
|
21
22
|
// Create body stream
|
|
22
23
|
try {
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
+
const writable = s3Zip.archive({ region: region, bucket: bucket}, folder, files)
|
|
26
|
+
const body = Readable.from(writable)
|
|
25
27
|
const zipParams = { params: { Bucket: bucket, Key: folder + zipFileName } }
|
|
26
28
|
const zipFile = new S3(zipParams)
|
|
27
29
|
new Upload({
|
|
@@ -29,15 +31,16 @@ exports.handler = function (event, context) {
|
|
|
29
31
|
params: { Body: body }
|
|
30
32
|
})
|
|
31
33
|
.on('httpUploadProgress', function (evt) { console.log(evt) })
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
.done().then(
|
|
35
|
+
(r) => {
|
|
36
|
+
console.log(r)
|
|
37
|
+
context.succeed(r)
|
|
38
|
+
},
|
|
39
|
+
(e) => {
|
|
40
|
+
console.log('zipFile.upload error', e)
|
|
36
41
|
context.fail(err)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
context.succeed(r)
|
|
40
|
-
})
|
|
42
|
+
}
|
|
43
|
+
)
|
|
41
44
|
|
|
42
45
|
} catch (e) {
|
|
43
46
|
const err = 'catched error: ' + e
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s3-zip",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.1-a0",
|
|
4
4
|
"description": "Download selected files from an Amazon S3 bucket as a zip file.",
|
|
5
5
|
"main": "s3-zip.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/orangewise/s3-zip.git"
|
|
12
|
+
"url": "git+https://github.com/orangewise/s3-zip.git"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"amazon",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"homepage": "https://github.com/orangewise/s3-zip#readme",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"archiver": "^6.0.1",
|
|
29
|
+
"normalize-path": "^3.0.0",
|
|
29
30
|
"s3-files": "^3.0.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
package/s3-zip.js
CHANGED
|
@@ -13,6 +13,11 @@ s3Zip.archive = function (opts, folder, filesS3, filesZip) {
|
|
|
13
13
|
self.debug = opts.debug || false
|
|
14
14
|
|
|
15
15
|
if ('s3' in opts) {
|
|
16
|
+
// Validate that the provided S3 client is compatible with AWS SDK v3
|
|
17
|
+
if (!opts.s3 || typeof opts.s3.send !== 'function') {
|
|
18
|
+
throw new Error('The provided S3 client must be an AWS SDK v3 client with a .send() method. ' +
|
|
19
|
+
'Please use @aws-sdk/client-s3 (v3) instead of aws-sdk (v2).')
|
|
20
|
+
}
|
|
16
21
|
connectionConfig = {
|
|
17
22
|
s3: opts.s3
|
|
18
23
|
}
|
|
@@ -39,7 +44,10 @@ s3Zip.archiveStream = function (stream, filesS3, filesZip) {
|
|
|
39
44
|
const self = this
|
|
40
45
|
const folder = this.folder || ''
|
|
41
46
|
if (this.registerFormat) {
|
|
42
|
-
|
|
47
|
+
// Only register the format if it hasn't been registered before
|
|
48
|
+
if (!archiver.isRegisteredFormat(this.registerFormat)) {
|
|
49
|
+
archiver.registerFormat(this.registerFormat, this.formatModule)
|
|
50
|
+
}
|
|
43
51
|
}
|
|
44
52
|
const archive = archiver(this.format || 'zip', this.archiverOpts || {})
|
|
45
53
|
archive.on('error', function (err) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Test to cover missing lines for full coverage
|
|
2
|
+
|
|
3
|
+
const t = require('tap')
|
|
4
|
+
const Stream = require('stream')
|
|
5
|
+
const sinon = require('sinon')
|
|
6
|
+
const proxyquire = require('proxyquire')
|
|
7
|
+
|
|
8
|
+
// Mock s3Files to avoid real S3 calls
|
|
9
|
+
const mockS3Files = {
|
|
10
|
+
connect: sinon.stub().returns({
|
|
11
|
+
createKeyStream: sinon.stub().returns(new Stream())
|
|
12
|
+
}),
|
|
13
|
+
createFileStream: sinon.stub().returns(new Stream())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const s3Zip = proxyquire('../s3-zip.js', {
|
|
17
|
+
's3-files': mockS3Files
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
t.test('test archive with valid AWS SDK v3 client', function (child) {
|
|
21
|
+
// Mock valid AWS SDK v3 client (has .send method)
|
|
22
|
+
const awsV3Client = {
|
|
23
|
+
send: function (command) {
|
|
24
|
+
return Promise.resolve({ Body: Buffer.from('test data') })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// This should work and cover line 21: connectionConfig = { s3: opts.s3 }
|
|
30
|
+
const archive = s3Zip.archive(
|
|
31
|
+
{ s3: awsV3Client, bucket: 'test-bucket' },
|
|
32
|
+
'folder/',
|
|
33
|
+
['test-file.txt']
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
child.type(archive, 'object', 'Should return archive object')
|
|
37
|
+
child.end()
|
|
38
|
+
} catch (error) {
|
|
39
|
+
child.fail(`Should not throw error with valid SDK v3 client: ${error.message}`)
|
|
40
|
+
child.end()
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
t.test('test archiveStream with debug mode and directory paths', function (child) {
|
|
45
|
+
const rs = new Stream()
|
|
46
|
+
rs.readable = true
|
|
47
|
+
|
|
48
|
+
// Enable debug mode to cover lines 59-60
|
|
49
|
+
s3Zip.debug = true
|
|
50
|
+
|
|
51
|
+
const archive = s3Zip.archiveStream(rs, [])
|
|
52
|
+
|
|
53
|
+
// Emit a directory path to trigger the debug log
|
|
54
|
+
rs.emit('data', { data: Buffer.alloc(0), path: 'test-folder/' })
|
|
55
|
+
rs.emit('end')
|
|
56
|
+
|
|
57
|
+
child.type(archive, 'object', 'Should return archive object')
|
|
58
|
+
child.end()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
t.test('test archiveStream with file not in filesS3 array', function (child) {
|
|
62
|
+
const rs = new Stream()
|
|
63
|
+
rs.readable = true
|
|
64
|
+
|
|
65
|
+
// Test the ternary on line 66 - when file is not found in filesS3 array
|
|
66
|
+
const archive = s3Zip.archiveStream(rs, ['different-file.txt'], ['renamed.txt'])
|
|
67
|
+
|
|
68
|
+
// Emit a file that's not in the filesS3 array
|
|
69
|
+
rs.emit('data', {
|
|
70
|
+
data: Buffer.from('test content'),
|
|
71
|
+
path: 'not-in-list.txt'
|
|
72
|
+
})
|
|
73
|
+
rs.emit('end')
|
|
74
|
+
|
|
75
|
+
child.type(archive, 'object', 'Should return archive object')
|
|
76
|
+
child.end()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
t.test('test archiveStream with debug and archive error', function (child) {
|
|
80
|
+
const rs = new Stream()
|
|
81
|
+
rs.readable = true
|
|
82
|
+
|
|
83
|
+
// Enable debug mode to cover line 54
|
|
84
|
+
s3Zip.debug = true
|
|
85
|
+
|
|
86
|
+
const archive = s3Zip.archiveStream(rs, [])
|
|
87
|
+
|
|
88
|
+
// Force an archive error to trigger the debug log on line 54
|
|
89
|
+
setImmediate(() => {
|
|
90
|
+
archive.emit('error', new Error('test error'))
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
rs.emit('end')
|
|
94
|
+
|
|
95
|
+
child.type(archive, 'object', 'Should return archive object')
|
|
96
|
+
child.end()
|
|
97
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const s3Zip = require('../s3-zip.js')
|
|
2
|
+
const t = require('tap')
|
|
3
|
+
const archiverZipEncryptable = require('archiver-zip-encryptable')
|
|
4
|
+
|
|
5
|
+
t.test('test duplicate format registration does not error', function (child) {
|
|
6
|
+
// First registration should work
|
|
7
|
+
s3Zip.setRegisterFormatOptions('zip-encryptable', archiverZipEncryptable)
|
|
8
|
+
|
|
9
|
+
// Try to register the format via archiveStream simulation
|
|
10
|
+
const mockStream = {
|
|
11
|
+
on: function (event, callback) {
|
|
12
|
+
if (event === 'end') {
|
|
13
|
+
setTimeout(callback, 10) // Simulate async behavior
|
|
14
|
+
} else if (event === 'data') {
|
|
15
|
+
// Don't emit any data
|
|
16
|
+
} else if (event === 'error') {
|
|
17
|
+
// Don't emit any errors
|
|
18
|
+
}
|
|
19
|
+
return this
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// First call to archiveStream (should register format)
|
|
25
|
+
const archive1 = s3Zip.archiveStream(mockStream, [], [])
|
|
26
|
+
child.ok(archive1, 'First archiveStream call succeeded')
|
|
27
|
+
|
|
28
|
+
// Second call to archiveStream (should NOT fail due to duplicate registration)
|
|
29
|
+
const archive2 = s3Zip.archiveStream(mockStream, [], [])
|
|
30
|
+
child.ok(archive2, 'Second archiveStream call succeeded')
|
|
31
|
+
|
|
32
|
+
child.end()
|
|
33
|
+
} catch (err) {
|
|
34
|
+
child.fail(`archiveStream calls failed: ${err.message}`)
|
|
35
|
+
child.end()
|
|
36
|
+
}
|
|
37
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Test s3-zip with AWS SDK v2 client which should produce clear error
|
|
2
|
+
|
|
3
|
+
const s3Zip = require('../s3-zip.js')
|
|
4
|
+
const t = require('tap')
|
|
5
|
+
|
|
6
|
+
t.test('test s3-zip with AWS SDK v2 client should fail with clear error', function (child) {
|
|
7
|
+
// Mock AWS SDK v2 client (no .send method)
|
|
8
|
+
const awsV2Client = {
|
|
9
|
+
getObject: function (params) {
|
|
10
|
+
return {
|
|
11
|
+
promise: function () {
|
|
12
|
+
return Promise.resolve({ Body: Buffer.from('test data') })
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// This should fail with a clear error message about AWS SDK compatibility
|
|
20
|
+
s3Zip.archive(
|
|
21
|
+
{ s3: awsV2Client, bucket: 'test-bucket' },
|
|
22
|
+
'folder/',
|
|
23
|
+
['test-file.txt']
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// If we get here without an error, the test failed
|
|
27
|
+
child.fail('Expected an error about AWS SDK compatibility but none was thrown')
|
|
28
|
+
child.end()
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// We should get a clear error about AWS SDK version compatibility
|
|
31
|
+
child.ok(error.message.includes('AWS SDK v3'), 'Should get clear error about AWS SDK v3 requirement')
|
|
32
|
+
child.ok(error.message.includes('@aws-sdk/client-s3'), 'Should mention the correct package')
|
|
33
|
+
child.end()
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
t.test('test s3-zip with null s3 client should fail with clear error', function (child) {
|
|
38
|
+
try {
|
|
39
|
+
// This should fail with a clear error message about AWS SDK compatibility
|
|
40
|
+
s3Zip.archive(
|
|
41
|
+
{ s3: null, bucket: 'test-bucket' },
|
|
42
|
+
'folder/',
|
|
43
|
+
['test-file.txt']
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// If we get here without an error, the test failed
|
|
47
|
+
child.fail('Expected an error about AWS SDK compatibility but none was thrown')
|
|
48
|
+
child.end()
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// We should get a clear error about AWS SDK version compatibility
|
|
51
|
+
child.ok(error.message.includes('AWS SDK v3'), 'Should get clear error about AWS SDK v3 requirement')
|
|
52
|
+
child.ok(error.message.includes('.send() method'), 'Should mention the .send() method requirement')
|
|
53
|
+
child.end()
|
|
54
|
+
}
|
|
55
|
+
})
|