s3-ship 0.1.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/LICENSE +21 -0
- package/README.md +196 -0
- package/dist/aws/cloudfront-client.d.ts +10 -0
- package/dist/aws/cloudfront-client.d.ts.map +1 -0
- package/dist/aws/s3-client.d.ts +11 -0
- package/dist/aws/s3-client.d.ts.map +1 -0
- package/dist/cli/init.d.ts +11 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/run.d.ts +6 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1000 -0
- package/dist/config/define.d.ts +3 -0
- package/dist/config/define.d.ts.map +1 -0
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.d.ts.map +1 -0
- package/dist/config/merge.d.ts +28 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/schema.d.ts +348 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/deploy.d.ts +32 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +707 -0
- package/dist/reporter.d.ts +8 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/stages/delete-stale.d.ts +10 -0
- package/dist/stages/delete-stale.d.ts.map +1 -0
- package/dist/stages/diff.d.ts +34 -0
- package/dist/stages/diff.d.ts.map +1 -0
- package/dist/stages/execute.d.ts +28 -0
- package/dist/stages/execute.d.ts.map +1 -0
- package/dist/stages/invalidate.d.ts +8 -0
- package/dist/stages/invalidate.d.ts.map +1 -0
- package/dist/stages/list-remote.d.ts +13 -0
- package/dist/stages/list-remote.d.ts.map +1 -0
- package/dist/stages/redirects.d.ts +11 -0
- package/dist/stages/redirects.d.ts.map +1 -0
- package/dist/stages/scan.d.ts +12 -0
- package/dist/stages/scan.d.ts.map +1 -0
- package/dist/stages/upload.d.ts +12 -0
- package/dist/stages/upload.d.ts.map +1 -0
- package/dist/util/content-type.d.ts +2 -0
- package/dist/util/content-type.d.ts.map +1 -0
- package/dist/util/glob-match.d.ts +3 -0
- package/dist/util/glob-match.d.ts.map +1 -0
- package/dist/util/parallel-limit.d.ts +10 -0
- package/dist/util/parallel-limit.d.ts.map +1 -0
- package/dist/util/retry.d.ts +6 -0
- package/dist/util/retry.d.ts.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 s3-ship contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# s3-ship
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/s3-ship)
|
|
4
|
+
[](https://www.npmjs.com/package/s3-ship)
|
|
5
|
+
[](https://github.com/yfxie/s3-ship/actions/workflows/ci.yml)
|
|
6
|
+
[](https://codecov.io/gh/yfxie/s3-ship)
|
|
7
|
+
[](https://github.com/yfxie/s3-ship/blob/main/LICENSE)
|
|
8
|
+
[](https://www.npmjs.com/package/s3-ship)
|
|
9
|
+
[](https://bun.sh)
|
|
10
|
+
|
|
11
|
+
Deploy a directory of static files to AWS S3 with redirects, CloudFront invalidation, and multi-environment support — in a single command.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install -g s3-ship
|
|
15
|
+
s3-ship init
|
|
16
|
+
# edit s3-ship.config.ts
|
|
17
|
+
s3-ship deploy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **One-shot deploy** — scan local files, diff against S3 (by MD5/ETag), upload only what changed
|
|
23
|
+
- **Redirects** — bulk-define `WebsiteRedirectLocation` redirect objects in config
|
|
24
|
+
- **CloudFront invalidation** — automatically clear edge caches after upload
|
|
25
|
+
- **Multi-environment** — separate buckets/distributions per `staging` / `production`
|
|
26
|
+
- **AWS profile and env-var support** — works with your existing AWS credentials
|
|
27
|
+
- **Dry-run** — preview every action before any network write
|
|
28
|
+
- **Sync-delete (opt-in)** — remove S3 keys no longer present locally, **bounded to your target prefix**
|
|
29
|
+
- **Smart ignore** — `.DS_Store`, `Thumbs.db`, `.git/`, `*.swp` are always excluded
|
|
30
|
+
- **Multiple config formats** — auto-detects `.ts`, `.js`, `.mjs`, `.cjs`, `.json`, `.yml`, `.yaml`
|
|
31
|
+
- **Fail-fast validation** — config errors are reported all at once before any AWS call
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
# global
|
|
37
|
+
npm install -g s3-ship
|
|
38
|
+
|
|
39
|
+
# or per-project
|
|
40
|
+
npm install --save-dev s3-ship
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Requires Node.js 18+.
|
|
44
|
+
|
|
45
|
+
## Quickstart
|
|
46
|
+
|
|
47
|
+
1. Create a config file in your project root:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
s3-ship init # writes s3-ship.config.ts
|
|
51
|
+
s3-ship init --format json # or json / yml / js / mjs
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
2. Edit the file:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { defineConfig } from 's3-ship'
|
|
58
|
+
|
|
59
|
+
export default defineConfig({
|
|
60
|
+
source: 'dist',
|
|
61
|
+
bucket: 'my-website-bucket',
|
|
62
|
+
region: 'us-east-1',
|
|
63
|
+
cloudfront: { distributionId: 'EABCDEF12345' },
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
3. Preview, then deploy:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
s3-ship deploy --dry-run
|
|
71
|
+
s3-ship deploy
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Top-level options:
|
|
77
|
+
|
|
78
|
+
| Option | Type | Default | Description |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| `source` | `string` | `'dist'` | Local directory to upload |
|
|
81
|
+
| `target` | `string` | `''` | Target prefix in the bucket. All list/delete operations are bounded to this prefix |
|
|
82
|
+
| `bucket` | `string` | — | S3 bucket name (required at top level or per environment) |
|
|
83
|
+
| `region` | `string` | — | AWS region. Falls back to `AWS_REGION` env var |
|
|
84
|
+
| `profile` | `string` | — | AWS profile name. Falls back to `AWS_PROFILE` env var |
|
|
85
|
+
| `cloudfront` | `object` | — | `{ distributionId: string, invalidationPaths?: string[] }`. Default paths `['/*']` |
|
|
86
|
+
| `syncDelete` | `boolean` | `false` | Delete remote keys not present locally (bounded to `target`) |
|
|
87
|
+
| `ignore` | `string[]` | `[]` | Additional glob patterns to ignore on top of the always-ignored set |
|
|
88
|
+
| `redirects` | `RedirectRule[]` | `[]` | See [Redirects](#redirects) below |
|
|
89
|
+
| `cacheControl` | `CacheRule[]` | `[]` | Per-glob `Cache-Control` headers, e.g. `[{ match: 'assets/**', cacheControl: 'public, max-age=31536000, immutable' }]` |
|
|
90
|
+
| `environments` | `Record<string, Partial<Config>>` | — | Named overrides; activated with `--env <name>` |
|
|
91
|
+
|
|
92
|
+
### Multi-environment
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
export default defineConfig({
|
|
96
|
+
source: 'dist',
|
|
97
|
+
cloudfront: { invalidationPaths: ['/*'] }, // shared
|
|
98
|
+
environments: {
|
|
99
|
+
staging: {
|
|
100
|
+
bucket: 'staging.example.com',
|
|
101
|
+
cloudfront: { distributionId: 'ESTAGE' },
|
|
102
|
+
},
|
|
103
|
+
production: {
|
|
104
|
+
bucket: 'www.example.com',
|
|
105
|
+
cloudfront: { distributionId: 'EPROD' },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
s3-ship deploy --env staging
|
|
113
|
+
s3-ship deploy --env production
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Precedence (high → low): **CLI flags > environment vars (`AWS_*`) > `environments.<name>` > top-level > built-in defaults**.
|
|
117
|
+
|
|
118
|
+
### Redirects
|
|
119
|
+
|
|
120
|
+
S3 supports per-object 301 redirects via `WebsiteRedirectLocation`. Each rule creates a 0-byte object whose only purpose is to redirect.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
redirects: [
|
|
124
|
+
{ from: 'blog/old-post', to: '/blog/new-post' },
|
|
125
|
+
{ from: 'legacy.html', to: 'https://example.com', statusCode: 302 },
|
|
126
|
+
]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- `from` is automatically prefixed by `target` if set.
|
|
130
|
+
- If `from` collides with a real source file at the same key, the redirect wins — the source file is silently dropped from the upload plan.
|
|
131
|
+
- Redirects are excluded from `syncDelete`.
|
|
132
|
+
|
|
133
|
+
### Target prefix and sync-delete safety
|
|
134
|
+
|
|
135
|
+
If `target = 'docs/v2'`, **every** S3 list and delete operation is bounded to `Prefix='docs/v2/'`. `syncDelete` will never touch a key outside this prefix, even if it's not in your local source.
|
|
136
|
+
|
|
137
|
+
If `target` is empty (the bucket root), `syncDelete` operates on the whole bucket. Use with care.
|
|
138
|
+
|
|
139
|
+
## CLI
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
s3-ship deploy [options]
|
|
143
|
+
--env <name> Use environments.<name> from config
|
|
144
|
+
--profile <name> Override AWS profile
|
|
145
|
+
--bucket <name> Override target bucket
|
|
146
|
+
--target <prefix> Override target prefix
|
|
147
|
+
--source <dir> Override local source directory
|
|
148
|
+
--dry-run Print plan, exit without uploading
|
|
149
|
+
--sync-delete Enable sync-delete (overrides config)
|
|
150
|
+
--no-sync-delete Disable sync-delete (overrides config)
|
|
151
|
+
--no-invalidate Skip CloudFront invalidation
|
|
152
|
+
--config <path> Path to config file
|
|
153
|
+
--verbose List every file (uploads, updates, deletes, skipped, redirects, failures) without the default 20-item truncation
|
|
154
|
+
|
|
155
|
+
s3-ship init [--format ts|js|mjs|json|yml|yaml]
|
|
156
|
+
Create a starter config file (default: ts)
|
|
157
|
+
|
|
158
|
+
s3-ship validate [--config <path>]
|
|
159
|
+
Validate the config file without contacting AWS
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Exit codes: `0` success — `1` config error — `2` AWS / upload failure.
|
|
163
|
+
|
|
164
|
+
## Programmatic API
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { deploy } from 's3-ship'
|
|
168
|
+
|
|
169
|
+
const result = await deploy({
|
|
170
|
+
cwd: process.cwd(),
|
|
171
|
+
envVars: process.env,
|
|
172
|
+
env: 'production',
|
|
173
|
+
dryRun: true,
|
|
174
|
+
})
|
|
175
|
+
console.log(result.plan)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`deploy()` returns `{ plan, report?, resolvedConfig, dryRun, configPath }`.
|
|
179
|
+
|
|
180
|
+
## Development
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
bun install
|
|
184
|
+
bun test # run tests
|
|
185
|
+
bun test --watch # watch mode
|
|
186
|
+
bun run test:coverage # generate coverage report (text + lcov)
|
|
187
|
+
bun run typecheck
|
|
188
|
+
bun run lint
|
|
189
|
+
bun run build # produces dist/
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Coverage output is written to `coverage/lcov.info` for upload to Codecov in CI.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CloudFrontClient } from '@aws-sdk/client-cloudfront';
|
|
2
|
+
export interface CloudFrontLike {
|
|
3
|
+
send(command: unknown): Promise<unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface CloudFrontClientOptions {
|
|
6
|
+
region?: string;
|
|
7
|
+
profile?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createCloudFrontClient(options: CloudFrontClientOptions): CloudFrontClient;
|
|
10
|
+
//# sourceMappingURL=cloudfront-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudfront-client.d.ts","sourceRoot":"","sources":["../../src/aws/cloudfront-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAG7D,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACzC;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CAKzF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
export interface S3Like {
|
|
3
|
+
send(command: unknown): Promise<unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface S3ClientOptions {
|
|
6
|
+
region?: string;
|
|
7
|
+
profile?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createS3Client(options: S3ClientOptions): S3Client;
|
|
10
|
+
export declare function normalizeTargetPrefix(target: string): string;
|
|
11
|
+
//# sourceMappingURL=s3-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-client.d.ts","sourceRoot":"","sources":["../../src/aws/s3-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAG7C,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CAKjE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG5D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type InitFormat = 'js' | 'ts' | 'mjs' | 'json' | 'yml' | 'yaml';
|
|
2
|
+
export interface InitOptions {
|
|
3
|
+
cwd: string;
|
|
4
|
+
format?: InitFormat;
|
|
5
|
+
}
|
|
6
|
+
export interface InitResult {
|
|
7
|
+
path: string;
|
|
8
|
+
format: InitFormat;
|
|
9
|
+
}
|
|
10
|
+
export declare function initConfig(options: InitOptions): Promise<InitResult>;
|
|
11
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;AAEtE,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;CACnB;AA4FD,wBAAsB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAc1E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAoCrC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,OAAO,MAAM,CAAA;CACzB;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+FzF"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|