@s-hirano-ist/s-scripts 4.2.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-4CXH2QIX.js +445 -0
- package/dist/chunk-67LIMNXH.js +36 -0
- package/dist/{chunk-R5EI7JXR.js → chunk-GBQOCN2C.js} +5 -16
- package/dist/chunk-MJYGMGH5.js +382 -0
- package/dist/{chunk-M6LFKOUH.js → chunk-MVYAZJB2.js} +2 -2
- package/dist/chunk-SFKQ2RAP.js +87 -0
- package/dist/cleanup-minio-images.js +65 -84
- package/dist/enrich-books.js +6 -4
- package/dist/fetch-articles.js +5 -23
- package/dist/fetch-books.js +6 -24
- package/dist/fetch-images.js +6 -24
- package/dist/fetch-notes.js +5 -23
- 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 +6 -21
- package/dist/reset-books.js +6 -21
- package/dist/reset-images.js +6 -21
- package/dist/reset-notes.js +6 -21
- package/dist/revert-articles.js +6 -21
- package/dist/revert-books.js +6 -21
- package/dist/revert-images.js +6 -21
- package/dist/revert-notes.js +6 -21
- 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 +6 -4
- package/dist/chunk-7PVU2Z32.js +0 -71
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.
|
|
@@ -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
|
+
};
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
createObjectStorageClient,
|
|
9
9
|
createStorageService,
|
|
10
10
|
isObjectNotFound
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MVYAZJB2.js";
|
|
12
12
|
import {
|
|
13
13
|
CreatedAt,
|
|
14
14
|
ExportedStatus,
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
withTransactionRetry
|
|
23
23
|
} from "./chunk-RLAYINCA.js";
|
|
24
24
|
import {
|
|
25
|
-
|
|
26
|
-
} from "./chunk-
|
|
25
|
+
cliNotifications
|
|
26
|
+
} from "./chunk-4CXH2QIX.js";
|
|
27
27
|
|
|
28
28
|
// ../core/shared-kernel/entities/file-entity.ts
|
|
29
29
|
import { z } from "zod";
|
|
@@ -542,11 +542,7 @@ async function runContentImport(domain, source) {
|
|
|
542
542
|
throw new Error("DATABASE_URL and USERNAME_TO_EXPORT are required.");
|
|
543
543
|
const dryRun = process.argv.includes("--dry-run");
|
|
544
544
|
const prisma = createPrismaClient(databaseUrl);
|
|
545
|
-
const notification =
|
|
546
|
-
url: process.env.PUSHOVER_URL ?? "",
|
|
547
|
-
userKey: process.env.PUSHOVER_USER_KEY ?? "",
|
|
548
|
-
appToken: process.env.PUSHOVER_APP_TOKEN ?? ""
|
|
549
|
-
});
|
|
545
|
+
const notification = cliNotifications;
|
|
550
546
|
try {
|
|
551
547
|
const mediaDomain = domain === "images" || domain === "books";
|
|
552
548
|
const bucket = process.env.S3_BUCKET_NAME;
|
|
@@ -580,14 +576,7 @@ async function runContentImport(domain, source) {
|
|
|
580
576
|
if (!dryRun)
|
|
581
577
|
await notification.notifyInfo(`ingest-${domain} completed`, {
|
|
582
578
|
caller: `ingest-${domain}`
|
|
583
|
-
})
|
|
584
|
-
} catch (error) {
|
|
585
|
-
console.error(error);
|
|
586
|
-
process.exitCode = 1;
|
|
587
|
-
if (!dryRun)
|
|
588
|
-
await notification.notifyError(`ingest-${domain} failed`, { caller: `ingest-${domain}` }).catch(
|
|
589
|
-
(notifyError) => console.error("\u901A\u77E5\u5931\u6557:", notifyError)
|
|
590
|
-
);
|
|
579
|
+
});
|
|
591
580
|
} finally {
|
|
592
581
|
await prisma.$disconnect();
|
|
593
582
|
}
|