@_linked/s3 1.1.0 → 1.2.0
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/pr.yml +18 -0
- package/.github/workflows/publish.yml +16 -63
- package/CHANGELOG.md +49 -0
- package/README.md +83 -0
- package/jest.config.cjs +23 -0
- package/lib/esm/shapes/S3Bucket.d.ts +10 -2
- package/lib/esm/shapes/S3Bucket.js +58 -57
- package/lib/esm/shapes/S3Bucket.js.map +1 -1
- package/lib/esm/shapes/S3FileStore.d.ts +13 -2
- package/lib/esm/shapes/S3FileStore.js +53 -11
- package/lib/esm/shapes/S3FileStore.js.map +1 -1
- package/package.json +11 -4
- package/tsconfig-esm.json +7 -1
- package/tsconfig-tests.json +11 -0
- package/.github/workflows/changeset-check.yml +0 -44
- package/.github/workflows/ci.yml +0 -43
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: PR
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
checks:
|
|
10
|
+
uses: linked-fw/.github/.github/workflows/pr.yml@v1
|
|
11
|
+
# The changeset reminder posts a PR comment. A reusable workflow only holds what its
|
|
12
|
+
# caller granted, and the org default is read-only.
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
pull-requests: write
|
|
16
|
+
with:
|
|
17
|
+
require-tests: false
|
|
18
|
+
run-e2e: false
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Calls the shared workflow in linked-fw/.github.
|
|
2
|
+
#
|
|
3
|
+
# This FILENAME is what npm's trusted-publisher check validates — not the shared workflow's.
|
|
4
|
+
# Renaming this file invalidates this package's trusted publisher, and the only symptom is
|
|
5
|
+
# that releases quietly start needing manual approval again.
|
|
1
6
|
name: Publish
|
|
2
7
|
|
|
3
8
|
on:
|
|
@@ -9,69 +14,17 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|
|
9
14
|
|
|
10
15
|
jobs:
|
|
11
16
|
release:
|
|
12
|
-
|
|
13
|
-
runs-on: ubuntu-latest
|
|
17
|
+
uses: linked-fw/.github/.github/workflows/publish.yml@v1
|
|
14
18
|
permissions:
|
|
15
19
|
contents: write
|
|
16
20
|
pull-requests: write
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- name: Checkout
|
|
29
|
-
uses: actions/checkout@v4
|
|
30
|
-
with:
|
|
31
|
-
token: ${{ steps.app-token.outputs.token }}
|
|
32
|
-
|
|
33
|
-
- name: Setup Node.js
|
|
34
|
-
uses: actions/setup-node@v4
|
|
35
|
-
with:
|
|
36
|
-
node-version: 22.16.0
|
|
37
|
-
registry-url: 'https://registry.npmjs.org'
|
|
38
|
-
|
|
39
|
-
- name: Install dependencies
|
|
40
|
-
run: npm ci --legacy-peer-deps
|
|
41
|
-
|
|
42
|
-
- name: Build
|
|
43
|
-
run: npm run build
|
|
44
|
-
|
|
45
|
-
- name: Create release Pull Request or Publish
|
|
46
|
-
id: changesets
|
|
47
|
-
uses: changesets/action@v1
|
|
48
|
-
with:
|
|
49
|
-
publish: npx changeset publish
|
|
50
|
-
title: 'chore: release'
|
|
51
|
-
commit: 'chore: release'
|
|
52
|
-
env:
|
|
53
|
-
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
54
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
55
|
-
NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
56
|
-
|
|
57
|
-
# When changesets opened/updated the version PR (rather than publishing), merge it so the
|
|
58
|
-
# release ships without a manual click. App token → the merge's push re-triggers this
|
|
59
|
-
# workflow, which then runs `changeset publish`. DIRECT-merge (not `--auto`): GitHub
|
|
60
|
-
# auto-merge ignores branch-protection review-bypass allowances; main is check-only so a
|
|
61
|
-
# direct merge lands once the required check is green — retry until then.
|
|
62
|
-
- name: Merge the version PR to publish
|
|
63
|
-
if: steps.changesets.outputs.hasChangesets == 'true' && steps.changesets.outputs.published != 'true'
|
|
64
|
-
env:
|
|
65
|
-
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
66
|
-
run: |
|
|
67
|
-
pr=$(gh pr list --base main --head changeset-release/main --state open --json number --jq '.[0].number // empty')
|
|
68
|
-
if [ -z "$pr" ]; then echo "No open version PR (nothing to merge)."; exit 0; fi
|
|
69
|
-
for i in $(seq 1 40); do
|
|
70
|
-
if gh pr merge "$pr" --merge 2>/tmp/merge_err; then
|
|
71
|
-
echo "Merged version PR #$pr on attempt $i."
|
|
72
|
-
exit 0
|
|
73
|
-
fi
|
|
74
|
-
echo "Attempt $i/40: version PR #$pr not mergeable yet ($(tr -d '\n' < /tmp/merge_err)); waiting for the required check…"
|
|
75
|
-
sleep 15
|
|
76
|
-
done
|
|
77
|
-
echo "::warning::Version PR #$pr not merged after retries — merge it manually."
|
|
21
|
+
# Needed HERE as well as in the shared workflow: a reusable workflow cannot widen the
|
|
22
|
+
# permissions its caller was granted, and without this npm never gets an id-token and
|
|
23
|
+
# every release silently falls back to the approval queue.
|
|
24
|
+
id-token: write
|
|
25
|
+
# Named rather than `inherit`: `inherit` forwards everything this repo can see, which is
|
|
26
|
+
# how a community repo would end up forwarding a first-party token.
|
|
27
|
+
secrets:
|
|
28
|
+
NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
29
|
+
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
|
30
|
+
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# @\_linked/s3
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#10](https://github.com/linked-fw/s3/pull/10) [`d3b374e`](https://github.com/linked-fw/s3/commit/d3b374e1f764daac87c52e706a8e9e12ba6087c2) Thanks [@flyon](https://github.com/flyon)! - Cache-Control and metadata on save, and `statFile` via HeadObject.
|
|
8
|
+
|
|
9
|
+
`S3FileStore.saveFile` now takes core's `SaveFileOptions` as its third argument
|
|
10
|
+
(`{mimeType, cacheControl, metadata, preventDuplicates}`) and sends
|
|
11
|
+
`CacheControl` and `Metadata` to S3 when they are given. A plain string is still
|
|
12
|
+
read as the positional mime type, with the positional `preventDuplicates`, so
|
|
13
|
+
existing callers are unaffected.
|
|
14
|
+
|
|
15
|
+
`S3FileStore.statFile(filePath)` is new: it heads the same key `saveFile` would
|
|
16
|
+
write and returns `{size, sha256, etag}`, or `null` when the object is missing.
|
|
17
|
+
`sha256` comes from `ChecksumSHA256` and is left undefined when the object was
|
|
18
|
+
stored without a checksum — the `ETag` is not a content hash and is never
|
|
19
|
+
reported as one.
|
|
20
|
+
|
|
21
|
+
`preventDuplicates` keeps its old meaning. Core reports an unspecified value as
|
|
22
|
+
`undefined` and never invents a default, so `S3FileStore` now applies its own:
|
|
23
|
+
unspecified overwrites, exactly as before; only an explicit `true` renames.
|
|
24
|
+
|
|
25
|
+
`S3Bucket.headObject(key)` is new and public: the HeadObject output, or `null`
|
|
26
|
+
when the object does not exist, rethrowing every other error. It handles both
|
|
27
|
+
the SDK's `NotFound` and the bare 404 that some S3-compatible endpoints return,
|
|
28
|
+
and it is now the single existence check in the package — `ensureKeyExists` uses
|
|
29
|
+
it instead of hand-rolling its own detection, so seeding a key works against
|
|
30
|
+
those endpoints too. It also fixes `new S3Bucket(name)` passing `null` into the
|
|
31
|
+
`Shape` constructor, which throws with recent `@_linked/core`.
|
|
32
|
+
|
|
33
|
+
`S3FileStore.fileExists` uses HeadObject instead of GetObject: checking whether
|
|
34
|
+
a path is taken no longer downloads the object that is about to be overwritten,
|
|
35
|
+
and a 0-byte object is now correctly reported as existing (GetObject returned an
|
|
36
|
+
empty string, which read as "absent").
|
|
37
|
+
|
|
38
|
+
`statFile` reports `size: 0` when the endpoint omits `ContentLength`, so a
|
|
39
|
+
verify-after-upload size check fails closed rather than seeing `undefined`.
|
|
40
|
+
|
|
41
|
+
Requires the `SaveFileOptions`/`FileStat`/`normalizeSaveFileOptions` API added in
|
|
42
|
+
`@_linked/core` (linked-fw/core#230), hence the `@_linked/core` bump to
|
|
43
|
+
`^2.20.0` — `normalizeSaveFileOptions` is a runtime export that no published
|
|
44
|
+
2.x has.
|
|
45
|
+
|
|
46
|
+
## 1.1.1
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- [#12](https://github.com/linked-fw/s3/pull/12) [`9b5150c`](https://github.com/linked-fw/s3/commit/9b5150c57b87609ed6f456fd9fa80b60dbb76d66) Thanks [@flyon](https://github.com/flyon)! - Point `repository.url` at the linked-fw organisation, so npm provenance verification matches the repository that builds the package.
|
|
51
|
+
|
|
3
52
|
## 1.1.0
|
|
4
53
|
|
|
5
54
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -29,6 +29,89 @@ LinkedFileStorage.setDefaultStore(s3Files);
|
|
|
29
29
|
// ...
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Saving files
|
|
33
|
+
|
|
34
|
+
`saveFile` takes core's `SaveFileOptions` as its third argument:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
await s3Files.saveFile('img/hero.webp', bytes, {
|
|
38
|
+
mimeType: 'image/webp',
|
|
39
|
+
cacheControl: 'public, max-age=31536000, immutable',
|
|
40
|
+
metadata: { release: '1.0.0' },
|
|
41
|
+
preventDuplicates: false,
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`cacheControl` and `metadata` are only sent to S3 when you give them, so the
|
|
46
|
+
bucket's own defaults keep applying otherwise. The old positional form still
|
|
47
|
+
works — a plain string is read as the mime type, with `preventDuplicates`
|
|
48
|
+
following it:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
await s3Files.saveFile('img/hero.webp', bytes, 'image/webp', true);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### S3FileStore overwrites by default
|
|
55
|
+
|
|
56
|
+
`preventDuplicates` left unspecified means `false` **here**. Saving to a path
|
|
57
|
+
that is already taken replaces the object; only an explicit `true` renames the
|
|
58
|
+
file (to `<dir>/<timestamp>-<name>`).
|
|
59
|
+
|
|
60
|
+
Core deliberately supplies no default: an unspecified `preventDuplicates`
|
|
61
|
+
travels through `LinkedFileStorage` as `undefined` and each store decides for
|
|
62
|
+
itself. Do not assume this store's choice holds elsewhere — `LocalFileStore` in
|
|
63
|
+
`@_linked/server`, for instance, suffixes by default.
|
|
64
|
+
|
|
65
|
+
## Reading file metadata
|
|
66
|
+
|
|
67
|
+
`statFile(path)` reads `HeadObject` on the same key `saveFile` would write, and
|
|
68
|
+
returns `{size, sha256?, etag?}` — or `null` when the object does not exist:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const stat = await s3Files.statFile('img/hero.webp');
|
|
72
|
+
if (stat === null) throw new Error('upload went missing');
|
|
73
|
+
|
|
74
|
+
if (stat.size !== bytes.length) throw new Error('size mismatch');
|
|
75
|
+
|
|
76
|
+
if (stat.sha256) {
|
|
77
|
+
// only verifiable when the object carries an S3 checksum
|
|
78
|
+
if (stat.sha256 !== expectedSha256) throw new Error('content mismatch');
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `sha256` comes from the object's `ChecksumSHA256` and is therefore present
|
|
83
|
+
**only when the object was uploaded with an S3 checksum**. Without one, treat
|
|
84
|
+
the file as "cannot verify".
|
|
85
|
+
- `etag` is **not** a content hash. It is MD5 at best, and something else
|
|
86
|
+
entirely for multipart uploads. Never compare it against a sha256 or any other
|
|
87
|
+
digest you computed yourself.
|
|
88
|
+
- `size` is reported as `0` when the endpoint omits `ContentLength`, so a
|
|
89
|
+
verify-after-upload size check fails closed.
|
|
90
|
+
|
|
91
|
+
### S3Bucket.headObject
|
|
92
|
+
|
|
93
|
+
`S3Bucket.headObject(key)` is public and is the single existence check in this
|
|
94
|
+
package. It returns the `HeadObjectCommandOutput`, or `null` when the object is
|
|
95
|
+
absent — handling both the SDK's `NotFound` and the bare 404 some S3-compatible
|
|
96
|
+
endpoints return — and rethrows every other error.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const head = await bucket.headObject('img/hero.webp');
|
|
100
|
+
if (head === null) {
|
|
101
|
+
// not there
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`fileExists` is built on it, so checking whether a path is taken no longer
|
|
106
|
+
downloads the object that is about to be overwritten.
|
|
107
|
+
|
|
108
|
+
## Registering the store
|
|
109
|
+
|
|
110
|
+
Registering stores by purpose (`LinkedFileStorage.setStore`, `getStore`,
|
|
111
|
+
`registerPurpose`) is core's concern, not this package's — see
|
|
112
|
+
[`@_linked/core`](https://github.com/linked-cm/core) and its
|
|
113
|
+
`utils/LinkedFileStorage` / `interfaces/IFileStore`.
|
|
114
|
+
|
|
32
115
|
## Environment Variables
|
|
33
116
|
|
|
34
117
|
Unless stated otherwise, the following environment variables are required:
|
package/jest.config.cjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest/presets/default-esm',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
rootDir: '.',
|
|
6
|
+
roots: ['<rootDir>/src/tests'],
|
|
7
|
+
testMatch: ['<rootDir>/src/tests/*.test.ts'],
|
|
8
|
+
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
|
9
|
+
transform: {
|
|
10
|
+
'^.+\\.(ts|tsx)$': [
|
|
11
|
+
'ts-jest',
|
|
12
|
+
{
|
|
13
|
+
useESM: true,
|
|
14
|
+
tsconfig: '<rootDir>/tsconfig-tests.json',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
// Strip the `.js` extension from relative ESM imports so ts-jest resolves the
|
|
19
|
+
// `.ts` source (NodeNext-style specifiers carry `.js`; jest maps back to src).
|
|
20
|
+
moduleNameMapper: {
|
|
21
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeleteObjectCommandInput, S3Client } from '@aws-sdk/client-s3';
|
|
1
|
+
import { DeleteObjectCommandInput, HeadObjectCommandOutput, S3Client } from '@aws-sdk/client-s3';
|
|
2
2
|
import { PutObjectCommandInput, PutObjectCommandOutput } from '@aws-sdk/client-s3/dist-types/commands/PutObjectCommand';
|
|
3
3
|
import { StreamingBlobPayloadInputTypes } from '@smithy/types';
|
|
4
4
|
import { Shape } from '@_linked/core/shapes/Shape';
|
|
@@ -62,6 +62,15 @@ export declare class S3Bucket extends Shape {
|
|
|
62
62
|
* @returns The contents of the object as a string
|
|
63
63
|
*/
|
|
64
64
|
getObject(key: string): Promise<string>;
|
|
65
|
+
/**
|
|
66
|
+
* Read an object's metadata without downloading its contents.
|
|
67
|
+
*
|
|
68
|
+
* @param key The key of the object to head
|
|
69
|
+
* @returns The HeadObject response, or null when the object does not exist.
|
|
70
|
+
* Any other error (permissions, network, no such bucket) is rethrown, so a
|
|
71
|
+
* caller cannot mistake a broken connection for a missing file.
|
|
72
|
+
*/
|
|
73
|
+
headObject(key: string): Promise<HeadObjectCommandOutput | null>;
|
|
65
74
|
/**
|
|
66
75
|
* Ensure that the given key exists in the bucket, the file contents
|
|
67
76
|
* will be an empty json object.
|
|
@@ -71,7 +80,6 @@ export declare class S3Bucket extends Shape {
|
|
|
71
80
|
*/
|
|
72
81
|
ensureKeyExists(key: string): Promise<string | void>;
|
|
73
82
|
private _ensureKeyExists;
|
|
74
|
-
private keyExists;
|
|
75
83
|
/**
|
|
76
84
|
* Create the bucket if it does not exist.
|
|
77
85
|
* @returns The promise that will resolve when the bucket exists
|
|
@@ -35,7 +35,9 @@ export const createS3Client = (config = {}) => {
|
|
|
35
35
|
export const s3Client = createS3Client();
|
|
36
36
|
let S3Bucket = class S3Bucket extends Shape {
|
|
37
37
|
constructor(n, clientConfig) {
|
|
38
|
-
|
|
38
|
+
// `undefined`, not `null`: Shape only skips assigning an id for undefined,
|
|
39
|
+
// and would read `.id` off null otherwise.
|
|
40
|
+
super(typeof n === 'string' ? undefined : n);
|
|
39
41
|
this.ensureKeyPromise = new Map();
|
|
40
42
|
if (typeof n === 'string') {
|
|
41
43
|
this.label = n;
|
|
@@ -175,6 +177,33 @@ let S3Bucket = class S3Bucket extends Shape {
|
|
|
175
177
|
}
|
|
176
178
|
});
|
|
177
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Read an object's metadata without downloading its contents.
|
|
182
|
+
*
|
|
183
|
+
* @param key The key of the object to head
|
|
184
|
+
* @returns The HeadObject response, or null when the object does not exist.
|
|
185
|
+
* Any other error (permissions, network, no such bucket) is rethrown, so a
|
|
186
|
+
* caller cannot mistake a broken connection for a missing file.
|
|
187
|
+
*/
|
|
188
|
+
async headObject(key) {
|
|
189
|
+
var _a;
|
|
190
|
+
let bucketParams = {
|
|
191
|
+
Bucket: this.label,
|
|
192
|
+
Key: key,
|
|
193
|
+
};
|
|
194
|
+
try {
|
|
195
|
+
return await this._client.send(new HeadObjectCommand(bucketParams));
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
// v3 of the AWS SDK reports a missing object as NotFound; some
|
|
199
|
+
// S3-compatible endpoints surface the bare 404 instead.
|
|
200
|
+
if ((error === null || error === void 0 ? void 0 : error.name) === 'NotFound' ||
|
|
201
|
+
((_a = error === null || error === void 0 ? void 0 : error.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode) === 404) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
178
207
|
/**
|
|
179
208
|
* Ensure that the given key exists in the bucket, the file contents
|
|
180
209
|
* will be an empty json object.
|
|
@@ -194,63 +223,35 @@ let S3Bucket = class S3Bucket extends Shape {
|
|
|
194
223
|
Bucket: this.label,
|
|
195
224
|
Key: key,
|
|
196
225
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
reject('Error putting object: ' + err);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
reject('Error checking existence of object: ' + error);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
async keyExists(key) {
|
|
234
|
-
let bucketParams = {
|
|
235
|
-
Bucket: this.label,
|
|
236
|
-
Key: key,
|
|
237
|
-
};
|
|
238
|
-
return new Promise(async (resolve, reject) => {
|
|
239
|
-
//see if the object already exists
|
|
240
|
-
try {
|
|
241
|
-
await this._client.send(new HeadObjectCommand(bucketParams));
|
|
242
|
-
resolve(true);
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
if (error.name === 'NotFound') {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
console.warn('Error checking existence of object: ' + error);
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
226
|
+
//see if the object already exists. `headObject` owns the single
|
|
227
|
+
//"does this key exist" decision in this class, including the bare-404
|
|
228
|
+
//fallback that S3-compatible endpoints need.
|
|
229
|
+
let head;
|
|
230
|
+
try {
|
|
231
|
+
head = await this.headObject(key);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
throw 'Error checking existence of object: ' + error;
|
|
235
|
+
}
|
|
236
|
+
if (head) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
//If the object does not exist, create it,
|
|
240
|
+
// with the content being an empty json object
|
|
241
|
+
try {
|
|
242
|
+
await this._client.send(new PutObjectCommand({
|
|
243
|
+
...bucketParams,
|
|
244
|
+
Body: JSON.stringify({}),
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
if (err.name === 'NoSuchBucket') {
|
|
249
|
+
await this.createBucket();
|
|
250
|
+
await this._ensureKeyExists(key);
|
|
251
|
+
return;
|
|
252
252
|
}
|
|
253
|
-
|
|
253
|
+
throw 'Error putting object: ' + err;
|
|
254
|
+
}
|
|
254
255
|
}
|
|
255
256
|
/**
|
|
256
257
|
* Create the bucket if it does not exist.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"S3Bucket.js","sourceRoot":"","sources":["../../../src/shapes/S3Bucket.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,EACnB,mBAAmB,EAEnB,gBAAgB,EAEhB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"S3Bucket.js","sourceRoot":"","sources":["../../../src/shapes/S3Bucket.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IFileStore } from '@_linked/core/interfaces/IFileStore';
|
|
1
|
+
import { FileStat, IFileStore, SaveFileOptions } from '@_linked/core/interfaces/IFileStore';
|
|
2
2
|
import { Shape } from '@_linked/core/shapes/Shape';
|
|
3
3
|
import { type S3ClientConfigInput } from './S3Bucket.js';
|
|
4
4
|
import type { Readable } from 'stream';
|
|
@@ -36,9 +36,11 @@ export declare class S3FileStore extends Shape implements IFileStore {
|
|
|
36
36
|
* Save a file to the bucket
|
|
37
37
|
* @param filePath The path to save the file to, relative to the endpoint
|
|
38
38
|
* @param fileContent The contents of the file as a buffer
|
|
39
|
+
* @param options Save options, or a plain string read as the old positional mime type
|
|
40
|
+
* @param preventDuplicates Only honoured when `options` is a string
|
|
39
41
|
* @returns The public URL of the file
|
|
40
42
|
*/
|
|
41
|
-
saveFile(filePath: string, fileContent: string | Uint8Array | Buffer | Readable,
|
|
43
|
+
saveFile(filePath: string, fileContent: string | Uint8Array | Buffer | Readable, options?: SaveFileOptions | string, preventDuplicates?: boolean): Promise<string>;
|
|
42
44
|
/**
|
|
43
45
|
* Get a file from the bucket
|
|
44
46
|
* @param filePath The path of the file to get, relative to the endpoint
|
|
@@ -56,4 +58,13 @@ export declare class S3FileStore extends Shape implements IFileStore {
|
|
|
56
58
|
* @returns Whether the file exists
|
|
57
59
|
*/
|
|
58
60
|
fileExists(filePath: string): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Read the metadata of a stored file, for verify-after-upload.
|
|
63
|
+
*
|
|
64
|
+
* @param filePath The path of the file, relative to the endpoint. Normalised
|
|
65
|
+
* and prefixed exactly as `saveFile` does, so a path that was just saved
|
|
66
|
+
* can be handed straight back here.
|
|
67
|
+
* @returns The stat, or null when the object does not exist.
|
|
68
|
+
*/
|
|
69
|
+
statFile(filePath: string): Promise<FileStat | null>;
|
|
59
70
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSaveFileOptions, } from '@_linked/core/interfaces/IFileStore';
|
|
1
2
|
import { Shape } from '@_linked/core/shapes/Shape';
|
|
2
3
|
import mime from 'mime';
|
|
3
4
|
import path from 'path';
|
|
@@ -103,17 +104,23 @@ export class S3FileStore extends Shape {
|
|
|
103
104
|
* Save a file to the bucket
|
|
104
105
|
* @param filePath The path to save the file to, relative to the endpoint
|
|
105
106
|
* @param fileContent The contents of the file as a buffer
|
|
107
|
+
* @param options Save options, or a plain string read as the old positional mime type
|
|
108
|
+
* @param preventDuplicates Only honoured when `options` is a string
|
|
106
109
|
* @returns The public URL of the file
|
|
107
110
|
*/
|
|
108
|
-
async saveFile(filePath, fileContent,
|
|
111
|
+
async saveFile(filePath, fileContent, options, preventDuplicates) {
|
|
112
|
+
var _a, _b;
|
|
109
113
|
let bucket = this.bucket;
|
|
114
|
+
// Normalised in core so this store cannot drift from the other IFileStores.
|
|
115
|
+
const opts = normalizeSaveFileOptions(options, preventDuplicates);
|
|
116
|
+
// Core reports an unspecified `preventDuplicates` as `undefined` and never
|
|
117
|
+
// invents a default, so each store applies its own. This store's default is
|
|
118
|
+
// to overwrite, which is what it has always done.
|
|
119
|
+
const shouldPreventDuplicates = (_a = opts.preventDuplicates) !== null && _a !== void 0 ? _a : false;
|
|
110
120
|
filePath = this.normalizePath(filePath);
|
|
111
|
-
|
|
112
|
-
mimeType = mime.getType(filePath);
|
|
113
|
-
}
|
|
114
|
-
// console.log(`mimeType of ${filePath} is ${mimeType}`);
|
|
121
|
+
const mimeType = (_b = opts.mimeType) !== null && _b !== void 0 ? _b : mime.getType(filePath);
|
|
115
122
|
//first check if the object already exists
|
|
116
|
-
if (
|
|
123
|
+
if (shouldPreventDuplicates && (await this.fileExists(filePath))) {
|
|
117
124
|
//if yes, use a more unique name based on the current unix timestamp
|
|
118
125
|
filePath = `${path.dirname(filePath)}/${Date.now()}-${path.basename(filePath)}`;
|
|
119
126
|
console.log('File already exists, renaming to avoid overwrite: ' + filePath);
|
|
@@ -122,6 +129,10 @@ export class S3FileStore extends Shape {
|
|
|
122
129
|
let res = await bucket
|
|
123
130
|
.putObject(key, fileContent, {
|
|
124
131
|
ContentType: mimeType,
|
|
132
|
+
// Only send the optional headers when they were asked for, so the
|
|
133
|
+
// bucket's own defaults keep applying otherwise.
|
|
134
|
+
...(opts.cacheControl ? { CacheControl: opts.cacheControl } : {}),
|
|
135
|
+
...(opts.metadata ? { Metadata: opts.metadata } : {}),
|
|
125
136
|
})
|
|
126
137
|
.catch((err) => {
|
|
127
138
|
console.error('Error saving file to S3:', err);
|
|
@@ -158,16 +169,47 @@ export class S3FileStore extends Shape {
|
|
|
158
169
|
*/
|
|
159
170
|
async fileExists(filePath) {
|
|
160
171
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return
|
|
172
|
+
// HeadObject, not GetObject: existence must not cost a full download of
|
|
173
|
+
// the very object `saveFile` is about to overwrite. Compared against
|
|
174
|
+
// `null` rather than truthiness, so a 0-byte object counts as existing.
|
|
175
|
+
const head = await this.bucket.headObject(this.applyPrefix(this.normalizePath(filePath)));
|
|
176
|
+
return head !== null;
|
|
166
177
|
}
|
|
167
178
|
catch (e) {
|
|
168
179
|
return false;
|
|
169
180
|
}
|
|
170
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Read the metadata of a stored file, for verify-after-upload.
|
|
184
|
+
*
|
|
185
|
+
* @param filePath The path of the file, relative to the endpoint. Normalised
|
|
186
|
+
* and prefixed exactly as `saveFile` does, so a path that was just saved
|
|
187
|
+
* can be handed straight back here.
|
|
188
|
+
* @returns The stat, or null when the object does not exist.
|
|
189
|
+
*/
|
|
190
|
+
async statFile(filePath) {
|
|
191
|
+
var _a, _b;
|
|
192
|
+
const key = this.applyPrefix(this.normalizePath(filePath));
|
|
193
|
+
const head = await this.bucket.headObject(key);
|
|
194
|
+
if (!head) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
// S3 always reports ContentLength for an existing object, but the type is
|
|
199
|
+
// `number | undefined` and some S3-compatible endpoints omit it. FileStat
|
|
200
|
+
// requires a number, so an unreported size is read as 0: a caller doing
|
|
201
|
+
// verify-after-upload then sees a size mismatch and fails closed, which is
|
|
202
|
+
// the right outcome when the store cannot tell us how big the file is.
|
|
203
|
+
size: (_a = head.ContentLength) !== null && _a !== void 0 ? _a : 0,
|
|
204
|
+
// Only present when the object was uploaded with a checksum. Left
|
|
205
|
+
// undefined otherwise: the caller must then treat the file as
|
|
206
|
+
// "cannot verify". ETag is never a content hash (MD5 at best, and
|
|
207
|
+
// something else entirely for multipart uploads), so it is reported
|
|
208
|
+
// separately and never as sha256.
|
|
209
|
+
sha256: (_b = head.ChecksumSHA256) !== null && _b !== void 0 ? _b : undefined,
|
|
210
|
+
etag: head.ETag,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
171
213
|
}
|
|
172
214
|
S3FileStore.targetClass = s3.FileStore;
|
|
173
215
|
//# sourceMappingURL=S3FileStore.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"S3FileStore.js","sourceRoot":"","sources":["../../../src/shapes/S3FileStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"S3FileStore.js","sourceRoot":"","sources":["../../../src/shapes/S3FileStore.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": ""
|
|
12
|
+
"url": "git+https://github.com/linked-fw/s3.git"
|
|
13
13
|
},
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.2.0",
|
|
15
15
|
"linkedPackage": true,
|
|
16
16
|
"type": "module",
|
|
17
17
|
"main": "lib/esm/index.js",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"start": "npm exec linked dev",
|
|
22
22
|
"build": "rimraf ./lib && yarn build-esm && yarn copy-to-lib",
|
|
23
23
|
"build-esm": "echo \"compiling ESM\" && rimraf ./lib/esm && (yarn tsc -p tsconfig-esm.json || echo 'tsc returned non-zero; .js still emitted via noEmitOnError default')",
|
|
24
|
-
"copy-to-lib": "echo 'copying JSON assets to lib folder' && yarn copyfiles -u 1 'src/**/*.json' lib/esm"
|
|
24
|
+
"copy-to-lib": "echo 'copying JSON assets to lib folder' && yarn copyfiles -u 1 'src/**/*.json' lib/esm",
|
|
25
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules npx jest --config jest.config.cjs",
|
|
26
|
+
"typecheck": "npx tsc -p tsconfig-tests.json"
|
|
25
27
|
},
|
|
26
28
|
"keywords": [
|
|
27
29
|
"lincd",
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
"aws"
|
|
36
38
|
],
|
|
37
39
|
"dependencies": {
|
|
38
|
-
"@_linked/core": "^2.
|
|
40
|
+
"@_linked/core": "^2.20.0",
|
|
39
41
|
"@_linked/server": "^2.0.0",
|
|
40
42
|
"@_linked/server-utils": "^1.0",
|
|
41
43
|
"@aws-sdk/client-s3": "^3.159.0",
|
|
@@ -47,9 +49,14 @@
|
|
|
47
49
|
"@_linked/cli": "^1.2",
|
|
48
50
|
"@changesets/changelog-github": "^0.5.2",
|
|
49
51
|
"@changesets/cli": "^2.29.8",
|
|
52
|
+
"@jest/globals": "^29.7.0",
|
|
53
|
+
"@types/jest": "^29.5.14",
|
|
50
54
|
"@types/mime": "^3.0.4",
|
|
55
|
+
"@types/node": "^20.19.43",
|
|
51
56
|
"copyfiles": "^2.4.1",
|
|
57
|
+
"jest": "^29.7.0",
|
|
52
58
|
"rimraf": "^5.0.7",
|
|
59
|
+
"ts-jest": "^29.4.12",
|
|
53
60
|
"tsconfig-to-dual-package": "^1.2.0",
|
|
54
61
|
"typescript": "^5.7.3",
|
|
55
62
|
"typescript-plugin-css-modules": "^5.1.0"
|
package/tsconfig-esm.json
CHANGED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
name: Changeset Check
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
changeset-check:
|
|
10
|
-
name: Check for changeset
|
|
11
|
-
# Skip the check on the automated Release PR.
|
|
12
|
-
if: ${{ github.head_ref != 'changeset-release/main' }}
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
steps:
|
|
15
|
-
- name: Checkout
|
|
16
|
-
uses: actions/checkout@v4
|
|
17
|
-
with:
|
|
18
|
-
fetch-depth: 0
|
|
19
|
-
|
|
20
|
-
- name: Check for changeset files
|
|
21
|
-
uses: actions/github-script@v7
|
|
22
|
-
with:
|
|
23
|
-
script: |
|
|
24
|
-
const { data: files } = await github.rest.pulls.listFiles({
|
|
25
|
-
owner: context.repo.owner,
|
|
26
|
-
repo: context.repo.repo,
|
|
27
|
-
pull_number: context.issue.number,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const hasChangeset = files.some(f =>
|
|
31
|
-
f.filename.startsWith('.changeset/') &&
|
|
32
|
-
f.filename.endsWith('.md') &&
|
|
33
|
-
f.filename !== '.changeset/README.md'
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
if (!hasChangeset) {
|
|
37
|
-
await github.rest.issues.createComment({
|
|
38
|
-
owner: context.repo.owner,
|
|
39
|
-
repo: context.repo.repo,
|
|
40
|
-
issue_number: context.issue.number,
|
|
41
|
-
body: "No changeset found. If this PR should trigger a release, run `npx changeset` and commit the generated file.\n\nIf this change doesn't need a release (docs, CI config, etc.), you can ignore this message."
|
|
42
|
-
});
|
|
43
|
-
core.warning('No changeset file found in this PR.');
|
|
44
|
-
}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
build-and-test:
|
|
10
|
-
name: Build & Test
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- name: Detect Release PR
|
|
14
|
-
id: rpr
|
|
15
|
-
run: |
|
|
16
|
-
if [[ "${{ github.event.pull_request.head.ref }}" == "changeset-release/main" ]]; then
|
|
17
|
-
echo "is_release_pr=true" >> $GITHUB_OUTPUT
|
|
18
|
-
echo "Release PR from changesets/action detected — skipping build/test (auto-generated version+CHANGELOG only)."
|
|
19
|
-
else
|
|
20
|
-
echo "is_release_pr=false" >> $GITHUB_OUTPUT
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
- name: Checkout
|
|
24
|
-
if: steps.rpr.outputs.is_release_pr == 'false'
|
|
25
|
-
uses: actions/checkout@v4
|
|
26
|
-
|
|
27
|
-
- name: Setup Node.js
|
|
28
|
-
if: steps.rpr.outputs.is_release_pr == 'false'
|
|
29
|
-
uses: actions/setup-node@v4
|
|
30
|
-
with:
|
|
31
|
-
node-version: 22.16.0
|
|
32
|
-
|
|
33
|
-
- name: Install dependencies
|
|
34
|
-
if: steps.rpr.outputs.is_release_pr == 'false'
|
|
35
|
-
run: npm ci --legacy-peer-deps
|
|
36
|
-
|
|
37
|
-
- name: Build
|
|
38
|
-
if: steps.rpr.outputs.is_release_pr == 'false'
|
|
39
|
-
run: npm run build
|
|
40
|
-
|
|
41
|
-
- name: Test
|
|
42
|
-
if: steps.rpr.outputs.is_release_pr == 'false'
|
|
43
|
-
run: npm test --if-present
|