@s-hirano-ist/s-scripts 4.1.0 → 4.3.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 (43) hide show
  1. package/README.md +8 -0
  2. package/dist/{chunk-7XX57D7G.js → chunk-3INTMHPH.js} +1 -1
  3. package/dist/chunk-4CXH2QIX.js +445 -0
  4. package/dist/chunk-67LIMNXH.js +36 -0
  5. package/dist/{chunk-OCEV2QX3.js → chunk-BYEEIQ7O.js} +1 -1
  6. package/dist/{chunk-2PZXXOYG.js → chunk-GBQOCN2C.js} +24 -25
  7. package/dist/chunk-MJYGMGH5.js +382 -0
  8. package/dist/chunk-MVYAZJB2.js +160 -0
  9. package/dist/{chunk-DIG75T3J.js → chunk-RLAYINCA.js} +10 -13
  10. package/dist/{chunk-MNBEFEFR.js → chunk-RO5QUZFR.js} +1 -1
  11. package/dist/chunk-SFKQ2RAP.js +87 -0
  12. package/dist/{chunk-FVK44UTM.js → chunk-ZMQUO6OA.js} +1 -1
  13. package/dist/cleanup-minio-images.js +62 -91
  14. package/dist/enrich-books.js +6 -4
  15. package/dist/fetch-articles.js +6 -24
  16. package/dist/fetch-books.js +12 -35
  17. package/dist/fetch-images.js +12 -35
  18. package/dist/fetch-notes.js +6 -24
  19. package/dist/find-duplicate-json-articles.js +11 -13
  20. package/dist/fix-book-frontmatter.js +6 -4
  21. package/dist/fix-image-formats.js +7 -5
  22. package/dist/ingest-articles.js +7 -5
  23. package/dist/ingest-books.js +10 -8
  24. package/dist/ingest-images.js +7 -5
  25. package/dist/ingest-notes.js +10 -8
  26. package/dist/rag/delete-collection.js +5 -30
  27. package/dist/rag/ingest.js +5 -379
  28. package/dist/rag/search.js +5 -84
  29. package/dist/reset-articles.js +8 -23
  30. package/dist/reset-books.js +8 -23
  31. package/dist/reset-images.js +8 -23
  32. package/dist/reset-notes.js +8 -23
  33. package/dist/revert-articles.js +8 -23
  34. package/dist/revert-books.js +8 -23
  35. package/dist/revert-images.js +8 -23
  36. package/dist/revert-notes.js +8 -23
  37. package/dist/s-content-format.js +9 -11
  38. package/dist/update-json-articles.js +19 -41
  39. package/dist/update-raw-articles.js +14 -37
  40. package/dist/validate-json-articles.js +8 -5
  41. package/package.json +13 -11
  42. package/dist/chunk-7PVU2Z32.js +0 -71
  43. package/dist/chunk-F4J6JBR4.js +0 -115
package/README.md CHANGED
@@ -73,3 +73,11 @@ validate-json-articles
73
73
  ## License
74
74
 
75
75
  AGPL-3.0
