@s-hirano-ist/s-scripts 4.1.0 → 4.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.
@@ -2,7 +2,7 @@ import { createRequire as __cliCreateRequire } from "node:module"; const require
2
2
  import {
3
3
  makeExportedAt,
4
4
  withTransactionRetry
5
- } from "./chunk-DIG75T3J.js";
5
+ } from "./chunk-RLAYINCA.js";
6
6
 
7
7
  // ../application/dist/batch/notes-batch-service.js
8
8
  var NotesBatchApplicationService = class {
@@ -2,7 +2,7 @@ import { createRequire as __cliCreateRequire } from "node:module"; const require
2
2
  import {
3
3
  makeExportedAt,
4
4
  withTransactionRetry
5
- } from "./chunk-DIG75T3J.js";
5
+ } from "./chunk-RLAYINCA.js";
6
6
 
7
7
  // ../application/dist/batch/articles-batch-service.js
8
8
  var ArticlesBatchApplicationService = class {
@@ -0,0 +1,160 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+
3
+ // ../storage/dist/src/client.js
4
+ import { S3Client, CreateBucketCommand, DeleteBucketCommand, PutObjectCommand, GetObjectCommand, HeadObjectCommand, DeleteObjectCommand, paginateListObjectsV2 } from "@aws-sdk/client-s3";
5
+ import { createWriteStream } from "node:fs";
6
+ import { Readable } from "node:stream";
7
+ import { pipeline } from "node:stream/promises";
8
+ function loadStorageConfig(env = process.env) {
9
+ const required = (key) => {
10
+ const value = env[key];
11
+ if (!value)
12
+ throw new Error(`${key} is required`);
13
+ return value;
14
+ };
15
+ const endpoint = required("S3_ENDPOINT");
16
+ const url = new URL(endpoint);
17
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== "/")
18
+ throw new Error("S3_ENDPOINT must be an HTTP(S) origin");
19
+ const pathStyle = env.S3_FORCE_PATH_STYLE ?? "false";
20
+ if (pathStyle !== "true" && pathStyle !== "false")
21
+ throw new Error("S3_FORCE_PATH_STYLE must be true or false");
22
+ return {
23
+ endpoint,
24
+ region: required("S3_REGION"),
25
+ accessKeyId: required("S3_ACCESS_KEY_ID"),
26
+ secretAccessKey: required("S3_SECRET_ACCESS_KEY"),
27
+ forcePathStyle: pathStyle === "true"
28
+ };
29
+ }
30
+ function isObjectNotFound(error) {
31
+ if (typeof error !== "object" || error === null)
32
+ return false;
33
+ const value = error;
34
+ return value.$metadata?.httpStatusCode === 404 && (value.name === "NotFound" || value.name === "NoSuchKey");
35
+ }
36
+ function createObjectStorageClient(config = loadStorageConfig()) {
37
+ const client = new S3Client({
38
+ endpoint: config.endpoint,
39
+ region: config.region,
40
+ credentials: {
41
+ accessKeyId: config.accessKeyId,
42
+ secretAccessKey: config.secretAccessKey
43
+ },
44
+ forcePathStyle: config.forcePathStyle
45
+ });
46
+ return {
47
+ async createBucket(bucket) {
48
+ await client.send(new CreateBucketCommand({ Bucket: bucket }));
49
+ },
50
+ async deleteBucket(bucket) {
51
+ await client.send(new DeleteBucketCommand({ Bucket: bucket }));
52
+ },
53
+ async putObject(bucket, key, body) {
54
+ const result = await client.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: body }));
55
+ return { etag: result.ETag, versionId: result.VersionId };
56
+ },
57
+ async getObject(bucket, key) {
58
+ const result = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
59
+ if (!(result.Body instanceof Readable))
60
+ throw new Error("Object storage returned no Node readable body");
61
+ return result.Body;
62
+ },
63
+ async downloadObject(bucket, key, destination) {
64
+ const result = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
65
+ if (!(result.Body instanceof Readable))
66
+ throw new Error("Object storage returned no Node readable body");
67
+ await pipeline(result.Body, createWriteStream(destination));
68
+ },
69
+ async statObject(bucket, key) {
70
+ const result = await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
71
+ return {
72
+ size: result.ContentLength,
73
+ etag: result.ETag,
74
+ lastModified: result.LastModified,
75
+ metaData: result.Metadata ?? {}
76
+ };
77
+ },
78
+ async removeObject(bucket, key) {
79
+ await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
80
+ },
81
+ async listObjects(bucket, prefix) {
82
+ const keys = [];
83
+ for await (const page of paginateListObjectsV2({ client }, { Bucket: bucket, Prefix: prefix })) {
84
+ for (const object of page.Contents ?? [])
85
+ if (object.Key)
86
+ keys.push(object.Key);
87
+ }
88
+ return keys;
89
+ },
90
+ destroy() {
91
+ client.destroy();
92
+ }
93
+ };
94
+ }
95
+
96
+ // ../storage/dist/index.js
97
+ import { S3ServiceException } from "@aws-sdk/client-s3";
98
+
99
+ // ../storage/dist/src/errors.js
100
+ var StorageOperationError = class extends Error {
101
+ context;
102
+ constructor(context, cause) {
103
+ super(`Storage ${context.operation} failed for ${context.objectKey}`, {
104
+ cause
105
+ });
106
+ this.name = "StorageOperationError";
107
+ this.context = context;
108
+ }
109
+ };
110
+
111
+ // ../storage/dist/src/storage-service.js
112
+ function createStorageService(client, bucketName, pathConfig) {
113
+ function buildObjectKey(path, isThumbnail) {
114
+ return `${isThumbnail ? pathConfig.thumbnailPrefix : pathConfig.originalPrefix}/${path}`;
115
+ }
116
+ function wrapStorageError(operation, objectKey, isThumbnail, error) {
117
+ throw new StorageOperationError({ operation, objectKey, bucketName, isThumbnail }, error);
118
+ }
119
+ return {
120
+ async uploadImage(path, bytes, isThumbnail) {
121
+ const objKey = buildObjectKey(path, isThumbnail);
122
+ try {
123
+ await client.putObject(bucketName, objKey, Buffer.from(bytes));
124
+ } catch (error) {
125
+ wrapStorageError("uploadImage", objKey, isThumbnail, error);
126
+ }
127
+ },
128
+ async getImage(path, isThumbnail) {
129
+ const objKey = buildObjectKey(path, isThumbnail);
130
+ try {
131
+ return await client.getObject(bucketName, objKey);
132
+ } catch (error) {
133
+ return wrapStorageError("getImage", objKey, isThumbnail, error);
134
+ }
135
+ },
136
+ async getImageOrThrow(path, isThumbnail) {
137
+ const objKey = buildObjectKey(path, isThumbnail);
138
+ try {
139
+ await client.statObject(bucketName, objKey);
140
+ } catch (error) {
141
+ wrapStorageError("getImageOrThrow", objKey, isThumbnail, error);
142
+ }
143
+ },
144
+ async deleteImage(path, isThumbnail) {
145
+ const objKey = buildObjectKey(path, isThumbnail);
146
+ try {
147
+ await client.removeObject(bucketName, objKey);
148
+ } catch (error) {
149
+ wrapStorageError("deleteImage", objKey, isThumbnail, error);
150
+ }
151
+ }
152
+ };
153
+ }
154
+
155
+ export {
156
+ isObjectNotFound,
157
+ createObjectStorageClient,
158
+ StorageOperationError,
159
+ createStorageService
160
+ };
@@ -4,9 +4,11 @@ import {
4
4
  readImageMetadata
5
5
  } from "./chunk-KKZP7ZF3.js";
