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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/dist/aws/cloudfront-client.d.ts +10 -0
  4. package/dist/aws/cloudfront-client.d.ts.map +1 -0
  5. package/dist/aws/s3-client.d.ts +11 -0
  6. package/dist/aws/s3-client.d.ts.map +1 -0
  7. package/dist/cli/init.d.ts +11 -0
  8. package/dist/cli/init.d.ts.map +1 -0
  9. package/dist/cli/run.d.ts +6 -0
  10. package/dist/cli/run.d.ts.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +1000 -0
  14. package/dist/config/define.d.ts +3 -0
  15. package/dist/config/define.d.ts.map +1 -0
  16. package/dist/config/load.d.ts +10 -0
  17. package/dist/config/load.d.ts.map +1 -0
  18. package/dist/config/merge.d.ts +28 -0
  19. package/dist/config/merge.d.ts.map +1 -0
  20. package/dist/config/schema.d.ts +348 -0
  21. package/dist/config/schema.d.ts.map +1 -0
  22. package/dist/deploy.d.ts +32 -0
  23. package/dist/deploy.d.ts.map +1 -0
  24. package/dist/index.d.ts +8 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +707 -0
  27. package/dist/reporter.d.ts +8 -0
  28. package/dist/reporter.d.ts.map +1 -0
  29. package/dist/stages/delete-stale.d.ts +10 -0
  30. package/dist/stages/delete-stale.d.ts.map +1 -0
  31. package/dist/stages/diff.d.ts +34 -0
  32. package/dist/stages/diff.d.ts.map +1 -0
  33. package/dist/stages/execute.d.ts +28 -0
  34. package/dist/stages/execute.d.ts.map +1 -0
  35. package/dist/stages/invalidate.d.ts +8 -0
  36. package/dist/stages/invalidate.d.ts.map +1 -0
  37. package/dist/stages/list-remote.d.ts +13 -0
  38. package/dist/stages/list-remote.d.ts.map +1 -0
  39. package/dist/stages/redirects.d.ts +11 -0
  40. package/dist/stages/redirects.d.ts.map +1 -0
  41. package/dist/stages/scan.d.ts +12 -0
  42. package/dist/stages/scan.d.ts.map +1 -0
  43. package/dist/stages/upload.d.ts +12 -0
  44. package/dist/stages/upload.d.ts.map +1 -0
  45. package/dist/util/content-type.d.ts +2 -0
  46. package/dist/util/content-type.d.ts.map +1 -0
  47. package/dist/util/glob-match.d.ts +3 -0
  48. package/dist/util/glob-match.d.ts.map +1 -0
  49. package/dist/util/parallel-limit.d.ts +10 -0
  50. package/dist/util/parallel-limit.d.ts.map +1 -0
  51. package/dist/util/retry.d.ts +6 -0
  52. package/dist/util/retry.d.ts.map +1 -0
  53. 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
+ [![npm version](https://img.shields.io/npm/v/s3-ship.svg)](https://www.npmjs.com/package/s3-ship)
4
+ [![npm downloads](https://img.shields.io/npm/dm/s3-ship.svg)](https://www.npmjs.com/package/s3-ship)
5
+ [![CI](https://github.com/yfxie/s3-ship/actions/workflows/ci.yml/badge.svg)](https://github.com/yfxie/s3-ship/actions/workflows/ci.yml)
6
+ [![codecov](https://codecov.io/gh/yfxie/s3-ship/branch/main/graph/badge.svg)](https://codecov.io/gh/yfxie/s3-ship)
7
+ [![License](https://img.shields.io/npm/l/s3-ship.svg)](https://github.com/yfxie/s3-ship/blob/main/LICENSE)
8
+ [![Types](https://img.shields.io/npm/types/s3-ship.svg)](https://www.npmjs.com/package/s3-ship)
9
+ [![Built with Bun](https://img.shields.io/badge/built%20with-Bun-fbf0df)](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,6 @@
1
+ import { deploy } from '../deploy.js';
2
+ export interface RunCliOptions {
3
+ deployFn?: typeof deploy;
4
+ }
5
+ export declare function runCli(argv: string[], options?: RunCliOptions): Promise<number>;
6
+ //# sourceMappingURL=run.d.ts.map
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}