76
+
77
+ ## Operational notifications
78
+
79
+ Set `NEXT_PUBLIC_SENTRY_DSN` to record CLI errors in Sentry. If it is missing, the command warns and continues with local diagnostics. Set `PUSHOVER_USER_KEY` and `PUSHOVER_APP_TOKEN` for completion and critical alerts; `PUSHOVER_URL` defaults to `https://api.pushover.net/1/messages.json`.
80
+
81
+ Unexpected item failures go to Sentry only. A failure that aborts the command, including initialization or cleanup, also sends a critical Pushover alert and sets a nonzero exit code. Existing completion notifications use Pushover only; auxiliary commands do not add success notifications. Expected usage and content validation failures keep their nonzero exit codes without critical alerts.
82
+
83
+ Notification delivery errors go to Sentry without changing the command's result or sending another Pushover alert. Commands wait up to two seconds for Sentry to flush before exiting; Pushover requests have a five-second timeout. Commands running with `--dry-run` do not initialize Sentry or send external notifications.
@@ -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 {
@@ -0,0 +1,445 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+
3
+ // ../infrastructure/dist/observability/diagnostic-error.js
4
+ function createDiagnosticError(env) {
5
+ const secrets = [
6
+ env.DATABASE_URL,
7
+ env.AUTH_SECRET,
8
+ env.AUTH0_CLIENT_SECRET,
9
+ env.LOCAL_AUTH_PASSWORD,
10
+ env.QDRANT_API_KEY,
11
+ env.S3_ACCESS_KEY_ID,
12
+ env.S3_SECRET_ACCESS_KEY,
13
+ env.WEB_PUSH_PRIVATE_KEY,
14
+ env.PUSHOVER_APP_TOKEN,
15
+ env.PUSHOVER_USER_KEY,
16
+ env.CRON_SECRET,
17
+ env.GOOGLE_BOOKS_API_KEY,
18
+ env.NEXT_PUBLIC_SENTRY_DSN
19
+ ].filter((value) => typeof value === "string" && value.length > 0);
20
+ function redact(input) {
21
+ let value = input;
22
+ for (const secret of secrets)
23
+ value = value.replaceAll(secret, "[REDACTED]");
24
+ return value.replaceAll(/\bBearer\s+[^\s,;]+/giu, "Bearer [REDACTED]").replaceAll(/\b(?:cookie|authorization|set-cookie)\s*[:=][^\r\n]*/giu, "[REDACTED HEADER]").replaceAll(/\b(?:https?|postgres(?:ql)?):\/\/\S+/giu, (match) => {
25
+ try {
26
+ const url = new URL(match);
27
+ if (!url.username && !url.password && !url.search && !url.hash)
28
+ return match;
29
+ if (url.username || url.password) {
30
+ url.username = "REDACTED";
31
+ url.password = "";
32
+ }
33
+ if (url.search || url.hash)
34
+ url.search = "?[REDACTED]";
35
+ url.hash = "";
36
+ return url.href;
37
+ } catch {
38
+ return "[REDACTED URL]";
39
+ }
40
+ });
41
+ }
42
+ return (error) => {
43
+ const diagnostic = new Error(error instanceof Error ? redact(error.message) : "Unknown operational error");
44
+ if (error instanceof Error) {
45
+ diagnostic.name = redact(error.name);
46
+ diagnostic.stack = error.stack ? redact(error.stack) : void 0;
47
+ }
48
+ return diagnostic;
49
+ };
50
+ }
51
+
52
+ // ../infrastructure/dist/observability/operational-telemetry.js
53
+ function observe(action) {
54
+ try {
55
+ action();
56
+ } catch {
57
+ }
58
+ }
59
+ var jobs = /* @__PURE__ */ new Set([
60
+ "cleanup-minio-images",
61
+ "enrich-books",
62
+ "find-duplicate-json-articles",
63
+ "fix-book-frontmatter",
64
+ "fix-image-formats",
65
+ "validate-json-articles",
66
+ "update-json-articles",
67
+ "update-raw-articles",
68
+ "s-content-format",
69
+ "rag-delete-collection",
70
+ "rag-search",
71
+ "rag-ingest",
72
+ ...["fetch", "ingest", "reset", "revert"].flatMap((operation) => ["articles", "books", "images", "notes"].map((domain) => `${operation}-${domain}`))
73
+ ]);
74
+ function telemetryJob(value) {
75
+ return jobs.has(value) ? value : "other";
76
+ }
77
+ function operationalAttributes(attributes = {}) {
78
+ return Object.fromEntries(Object.entries(attributes).filter((entry) => {
79
+ const [key, value] = entry;
80
+ return [
81
+ "method",
82
+ "route",
83
+ "domain",
84
+ "status",
85
+ "job",
86
+ "sentry.environment",
87
+ "sentry.release",
88
+ "sentry.sdk.name",
89
+ "sentry.sdk.version",
90
+ "sentry.trace.parent_span_id",
91
+ "sentry.replay_id"
92
+ ].includes(key) && (typeof value === "string" || typeof value === "number");
93
+ }));
94
+ }
95
+ function createOperationalTelemetry(sdk, enabled) {
96
+ return {
97
+ log(level, message, attributes = {}) {
98
+ if (enabled())
99
+ observe(() => sdk.logger[level](message, operationalAttributes(attributes)));
100
+ },
101
+ count(name, value, attributes = {}) {
102
+ if (enabled())
103
+ observe(() => sdk.metrics.count(name, value, {
104
+ attributes: operationalAttributes(attributes)
105
+ }));
106
+ },
107
+ duration(name, milliseconds, attributes = {}) {
108
+ if (enabled())
109
+ observe(() => sdk.metrics.distribution(name, milliseconds, {
110
+ attributes: operationalAttributes(attributes),
111
+ unit: "millisecond"
112
+ }));
113
+ }
114
+ };
115
+ }
116
+
117
+ // ../infrastructure/dist/observability/log-level.js
118
+ function mapStatusToLogLevel(status) {
119
+ if (status >= 500)
120
+ return "error";
121
+ if (status >= 400)
122
+ return "warn";
123
+ return "info";
124
+ }
125
+
126
+ // ../infrastructure/dist/observability/server-logger.js
127
+ import pino from "pino";
128
+ var pinoConfig = {
129
+ browser: { asObject: true }
130
+ };
131
+ var ServerLogger = class {
132
+ telemetry;
133
+ logger;
134
+ notificationService;
135
+ reporter;
136
+ constructor(notificationService, reporter, telemetry2) {
137
+ this.telemetry = telemetry2;
138
+ this.reporter = reporter;
139
+ this.logger = pino(pinoConfig);
140
+ this.notificationService = notificationService;
141
+ }
142
+ async info(message, context, options) {
143
+ await this.logWithLevel("info", message, context, void 0, options);
144
+ }
145
+ async warn(message, context, options) {
146
+ await this.logWithLevel("warn", message, context, void 0, options);
147
+ }
148
+ async error(message, context, error, options) {
149
+ await this.logWithLevel("error", message, context, error, options);
150
+ }
151
+ async logWithLevel(level, message, context, error, options) {
152
+ const expectedLevel = mapStatusToLogLevel(context.status);
153
+ const finalLevel = level === expectedLevel ? level : expectedLevel;
154
+ this.telemetry?.log(finalLevel, "Application operation", {
155
+ status: context.status
156
+ });
157
+ const logData = {
158
+ caller: context.caller,
159
+ status: context.status,
160
+ userId: context.userId,
161
+ ...context.additionalContext
162
+ };
163
+ if (level === "error") {
164
+ try {
165
+ this.captureError(message, context, error);
166
+ } catch (reportError) {
167
+ this.logger.warn({ error: reportError }, "Failed to report error");
168
+ }
169
+ }
170
+ if (error !== void 0) {
171
+ console.error("Additional error details:", error);
172
+ }
173
+ switch (finalLevel) {
174
+ case "info":
175
+ this.logger.info(logData, message);
176
+ break;
177
+ case "warn":
178
+ this.logger.warn(logData, message);
179
+ break;
180
+ case "error":
181
+ this.logger.error(logData, message);
182
+ break;
183
+ }
184
+ if (level === "error" && options?.severity === "critical" || level === "info" && options?.notifyCompletion) {
185
+ const notificationContext = {
186
+ caller: context.caller,
187
+ userId: context.userId
188
+ };
189
+ try {
190
+ switch (level) {
191
+ case "info":
192
+ await this.notificationService.notifyInfo(message, notificationContext);
193
+ break;
194
+ case "error":
195
+ await this.notificationService.notifyError(message, notificationContext);
196
+ break;
197
+ }
198
+ } catch (notificationError) {
199
+ this.logger.warn({ error: notificationError }, "Failed to send notification");
200
+ try {
201
+ this.reporter.captureException(notificationError, {
202
+ tags: {
203
+ caller: context.caller,
204
+ error_type: "notification_delivery",
205
+ status: String(context.status)
206
+ },
207
+ user: context.userId ? { id: context.userId } : void 0
208
+ });
209
+ } catch (reportError) {
210
+ this.logger.warn({ error: reportError }, "Failed to report notification failure");
211
+ }
212
+ }
213
+ }
214
+ }
215
+ captureError(message, context, error) {
216
+ const capturedError = this.findError(error);
217
+ const options = {
218
+ extra: {
219
+ additionalContext: context.additionalContext,
220
+ originalError: capturedError ? void 0 : error
221
+ },
222
+ tags: {
223
+ caller: context.caller,
224
+ error_type: capturedError?.name ?? "message",
225
+ status: String(context.status)
226
+ },
227
+ user: context.userId ? { id: context.userId } : void 0
228
+ };
229
+ if (capturedError) {
230
+ this.reporter.captureException(capturedError, options);
231
+ return;
232
+ }
233
+ this.reporter.captureMessage(message, { ...options, level: "error" });
234
+ }
235
+ findError(value) {
236
+ if (value instanceof Error)
237
+ return value;
238
+ if (typeof value === "object" && value !== null && "cause" in value && value.cause instanceof Error) {
239
+ return value.cause;
240
+ }
241
+ return void 0;
242
+ }
243
+ };
244
+
245
+ // ../notification/dist/errors.js
246
+ var NotificationError = class extends Error {
247
+ constructor(message = "notificationSend") {
248
+ super(message);
249
+ this.name = "NotificationError";
250
+ }
251
+ };
252
+
253
+ // ../notification/dist/pushover.js
254
+ var PushoverService = class {
255
+ config;
256
+ constructor(config) {
257
+ this.config = config;
258
+ }
259
+ /**
260
+ * Sends a message to the Pushover API.
261
+ *
262
+ * @param message - The message to send
263
+ * @param priority - The priority level (-1 = low, 0 = normal, 1 = high)
264
+ *
265
+ * @internal
266
+ */
267
+ async sendMessage(message, priority = 0) {
268
+ const body = new URLSearchParams({
269
+ token: this.config.appToken,
270
+ user: this.config.userKey,
271
+ message,
272
+ priority: String(priority)
273
+ });
274
+ const result = await fetch(this.config.url, {
275
+ method: "POST",
276
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
277
+ body,
278
+ signal: AbortSignal.timeout(5e3)
279
+ });
280
+ if (!result.ok)
281
+ throw new NotificationError();
282
+ }
283
+ async notifyError(message, context) {
284
+ const enrichedMessage = `[ERROR] ${context.caller}: ${message}`;
285
+ await this.sendMessage(enrichedMessage, 1);
286
+ }
287
+ async notifyWarning(message, context) {
288
+ const enrichedMessage = `[WARN] ${context.caller}: ${message}`;
289
+ await this.sendMessage(enrichedMessage, 0);
290
+ }
291
+ async notifyInfo(message, context) {
292
+ const enrichedMessage = `[INFO] ${context.caller}: ${message}`;
293
+ await this.sendMessage(enrichedMessage, -1);
294
+ }
295
+ };
296
+ function createPushoverService(config) {
297
+ return new PushoverService(config);
298
+ }
299
+
300
+ // ../notification/dist/web-push.js
301
+ import { sendNotification, setVapidDetails } from "web-push";
302
+
303
+ // src/lib/cli-observability.ts
304
+ import * as Sentry from "@sentry/node";
305
+ var dryRun = () => process.argv.includes("--dry-run");
306
+ var initialized = false;
307
+ var telemetry = createOperationalTelemetry(
308
+ Sentry,
309
+ () => !dryRun() && process.env.NODE_ENV === "production" && Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN)
310
+ );
311
+ function initialize() {
312
+ if (initialized || dryRun()) return;
313
+ initialized = true;
314
+ if (!process.env.NEXT_PUBLIC_SENTRY_DSN)
315
+ console.warn(
316
+ "NEXT_PUBLIC_SENTRY_DSN is not set; errors will only be logged locally and critical alerts sent via Pushover."
317
+ );
318
+ try {
319
+ Sentry.init({
320
+ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
321
+ enabled: process.env.NODE_ENV === "production" && Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN),
322
+ environment: process.env.VERCEL_ENV ?? process.env.NODE_ENV,
323
+ release: process.env.SENTRY_RELEASE ?? process.env.VERCEL_GIT_COMMIT_SHA,
324
+ beforeSendLog(log) {
325
+ log.attributes = operationalAttributes(log.attributes);
326
+ return log;
327
+ },
328
+ beforeSendMetric(metric) {
329
+ metric.attributes = operationalAttributes(metric.attributes);
330
+ return metric;
331
+ },
332
+ // The runner owns reporting and flushing, including handled item failures.
333
+ defaultIntegrations: false,
334
+ dataCollection: {
335
+ userInfo: false,
336
+ cookies: false,
337
+ httpHeaders: false,
338
+ httpBodies: [],
339
+ urlQueryParams: false,
340
+ databaseQueryData: false,
341
+ stackFrameVariables: false
342
+ },
343
+ beforeSend(event) {
344
+ delete event.user;
345
+ delete event.request;
346
+ delete event.breadcrumbs;
347
+ return event;
348
+ }
349
+ });
350
+ } catch (error) {
351
+ console.error(
352
+ "Failed to initialize Sentry",
353
+ createDiagnosticError(process.env)(error)
354
+ );
355
+ }
356
+ }
357
+ function logger() {
358
+ initialize();
359
+ const diagnose = createDiagnosticError(process.env);
360
+ return new ServerLogger(
361
+ createPushoverService({
362
+ url: process.env.PUSHOVER_URL ?? "https://api.pushover.net/1/messages.json",
363
+ userKey: process.env.PUSHOVER_USER_KEY ?? "",
364
+ appToken: process.env.PUSHOVER_APP_TOKEN ?? ""
365
+ }),
366
+ {
367
+ captureException: (error, context) => Sentry.captureException(diagnose(error), context),
368
+ captureMessage: (message, context) => Sentry.captureMessage(message, context)
369
+ }
370
+ );
371
+ }
372
+ function reportCliError(error, caller) {
373
+ const diagnostic = createDiagnosticError(process.env)(error);
374
+ console.error(diagnostic);
375
+ if (dryRun()) return;
376
+ initialize();
377
+ telemetry.log("error", "CLI item failed", { job: telemetryJob(caller) });
378
+ try {
379
+ Sentry.captureException(diagnostic, {
380
+ tags: { caller, severity: "normal" }
381
+ });
382
+ } catch (reportError) {
383
+ console.error(
384
+ "Failed to report error",
385
+ createDiagnosticError(process.env)(reportError)
386
+ );
387
+ }
388
+ }
389
+ var cliNotifications = {
390
+ async notifyInfo(message, context) {
391
+ if (dryRun()) return;
392
+ await logger().info(
393
+ message,
394
+ { ...context, caller: context.caller ?? "cli", status: 200 },
395
+ { notifyCompletion: true }
396
+ );
397
+ }
398
+ };
399
+ async function runCli(caller, execute) {
400
+ const started = performance.now();
401
+ const job = telemetryJob(caller);
402
+ let status = "ok";
403
+ try {
404
+ initialize();
405
+ telemetry.log("info", "CLI started", { job });
406
+ await execute();
407
+ telemetry.log("info", "CLI completed", { job, status: "ok" });
408
+ } catch (error) {
409
+ status = "error";
410
+ telemetry.log("error", "CLI failed", { job, status: "error" });
411
+ process.exitCode = 1;
412
+ const diagnostic = createDiagnosticError(process.env)(error);
413
+ console.error(diagnostic);
414
+ if (!dryRun())
415
+ await logger().error(
416
+ `${caller} failed`,
417
+ { caller, status: 500 },
418
+ diagnostic,
419
+ { severity: "critical" }
420
+ );
421
+ } finally {
422
+ telemetry.count("cli.runs", 1, { job, status });
423
+ telemetry.duration("cli.duration", performance.now() - started, {
424
+ job,
425
+ status
426
+ });
427
+ if (initialized && !dryRun()) {
428
+ try {
429
+ if (!await Sentry.flush(2e3))
430
+ console.error("Sentry flush timed out");
431
+ } catch (error) {
432
+ console.error(
433
+ "Sentry flush failed",
434
+ createDiagnosticError(process.env)(error)
435
+ );
436
+ }
437
+ }
438
+ }
439
+ }
440
+
441
+ export {
442
+ reportCliError,
443
+ cliNotifications,
444
+ runCli
445
+ };
@@ -0,0 +1,36 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+ import {
3
+ deleteCollection,
4
+ getQdrantClient
5
+ } from "./chunk-J4VLH4VA.js";
6
+ import "./chunk-JZOR5GA2.js";
7
+
8
+ // src/rag/delete-collection-command.ts
9
+ async function main() {
10
+ const collectionName = process.argv[2];
11
+ if (!collectionName) {
12
+ console.error(
13
+ "Usage: rag-delete-collection <collection-name>\nExample: rag-delete-collection knowledge_v1"
14
+ );
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+ if (!process.env.QDRANT_URL) {
19
+ throw new Error("QDRANT_URL environment variable is required.");
20
+ }
21
+ const qdrant = getQdrantClient();
22
+ const collections = await qdrant.getCollections();
23
+ const exists = collections.collections.some((c) => c.name === collectionName);
24
+ if (!exists) {
25
+ console.log(
26
+ `Collection "${collectionName}" does not exist. Nothing to delete.`
27
+ );
28
+ return;
29
+ }
30
+ console.log(`Deleting collection "${collectionName}"...`);
31
+ await deleteCollection(collectionName);
32
+ console.log(`Collection "${collectionName}" deleted successfully.`);
33
+ }
34
+ export {
35
+ main
36
+ };
@@ -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 {
@@ -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-MVYAZJB2.js";
10
12
  import {
11
13
  CreatedAt,
12
14
  ExportedStatus,
@@ -18,10 +20,10 @@ 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
- createPushoverService
24
- } from "./chunk-7PVU2Z32.js";
25
+ cliNotifications
26
+ } from "./chunk-4CXH2QIX.js";
25
27
 
26
28
  // ../core/shared-kernel/entities/file-entity.ts
27
29
  import { z } from "zod";
@@ -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
  });