6
6
  import {
7
- createMinioClient,
8
- createStorageService
9
- } from "./chunk-F4J6JBR4.js";
7
+ StorageOperationError,
8
+ createObjectStorageClient,
9
+ createStorageService,
10
+ isObjectNotFound
11
+ } from "./chunk-M6LFKOUH.js";
10
12
  import {
11
13
  CreatedAt,
12
14
  ExportedStatus,
@@ -18,7 +20,7 @@ import {
18
20
  import_generated,
19
21
  makeId,
20
22
  withTransactionRetry
21
- } from "./chunk-DIG75T3J.js";
23
+ } from "./chunk-RLAYINCA.js";
22
24
  import {
23
25
  createPushoverService
24
26
  } from "./chunk-7PVU2Z32.js";
@@ -497,7 +499,9 @@ function createImportMediaStore(storage, processor) {
497
499
  const target = storage[domain];
498
500
  try {
499
501
  await target.getImageOrThrow(path, false);
500
- } catch {
502
+ } catch (error) {
503
+ if (!isObjectNotFound(error instanceof StorageOperationError ? error.cause : error))
504
+ throw error;
501
505
  await target.uploadImage(path, bytes, false);
502
506
  }
503
507
  await target.uploadImage(path, await processor.createThumbnail(bytes, 192, 192), true);
@@ -512,16 +516,16 @@ function createImportMediaStore(storage, processor) {
512
516
  // ../infrastructure/dist/shared/books-storage-service.js
513
517
  var ORIGINAL_BOOK_IMAGE_PATH = "books/original";
514
518
  var THUMBNAIL_BOOK_IMAGE_PATH = "books/thumbnail";
515
- function createBooksStorageService(minioClient, bucketName) {
516
- return createStorageService(minioClient, bucketName, {
519
+ function createBooksStorageService(storageClient, bucketName) {
520
+ return createStorageService(storageClient, bucketName, {
517
521
  originalPrefix: ORIGINAL_BOOK_IMAGE_PATH,
518
522
  thumbnailPrefix: THUMBNAIL_BOOK_IMAGE_PATH
519
523
  });
520
524
  }
521
525
 
522
- // ../infrastructure/dist/shared/minio-storage-service.js
523
- function createImagesStorageService(minioClient, bucketName) {
524
- return createStorageService(minioClient, bucketName, {
526
+ // ../infrastructure/dist/shared/images-storage-service.js
527
+ function createImagesStorageService(storageClient, bucketName) {
528
+ return createStorageService(storageClient, bucketName, {
525
529
  originalPrefix: "images/original",
526
530
  thumbnailPrefix: "images/thumbnail"
527
531
  });
@@ -545,10 +549,10 @@ async function runContentImport(domain, source) {
545
549
  });
546
550
  try {
547
551
  const mediaDomain = domain === "images" || domain === "books";
548
- const bucket = process.env.MINIO_BUCKET_NAME;
552
+ const bucket = process.env.S3_BUCKET_NAME;
549
553
  if (mediaDomain && !dryRun && !bucket)
550
- throw new Error("MINIO_BUCKET_NAME is required.");
551
- const client = mediaDomain && !dryRun ? createMinioClient() : void 0;
554
+ throw new Error("S3_BUCKET_NAME is required.");
555
+ const client = mediaDomain && !dryRun ? createObjectStorageClient() : void 0;
552
556
  const media = client && bucket ? createImportMediaStore(
553
557
  {
554
558
  images: createImagesStorageService(client, bucket),
@@ -573,11 +577,17 @@ async function runContentImport(domain, source) {
573
577
  }
574
578
  );
575
579
  console.log(summary);
576
- await notification.notifyInfo(`ingest-${domain} completed`, { caller: `ingest-${domain}` }).catch((error) => console.error("\u901A\u77E5\u5931\u6557:", error));
580
+ if (!dryRun)
581
+ await notification.notifyInfo(`ingest-${domain} completed`, {
582
+ caller: `ingest-${domain}`
583
+ }).catch((error) => console.error("\u901A\u77E5\u5931\u6557:", error));
577
584
  } catch (error) {
578
585
  console.error(error);
579
586
  process.exitCode = 1;
580
- await notification.notifyError(`ingest-${domain} failed`, { caller: `ingest-${domain}` }).catch((notifyError) => console.error("\u901A\u77E5\u5931\u6557:", notifyError));
587
+ if (!dryRun)
588
+ await notification.notifyError(`ingest-${domain} failed`, { caller: `ingest-${domain}` }).catch(
589
+ (notifyError) => console.error("\u901A\u77E5\u5931\u6557:", notifyError)
590
+ );
581
591
  } finally {
582
592
  await prisma.$disconnect();
583
593
  }