@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.
- package/README.md +8 -0
- package/dist/{chunk-7XX57D7G.js → chunk-3INTMHPH.js} +1 -1
- package/dist/chunk-4CXH2QIX.js +445 -0
- package/dist/chunk-67LIMNXH.js +36 -0
- package/dist/{chunk-OCEV2QX3.js → chunk-BYEEIQ7O.js} +1 -1
- package/dist/{chunk-2PZXXOYG.js → chunk-GBQOCN2C.js} +24 -25
- package/dist/chunk-MJYGMGH5.js +382 -0
- package/dist/chunk-MVYAZJB2.js +160 -0
- package/dist/{chunk-DIG75T3J.js → chunk-RLAYINCA.js} +10 -13
- package/dist/{chunk-MNBEFEFR.js → chunk-RO5QUZFR.js} +1 -1
- package/dist/chunk-SFKQ2RAP.js +87 -0
- package/dist/{chunk-FVK44UTM.js → chunk-ZMQUO6OA.js} +1 -1
- package/dist/cleanup-minio-images.js +62 -91
- package/dist/enrich-books.js +6 -4
- package/dist/fetch-articles.js +6 -24
- package/dist/fetch-books.js +12 -35
- package/dist/fetch-images.js +12 -35
- package/dist/fetch-notes.js +6 -24
- package/dist/find-duplicate-json-articles.js +11 -13
- package/dist/fix-book-frontmatter.js +6 -4
- package/dist/fix-image-formats.js +7 -5
- package/dist/ingest-articles.js +7 -5
- package/dist/ingest-books.js +10 -8
- package/dist/ingest-images.js +7 -5
- package/dist/ingest-notes.js +10 -8
- package/dist/rag/delete-collection.js +5 -30
- package/dist/rag/ingest.js +5 -379
- package/dist/rag/search.js +5 -84
- package/dist/reset-articles.js +8 -23
- package/dist/reset-books.js +8 -23
- package/dist/reset-images.js +8 -23
- package/dist/reset-notes.js +8 -23
- package/dist/revert-articles.js +8 -23
- package/dist/revert-books.js +8 -23
- package/dist/revert-images.js +8 -23
- package/dist/revert-notes.js +8 -23
- package/dist/s-content-format.js +9 -11
- package/dist/update-json-articles.js +19 -41
- package/dist/update-raw-articles.js +14 -37
- package/dist/validate-json-articles.js +8 -5
- package/package.json +13 -11
- package/dist/chunk-7PVU2Z32.js +0 -71
- 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-
|
|
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-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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-
|
|
23
|
+
} from "./chunk-RLAYINCA.js";
|
|
22
24
|
import {
|
|
23
|
-
|
|
24
|
-
} from "./chunk-
|
|
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(
|
|
516
|
-
return createStorageService(
|
|
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/
|
|
523
|
-
function createImagesStorageService(
|
|
524
|
-
return createStorageService(
|
|
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 =
|
|
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.
|
|
548
|
+
const bucket = process.env.S3_BUCKET_NAME;
|
|
549
549
|
if (mediaDomain && !dryRun && !bucket)
|
|
550
|
-
throw new Error("
|
|
551
|
-
const client = mediaDomain && !dryRun ?
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
}
|