@@ -538,17 +542,13 @@ async function runContentImport(domain, source) {
538
542
  throw new Error("DATABASE_URL and USERNAME_TO_EXPORT are required.");
539
543
  const dryRun = process.argv.includes("--dry-run");
540
544
  const prisma = createPrismaClient(databaseUrl);
541
- const notification = createPushoverService({
542
- url: process.env.PUSHOVER_URL ?? "",
543
- userKey: process.env.PUSHOVER_USER_KEY ?? "",
544
- appToken: process.env.PUSHOVER_APP_TOKEN ?? ""
545
- });
545
+ const notification = cliNotifications;
546
546
  try {
547
547
  const mediaDomain = domain === "images" || domain === "books";
548
- const bucket = process.env.MINIO_BUCKET_NAME;
548
+ const bucket = process.env.S3_BUCKET_NAME;
549
549
  if (mediaDomain && !dryRun && !bucket)
550
- throw new Error("MINIO_BUCKET_NAME is required.");
551
- const client = mediaDomain && !dryRun ? createMinioClient() : void 0;
550
+ throw new Error("S3_BUCKET_NAME is required.");
551
+ const client = mediaDomain && !dryRun ? createObjectStorageClient() : void 0;
552
552
  const media = client && bucket ? createImportMediaStore(
553
553
  {
554
554
  images: createImagesStorageService(client, bucket),
@@ -573,11 +573,10 @@ async function runContentImport(domain, source) {
573
573
  }
574
574
  );
575
575
  console.log(summary);
576
- await notification.notifyInfo(`ingest-${domain} completed`, { caller: `ingest-${domain}` }).catch((error) => console.error("\u901A\u77E5\u5931\u6557:", error));
577
- } catch (error) {
578
- console.error(error);
579
- process.exitCode = 1;
580
- await notification.notifyError(`ingest-${domain} failed`, { caller: `ingest-${domain}` }).catch((notifyError) => console.error("\u901A\u77E5\u5931\u6557:", notifyError));
576
+ if (!dryRun)
577
+ await notification.notifyInfo(`ingest-${domain} completed`, {
578
+ caller: `ingest-${domain}`
579
+ });
581
580
  } finally {
582
581
  await prisma.$disconnect();
583
582
  }