@nmakarov/cli-toolkit 0.18.0 → 0.23.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/README.md +9 -0
- package/dist/args.cjs +1 -4
- package/dist/args.cjs.map +1 -1
- package/dist/args.js +1 -1
- package/dist/args.js.map +1 -1
- package/dist/cli-runner.cjs +1493 -516
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +1509 -531
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +85 -157
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +84 -150
- package/dist/db.js.map +1 -1
- package/dist/errors.cjs +2 -2
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +2 -1
- package/dist/errors.js.map +1 -1
- package/dist/filedatabase.cjs +19 -19
- package/dist/filedatabase.cjs.map +1 -1
- package/dist/filedatabase.js +19 -16
- package/dist/filedatabase.js.map +1 -1
- package/dist/http-client.cjs +9 -11
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.js +10 -9
- package/dist/http-client.js.map +1 -1
- package/dist/http-client2.cjs +34 -33
- package/dist/http-client2.cjs.map +1 -1
- package/dist/http-client2.js +34 -30
- package/dist/http-client2.js.map +1 -1
- package/dist/index.cjs +2063 -658
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2063 -663
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +97 -69
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +112 -83
- package/dist/init.js.map +1 -1
- package/dist/logger.cjs +5 -5
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +5 -4
- package/dist/logger.js.map +1 -1
- package/dist/mock-server.cjs +21 -33
- package/dist/mock-server.cjs.map +1 -1
- package/dist/mock-server.js +21 -28
- package/dist/mock-server.js.map +1 -1
- package/dist/params.cjs +22 -10
- package/dist/params.cjs.map +1 -1
- package/dist/params.js +22 -7
- package/dist/params.js.map +1 -1
- package/dist/s3.cjs +286 -0
- package/dist/s3.cjs.map +1 -0
- package/dist/s3.js +273 -0
- package/dist/s3.js.map +1 -0
- package/dist/screen.cjs +34 -39
- package/dist/screen.cjs.map +1 -1
- package/dist/screen.js +48 -46
- package/dist/screen.js.map +1 -1
- package/dist/tasks.cjs +1640 -416
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +1614 -412
- package/dist/tasks.js.map +1 -1
- package/dist/utils.cjs +7 -8
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.js +6 -6
- package/dist/utils.js.map +1 -1
- package/package.json +36 -44
- package/scripts/ssm/parse-cli.js +35 -0
- package/scripts/ssm/ssm-admin.js +151 -0
- package/scripts/ssm/ssm-pull.js +147 -0
package/dist/s3.cjs
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/s3/index.js
|
|
20
|
+
var s3_exports = {};
|
|
21
|
+
__export(s3_exports, {
|
|
22
|
+
S3: () => S3
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(s3_exports);
|
|
25
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
26
|
+
|
|
27
|
+
// src/errors.js
|
|
28
|
+
var FrameworkError = class extends Error {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "FrameworkError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var ParamError = class extends FrameworkError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "ParamError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/s3/index.js
|
|
42
|
+
var DEFAULT_PROFILE = "local";
|
|
43
|
+
function capitalize(str) {
|
|
44
|
+
if (!str) return str;
|
|
45
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
46
|
+
}
|
|
47
|
+
var S3 = class _S3 {
|
|
48
|
+
/**
|
|
49
|
+
* Build an S3 instance. Reads bucket profile from --bucket (default "local"),
|
|
50
|
+
* then resolves per-profile params, builds the SDK client, and returns the
|
|
51
|
+
* instance. Optionally pings the bucket once to verify reachability.
|
|
52
|
+
*/
|
|
53
|
+
static async init(context, options = {}) {
|
|
54
|
+
const profileDef = { bucket: "string" };
|
|
55
|
+
const discovered = context?.params?.getAllForModule?.("s3", profileDef) ?? {};
|
|
56
|
+
const profile = options.bucket ?? discovered.bucket ?? DEFAULT_PROFILE;
|
|
57
|
+
const cap = capitalize(profile);
|
|
58
|
+
const config = {
|
|
59
|
+
profile,
|
|
60
|
+
bucketName: options.bucketName ?? await context.params.get(`s3Bucket${cap}`, "string"),
|
|
61
|
+
region: options.region ?? await context.params.get(`s3Region${cap}`, "string default us-east-1"),
|
|
62
|
+
endpoint: options.endpoint ?? await context.params.get(`s3Endpoint${cap}`, "string"),
|
|
63
|
+
forcePathStyle: options.forcePathStyle ?? await context.params.get(`s3ForcePathStyle${cap}`, "boolean default false"),
|
|
64
|
+
accessKeyId: options.accessKeyId ?? await context.params.get(`s3AccessKeyId${cap}`, "string"),
|
|
65
|
+
secretAccessKey: options.secretAccessKey ?? await context.params.get(`s3SecretAccessKey${cap}`, "string")
|
|
66
|
+
};
|
|
67
|
+
if (!config.bucketName) {
|
|
68
|
+
throw new ParamError(
|
|
69
|
+
`S3: bucket name not configured for profile "${profile}" (set s3Bucket${cap} or S3_BUCKET_${profile.toUpperCase()})`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const s3 = new _S3(context, config);
|
|
73
|
+
if (options.testBucket !== false) {
|
|
74
|
+
try {
|
|
75
|
+
await s3.bucketExists();
|
|
76
|
+
context.logger?.debug?.(
|
|
77
|
+
`[S3] profile="${profile}" bucket="${config.bucketName}" reachable`
|
|
78
|
+
);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
context.logger?.warn?.(
|
|
81
|
+
`[S3] profile="${profile}" bucket="${config.bucketName}" reachability test failed: ${err?.message ?? err}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return s3;
|
|
86
|
+
}
|
|
87
|
+
constructor(context, config) {
|
|
88
|
+
this.logger = context?.logger ?? console;
|
|
89
|
+
this.profile = config.profile;
|
|
90
|
+
this.bucketName = config.bucketName;
|
|
91
|
+
this.region = config.region;
|
|
92
|
+
this.endpoint = config.endpoint || null;
|
|
93
|
+
const clientConfig = { region: config.region };
|
|
94
|
+
if (config.endpoint) clientConfig.endpoint = config.endpoint;
|
|
95
|
+
if (config.forcePathStyle) clientConfig.forcePathStyle = true;
|
|
96
|
+
if (config.accessKeyId && config.secretAccessKey) {
|
|
97
|
+
clientConfig.credentials = {
|
|
98
|
+
accessKeyId: config.accessKeyId,
|
|
99
|
+
secretAccessKey: config.secretAccessKey
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
this.client = new import_client_s3.S3Client(clientConfig);
|
|
103
|
+
}
|
|
104
|
+
// ── info ────────────────────────────────────────────────────────────────
|
|
105
|
+
getBucketName() {
|
|
106
|
+
return this.bucketName;
|
|
107
|
+
}
|
|
108
|
+
getProfile() {
|
|
109
|
+
return this.profile;
|
|
110
|
+
}
|
|
111
|
+
getRegion() {
|
|
112
|
+
return this.region;
|
|
113
|
+
}
|
|
114
|
+
getEndpoint() {
|
|
115
|
+
return this.endpoint;
|
|
116
|
+
}
|
|
117
|
+
// ── reachability ────────────────────────────────────────────────────────
|
|
118
|
+
async bucketExists() {
|
|
119
|
+
await this.client.send(new import_client_s3.HeadBucketCommand({ Bucket: this.bucketName }));
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
// ── HEAD / GET ──────────────────────────────────────────────────────────
|
|
123
|
+
/** Returns null on 404; never throws for "missing". Other errors throw. */
|
|
124
|
+
async headObject(key) {
|
|
125
|
+
try {
|
|
126
|
+
const out = await this.client.send(new import_client_s3.HeadObjectCommand({
|
|
127
|
+
Bucket: this.bucketName,
|
|
128
|
+
Key: key
|
|
129
|
+
}));
|
|
130
|
+
return {
|
|
131
|
+
etag: out.ETag,
|
|
132
|
+
size: out.ContentLength,
|
|
133
|
+
contentType: out.ContentType,
|
|
134
|
+
lastModified: out.LastModified,
|
|
135
|
+
metadata: out.Metadata,
|
|
136
|
+
storageClass: out.StorageClass
|
|
137
|
+
};
|
|
138
|
+
} catch (err) {
|
|
139
|
+
if (this._isNotFound(err)) return null;
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Returns { body: Readable, contentType, contentLength, etag, ... } or null on 404. */
|
|
144
|
+
async getObject(key) {
|
|
145
|
+
try {
|
|
146
|
+
const out = await this.client.send(new import_client_s3.GetObjectCommand({
|
|
147
|
+
Bucket: this.bucketName,
|
|
148
|
+
Key: key
|
|
149
|
+
}));
|
|
150
|
+
return {
|
|
151
|
+
body: out.Body,
|
|
152
|
+
contentType: out.ContentType,
|
|
153
|
+
contentLength: out.ContentLength,
|
|
154
|
+
etag: out.ETag,
|
|
155
|
+
lastModified: out.LastModified,
|
|
156
|
+
metadata: out.Metadata
|
|
157
|
+
};
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (this._isNotFound(err)) return null;
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** Buffers the whole object. Use only for small objects (manifests, JSON). */
|
|
164
|
+
async getObjectBytes(key) {
|
|
165
|
+
const obj = await this.getObject(key);
|
|
166
|
+
if (!obj) return null;
|
|
167
|
+
const chunks = [];
|
|
168
|
+
for await (const chunk of obj.body) chunks.push(chunk);
|
|
169
|
+
return { ...obj, body: Buffer.concat(chunks) };
|
|
170
|
+
}
|
|
171
|
+
/** Convenience for JSON manifests. Returns parsed object or null on 404. */
|
|
172
|
+
async getJson(key) {
|
|
173
|
+
const obj = await this.getObjectBytes(key);
|
|
174
|
+
if (!obj) return null;
|
|
175
|
+
return JSON.parse(obj.body.toString("utf8"));
|
|
176
|
+
}
|
|
177
|
+
// ── PUT ─────────────────────────────────────────────────────────────────
|
|
178
|
+
async putObject({ key, body, contentType, contentLength, tags, metadata }) {
|
|
179
|
+
const cmd = new import_client_s3.PutObjectCommand({
|
|
180
|
+
Bucket: this.bucketName,
|
|
181
|
+
Key: key,
|
|
182
|
+
Body: body,
|
|
183
|
+
...contentType && { ContentType: contentType },
|
|
184
|
+
...contentLength != null && { ContentLength: contentLength },
|
|
185
|
+
...metadata && { Metadata: metadata },
|
|
186
|
+
...tags && { Tagging: this._tagsToQuery(tags) }
|
|
187
|
+
});
|
|
188
|
+
return this.client.send(cmd);
|
|
189
|
+
}
|
|
190
|
+
/** Convenience for JSON manifests. */
|
|
191
|
+
async putJson(key, value, opts = {}) {
|
|
192
|
+
const json = JSON.stringify(value, null, opts.pretty ? 2 : 0);
|
|
193
|
+
const body = Buffer.from(json, "utf8");
|
|
194
|
+
return this.putObject({
|
|
195
|
+
key,
|
|
196
|
+
body,
|
|
197
|
+
contentType: "application/json",
|
|
198
|
+
contentLength: body.length,
|
|
199
|
+
tags: opts.tags,
|
|
200
|
+
metadata: opts.metadata
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// ── DELETE ──────────────────────────────────────────────────────────────
|
|
204
|
+
async deleteObject(key) {
|
|
205
|
+
return this.client.send(new import_client_s3.DeleteObjectCommand({
|
|
206
|
+
Bucket: this.bucketName,
|
|
207
|
+
Key: key
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
// ── COPY (for migration: legacy → new bucket, or intra-bucket "rename") ─
|
|
211
|
+
async copyObject({ sourceBucket, sourceKey, key, contentType, metadata, tags }) {
|
|
212
|
+
const src = sourceBucket || this.bucketName;
|
|
213
|
+
const cmd = new import_client_s3.CopyObjectCommand({
|
|
214
|
+
Bucket: this.bucketName,
|
|
215
|
+
Key: key,
|
|
216
|
+
CopySource: encodeURIComponent(`${src}/${sourceKey}`),
|
|
217
|
+
...contentType && {
|
|
218
|
+
ContentType: contentType,
|
|
219
|
+
MetadataDirective: "REPLACE"
|
|
220
|
+
},
|
|
221
|
+
...metadata && {
|
|
222
|
+
Metadata: metadata,
|
|
223
|
+
MetadataDirective: "REPLACE"
|
|
224
|
+
},
|
|
225
|
+
...tags && {
|
|
226
|
+
Tagging: this._tagsToQuery(tags),
|
|
227
|
+
TaggingDirective: "REPLACE"
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
return this.client.send(cmd);
|
|
231
|
+
}
|
|
232
|
+
// ── LIST ────────────────────────────────────────────────────────────────
|
|
233
|
+
async listObjects(prefix, { keysOnly = false, maxKeys = 1e3, continuationToken } = {}) {
|
|
234
|
+
const out = await this.client.send(new import_client_s3.ListObjectsV2Command({
|
|
235
|
+
Bucket: this.bucketName,
|
|
236
|
+
Prefix: prefix,
|
|
237
|
+
MaxKeys: maxKeys,
|
|
238
|
+
ContinuationToken: continuationToken
|
|
239
|
+
}));
|
|
240
|
+
const items = (out.Contents ?? []).map((o) => ({
|
|
241
|
+
key: o.Key,
|
|
242
|
+
size: o.Size,
|
|
243
|
+
etag: o.ETag,
|
|
244
|
+
lastModified: o.LastModified,
|
|
245
|
+
storageClass: o.StorageClass
|
|
246
|
+
}));
|
|
247
|
+
return {
|
|
248
|
+
items: keysOnly ? items.map((i) => i.key) : items,
|
|
249
|
+
isTruncated: !!out.IsTruncated,
|
|
250
|
+
nextContinuationToken: out.NextContinuationToken
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
// ── TAGS (used for lifecycle rules, e.g. status=closed → Glacier IR) ────
|
|
254
|
+
async putObjectTagging(key, tags) {
|
|
255
|
+
return this.client.send(new import_client_s3.PutObjectTaggingCommand({
|
|
256
|
+
Bucket: this.bucketName,
|
|
257
|
+
Key: key,
|
|
258
|
+
Tagging: { TagSet: this._tagsToTagSet(tags) }
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
async getObjectTagging(key) {
|
|
262
|
+
const out = await this.client.send(new import_client_s3.GetObjectTaggingCommand({
|
|
263
|
+
Bucket: this.bucketName,
|
|
264
|
+
Key: key
|
|
265
|
+
}));
|
|
266
|
+
const tags = {};
|
|
267
|
+
for (const t of out.TagSet ?? []) tags[t.Key] = t.Value;
|
|
268
|
+
return tags;
|
|
269
|
+
}
|
|
270
|
+
// ── internals ───────────────────────────────────────────────────────────
|
|
271
|
+
_isNotFound(err) {
|
|
272
|
+
const status = err?.$metadata?.httpStatusCode;
|
|
273
|
+
return status === 404 || err?.name === "NotFound" || err?.name === "NoSuchKey" || err?.Code === "NoSuchKey";
|
|
274
|
+
}
|
|
275
|
+
_tagsToQuery(tags) {
|
|
276
|
+
return Object.entries(tags).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
277
|
+
}
|
|
278
|
+
_tagsToTagSet(tags) {
|
|
279
|
+
return Object.entries(tags).map(([Key, Value]) => ({ Key, Value: String(Value) }));
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
283
|
+
0 && (module.exports = {
|
|
284
|
+
S3
|
|
285
|
+
});
|
|
286
|
+
//# sourceMappingURL=s3.cjs.map
|
package/dist/s3.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/s3/index.js","../src/errors.js"],"sourcesContent":["/**\n * S3 component\n *\n * Thin async wrapper over @aws-sdk/client-s3 v3 with the cli-toolkit conventions:\n * const s3 = await S3.init(context);\n *\n * Supports multiple \"profiles\" (local, prod, stage, …) selected at runtime via\n * `--bucket=<profile>` (or `S3_BUCKET=<profile>`). Each profile has its own set\n * of env-suffixed parameters resolved by the framework, mirroring how `Db.init`\n * uses `dbName` to look up `dbConnectionString${Name}`.\n *\n * Profile selector:\n * --bucket=local (default)\n * --bucket=prod\n * --bucket=stage\n *\n * Per-profile parameters (all read via context.params; resolved from CLI,\n * env, .env files, defaults — see @nmakarov/cli-toolkit/params docs):\n * s3Bucket{Profile} string required\n * s3Region{Profile} string default us-east-1\n * s3Endpoint{Profile} string optional (set for MinIO/local; unset = real AWS)\n * s3ForcePathStyle{Profile} boolean default false (true for MinIO)\n * s3AccessKeyId{Profile} string optional (omit on EC2 to use instance profile)\n * s3SecretAccessKey{Profile} string optional\n *\n * Equivalent env vars are SCREAMING_SNAKE_CASE with the profile suffix:\n * S3_BUCKET_LOCAL, S3_REGION_LOCAL, S3_ENDPOINT_LOCAL,\n * S3_FORCE_PATH_STYLE_LOCAL, S3_ACCESS_KEY_ID_LOCAL, S3_SECRET_ACCESS_KEY_LOCAL\n *\n * Example (local with MinIO from docker-compose):\n * S3_BUCKET_LOCAL=photos-local\n * S3_REGION_LOCAL=us-east-1\n * S3_ENDPOINT_LOCAL=http://localhost:9000\n * S3_FORCE_PATH_STYLE_LOCAL=true\n * S3_ACCESS_KEY_ID_LOCAL=dev\n * S3_SECRET_ACCESS_KEY_LOCAL=devdevdev\n */\n\nimport {\n S3Client,\n HeadBucketCommand,\n HeadObjectCommand,\n GetObjectCommand,\n PutObjectCommand,\n DeleteObjectCommand,\n CopyObjectCommand,\n ListObjectsV2Command,\n PutObjectTaggingCommand,\n GetObjectTaggingCommand,\n} from \"@aws-sdk/client-s3\";\n\nimport { ParamError } from \"../errors.js\";\n\nconst DEFAULT_PROFILE = \"local\";\n\nfunction capitalize(str) {\n if (!str) return str;\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport class S3 {\n /**\n * Build an S3 instance. Reads bucket profile from --bucket (default \"local\"),\n * then resolves per-profile params, builds the SDK client, and returns the\n * instance. Optionally pings the bucket once to verify reachability.\n */\n static async init(context, options = {}) {\n const profileDef = { bucket: \"string\" };\n const discovered = context?.params?.getAllForModule?.(\"s3\", profileDef) ?? {};\n const profile = options.bucket ?? discovered.bucket ?? DEFAULT_PROFILE;\n const cap = capitalize(profile);\n\n const config = {\n profile,\n bucketName: options.bucketName\n ?? await context.params.get(`s3Bucket${cap}`, \"string\"),\n region: options.region\n ?? await context.params.get(`s3Region${cap}`, \"string default us-east-1\"),\n endpoint: options.endpoint\n ?? await context.params.get(`s3Endpoint${cap}`, \"string\"),\n forcePathStyle: options.forcePathStyle\n ?? await context.params.get(`s3ForcePathStyle${cap}`, \"boolean default false\"),\n accessKeyId: options.accessKeyId\n ?? await context.params.get(`s3AccessKeyId${cap}`, \"string\"),\n secretAccessKey: options.secretAccessKey\n ?? await context.params.get(`s3SecretAccessKey${cap}`, \"string\"),\n };\n\n if (!config.bucketName) {\n throw new ParamError(\n `S3: bucket name not configured for profile \"${profile}\" `\n + `(set s3Bucket${cap} or S3_BUCKET_${profile.toUpperCase()})`\n );\n }\n\n const s3 = new S3(context, config);\n\n if (options.testBucket !== false) {\n try {\n await s3.bucketExists();\n context.logger?.debug?.(\n `[S3] profile=\"${profile}\" bucket=\"${config.bucketName}\" reachable`\n );\n } catch (err) {\n context.logger?.warn?.(\n `[S3] profile=\"${profile}\" bucket=\"${config.bucketName}\" `\n + `reachability test failed: ${err?.message ?? err}`\n );\n }\n }\n\n return s3;\n }\n\n constructor(context, config) {\n this.logger = context?.logger ?? console;\n this.profile = config.profile;\n this.bucketName = config.bucketName;\n this.region = config.region;\n this.endpoint = config.endpoint || null;\n\n const clientConfig = { region: config.region };\n if (config.endpoint) clientConfig.endpoint = config.endpoint;\n if (config.forcePathStyle) clientConfig.forcePathStyle = true;\n if (config.accessKeyId && config.secretAccessKey) {\n clientConfig.credentials = {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n };\n }\n\n this.client = new S3Client(clientConfig);\n }\n\n // ── info ────────────────────────────────────────────────────────────────\n getBucketName() { return this.bucketName; }\n getProfile() { return this.profile; }\n getRegion() { return this.region; }\n getEndpoint() { return this.endpoint; }\n\n // ── reachability ────────────────────────────────────────────────────────\n async bucketExists() {\n await this.client.send(new HeadBucketCommand({ Bucket: this.bucketName }));\n return true;\n }\n\n // ── HEAD / GET ──────────────────────────────────────────────────────────\n /** Returns null on 404; never throws for \"missing\". Other errors throw. */\n async headObject(key) {\n try {\n const out = await this.client.send(new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n return {\n etag: out.ETag,\n size: out.ContentLength,\n contentType: out.ContentType,\n lastModified: out.LastModified,\n metadata: out.Metadata,\n storageClass: out.StorageClass,\n };\n } catch (err) {\n if (this._isNotFound(err)) return null;\n throw err;\n }\n }\n\n /** Returns { body: Readable, contentType, contentLength, etag, ... } or null on 404. */\n async getObject(key) {\n try {\n const out = await this.client.send(new GetObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n return {\n body: out.Body,\n contentType: out.ContentType,\n contentLength: out.ContentLength,\n etag: out.ETag,\n lastModified: out.LastModified,\n metadata: out.Metadata,\n };\n } catch (err) {\n if (this._isNotFound(err)) return null;\n throw err;\n }\n }\n\n /** Buffers the whole object. Use only for small objects (manifests, JSON). */\n async getObjectBytes(key) {\n const obj = await this.getObject(key);\n if (!obj) return null;\n const chunks = [];\n for await (const chunk of obj.body) chunks.push(chunk);\n return { ...obj, body: Buffer.concat(chunks) };\n }\n\n /** Convenience for JSON manifests. Returns parsed object or null on 404. */\n async getJson(key) {\n const obj = await this.getObjectBytes(key);\n if (!obj) return null;\n return JSON.parse(obj.body.toString(\"utf8\"));\n }\n\n // ── PUT ─────────────────────────────────────────────────────────────────\n async putObject({ key, body, contentType, contentLength, tags, metadata }) {\n const cmd = new PutObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n Body: body,\n ...(contentType && { ContentType: contentType }),\n ...(contentLength != null && { ContentLength: contentLength }),\n ...(metadata && { Metadata: metadata }),\n ...(tags && { Tagging: this._tagsToQuery(tags) }),\n });\n return this.client.send(cmd);\n }\n\n /** Convenience for JSON manifests. */\n async putJson(key, value, opts = {}) {\n const json = JSON.stringify(value, null, opts.pretty ? 2 : 0);\n const body = Buffer.from(json, \"utf8\");\n return this.putObject({\n key,\n body,\n contentType: \"application/json\",\n contentLength: body.length,\n tags: opts.tags,\n metadata: opts.metadata,\n });\n }\n\n // ── DELETE ──────────────────────────────────────────────────────────────\n async deleteObject(key) {\n return this.client.send(new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n }\n\n // ── COPY (for migration: legacy → new bucket, or intra-bucket \"rename\") ─\n async copyObject({ sourceBucket, sourceKey, key, contentType, metadata, tags }) {\n const src = sourceBucket || this.bucketName;\n const cmd = new CopyObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n CopySource: encodeURIComponent(`${src}/${sourceKey}`),\n ...(contentType && {\n ContentType: contentType,\n MetadataDirective: \"REPLACE\",\n }),\n ...(metadata && {\n Metadata: metadata,\n MetadataDirective: \"REPLACE\",\n }),\n ...(tags && {\n Tagging: this._tagsToQuery(tags),\n TaggingDirective: \"REPLACE\",\n }),\n });\n return this.client.send(cmd);\n }\n\n // ── LIST ────────────────────────────────────────────────────────────────\n async listObjects(prefix, { keysOnly = false, maxKeys = 1000, continuationToken } = {}) {\n const out = await this.client.send(new ListObjectsV2Command({\n Bucket: this.bucketName,\n Prefix: prefix,\n MaxKeys: maxKeys,\n ContinuationToken: continuationToken,\n }));\n const items = (out.Contents ?? []).map(o => ({\n key: o.Key,\n size: o.Size,\n etag: o.ETag,\n lastModified: o.LastModified,\n storageClass: o.StorageClass,\n }));\n return {\n items: keysOnly ? items.map(i => i.key) : items,\n isTruncated: !!out.IsTruncated,\n nextContinuationToken: out.NextContinuationToken,\n };\n }\n\n // ── TAGS (used for lifecycle rules, e.g. status=closed → Glacier IR) ────\n async putObjectTagging(key, tags) {\n return this.client.send(new PutObjectTaggingCommand({\n Bucket: this.bucketName,\n Key: key,\n Tagging: { TagSet: this._tagsToTagSet(tags) },\n }));\n }\n\n async getObjectTagging(key) {\n const out = await this.client.send(new GetObjectTaggingCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n const tags = {};\n for (const t of out.TagSet ?? []) tags[t.Key] = t.Value;\n return tags;\n }\n\n // ── internals ───────────────────────────────────────────────────────────\n _isNotFound(err) {\n const status = err?.$metadata?.httpStatusCode;\n return status === 404\n || err?.name === \"NotFound\"\n || err?.name === \"NoSuchKey\"\n || err?.Code === \"NoSuchKey\";\n }\n\n _tagsToQuery(tags) {\n return Object.entries(tags)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join(\"&\");\n }\n\n _tagsToTagSet(tags) {\n return Object.entries(tags).map(([Key, Value]) => ({ Key, Value: String(Value) }));\n }\n}\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message, cause) {\n super(message);this.cause = cause;;\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n"],"mappings":"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","names":[]}
|
package/dist/s3.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// src/s3/index.js
|
|
2
|
+
import {
|
|
3
|
+
S3Client,
|
|
4
|
+
HeadBucketCommand,
|
|
5
|
+
HeadObjectCommand,
|
|
6
|
+
GetObjectCommand,
|
|
7
|
+
PutObjectCommand,
|
|
8
|
+
DeleteObjectCommand,
|
|
9
|
+
CopyObjectCommand,
|
|
10
|
+
ListObjectsV2Command,
|
|
11
|
+
PutObjectTaggingCommand,
|
|
12
|
+
GetObjectTaggingCommand
|
|
13
|
+
} from "@aws-sdk/client-s3";
|
|
14
|
+
|
|
15
|
+
// src/errors.js
|
|
16
|
+
var FrameworkError = class extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "FrameworkError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var ParamError = class extends FrameworkError {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "ParamError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/s3/index.js
|
|
30
|
+
var DEFAULT_PROFILE = "local";
|
|
31
|
+
function capitalize(str) {
|
|
32
|
+
if (!str) return str;
|
|
33
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
34
|
+
}
|
|
35
|
+
var S3 = class _S3 {
|
|
36
|
+
/**
|
|
37
|
+
* Build an S3 instance. Reads bucket profile from --bucket (default "local"),
|
|
38
|
+
* then resolves per-profile params, builds the SDK client, and returns the
|
|
39
|
+
* instance. Optionally pings the bucket once to verify reachability.
|
|
40
|
+
*/
|
|
41
|
+
static async init(context, options = {}) {
|
|
42
|
+
const profileDef = { bucket: "string" };
|
|
43
|
+
const discovered = context?.params?.getAllForModule?.("s3", profileDef) ?? {};
|
|
44
|
+
const profile = options.bucket ?? discovered.bucket ?? DEFAULT_PROFILE;
|
|
45
|
+
const cap = capitalize(profile);
|
|
46
|
+
const config = {
|
|
47
|
+
profile,
|
|
48
|
+
bucketName: options.bucketName ?? await context.params.get(`s3Bucket${cap}`, "string"),
|
|
49
|
+
region: options.region ?? await context.params.get(`s3Region${cap}`, "string default us-east-1"),
|
|
50
|
+
endpoint: options.endpoint ?? await context.params.get(`s3Endpoint${cap}`, "string"),
|
|
51
|
+
forcePathStyle: options.forcePathStyle ?? await context.params.get(`s3ForcePathStyle${cap}`, "boolean default false"),
|
|
52
|
+
accessKeyId: options.accessKeyId ?? await context.params.get(`s3AccessKeyId${cap}`, "string"),
|
|
53
|
+
secretAccessKey: options.secretAccessKey ?? await context.params.get(`s3SecretAccessKey${cap}`, "string")
|
|
54
|
+
};
|
|
55
|
+
if (!config.bucketName) {
|
|
56
|
+
throw new ParamError(
|
|
57
|
+
`S3: bucket name not configured for profile "${profile}" (set s3Bucket${cap} or S3_BUCKET_${profile.toUpperCase()})`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const s3 = new _S3(context, config);
|
|
61
|
+
if (options.testBucket !== false) {
|
|
62
|
+
try {
|
|
63
|
+
await s3.bucketExists();
|
|
64
|
+
context.logger?.debug?.(
|
|
65
|
+
`[S3] profile="${profile}" bucket="${config.bucketName}" reachable`
|
|
66
|
+
);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
context.logger?.warn?.(
|
|
69
|
+
`[S3] profile="${profile}" bucket="${config.bucketName}" reachability test failed: ${err?.message ?? err}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return s3;
|
|
74
|
+
}
|
|
75
|
+
constructor(context, config) {
|
|
76
|
+
this.logger = context?.logger ?? console;
|
|
77
|
+
this.profile = config.profile;
|
|
78
|
+
this.bucketName = config.bucketName;
|
|
79
|
+
this.region = config.region;
|
|
80
|
+
this.endpoint = config.endpoint || null;
|
|
81
|
+
const clientConfig = { region: config.region };
|
|
82
|
+
if (config.endpoint) clientConfig.endpoint = config.endpoint;
|
|
83
|
+
if (config.forcePathStyle) clientConfig.forcePathStyle = true;
|
|
84
|
+
if (config.accessKeyId && config.secretAccessKey) {
|
|
85
|
+
clientConfig.credentials = {
|
|
86
|
+
accessKeyId: config.accessKeyId,
|
|
87
|
+
secretAccessKey: config.secretAccessKey
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
this.client = new S3Client(clientConfig);
|
|
91
|
+
}
|
|
92
|
+
// ── info ────────────────────────────────────────────────────────────────
|
|
93
|
+
getBucketName() {
|
|
94
|
+
return this.bucketName;
|
|
95
|
+
}
|
|
96
|
+
getProfile() {
|
|
97
|
+
return this.profile;
|
|
98
|
+
}
|
|
99
|
+
getRegion() {
|
|
100
|
+
return this.region;
|
|
101
|
+
}
|
|
102
|
+
getEndpoint() {
|
|
103
|
+
return this.endpoint;
|
|
104
|
+
}
|
|
105
|
+
// ── reachability ────────────────────────────────────────────────────────
|
|
106
|
+
async bucketExists() {
|
|
107
|
+
await this.client.send(new HeadBucketCommand({ Bucket: this.bucketName }));
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
// ── HEAD / GET ──────────────────────────────────────────────────────────
|
|
111
|
+
/** Returns null on 404; never throws for "missing". Other errors throw. */
|
|
112
|
+
async headObject(key) {
|
|
113
|
+
try {
|
|
114
|
+
const out = await this.client.send(new HeadObjectCommand({
|
|
115
|
+
Bucket: this.bucketName,
|
|
116
|
+
Key: key
|
|
117
|
+
}));
|
|
118
|
+
return {
|
|
119
|
+
etag: out.ETag,
|
|
120
|
+
size: out.ContentLength,
|
|
121
|
+
contentType: out.ContentType,
|
|
122
|
+
lastModified: out.LastModified,
|
|
123
|
+
metadata: out.Metadata,
|
|
124
|
+
storageClass: out.StorageClass
|
|
125
|
+
};
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (this._isNotFound(err)) return null;
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Returns { body: Readable, contentType, contentLength, etag, ... } or null on 404. */
|
|
132
|
+
async getObject(key) {
|
|
133
|
+
try {
|
|
134
|
+
const out = await this.client.send(new GetObjectCommand({
|
|
135
|
+
Bucket: this.bucketName,
|
|
136
|
+
Key: key
|
|
137
|
+
}));
|
|
138
|
+
return {
|
|
139
|
+
body: out.Body,
|
|
140
|
+
contentType: out.ContentType,
|
|
141
|
+
contentLength: out.ContentLength,
|
|
142
|
+
etag: out.ETag,
|
|
143
|
+
lastModified: out.LastModified,
|
|
144
|
+
metadata: out.Metadata
|
|
145
|
+
};
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (this._isNotFound(err)) return null;
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Buffers the whole object. Use only for small objects (manifests, JSON). */
|
|
152
|
+
async getObjectBytes(key) {
|
|
153
|
+
const obj = await this.getObject(key);
|
|
154
|
+
if (!obj) return null;
|
|
155
|
+
const chunks = [];
|
|
156
|
+
for await (const chunk of obj.body) chunks.push(chunk);
|
|
157
|
+
return { ...obj, body: Buffer.concat(chunks) };
|
|
158
|
+
}
|
|
159
|
+
/** Convenience for JSON manifests. Returns parsed object or null on 404. */
|
|
160
|
+
async getJson(key) {
|
|
161
|
+
const obj = await this.getObjectBytes(key);
|
|
162
|
+
if (!obj) return null;
|
|
163
|
+
return JSON.parse(obj.body.toString("utf8"));
|
|
164
|
+
}
|
|
165
|
+
// ── PUT ─────────────────────────────────────────────────────────────────
|
|
166
|
+
async putObject({ key, body, contentType, contentLength, tags, metadata }) {
|
|
167
|
+
const cmd = new PutObjectCommand({
|
|
168
|
+
Bucket: this.bucketName,
|
|
169
|
+
Key: key,
|
|
170
|
+
Body: body,
|
|
171
|
+
...contentType && { ContentType: contentType },
|
|
172
|
+
...contentLength != null && { ContentLength: contentLength },
|
|
173
|
+
...metadata && { Metadata: metadata },
|
|
174
|
+
...tags && { Tagging: this._tagsToQuery(tags) }
|
|
175
|
+
});
|
|
176
|
+
return this.client.send(cmd);
|
|
177
|
+
}
|
|
178
|
+
/** Convenience for JSON manifests. */
|
|
179
|
+
async putJson(key, value, opts = {}) {
|
|
180
|
+
const json = JSON.stringify(value, null, opts.pretty ? 2 : 0);
|
|
181
|
+
const body = Buffer.from(json, "utf8");
|
|
182
|
+
return this.putObject({
|
|
183
|
+
key,
|
|
184
|
+
body,
|
|
185
|
+
contentType: "application/json",
|
|
186
|
+
contentLength: body.length,
|
|
187
|
+
tags: opts.tags,
|
|
188
|
+
metadata: opts.metadata
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// ── DELETE ──────────────────────────────────────────────────────────────
|
|
192
|
+
async deleteObject(key) {
|
|
193
|
+
return this.client.send(new DeleteObjectCommand({
|
|
194
|
+
Bucket: this.bucketName,
|
|
195
|
+
Key: key
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
// ── COPY (for migration: legacy → new bucket, or intra-bucket "rename") ─
|
|
199
|
+
async copyObject({ sourceBucket, sourceKey, key, contentType, metadata, tags }) {
|
|
200
|
+
const src = sourceBucket || this.bucketName;
|
|
201
|
+
const cmd = new CopyObjectCommand({
|
|
202
|
+
Bucket: this.bucketName,
|
|
203
|
+
Key: key,
|
|
204
|
+
CopySource: encodeURIComponent(`${src}/${sourceKey}`),
|
|
205
|
+
...contentType && {
|
|
206
|
+
ContentType: contentType,
|
|
207
|
+
MetadataDirective: "REPLACE"
|
|
208
|
+
},
|
|
209
|
+
...metadata && {
|
|
210
|
+
Metadata: metadata,
|
|
211
|
+
MetadataDirective: "REPLACE"
|
|
212
|
+
},
|
|
213
|
+
...tags && {
|
|
214
|
+
Tagging: this._tagsToQuery(tags),
|
|
215
|
+
TaggingDirective: "REPLACE"
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return this.client.send(cmd);
|
|
219
|
+
}
|
|
220
|
+
// ── LIST ────────────────────────────────────────────────────────────────
|
|
221
|
+
async listObjects(prefix, { keysOnly = false, maxKeys = 1e3, continuationToken } = {}) {
|
|
222
|
+
const out = await this.client.send(new ListObjectsV2Command({
|
|
223
|
+
Bucket: this.bucketName,
|
|
224
|
+
Prefix: prefix,
|
|
225
|
+
MaxKeys: maxKeys,
|
|
226
|
+
ContinuationToken: continuationToken
|
|
227
|
+
}));
|
|
228
|
+
const items = (out.Contents ?? []).map((o) => ({
|
|
229
|
+
key: o.Key,
|
|
230
|
+
size: o.Size,
|
|
231
|
+
etag: o.ETag,
|
|
232
|
+
lastModified: o.LastModified,
|
|
233
|
+
storageClass: o.StorageClass
|
|
234
|
+
}));
|
|
235
|
+
return {
|
|
236
|
+
items: keysOnly ? items.map((i) => i.key) : items,
|
|
237
|
+
isTruncated: !!out.IsTruncated,
|
|
238
|
+
nextContinuationToken: out.NextContinuationToken
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// ── TAGS (used for lifecycle rules, e.g. status=closed → Glacier IR) ────
|
|
242
|
+
async putObjectTagging(key, tags) {
|
|
243
|
+
return this.client.send(new PutObjectTaggingCommand({
|
|
244
|
+
Bucket: this.bucketName,
|
|
245
|
+
Key: key,
|
|
246
|
+
Tagging: { TagSet: this._tagsToTagSet(tags) }
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
async getObjectTagging(key) {
|
|
250
|
+
const out = await this.client.send(new GetObjectTaggingCommand({
|
|
251
|
+
Bucket: this.bucketName,
|
|
252
|
+
Key: key
|
|
253
|
+
}));
|
|
254
|
+
const tags = {};
|
|
255
|
+
for (const t of out.TagSet ?? []) tags[t.Key] = t.Value;
|
|
256
|
+
return tags;
|
|
257
|
+
}
|
|
258
|
+
// ── internals ───────────────────────────────────────────────────────────
|
|
259
|
+
_isNotFound(err) {
|
|
260
|
+
const status = err?.$metadata?.httpStatusCode;
|
|
261
|
+
return status === 404 || err?.name === "NotFound" || err?.name === "NoSuchKey" || err?.Code === "NoSuchKey";
|
|
262
|
+
}
|
|
263
|
+
_tagsToQuery(tags) {
|
|
264
|
+
return Object.entries(tags).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
265
|
+
}
|
|
266
|
+
_tagsToTagSet(tags) {
|
|
267
|
+
return Object.entries(tags).map(([Key, Value]) => ({ Key, Value: String(Value) }));
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
export {
|
|
271
|
+
S3
|
|
272
|
+
};
|
|
273
|
+
//# sourceMappingURL=s3.js.map
|
package/dist/s3.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/s3/index.js","../src/errors.js"],"sourcesContent":["/**\n * S3 component\n *\n * Thin async wrapper over @aws-sdk/client-s3 v3 with the cli-toolkit conventions:\n * const s3 = await S3.init(context);\n *\n * Supports multiple \"profiles\" (local, prod, stage, …) selected at runtime via\n * `--bucket=<profile>` (or `S3_BUCKET=<profile>`). Each profile has its own set\n * of env-suffixed parameters resolved by the framework, mirroring how `Db.init`\n * uses `dbName` to look up `dbConnectionString${Name}`.\n *\n * Profile selector:\n * --bucket=local (default)\n * --bucket=prod\n * --bucket=stage\n *\n * Per-profile parameters (all read via context.params; resolved from CLI,\n * env, .env files, defaults — see @nmakarov/cli-toolkit/params docs):\n * s3Bucket{Profile} string required\n * s3Region{Profile} string default us-east-1\n * s3Endpoint{Profile} string optional (set for MinIO/local; unset = real AWS)\n * s3ForcePathStyle{Profile} boolean default false (true for MinIO)\n * s3AccessKeyId{Profile} string optional (omit on EC2 to use instance profile)\n * s3SecretAccessKey{Profile} string optional\n *\n * Equivalent env vars are SCREAMING_SNAKE_CASE with the profile suffix:\n * S3_BUCKET_LOCAL, S3_REGION_LOCAL, S3_ENDPOINT_LOCAL,\n * S3_FORCE_PATH_STYLE_LOCAL, S3_ACCESS_KEY_ID_LOCAL, S3_SECRET_ACCESS_KEY_LOCAL\n *\n * Example (local with MinIO from docker-compose):\n * S3_BUCKET_LOCAL=photos-local\n * S3_REGION_LOCAL=us-east-1\n * S3_ENDPOINT_LOCAL=http://localhost:9000\n * S3_FORCE_PATH_STYLE_LOCAL=true\n * S3_ACCESS_KEY_ID_LOCAL=dev\n * S3_SECRET_ACCESS_KEY_LOCAL=devdevdev\n */\n\nimport {\n S3Client,\n HeadBucketCommand,\n HeadObjectCommand,\n GetObjectCommand,\n PutObjectCommand,\n DeleteObjectCommand,\n CopyObjectCommand,\n ListObjectsV2Command,\n PutObjectTaggingCommand,\n GetObjectTaggingCommand,\n} from \"@aws-sdk/client-s3\";\n\nimport { ParamError } from \"../errors.js\";\n\nconst DEFAULT_PROFILE = \"local\";\n\nfunction capitalize(str) {\n if (!str) return str;\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport class S3 {\n /**\n * Build an S3 instance. Reads bucket profile from --bucket (default \"local\"),\n * then resolves per-profile params, builds the SDK client, and returns the\n * instance. Optionally pings the bucket once to verify reachability.\n */\n static async init(context, options = {}) {\n const profileDef = { bucket: \"string\" };\n const discovered = context?.params?.getAllForModule?.(\"s3\", profileDef) ?? {};\n const profile = options.bucket ?? discovered.bucket ?? DEFAULT_PROFILE;\n const cap = capitalize(profile);\n\n const config = {\n profile,\n bucketName: options.bucketName\n ?? await context.params.get(`s3Bucket${cap}`, \"string\"),\n region: options.region\n ?? await context.params.get(`s3Region${cap}`, \"string default us-east-1\"),\n endpoint: options.endpoint\n ?? await context.params.get(`s3Endpoint${cap}`, \"string\"),\n forcePathStyle: options.forcePathStyle\n ?? await context.params.get(`s3ForcePathStyle${cap}`, \"boolean default false\"),\n accessKeyId: options.accessKeyId\n ?? await context.params.get(`s3AccessKeyId${cap}`, \"string\"),\n secretAccessKey: options.secretAccessKey\n ?? await context.params.get(`s3SecretAccessKey${cap}`, \"string\"),\n };\n\n if (!config.bucketName) {\n throw new ParamError(\n `S3: bucket name not configured for profile \"${profile}\" `\n + `(set s3Bucket${cap} or S3_BUCKET_${profile.toUpperCase()})`\n );\n }\n\n const s3 = new S3(context, config);\n\n if (options.testBucket !== false) {\n try {\n await s3.bucketExists();\n context.logger?.debug?.(\n `[S3] profile=\"${profile}\" bucket=\"${config.bucketName}\" reachable`\n );\n } catch (err) {\n context.logger?.warn?.(\n `[S3] profile=\"${profile}\" bucket=\"${config.bucketName}\" `\n + `reachability test failed: ${err?.message ?? err}`\n );\n }\n }\n\n return s3;\n }\n\n constructor(context, config) {\n this.logger = context?.logger ?? console;\n this.profile = config.profile;\n this.bucketName = config.bucketName;\n this.region = config.region;\n this.endpoint = config.endpoint || null;\n\n const clientConfig = { region: config.region };\n if (config.endpoint) clientConfig.endpoint = config.endpoint;\n if (config.forcePathStyle) clientConfig.forcePathStyle = true;\n if (config.accessKeyId && config.secretAccessKey) {\n clientConfig.credentials = {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n };\n }\n\n this.client = new S3Client(clientConfig);\n }\n\n // ── info ────────────────────────────────────────────────────────────────\n getBucketName() { return this.bucketName; }\n getProfile() { return this.profile; }\n getRegion() { return this.region; }\n getEndpoint() { return this.endpoint; }\n\n // ── reachability ────────────────────────────────────────────────────────\n async bucketExists() {\n await this.client.send(new HeadBucketCommand({ Bucket: this.bucketName }));\n return true;\n }\n\n // ── HEAD / GET ──────────────────────────────────────────────────────────\n /** Returns null on 404; never throws for \"missing\". Other errors throw. */\n async headObject(key) {\n try {\n const out = await this.client.send(new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n return {\n etag: out.ETag,\n size: out.ContentLength,\n contentType: out.ContentType,\n lastModified: out.LastModified,\n metadata: out.Metadata,\n storageClass: out.StorageClass,\n };\n } catch (err) {\n if (this._isNotFound(err)) return null;\n throw err;\n }\n }\n\n /** Returns { body: Readable, contentType, contentLength, etag, ... } or null on 404. */\n async getObject(key) {\n try {\n const out = await this.client.send(new GetObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n return {\n body: out.Body,\n contentType: out.ContentType,\n contentLength: out.ContentLength,\n etag: out.ETag,\n lastModified: out.LastModified,\n metadata: out.Metadata,\n };\n } catch (err) {\n if (this._isNotFound(err)) return null;\n throw err;\n }\n }\n\n /** Buffers the whole object. Use only for small objects (manifests, JSON). */\n async getObjectBytes(key) {\n const obj = await this.getObject(key);\n if (!obj) return null;\n const chunks = [];\n for await (const chunk of obj.body) chunks.push(chunk);\n return { ...obj, body: Buffer.concat(chunks) };\n }\n\n /** Convenience for JSON manifests. Returns parsed object or null on 404. */\n async getJson(key) {\n const obj = await this.getObjectBytes(key);\n if (!obj) return null;\n return JSON.parse(obj.body.toString(\"utf8\"));\n }\n\n // ── PUT ─────────────────────────────────────────────────────────────────\n async putObject({ key, body, contentType, contentLength, tags, metadata }) {\n const cmd = new PutObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n Body: body,\n ...(contentType && { ContentType: contentType }),\n ...(contentLength != null && { ContentLength: contentLength }),\n ...(metadata && { Metadata: metadata }),\n ...(tags && { Tagging: this._tagsToQuery(tags) }),\n });\n return this.client.send(cmd);\n }\n\n /** Convenience for JSON manifests. */\n async putJson(key, value, opts = {}) {\n const json = JSON.stringify(value, null, opts.pretty ? 2 : 0);\n const body = Buffer.from(json, \"utf8\");\n return this.putObject({\n key,\n body,\n contentType: \"application/json\",\n contentLength: body.length,\n tags: opts.tags,\n metadata: opts.metadata,\n });\n }\n\n // ── DELETE ──────────────────────────────────────────────────────────────\n async deleteObject(key) {\n return this.client.send(new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n }\n\n // ── COPY (for migration: legacy → new bucket, or intra-bucket \"rename\") ─\n async copyObject({ sourceBucket, sourceKey, key, contentType, metadata, tags }) {\n const src = sourceBucket || this.bucketName;\n const cmd = new CopyObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n CopySource: encodeURIComponent(`${src}/${sourceKey}`),\n ...(contentType && {\n ContentType: contentType,\n MetadataDirective: \"REPLACE\",\n }),\n ...(metadata && {\n Metadata: metadata,\n MetadataDirective: \"REPLACE\",\n }),\n ...(tags && {\n Tagging: this._tagsToQuery(tags),\n TaggingDirective: \"REPLACE\",\n }),\n });\n return this.client.send(cmd);\n }\n\n // ── LIST ────────────────────────────────────────────────────────────────\n async listObjects(prefix, { keysOnly = false, maxKeys = 1000, continuationToken } = {}) {\n const out = await this.client.send(new ListObjectsV2Command({\n Bucket: this.bucketName,\n Prefix: prefix,\n MaxKeys: maxKeys,\n ContinuationToken: continuationToken,\n }));\n const items = (out.Contents ?? []).map(o => ({\n key: o.Key,\n size: o.Size,\n etag: o.ETag,\n lastModified: o.LastModified,\n storageClass: o.StorageClass,\n }));\n return {\n items: keysOnly ? items.map(i => i.key) : items,\n isTruncated: !!out.IsTruncated,\n nextContinuationToken: out.NextContinuationToken,\n };\n }\n\n // ── TAGS (used for lifecycle rules, e.g. status=closed → Glacier IR) ────\n async putObjectTagging(key, tags) {\n return this.client.send(new PutObjectTaggingCommand({\n Bucket: this.bucketName,\n Key: key,\n Tagging: { TagSet: this._tagsToTagSet(tags) },\n }));\n }\n\n async getObjectTagging(key) {\n const out = await this.client.send(new GetObjectTaggingCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n const tags = {};\n for (const t of out.TagSet ?? []) tags[t.Key] = t.Value;\n return tags;\n }\n\n // ── internals ───────────────────────────────────────────────────────────\n _isNotFound(err) {\n const status = err?.$metadata?.httpStatusCode;\n return status === 404\n || err?.name === \"NotFound\"\n || err?.name === \"NoSuchKey\"\n || err?.Code === \"NoSuchKey\";\n }\n\n _tagsToQuery(tags) {\n return Object.entries(tags)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join(\"&\");\n }\n\n _tagsToTagSet(tags) {\n return Object.entries(tags).map(([Key, Value]) => ({ Key, Value: String(Value) }));\n }\n}\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message, cause) {\n super(message);this.cause = cause;;\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n"],"mappings":"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","names":[]}
|