@ainyc/canonry 2.3.1 → 2.4.3
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 +1 -0
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +20 -0
- package/assets/assets/index-CAewPdsZ.css +1 -0
- package/assets/assets/index-Nrl3ecFY.js +301 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-CW6CAPBQ.js → chunk-GZF3YIHY.js} +6 -1
- package/dist/{chunk-JXOUZ6JH.js → chunk-KGOT5OFT.js} +321 -424
- package/dist/cli.js +14 -5
- package/dist/index.js +2 -2
- package/dist/{intelligence-service-232P7625.js → intelligence-service-KM64AW7J.js} +1 -1
- package/package.json +4 -4
- package/assets/assets/index-C_pxQt0X.js +0 -301
- package/assets/assets/index-yF1fs-OW.css +0 -1
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
runs,
|
|
31
31
|
schedules,
|
|
32
32
|
usageCounters
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-GZF3YIHY.js";
|
|
34
34
|
|
|
35
35
|
// src/config.ts
|
|
36
36
|
import fs from "fs";
|
|
@@ -347,7 +347,7 @@ function printCliError(err, format) {
|
|
|
347
347
|
// src/server.ts
|
|
348
348
|
import { createRequire as createRequire3 } from "module";
|
|
349
349
|
import crypto27 from "crypto";
|
|
350
|
-
import
|
|
350
|
+
import fs13 from "fs";
|
|
351
351
|
import path15 from "path";
|
|
352
352
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
353
353
|
import { eq as eq29 } from "drizzle-orm";
|
|
@@ -448,6 +448,7 @@ var projectUpsertRequestSchema = z3.object({
|
|
|
448
448
|
providers: z3.array(providerNameSchema).optional(),
|
|
449
449
|
locations: z3.array(locationContextSchema).optional(),
|
|
450
450
|
defaultLocation: z3.string().nullable().optional(),
|
|
451
|
+
autoExtractBacklinks: z3.boolean().optional(),
|
|
451
452
|
configSource: configSourceSchema.optional()
|
|
452
453
|
});
|
|
453
454
|
var projectDtoSchema = z3.object({
|
|
@@ -462,6 +463,7 @@ var projectDtoSchema = z3.object({
|
|
|
462
463
|
labels: z3.record(z3.string(), z3.string()).default({}),
|
|
463
464
|
locations: z3.array(locationContextSchema).default([]),
|
|
464
465
|
defaultLocation: z3.string().nullable().optional(),
|
|
466
|
+
autoExtractBacklinks: z3.boolean().default(false),
|
|
465
467
|
configSource: configSourceSchema.default("cli"),
|
|
466
468
|
configRevision: z3.number().int().positive().default(1),
|
|
467
469
|
createdAt: z3.string().optional(),
|
|
@@ -535,7 +537,8 @@ var configSpecSchema = z4.object({
|
|
|
535
537
|
defaultLocation: z4.string().optional(),
|
|
536
538
|
schedule: configScheduleSchema,
|
|
537
539
|
notifications: z4.array(configNotificationSchema).optional().default([]),
|
|
538
|
-
google: configGoogleSchema
|
|
540
|
+
google: configGoogleSchema,
|
|
541
|
+
autoExtractBacklinks: z4.boolean().optional().default(false)
|
|
539
542
|
}).superRefine((spec, ctx) => {
|
|
540
543
|
const duplicateLabels = findDuplicateLocationLabels(spec.locations);
|
|
541
544
|
if (duplicateLabels.length > 0) {
|
|
@@ -622,6 +625,9 @@ function agentBusy(projectName) {
|
|
|
622
625
|
function missingDependency(message, details) {
|
|
623
626
|
return new AppError("MISSING_DEPENDENCY", message, 422, details);
|
|
624
627
|
}
|
|
628
|
+
function internalError(message, details) {
|
|
629
|
+
return new AppError("INTERNAL_ERROR", message, 500, details);
|
|
630
|
+
}
|
|
625
631
|
|
|
626
632
|
// ../contracts/src/google.ts
|
|
627
633
|
import { z as z5 } from "zod";
|
|
@@ -1546,7 +1552,7 @@ function parseCookies(header) {
|
|
|
1546
1552
|
}, {});
|
|
1547
1553
|
}
|
|
1548
1554
|
async function authPlugin(app, opts = {}) {
|
|
1549
|
-
app.addHook("onRequest", async (request
|
|
1555
|
+
app.addHook("onRequest", async (request) => {
|
|
1550
1556
|
const url = request.url.split("?")[0];
|
|
1551
1557
|
if (shouldSkipAuth(url)) return;
|
|
1552
1558
|
const header = request.headers.authorization;
|
|
@@ -1554,15 +1560,13 @@ async function authPlugin(app, opts = {}) {
|
|
|
1554
1560
|
if (header) {
|
|
1555
1561
|
const parts = header.split(" ");
|
|
1556
1562
|
if (parts.length !== 2 || parts[0] !== "Bearer") {
|
|
1557
|
-
|
|
1558
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
1563
|
+
throw authRequired();
|
|
1559
1564
|
}
|
|
1560
1565
|
const token = parts[1];
|
|
1561
1566
|
const hash = hashKey(token);
|
|
1562
1567
|
key = app.db.select().from(apiKeys).where(eq(apiKeys.keyHash, hash)).get();
|
|
1563
1568
|
if (!key || key.revokedAt) {
|
|
1564
|
-
|
|
1565
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
1569
|
+
throw authInvalid();
|
|
1566
1570
|
}
|
|
1567
1571
|
} else if (opts.resolveSessionApiKeyId && opts.sessionCookieName) {
|
|
1568
1572
|
const sessionId = parseCookies(request.headers.cookie)[opts.sessionCookieName];
|
|
@@ -1573,12 +1577,10 @@ async function authPlugin(app, opts = {}) {
|
|
|
1573
1577
|
}
|
|
1574
1578
|
}
|
|
1575
1579
|
if (!key || key.revokedAt) {
|
|
1576
|
-
|
|
1577
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
1580
|
+
throw authRequired();
|
|
1578
1581
|
}
|
|
1579
1582
|
} else {
|
|
1580
|
-
|
|
1581
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
1583
|
+
throw authRequired();
|
|
1582
1584
|
}
|
|
1583
1585
|
app.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(apiKeys.id, key.id)).run();
|
|
1584
1586
|
});
|
|
@@ -1679,6 +1681,7 @@ async function projectRoutes(app, opts) {
|
|
|
1679
1681
|
defaultLocation: nextDefaultLocation
|
|
1680
1682
|
});
|
|
1681
1683
|
}
|
|
1684
|
+
const nextAutoExtractBacklinks = body.autoExtractBacklinks !== void 0 ? body.autoExtractBacklinks ? 1 : 0 : existing?.autoExtractBacklinks ?? 0;
|
|
1682
1685
|
if (existing) {
|
|
1683
1686
|
app.db.transaction((tx) => {
|
|
1684
1687
|
tx.update(projects).set({
|
|
@@ -1692,6 +1695,7 @@ async function projectRoutes(app, opts) {
|
|
|
1692
1695
|
providers: JSON.stringify(body.providers ?? []),
|
|
1693
1696
|
locations: JSON.stringify(nextLocations),
|
|
1694
1697
|
defaultLocation: nextDefaultLocation,
|
|
1698
|
+
autoExtractBacklinks: nextAutoExtractBacklinks,
|
|
1695
1699
|
configSource: body.configSource ?? "api",
|
|
1696
1700
|
configRevision: existing.configRevision + 1,
|
|
1697
1701
|
updatedAt: now
|
|
@@ -1723,6 +1727,7 @@ async function projectRoutes(app, opts) {
|
|
|
1723
1727
|
providers: JSON.stringify(body.providers ?? []),
|
|
1724
1728
|
locations: JSON.stringify(nextLocations),
|
|
1725
1729
|
defaultLocation: nextDefaultLocation,
|
|
1730
|
+
autoExtractBacklinks: nextAutoExtractBacklinks,
|
|
1726
1731
|
configSource: body.configSource ?? "api",
|
|
1727
1732
|
configRevision: 1,
|
|
1728
1733
|
createdAt: now,
|
|
@@ -1869,6 +1874,7 @@ async function projectRoutes(app, opts) {
|
|
|
1869
1874
|
providers: parseJsonColumn(project.providers, []),
|
|
1870
1875
|
locations: parseJsonColumn(project.locations, []),
|
|
1871
1876
|
...project.defaultLocation ? { defaultLocation: project.defaultLocation } : {},
|
|
1877
|
+
...project.autoExtractBacklinks === 1 ? { autoExtractBacklinks: true } : {},
|
|
1872
1878
|
notifications: notificationRows.map((row) => {
|
|
1873
1879
|
const cfg = parseJsonColumn(row.config, { url: "", events: [] });
|
|
1874
1880
|
return {
|
|
@@ -1903,6 +1909,7 @@ function formatProject(row) {
|
|
|
1903
1909
|
providers: parseJsonColumn(row.providers, []),
|
|
1904
1910
|
locations: parseJsonColumn(row.locations, []),
|
|
1905
1911
|
defaultLocation: row.defaultLocation,
|
|
1912
|
+
autoExtractBacklinks: row.autoExtractBacklinks === 1,
|
|
1906
1913
|
configSource: row.configSource,
|
|
1907
1914
|
configRevision: row.configRevision,
|
|
1908
1915
|
createdAt: row.createdAt,
|
|
@@ -2041,12 +2048,7 @@ async function keywordRoutes(app, opts) {
|
|
|
2041
2048
|
return reply.send({ keywords: generated, provider });
|
|
2042
2049
|
} catch (err) {
|
|
2043
2050
|
request.log.error({ err }, "Key phrase generation failed");
|
|
2044
|
-
|
|
2045
|
-
error: {
|
|
2046
|
-
code: "INTERNAL_ERROR",
|
|
2047
|
-
message: err instanceof Error ? err.message : "Failed to generate key phrases"
|
|
2048
|
-
}
|
|
2049
|
-
});
|
|
2051
|
+
throw internalError(err instanceof Error ? err.message : "Failed to generate key phrases");
|
|
2050
2052
|
}
|
|
2051
2053
|
});
|
|
2052
2054
|
}
|
|
@@ -2742,6 +2744,7 @@ async function applyRoutes(app, opts) {
|
|
|
2742
2744
|
providers: JSON.stringify(config.spec.providers ?? []),
|
|
2743
2745
|
locations: JSON.stringify(config.spec.locations ?? []),
|
|
2744
2746
|
defaultLocation: config.spec.defaultLocation ?? null,
|
|
2747
|
+
autoExtractBacklinks: config.spec.autoExtractBacklinks ? 1 : 0,
|
|
2745
2748
|
configSource: "config-file",
|
|
2746
2749
|
configRevision: existing.configRevision + 1,
|
|
2747
2750
|
updatedAt: now
|
|
@@ -2768,6 +2771,7 @@ async function applyRoutes(app, opts) {
|
|
|
2768
2771
|
providers: JSON.stringify(config.spec.providers ?? []),
|
|
2769
2772
|
locations: JSON.stringify(config.spec.locations ?? []),
|
|
2770
2773
|
defaultLocation: config.spec.defaultLocation ?? null,
|
|
2774
|
+
autoExtractBacklinks: config.spec.autoExtractBacklinks ? 1 : 0,
|
|
2771
2775
|
configSource: "config-file",
|
|
2772
2776
|
configRevision: 1,
|
|
2773
2777
|
createdAt: now,
|
|
@@ -2891,6 +2895,7 @@ async function applyRoutes(app, opts) {
|
|
|
2891
2895
|
providers: parseJsonColumn(project.providers, []),
|
|
2892
2896
|
locations: parseJsonColumn(project.locations, []),
|
|
2893
2897
|
defaultLocation: project.defaultLocation,
|
|
2898
|
+
autoExtractBacklinks: project.autoExtractBacklinks === 1,
|
|
2894
2899
|
configSource: project.configSource,
|
|
2895
2900
|
configRevision: project.configRevision,
|
|
2896
2901
|
createdAt: project.createdAt,
|
|
@@ -6057,7 +6062,7 @@ async function settingsRoutes(app, opts) {
|
|
|
6057
6062
|
google: opts.google ?? { configured: false },
|
|
6058
6063
|
bing: opts.bing ?? { configured: false }
|
|
6059
6064
|
}));
|
|
6060
|
-
app.put("/settings/providers/:name", async (request
|
|
6065
|
+
app.put("/settings/providers/:name", async (request) => {
|
|
6061
6066
|
const { apiKey, baseUrl, model, quota } = request.body ?? {};
|
|
6062
6067
|
const name = request.params.name;
|
|
6063
6068
|
const adapters = opts.providerAdapters ?? [];
|
|
@@ -6065,107 +6070,81 @@ async function settingsRoutes(app, opts) {
|
|
|
6065
6070
|
const adapterInfo = apiAdapters.find((a) => a.name === name);
|
|
6066
6071
|
if (!adapterInfo) {
|
|
6067
6072
|
const validNames = apiAdapters.map((a) => a.name);
|
|
6068
|
-
|
|
6073
|
+
throw validationError(`Invalid provider: ${name}. Must be one of: ${validNames.join(", ")}`, {
|
|
6069
6074
|
provider: name,
|
|
6070
6075
|
validProviders: validNames
|
|
6071
6076
|
});
|
|
6072
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6073
6077
|
}
|
|
6074
6078
|
if (name === "local") {
|
|
6075
6079
|
if (!baseUrl || typeof baseUrl !== "string") {
|
|
6076
|
-
|
|
6077
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6080
|
+
throw validationError("baseUrl is required for local provider");
|
|
6078
6081
|
}
|
|
6079
6082
|
} else if (name === "gemini" && !apiKey) {
|
|
6080
6083
|
const geminiSummary = (opts.providerSummary ?? []).find((p) => p.name === "gemini");
|
|
6081
6084
|
if (!geminiSummary?.vertexConfigured) {
|
|
6082
|
-
|
|
6085
|
+
throw validationError(
|
|
6083
6086
|
"apiKey is required for Gemini unless Vertex AI is configured (set GEMINI_VERTEX_PROJECT env var or vertexProject in config file)"
|
|
6084
6087
|
);
|
|
6085
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6086
6088
|
}
|
|
6087
6089
|
} else {
|
|
6088
6090
|
if (!apiKey || typeof apiKey !== "string") {
|
|
6089
|
-
|
|
6090
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6091
|
+
throw validationError("apiKey is required");
|
|
6091
6092
|
}
|
|
6092
6093
|
}
|
|
6093
6094
|
if (model !== void 0) {
|
|
6094
6095
|
if (!adapterInfo.modelValidationPattern.test(model)) {
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6096
|
+
throw validationError(
|
|
6097
|
+
`Invalid model "${model}" for provider "${name}" \u2014 ${adapterInfo.modelValidationHint}`
|
|
6098
|
+
);
|
|
6098
6099
|
}
|
|
6099
6100
|
}
|
|
6100
6101
|
if (!opts.onProviderUpdate) {
|
|
6101
|
-
|
|
6102
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6102
|
+
throw notImplemented("Provider configuration updates are not supported in this deployment");
|
|
6103
6103
|
}
|
|
6104
6104
|
if (quota !== void 0) {
|
|
6105
6105
|
if (typeof quota !== "object" || quota === null) {
|
|
6106
|
-
|
|
6106
|
+
throw validationError("quota must be an object");
|
|
6107
6107
|
}
|
|
6108
6108
|
for (const [key, val] of Object.entries(quota)) {
|
|
6109
6109
|
if (!["maxConcurrency", "maxRequestsPerMinute", "maxRequestsPerDay"].includes(key)) {
|
|
6110
|
-
|
|
6110
|
+
throw validationError(`Unknown quota field: ${key}`);
|
|
6111
6111
|
}
|
|
6112
6112
|
if (typeof val !== "number" || !Number.isInteger(val) || val <= 0) {
|
|
6113
|
-
|
|
6113
|
+
throw validationError(`${key} must be a positive integer`);
|
|
6114
6114
|
}
|
|
6115
6115
|
}
|
|
6116
6116
|
}
|
|
6117
6117
|
const result = opts.onProviderUpdate(name, apiKey ?? "", model, baseUrl, quota);
|
|
6118
6118
|
if (!result) {
|
|
6119
|
-
|
|
6120
|
-
error: {
|
|
6121
|
-
code: "INTERNAL_ERROR",
|
|
6122
|
-
message: "Failed to update provider configuration"
|
|
6123
|
-
}
|
|
6124
|
-
});
|
|
6119
|
+
throw internalError("Failed to update provider configuration");
|
|
6125
6120
|
}
|
|
6126
6121
|
return result;
|
|
6127
6122
|
});
|
|
6128
|
-
app.put("/settings/google", async (request
|
|
6123
|
+
app.put("/settings/google", async (request) => {
|
|
6129
6124
|
const { clientId, clientSecret } = request.body ?? {};
|
|
6130
6125
|
if (!clientId || typeof clientId !== "string" || !clientSecret || typeof clientSecret !== "string") {
|
|
6131
|
-
|
|
6132
|
-
error: { code: "VALIDATION_ERROR", message: "clientId and clientSecret are required" }
|
|
6133
|
-
});
|
|
6126
|
+
throw validationError("clientId and clientSecret are required");
|
|
6134
6127
|
}
|
|
6135
6128
|
if (!opts.onGoogleUpdate) {
|
|
6136
|
-
|
|
6137
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6129
|
+
throw notImplemented("Google OAuth configuration updates are not supported in this deployment");
|
|
6138
6130
|
}
|
|
6139
6131
|
const result = opts.onGoogleUpdate(clientId, clientSecret);
|
|
6140
6132
|
if (!result) {
|
|
6141
|
-
|
|
6142
|
-
error: {
|
|
6143
|
-
code: "INTERNAL_ERROR",
|
|
6144
|
-
message: "Failed to update Google OAuth configuration"
|
|
6145
|
-
}
|
|
6146
|
-
});
|
|
6133
|
+
throw internalError("Failed to update Google OAuth configuration");
|
|
6147
6134
|
}
|
|
6148
6135
|
return result;
|
|
6149
6136
|
});
|
|
6150
|
-
app.put("/settings/bing", async (request
|
|
6137
|
+
app.put("/settings/bing", async (request) => {
|
|
6151
6138
|
const { apiKey } = request.body ?? {};
|
|
6152
6139
|
if (!apiKey || typeof apiKey !== "string") {
|
|
6153
|
-
|
|
6154
|
-
error: { code: "VALIDATION_ERROR", message: "apiKey is required" }
|
|
6155
|
-
});
|
|
6140
|
+
throw validationError("apiKey is required");
|
|
6156
6141
|
}
|
|
6157
6142
|
if (!opts.onBingUpdate) {
|
|
6158
|
-
|
|
6159
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6143
|
+
throw notImplemented("Bing configuration updates are not supported in this deployment");
|
|
6160
6144
|
}
|
|
6161
6145
|
const result = opts.onBingUpdate(apiKey);
|
|
6162
6146
|
if (!result) {
|
|
6163
|
-
|
|
6164
|
-
error: {
|
|
6165
|
-
code: "INTERNAL_ERROR",
|
|
6166
|
-
message: "Failed to update Bing configuration"
|
|
6167
|
-
}
|
|
6168
|
-
});
|
|
6147
|
+
throw internalError("Failed to update Bing configuration");
|
|
6169
6148
|
}
|
|
6170
6149
|
return result;
|
|
6171
6150
|
});
|
|
@@ -6173,32 +6152,24 @@ async function settingsRoutes(app, opts) {
|
|
|
6173
6152
|
|
|
6174
6153
|
// ../api-routes/src/snapshot.ts
|
|
6175
6154
|
async function snapshotRoutes(app, opts) {
|
|
6176
|
-
app.post("/snapshot", async (request
|
|
6155
|
+
app.post("/snapshot", async (request) => {
|
|
6177
6156
|
const parsed = snapshotRequestSchema.safeParse(request.body);
|
|
6178
6157
|
if (!parsed.success) {
|
|
6179
|
-
|
|
6158
|
+
throw validationError("Invalid snapshot payload", {
|
|
6180
6159
|
issues: parsed.error.issues.map((issue) => ({
|
|
6181
6160
|
path: issue.path.join("."),
|
|
6182
6161
|
message: issue.message
|
|
6183
6162
|
}))
|
|
6184
6163
|
});
|
|
6185
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6186
6164
|
}
|
|
6187
6165
|
if (!opts.onSnapshotRequested) {
|
|
6188
|
-
|
|
6189
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
6166
|
+
throw notImplemented("Snapshot reporting is not supported in this deployment");
|
|
6190
6167
|
}
|
|
6191
6168
|
try {
|
|
6192
|
-
|
|
6193
|
-
return reply.send(report);
|
|
6169
|
+
return await opts.onSnapshotRequested(parsed.data);
|
|
6194
6170
|
} catch (err) {
|
|
6195
6171
|
request.log.error({ err }, "Snapshot report generation failed");
|
|
6196
|
-
|
|
6197
|
-
error: {
|
|
6198
|
-
code: "INTERNAL_ERROR",
|
|
6199
|
-
message: err instanceof Error ? err.message : "Failed to generate snapshot report"
|
|
6200
|
-
}
|
|
6201
|
-
});
|
|
6172
|
+
throw internalError(err instanceof Error ? err.message : "Failed to generate snapshot report");
|
|
6202
6173
|
}
|
|
6203
6174
|
});
|
|
6204
6175
|
}
|
|
@@ -7390,11 +7361,9 @@ async function googleRoutes(app, opts) {
|
|
|
7390
7361
|
function getAuthConfig() {
|
|
7391
7362
|
return opts.getGoogleAuthConfig?.() ?? {};
|
|
7392
7363
|
}
|
|
7393
|
-
function requireConnectionStore(
|
|
7364
|
+
function requireConnectionStore() {
|
|
7394
7365
|
if (opts.googleConnectionStore) return opts.googleConnectionStore;
|
|
7395
|
-
|
|
7396
|
-
reply.status(err.statusCode).send(err.toJSON());
|
|
7397
|
-
return null;
|
|
7366
|
+
throw validationError("Google auth storage is not configured for this deployment");
|
|
7398
7367
|
}
|
|
7399
7368
|
app.get("/projects/:name/google/connections", async (request) => {
|
|
7400
7369
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -7410,16 +7379,14 @@ async function googleRoutes(app, opts) {
|
|
|
7410
7379
|
updatedAt: connection.updatedAt
|
|
7411
7380
|
}));
|
|
7412
7381
|
});
|
|
7413
|
-
app.post("/projects/:name/google/connect", async (request
|
|
7382
|
+
app.post("/projects/:name/google/connect", async (request) => {
|
|
7414
7383
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
7415
7384
|
if (!googleClientId || !googleClientSecret) {
|
|
7416
|
-
|
|
7417
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7385
|
+
throw validationError("Google OAuth is not configured. Set Google OAuth credentials in the local Canonry config.");
|
|
7418
7386
|
}
|
|
7419
7387
|
const { type, propertyId, publicUrl } = request.body ?? {};
|
|
7420
7388
|
if (!type || type !== "gsc" && type !== "ga4") {
|
|
7421
|
-
|
|
7422
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7389
|
+
throw validationError('type must be "gsc" or "ga4"');
|
|
7423
7390
|
}
|
|
7424
7391
|
const project = resolveProject(app.db, request.params.name);
|
|
7425
7392
|
let redirectUri;
|
|
@@ -7445,8 +7412,7 @@ async function googleRoutes(app, opts) {
|
|
|
7445
7412
|
if (!googleClientId || !googleClientSecret) {
|
|
7446
7413
|
return reply.status(500).send("Google OAuth not configured");
|
|
7447
7414
|
}
|
|
7448
|
-
const store = requireConnectionStore(
|
|
7449
|
-
if (!store) return;
|
|
7415
|
+
const store = requireConnectionStore();
|
|
7450
7416
|
const escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
7451
7417
|
const { code, state, error } = request.query;
|
|
7452
7418
|
if (error) {
|
|
@@ -7536,13 +7502,11 @@ async function googleRoutes(app, opts) {
|
|
|
7536
7502
|
return handleOAuthCallback(request, reply);
|
|
7537
7503
|
});
|
|
7538
7504
|
app.delete("/projects/:name/google/connections/:type", async (request, reply) => {
|
|
7539
|
-
const store = requireConnectionStore(
|
|
7540
|
-
if (!store) return;
|
|
7505
|
+
const store = requireConnectionStore();
|
|
7541
7506
|
const project = resolveProject(app.db, request.params.name);
|
|
7542
7507
|
const deleted = store.deleteConnection(project.canonicalDomain, request.params.type);
|
|
7543
7508
|
if (!deleted) {
|
|
7544
|
-
|
|
7545
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7509
|
+
throw notFound("Google connection", request.params.type);
|
|
7546
7510
|
}
|
|
7547
7511
|
writeAuditLog(app.db, {
|
|
7548
7512
|
projectId: project.id,
|
|
@@ -7553,27 +7517,23 @@ async function googleRoutes(app, opts) {
|
|
|
7553
7517
|
});
|
|
7554
7518
|
return reply.status(204).send();
|
|
7555
7519
|
});
|
|
7556
|
-
app.get("/projects/:name/google/properties", async (request
|
|
7520
|
+
app.get("/projects/:name/google/properties", async (request) => {
|
|
7557
7521
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
7558
7522
|
if (!googleClientId || !googleClientSecret) {
|
|
7559
|
-
|
|
7560
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7523
|
+
throw validationError("Google OAuth is not configured");
|
|
7561
7524
|
}
|
|
7562
|
-
const store = requireConnectionStore(
|
|
7563
|
-
if (!store) return;
|
|
7525
|
+
const store = requireConnectionStore();
|
|
7564
7526
|
const project = resolveProject(app.db, request.params.name);
|
|
7565
7527
|
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
7566
7528
|
const sites = await listSites(accessToken);
|
|
7567
7529
|
return { sites };
|
|
7568
7530
|
});
|
|
7569
|
-
app.post("/projects/:name/google/gsc/sync", async (request
|
|
7570
|
-
const store = requireConnectionStore(
|
|
7571
|
-
if (!store) return;
|
|
7531
|
+
app.post("/projects/:name/google/gsc/sync", async (request) => {
|
|
7532
|
+
const store = requireConnectionStore();
|
|
7572
7533
|
const project = resolveProject(app.db, request.params.name);
|
|
7573
7534
|
const conn = store.getConnection(project.canonicalDomain, "gsc");
|
|
7574
7535
|
if (!conn) {
|
|
7575
|
-
|
|
7576
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7536
|
+
throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
|
|
7577
7537
|
}
|
|
7578
7538
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7579
7539
|
const runId = crypto14.randomUUID();
|
|
@@ -7615,24 +7575,20 @@ async function googleRoutes(app, opts) {
|
|
|
7615
7575
|
position: parseFloat(r.position)
|
|
7616
7576
|
}));
|
|
7617
7577
|
});
|
|
7618
|
-
app.post("/projects/:name/google/gsc/inspect", async (request
|
|
7578
|
+
app.post("/projects/:name/google/gsc/inspect", async (request) => {
|
|
7619
7579
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
7620
7580
|
if (!googleClientId || !googleClientSecret) {
|
|
7621
|
-
|
|
7622
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7581
|
+
throw validationError("Google OAuth is not configured");
|
|
7623
7582
|
}
|
|
7624
|
-
const store = requireConnectionStore(
|
|
7625
|
-
if (!store) return;
|
|
7583
|
+
const store = requireConnectionStore();
|
|
7626
7584
|
const project = resolveProject(app.db, request.params.name);
|
|
7627
7585
|
const { url } = request.body ?? {};
|
|
7628
7586
|
if (!url) {
|
|
7629
|
-
|
|
7630
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7587
|
+
throw validationError("url is required");
|
|
7631
7588
|
}
|
|
7632
7589
|
const { accessToken, propertyId } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
7633
7590
|
if (!propertyId) {
|
|
7634
|
-
|
|
7635
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7591
|
+
throw validationError("No GSC property configured for this connection");
|
|
7636
7592
|
}
|
|
7637
7593
|
const result = await inspectUrl(accessToken, url, propertyId);
|
|
7638
7594
|
const ir = result.inspectionResult;
|
|
@@ -7832,46 +7788,38 @@ async function googleRoutes(app, opts) {
|
|
|
7832
7788
|
reasonBreakdown: JSON.parse(r.reasonBreakdown)
|
|
7833
7789
|
})).reverse();
|
|
7834
7790
|
});
|
|
7835
|
-
app.get("/projects/:name/google/gsc/sitemaps", async (request
|
|
7791
|
+
app.get("/projects/:name/google/gsc/sitemaps", async (request) => {
|
|
7836
7792
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
7837
7793
|
if (!googleClientId || !googleClientSecret) {
|
|
7838
|
-
|
|
7839
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7794
|
+
throw validationError("Google OAuth is not configured");
|
|
7840
7795
|
}
|
|
7841
|
-
const store = requireConnectionStore(
|
|
7842
|
-
if (!store) return;
|
|
7796
|
+
const store = requireConnectionStore();
|
|
7843
7797
|
const project = resolveProject(app.db, request.params.name);
|
|
7844
7798
|
const { accessToken, propertyId } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
7845
7799
|
if (!propertyId) {
|
|
7846
|
-
|
|
7847
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7800
|
+
throw validationError('No GSC property configured for this connection. Set one with "canonry google set-property".');
|
|
7848
7801
|
}
|
|
7849
7802
|
const sitemaps = await listSitemaps(accessToken, propertyId);
|
|
7850
7803
|
return { sitemaps };
|
|
7851
7804
|
});
|
|
7852
|
-
app.post("/projects/:name/google/gsc/discover-sitemaps", async (request
|
|
7805
|
+
app.post("/projects/:name/google/gsc/discover-sitemaps", async (request) => {
|
|
7853
7806
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
7854
7807
|
if (!googleClientId || !googleClientSecret) {
|
|
7855
|
-
|
|
7856
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7808
|
+
throw validationError("Google OAuth is not configured");
|
|
7857
7809
|
}
|
|
7858
|
-
const store = requireConnectionStore(
|
|
7859
|
-
if (!store) return;
|
|
7810
|
+
const store = requireConnectionStore();
|
|
7860
7811
|
const project = resolveProject(app.db, request.params.name);
|
|
7861
7812
|
const conn = store.getConnection(project.canonicalDomain, "gsc");
|
|
7862
7813
|
if (!conn) {
|
|
7863
|
-
|
|
7864
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7814
|
+
throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
|
|
7865
7815
|
}
|
|
7866
7816
|
if (!conn.propertyId) {
|
|
7867
|
-
|
|
7868
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7817
|
+
throw validationError("No GSC property configured for this connection");
|
|
7869
7818
|
}
|
|
7870
7819
|
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
7871
7820
|
const sitemaps = await listSitemaps(accessToken, conn.propertyId);
|
|
7872
7821
|
if (sitemaps.length === 0) {
|
|
7873
|
-
|
|
7874
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7822
|
+
throw validationError("No sitemaps found for this GSC property. Submit a sitemap in Google Search Console first.");
|
|
7875
7823
|
}
|
|
7876
7824
|
const primary = sitemaps.find((s) => !s.isSitemapsIndex) ?? sitemaps[0];
|
|
7877
7825
|
const sitemapUrl = primary.path;
|
|
@@ -7895,18 +7843,15 @@ async function googleRoutes(app, opts) {
|
|
|
7895
7843
|
const run = app.db.select().from(runs).where(eq14(runs.id, runId)).get();
|
|
7896
7844
|
return { sitemaps, primarySitemapUrl: sitemapUrl, run };
|
|
7897
7845
|
});
|
|
7898
|
-
app.post("/projects/:name/google/gsc/inspect-sitemap", async (request
|
|
7899
|
-
const store = requireConnectionStore(
|
|
7900
|
-
if (!store) return;
|
|
7846
|
+
app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
|
|
7847
|
+
const store = requireConnectionStore();
|
|
7901
7848
|
const project = resolveProject(app.db, request.params.name);
|
|
7902
7849
|
const conn = store.getConnection(project.canonicalDomain, "gsc");
|
|
7903
7850
|
if (!conn) {
|
|
7904
|
-
|
|
7905
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7851
|
+
throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
|
|
7906
7852
|
}
|
|
7907
7853
|
if (!conn.propertyId) {
|
|
7908
|
-
|
|
7909
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7854
|
+
throw validationError("No GSC property configured for this connection");
|
|
7910
7855
|
}
|
|
7911
7856
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7912
7857
|
const runId = crypto14.randomUUID();
|
|
@@ -7925,14 +7870,12 @@ async function googleRoutes(app, opts) {
|
|
|
7925
7870
|
const run = app.db.select().from(runs).where(eq14(runs.id, runId)).get();
|
|
7926
7871
|
return run;
|
|
7927
7872
|
});
|
|
7928
|
-
app.put("/projects/:name/google/connections/:type/sitemap", async (request
|
|
7929
|
-
const store = requireConnectionStore(
|
|
7930
|
-
if (!store) return;
|
|
7873
|
+
app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
|
|
7874
|
+
const store = requireConnectionStore();
|
|
7931
7875
|
const project = resolveProject(app.db, request.params.name);
|
|
7932
7876
|
const { sitemapUrl } = request.body ?? {};
|
|
7933
7877
|
if (!sitemapUrl || !sitemapUrl.trim()) {
|
|
7934
|
-
|
|
7935
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7878
|
+
throw validationError("sitemapUrl is required");
|
|
7936
7879
|
}
|
|
7937
7880
|
const conn = store.updateConnection(
|
|
7938
7881
|
project.canonicalDomain,
|
|
@@ -7940,19 +7883,16 @@ async function googleRoutes(app, opts) {
|
|
|
7940
7883
|
{ sitemapUrl: sitemapUrl.trim(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
7941
7884
|
);
|
|
7942
7885
|
if (!conn) {
|
|
7943
|
-
|
|
7944
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7886
|
+
throw notFound("Google connection", request.params.type);
|
|
7945
7887
|
}
|
|
7946
7888
|
return { sitemapUrl: sitemapUrl.trim() };
|
|
7947
7889
|
});
|
|
7948
|
-
app.put("/projects/:name/google/connections/:type/property", async (request
|
|
7949
|
-
const store = requireConnectionStore(
|
|
7950
|
-
if (!store) return;
|
|
7890
|
+
app.put("/projects/:name/google/connections/:type/property", async (request) => {
|
|
7891
|
+
const store = requireConnectionStore();
|
|
7951
7892
|
const project = resolveProject(app.db, request.params.name);
|
|
7952
7893
|
const { propertyId } = request.body ?? {};
|
|
7953
7894
|
if (!propertyId) {
|
|
7954
|
-
|
|
7955
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7895
|
+
throw validationError("propertyId is required");
|
|
7956
7896
|
}
|
|
7957
7897
|
const conn = store.updateConnection(
|
|
7958
7898
|
project.canonicalDomain,
|
|
@@ -7960,19 +7900,16 @@ async function googleRoutes(app, opts) {
|
|
|
7960
7900
|
{ propertyId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
7961
7901
|
);
|
|
7962
7902
|
if (!conn) {
|
|
7963
|
-
|
|
7964
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7903
|
+
throw notFound("Google connection", request.params.type);
|
|
7965
7904
|
}
|
|
7966
7905
|
return { propertyId };
|
|
7967
7906
|
});
|
|
7968
|
-
app.post("/projects/:name/google/indexing/request", async (request
|
|
7907
|
+
app.post("/projects/:name/google/indexing/request", async (request) => {
|
|
7969
7908
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
7970
7909
|
if (!googleClientId || !googleClientSecret) {
|
|
7971
|
-
|
|
7972
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7910
|
+
throw validationError("Google OAuth is not configured");
|
|
7973
7911
|
}
|
|
7974
|
-
const store = requireConnectionStore(
|
|
7975
|
-
if (!store) return;
|
|
7912
|
+
const store = requireConnectionStore();
|
|
7976
7913
|
const project = resolveProject(app.db, request.params.name);
|
|
7977
7914
|
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
7978
7915
|
let urlsToNotify = request.body?.urls ?? [];
|
|
@@ -7991,18 +7928,15 @@ async function googleRoutes(app, opts) {
|
|
|
7991
7928
|
}
|
|
7992
7929
|
}
|
|
7993
7930
|
if (unindexedUrls.length === 0) {
|
|
7994
|
-
|
|
7995
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7931
|
+
throw validationError('No unindexed URLs found. Run "canonry google inspect-sitemap" first.');
|
|
7996
7932
|
}
|
|
7997
7933
|
urlsToNotify = unindexedUrls;
|
|
7998
7934
|
}
|
|
7999
7935
|
if (urlsToNotify.length === 0) {
|
|
8000
|
-
|
|
8001
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7936
|
+
throw validationError("At least one URL is required (or use allUnindexed: true)");
|
|
8002
7937
|
}
|
|
8003
7938
|
if (urlsToNotify.length > INDEXING_API_DAILY_LIMIT) {
|
|
8004
|
-
|
|
8005
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
7939
|
+
throw validationError(`Cannot request indexing for more than ${INDEXING_API_DAILY_LIMIT} URLs per request (got ${urlsToNotify.length})`);
|
|
8006
7940
|
}
|
|
8007
7941
|
const projectDomain = normalizeProjectDomain(project.canonicalDomain);
|
|
8008
7942
|
const invalidUrls = urlsToNotify.filter((url) => {
|
|
@@ -8014,10 +7948,9 @@ async function googleRoutes(app, opts) {
|
|
|
8014
7948
|
}
|
|
8015
7949
|
});
|
|
8016
7950
|
if (invalidUrls.length > 0) {
|
|
8017
|
-
|
|
7951
|
+
throw validationError(
|
|
8018
7952
|
`URLs must belong to project domain "${project.canonicalDomain}". Invalid: ${invalidUrls.slice(0, 5).join(", ")}`
|
|
8019
7953
|
);
|
|
8020
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8021
7954
|
}
|
|
8022
7955
|
const results = [];
|
|
8023
7956
|
for (const url of urlsToNotify) {
|
|
@@ -8231,28 +8164,22 @@ function bingLog(level, action, ctx) {
|
|
|
8231
8164
|
stream.write(JSON.stringify(entry) + "\n");
|
|
8232
8165
|
}
|
|
8233
8166
|
async function bingRoutes(app, opts) {
|
|
8234
|
-
function requireConnectionStore(
|
|
8167
|
+
function requireConnectionStore() {
|
|
8235
8168
|
if (opts.bingConnectionStore) return opts.bingConnectionStore;
|
|
8236
|
-
|
|
8237
|
-
reply.status(err.statusCode).send(err.toJSON());
|
|
8238
|
-
return null;
|
|
8169
|
+
throw validationError("Bing connection storage is not configured for this deployment");
|
|
8239
8170
|
}
|
|
8240
|
-
function requireConnection(store, domain
|
|
8171
|
+
function requireConnection(store, domain) {
|
|
8241
8172
|
const conn = store.getConnection(domain);
|
|
8242
8173
|
if (!conn) {
|
|
8243
|
-
|
|
8244
|
-
reply.status(err.statusCode).send(err.toJSON());
|
|
8245
|
-
return null;
|
|
8174
|
+
throw validationError('No Bing connection found for this domain. Run "canonry bing connect <project>" first.');
|
|
8246
8175
|
}
|
|
8247
8176
|
return conn;
|
|
8248
8177
|
}
|
|
8249
|
-
app.post("/projects/:name/bing/connect", async (request
|
|
8250
|
-
const store = requireConnectionStore(
|
|
8251
|
-
if (!store) return;
|
|
8178
|
+
app.post("/projects/:name/bing/connect", async (request) => {
|
|
8179
|
+
const store = requireConnectionStore();
|
|
8252
8180
|
const { apiKey } = request.body ?? {};
|
|
8253
8181
|
if (!apiKey || typeof apiKey !== "string") {
|
|
8254
|
-
|
|
8255
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8182
|
+
throw validationError("apiKey is required");
|
|
8256
8183
|
}
|
|
8257
8184
|
const project = resolveProject(app.db, request.params.name);
|
|
8258
8185
|
let sites;
|
|
@@ -8262,8 +8189,7 @@ async function bingRoutes(app, opts) {
|
|
|
8262
8189
|
} catch (e) {
|
|
8263
8190
|
const msg = e instanceof Error ? e.message : String(e);
|
|
8264
8191
|
bingLog("error", "connect.verify-key-failed", { domain: project.canonicalDomain, error: msg });
|
|
8265
|
-
|
|
8266
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8192
|
+
throw validationError(`Failed to verify Bing API key: ${msg}`);
|
|
8267
8193
|
}
|
|
8268
8194
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8269
8195
|
const existing = store.getConnection(project.canonicalDomain);
|
|
@@ -8289,13 +8215,11 @@ async function bingRoutes(app, opts) {
|
|
|
8289
8215
|
};
|
|
8290
8216
|
});
|
|
8291
8217
|
app.delete("/projects/:name/bing/disconnect", async (request, reply) => {
|
|
8292
|
-
const store = requireConnectionStore(
|
|
8293
|
-
if (!store) return;
|
|
8218
|
+
const store = requireConnectionStore();
|
|
8294
8219
|
const project = resolveProject(app.db, request.params.name);
|
|
8295
8220
|
const deleted = store.deleteConnection(project.canonicalDomain);
|
|
8296
8221
|
if (!deleted) {
|
|
8297
|
-
|
|
8298
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8222
|
+
throw notFound("Bing connection", project.canonicalDomain);
|
|
8299
8223
|
}
|
|
8300
8224
|
writeAuditLog(app.db, {
|
|
8301
8225
|
projectId: project.id,
|
|
@@ -8306,9 +8230,8 @@ async function bingRoutes(app, opts) {
|
|
|
8306
8230
|
});
|
|
8307
8231
|
return reply.status(204).send();
|
|
8308
8232
|
});
|
|
8309
|
-
app.get("/projects/:name/bing/status", async (request
|
|
8310
|
-
const store = requireConnectionStore(
|
|
8311
|
-
if (!store) return;
|
|
8233
|
+
app.get("/projects/:name/bing/status", async (request) => {
|
|
8234
|
+
const store = requireConnectionStore();
|
|
8312
8235
|
const project = resolveProject(app.db, request.params.name);
|
|
8313
8236
|
const conn = store.getConnection(project.canonicalDomain);
|
|
8314
8237
|
return {
|
|
@@ -8319,25 +8242,20 @@ async function bingRoutes(app, opts) {
|
|
|
8319
8242
|
updatedAt: conn?.updatedAt ?? null
|
|
8320
8243
|
};
|
|
8321
8244
|
});
|
|
8322
|
-
app.get("/projects/:name/bing/sites", async (request
|
|
8323
|
-
const store = requireConnectionStore(
|
|
8324
|
-
if (!store) return;
|
|
8245
|
+
app.get("/projects/:name/bing/sites", async (request) => {
|
|
8246
|
+
const store = requireConnectionStore();
|
|
8325
8247
|
const project = resolveProject(app.db, request.params.name);
|
|
8326
|
-
const conn = requireConnection(store, project.canonicalDomain
|
|
8327
|
-
if (!conn) return;
|
|
8248
|
+
const conn = requireConnection(store, project.canonicalDomain);
|
|
8328
8249
|
const sites = await getSites(conn.apiKey);
|
|
8329
8250
|
return { sites: sites.map((s) => ({ url: s.Url, verified: s.Verified ?? false })) };
|
|
8330
8251
|
});
|
|
8331
|
-
app.post("/projects/:name/bing/set-site", async (request
|
|
8332
|
-
const store = requireConnectionStore(
|
|
8333
|
-
if (!store) return;
|
|
8252
|
+
app.post("/projects/:name/bing/set-site", async (request) => {
|
|
8253
|
+
const store = requireConnectionStore();
|
|
8334
8254
|
const project = resolveProject(app.db, request.params.name);
|
|
8335
|
-
|
|
8336
|
-
if (!conn) return;
|
|
8255
|
+
requireConnection(store, project.canonicalDomain);
|
|
8337
8256
|
const { siteUrl } = request.body ?? {};
|
|
8338
8257
|
if (!siteUrl || typeof siteUrl !== "string") {
|
|
8339
|
-
|
|
8340
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8258
|
+
throw validationError("siteUrl is required");
|
|
8341
8259
|
}
|
|
8342
8260
|
store.updateConnection(project.canonicalDomain, {
|
|
8343
8261
|
siteUrl,
|
|
@@ -8345,12 +8263,10 @@ async function bingRoutes(app, opts) {
|
|
|
8345
8263
|
});
|
|
8346
8264
|
return { siteUrl };
|
|
8347
8265
|
});
|
|
8348
|
-
app.get("/projects/:name/bing/coverage", async (request
|
|
8349
|
-
const store = requireConnectionStore(
|
|
8350
|
-
if (!store) return;
|
|
8266
|
+
app.get("/projects/:name/bing/coverage", async (request) => {
|
|
8267
|
+
const store = requireConnectionStore();
|
|
8351
8268
|
const project = resolveProject(app.db, request.params.name);
|
|
8352
|
-
|
|
8353
|
-
if (!conn) return;
|
|
8269
|
+
requireConnection(store, project.canonicalDomain);
|
|
8354
8270
|
const allInspections = app.db.select().from(bingUrlInspections).where(eq15(bingUrlInspections.projectId, project.id)).orderBy(desc6(bingUrlInspections.inspectedAt)).all();
|
|
8355
8271
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
8356
8272
|
const definitiveByUrl = /* @__PURE__ */ new Map();
|
|
@@ -8435,9 +8351,8 @@ async function bingRoutes(app, opts) {
|
|
|
8435
8351
|
unknown: unknownUrls.map(formatRow)
|
|
8436
8352
|
};
|
|
8437
8353
|
});
|
|
8438
|
-
app.get("/projects/:name/bing/coverage/history", async (request
|
|
8439
|
-
|
|
8440
|
-
if (!store) return;
|
|
8354
|
+
app.get("/projects/:name/bing/coverage/history", async (request) => {
|
|
8355
|
+
requireConnectionStore();
|
|
8441
8356
|
const project = resolveProject(app.db, request.params.name);
|
|
8442
8357
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
8443
8358
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
@@ -8449,9 +8364,8 @@ async function bingRoutes(app, opts) {
|
|
|
8449
8364
|
unknown: r.unknown
|
|
8450
8365
|
}));
|
|
8451
8366
|
});
|
|
8452
|
-
app.get("/projects/:name/bing/inspections", async (request
|
|
8453
|
-
|
|
8454
|
-
if (!store) return;
|
|
8367
|
+
app.get("/projects/:name/bing/inspections", async (request) => {
|
|
8368
|
+
requireConnectionStore();
|
|
8455
8369
|
const project = resolveProject(app.db, request.params.name);
|
|
8456
8370
|
const { url, limit } = request.query;
|
|
8457
8371
|
const whereClause = url ? and4(eq15(bingUrlInspections.projectId, project.id), eq15(bingUrlInspections.url, url)) : eq15(bingUrlInspections.projectId, project.id);
|
|
@@ -8469,20 +8383,16 @@ async function bingRoutes(app, opts) {
|
|
|
8469
8383
|
discoveryDate: r.discoveryDate ?? null
|
|
8470
8384
|
}));
|
|
8471
8385
|
});
|
|
8472
|
-
app.post("/projects/:name/bing/inspect-url", async (request
|
|
8473
|
-
const store = requireConnectionStore(
|
|
8474
|
-
if (!store) return;
|
|
8386
|
+
app.post("/projects/:name/bing/inspect-url", async (request) => {
|
|
8387
|
+
const store = requireConnectionStore();
|
|
8475
8388
|
const project = resolveProject(app.db, request.params.name);
|
|
8476
|
-
const conn = requireConnection(store, project.canonicalDomain
|
|
8477
|
-
if (!conn) return;
|
|
8389
|
+
const conn = requireConnection(store, project.canonicalDomain);
|
|
8478
8390
|
if (!conn.siteUrl) {
|
|
8479
|
-
|
|
8480
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8391
|
+
throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
|
|
8481
8392
|
}
|
|
8482
8393
|
const { url } = request.body ?? {};
|
|
8483
8394
|
if (!url) {
|
|
8484
|
-
|
|
8485
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8395
|
+
throw validationError("url is required");
|
|
8486
8396
|
}
|
|
8487
8397
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8488
8398
|
const runId = crypto15.randomUUID();
|
|
@@ -8552,15 +8462,12 @@ async function bingRoutes(app, opts) {
|
|
|
8552
8462
|
throw e;
|
|
8553
8463
|
}
|
|
8554
8464
|
});
|
|
8555
|
-
app.post("/projects/:name/bing/request-indexing", async (request
|
|
8556
|
-
const store = requireConnectionStore(
|
|
8557
|
-
if (!store) return;
|
|
8465
|
+
app.post("/projects/:name/bing/request-indexing", async (request) => {
|
|
8466
|
+
const store = requireConnectionStore();
|
|
8558
8467
|
const project = resolveProject(app.db, request.params.name);
|
|
8559
|
-
const conn = requireConnection(store, project.canonicalDomain
|
|
8560
|
-
if (!conn) return;
|
|
8468
|
+
const conn = requireConnection(store, project.canonicalDomain);
|
|
8561
8469
|
if (!conn.siteUrl) {
|
|
8562
|
-
|
|
8563
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8470
|
+
throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
|
|
8564
8471
|
}
|
|
8565
8472
|
let urlsToSubmit = request.body?.urls ?? [];
|
|
8566
8473
|
if (request.body?.allUnindexed) {
|
|
@@ -8578,18 +8485,15 @@ async function bingRoutes(app, opts) {
|
|
|
8578
8485
|
}
|
|
8579
8486
|
}
|
|
8580
8487
|
if (unindexedUrls.length === 0) {
|
|
8581
|
-
|
|
8582
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8488
|
+
throw validationError('No unindexed or unknown URLs found. Run "canonry bing inspect <project> <url>" first.');
|
|
8583
8489
|
}
|
|
8584
8490
|
urlsToSubmit = unindexedUrls;
|
|
8585
8491
|
}
|
|
8586
8492
|
if (urlsToSubmit.length === 0) {
|
|
8587
|
-
|
|
8588
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8493
|
+
throw validationError("At least one URL is required (or use allUnindexed: true)");
|
|
8589
8494
|
}
|
|
8590
8495
|
if (urlsToSubmit.length > BING_SUBMIT_URL_DAILY_LIMIT) {
|
|
8591
|
-
|
|
8592
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8496
|
+
throw validationError(`Cannot submit more than ${BING_SUBMIT_URL_DAILY_LIMIT} URLs per day (got ${urlsToSubmit.length})`);
|
|
8593
8497
|
}
|
|
8594
8498
|
const results = [];
|
|
8595
8499
|
bingLog("info", "index-submit.start", { domain: project.canonicalDomain, siteUrl: conn.siteUrl, urlCount: urlsToSubmit.length, allUnindexed: !!request.body?.allUnindexed });
|
|
@@ -8632,15 +8536,12 @@ async function bingRoutes(app, opts) {
|
|
|
8632
8536
|
results
|
|
8633
8537
|
};
|
|
8634
8538
|
});
|
|
8635
|
-
app.get("/projects/:name/bing/performance", async (request
|
|
8636
|
-
const store = requireConnectionStore(
|
|
8637
|
-
if (!store) return;
|
|
8539
|
+
app.get("/projects/:name/bing/performance", async (request) => {
|
|
8540
|
+
const store = requireConnectionStore();
|
|
8638
8541
|
const project = resolveProject(app.db, request.params.name);
|
|
8639
|
-
const conn = requireConnection(store, project.canonicalDomain
|
|
8640
|
-
if (!conn) return;
|
|
8542
|
+
const conn = requireConnection(store, project.canonicalDomain);
|
|
8641
8543
|
if (!conn.siteUrl) {
|
|
8642
|
-
|
|
8643
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
8544
|
+
throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
|
|
8644
8545
|
}
|
|
8645
8546
|
const stats = await getKeywordStats(conn.apiKey, conn.siteUrl);
|
|
8646
8547
|
return stats.map((s) => ({
|
|
@@ -10547,68 +10448,51 @@ function parseEnvInput(value, fieldName = "env") {
|
|
|
10547
10448
|
}
|
|
10548
10449
|
return env;
|
|
10549
10450
|
}
|
|
10550
|
-
function
|
|
10551
|
-
if (!(error instanceof WordpressApiError)) return false;
|
|
10552
|
-
let appError;
|
|
10451
|
+
function toAppError(error) {
|
|
10553
10452
|
switch (error.code) {
|
|
10554
10453
|
case "AUTH_INVALID":
|
|
10555
|
-
|
|
10556
|
-
break;
|
|
10454
|
+
return new AppError("AUTH_INVALID", error.message, error.statusCode);
|
|
10557
10455
|
case "NOT_FOUND":
|
|
10558
|
-
|
|
10559
|
-
break;
|
|
10560
|
-
case "UPSTREAM_ERROR":
|
|
10561
|
-
appError = providerError(error.message, { statusCode: error.statusCode });
|
|
10562
|
-
break;
|
|
10456
|
+
return new AppError("NOT_FOUND", error.message, error.statusCode);
|
|
10563
10457
|
case "UNSUPPORTED":
|
|
10564
10458
|
case "VALIDATION_ERROR":
|
|
10565
|
-
|
|
10566
|
-
|
|
10459
|
+
return validationError(error.message);
|
|
10460
|
+
case "UPSTREAM_ERROR":
|
|
10567
10461
|
default:
|
|
10568
|
-
|
|
10569
|
-
break;
|
|
10462
|
+
return providerError(error.message, { statusCode: error.statusCode });
|
|
10570
10463
|
}
|
|
10571
|
-
reply.status(appError.statusCode).send(appError.toJSON());
|
|
10572
|
-
return true;
|
|
10573
10464
|
}
|
|
10574
|
-
async function withWordpressErrorHandling(
|
|
10465
|
+
async function withWordpressErrorHandling(handler) {
|
|
10575
10466
|
try {
|
|
10576
10467
|
return await handler();
|
|
10577
10468
|
} catch (error) {
|
|
10578
|
-
if (
|
|
10469
|
+
if (error instanceof WordpressApiError) throw toAppError(error);
|
|
10579
10470
|
throw error;
|
|
10580
10471
|
}
|
|
10581
10472
|
}
|
|
10582
10473
|
async function wordpressRoutes(app, opts) {
|
|
10583
|
-
function requireStore(
|
|
10474
|
+
function requireStore() {
|
|
10584
10475
|
if (opts.wordpressConnectionStore) return opts.wordpressConnectionStore;
|
|
10585
|
-
|
|
10586
|
-
reply.status(err.statusCode).send(err.toJSON());
|
|
10587
|
-
return null;
|
|
10476
|
+
throw validationError("WordPress connection storage is not configured for this deployment");
|
|
10588
10477
|
}
|
|
10589
|
-
function requireConnection(store, projectName
|
|
10478
|
+
function requireConnection(store, projectName) {
|
|
10590
10479
|
const connection = store.getConnection(projectName);
|
|
10591
10480
|
if (!connection) {
|
|
10592
|
-
|
|
10593
|
-
reply.status(err.statusCode).send(err.toJSON());
|
|
10594
|
-
return null;
|
|
10481
|
+
throw validationError(`No WordPress connection found for project "${projectName}". Run "canonry wordpress connect ${projectName}" first.`);
|
|
10595
10482
|
}
|
|
10596
10483
|
return connection;
|
|
10597
10484
|
}
|
|
10598
|
-
app.post("/projects/:name/wordpress/connect", async (request
|
|
10599
|
-
return withWordpressErrorHandling(
|
|
10600
|
-
const store = requireStore(
|
|
10601
|
-
if (!store) return;
|
|
10485
|
+
app.post("/projects/:name/wordpress/connect", async (request) => {
|
|
10486
|
+
return withWordpressErrorHandling(async () => {
|
|
10487
|
+
const store = requireStore();
|
|
10602
10488
|
const project = resolveProject(app.db, request.params.name);
|
|
10603
10489
|
const { url, stagingUrl, username, appPassword } = request.body ?? {};
|
|
10604
10490
|
if (!url || !username || !appPassword) {
|
|
10605
|
-
|
|
10606
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10491
|
+
throw validationError("url, username, and appPassword are required");
|
|
10607
10492
|
}
|
|
10608
10493
|
const defaultEnv = parseEnvInput(request.body?.defaultEnv, "defaultEnv") ?? (stagingUrl ? "staging" : "live");
|
|
10609
10494
|
if (defaultEnv === "staging" && !stagingUrl) {
|
|
10610
|
-
|
|
10611
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10495
|
+
throw validationError('defaultEnv "staging" requires stagingUrl');
|
|
10612
10496
|
}
|
|
10613
10497
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10614
10498
|
const existing = store.getConnection(project.name);
|
|
@@ -10644,13 +10528,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
10644
10528
|
});
|
|
10645
10529
|
});
|
|
10646
10530
|
app.delete("/projects/:name/wordpress/disconnect", async (request, reply) => {
|
|
10647
|
-
const store = requireStore(
|
|
10648
|
-
if (!store) return;
|
|
10531
|
+
const store = requireStore();
|
|
10649
10532
|
const project = resolveProject(app.db, request.params.name);
|
|
10650
10533
|
const deleted = store.deleteConnection(project.name);
|
|
10651
10534
|
if (!deleted) {
|
|
10652
|
-
|
|
10653
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10535
|
+
throw notFound("WordPress connection", project.name);
|
|
10654
10536
|
}
|
|
10655
10537
|
writeAuditLog(app.db, {
|
|
10656
10538
|
projectId: project.id,
|
|
@@ -10685,13 +10567,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
10685
10567
|
adminUrl: getWpStagingAdminUrl(connection.url)
|
|
10686
10568
|
};
|
|
10687
10569
|
});
|
|
10688
|
-
app.get("/projects/:name/wordpress/pages", async (request
|
|
10689
|
-
return withWordpressErrorHandling(
|
|
10690
|
-
const store = requireStore(
|
|
10691
|
-
if (!store) return;
|
|
10570
|
+
app.get("/projects/:name/wordpress/pages", async (request) => {
|
|
10571
|
+
return withWordpressErrorHandling(async () => {
|
|
10572
|
+
const store = requireStore();
|
|
10692
10573
|
const project = resolveProject(app.db, request.params.name);
|
|
10693
|
-
const connection = requireConnection(store, project.name
|
|
10694
|
-
if (!connection) return;
|
|
10574
|
+
const connection = requireConnection(store, project.name);
|
|
10695
10575
|
const env = parseEnvInput(request.query?.env);
|
|
10696
10576
|
return {
|
|
10697
10577
|
env: env ?? connection.defaultEnv,
|
|
@@ -10699,34 +10579,28 @@ async function wordpressRoutes(app, opts) {
|
|
|
10699
10579
|
};
|
|
10700
10580
|
});
|
|
10701
10581
|
});
|
|
10702
|
-
app.get("/projects/:name/wordpress/page", async (request
|
|
10703
|
-
return withWordpressErrorHandling(
|
|
10704
|
-
const store = requireStore(
|
|
10705
|
-
if (!store) return;
|
|
10582
|
+
app.get("/projects/:name/wordpress/page", async (request) => {
|
|
10583
|
+
return withWordpressErrorHandling(async () => {
|
|
10584
|
+
const store = requireStore();
|
|
10706
10585
|
const project = resolveProject(app.db, request.params.name);
|
|
10707
|
-
const connection = requireConnection(store, project.name
|
|
10708
|
-
if (!connection) return;
|
|
10586
|
+
const connection = requireConnection(store, project.name);
|
|
10709
10587
|
const slug = request.query?.slug?.trim();
|
|
10710
10588
|
if (!slug) {
|
|
10711
|
-
|
|
10712
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10589
|
+
throw validationError("slug is required");
|
|
10713
10590
|
}
|
|
10714
10591
|
const env = parseEnvInput(request.query?.env);
|
|
10715
10592
|
return getPageDetail(connection, slug, env);
|
|
10716
10593
|
});
|
|
10717
10594
|
});
|
|
10718
|
-
app.post("/projects/:name/wordpress/pages", async (request
|
|
10719
|
-
return withWordpressErrorHandling(
|
|
10720
|
-
const store = requireStore(
|
|
10721
|
-
if (!store) return;
|
|
10595
|
+
app.post("/projects/:name/wordpress/pages", async (request) => {
|
|
10596
|
+
return withWordpressErrorHandling(async () => {
|
|
10597
|
+
const store = requireStore();
|
|
10722
10598
|
const project = resolveProject(app.db, request.params.name);
|
|
10723
|
-
const connection = requireConnection(store, project.name
|
|
10724
|
-
if (!connection) return;
|
|
10599
|
+
const connection = requireConnection(store, project.name);
|
|
10725
10600
|
const { title, slug, content, status } = request.body ?? {};
|
|
10726
10601
|
const env = parseEnvInput(request.body?.env);
|
|
10727
10602
|
if (!title || !slug || !content) {
|
|
10728
|
-
|
|
10729
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10603
|
+
throw validationError("title, slug, and content are required");
|
|
10730
10604
|
}
|
|
10731
10605
|
const created = await createPage(connection, { title, slug, content, status }, env);
|
|
10732
10606
|
writeAuditLog(app.db, {
|
|
@@ -10739,17 +10613,14 @@ async function wordpressRoutes(app, opts) {
|
|
|
10739
10613
|
return created;
|
|
10740
10614
|
});
|
|
10741
10615
|
});
|
|
10742
|
-
app.put("/projects/:name/wordpress/page", async (request
|
|
10743
|
-
return withWordpressErrorHandling(
|
|
10744
|
-
const store = requireStore(
|
|
10745
|
-
if (!store) return;
|
|
10616
|
+
app.put("/projects/:name/wordpress/page", async (request) => {
|
|
10617
|
+
return withWordpressErrorHandling(async () => {
|
|
10618
|
+
const store = requireStore();
|
|
10746
10619
|
const project = resolveProject(app.db, request.params.name);
|
|
10747
|
-
const connection = requireConnection(store, project.name
|
|
10748
|
-
if (!connection) return;
|
|
10620
|
+
const connection = requireConnection(store, project.name);
|
|
10749
10621
|
const currentSlug = request.body?.currentSlug?.trim();
|
|
10750
10622
|
if (!currentSlug) {
|
|
10751
|
-
|
|
10752
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10623
|
+
throw validationError("currentSlug is required");
|
|
10753
10624
|
}
|
|
10754
10625
|
const env = parseEnvInput(request.body?.env);
|
|
10755
10626
|
const updated = await updatePageBySlug(connection, currentSlug, {
|
|
@@ -10768,17 +10639,14 @@ async function wordpressRoutes(app, opts) {
|
|
|
10768
10639
|
return updated;
|
|
10769
10640
|
});
|
|
10770
10641
|
});
|
|
10771
|
-
app.post("/projects/:name/wordpress/page/meta", async (request
|
|
10772
|
-
return withWordpressErrorHandling(
|
|
10773
|
-
const store = requireStore(
|
|
10774
|
-
if (!store) return;
|
|
10642
|
+
app.post("/projects/:name/wordpress/page/meta", async (request) => {
|
|
10643
|
+
return withWordpressErrorHandling(async () => {
|
|
10644
|
+
const store = requireStore();
|
|
10775
10645
|
const project = resolveProject(app.db, request.params.name);
|
|
10776
|
-
const connection = requireConnection(store, project.name
|
|
10777
|
-
if (!connection) return;
|
|
10646
|
+
const connection = requireConnection(store, project.name);
|
|
10778
10647
|
const slug = request.body?.slug?.trim();
|
|
10779
10648
|
if (!slug) {
|
|
10780
|
-
|
|
10781
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10649
|
+
throw validationError("slug is required");
|
|
10782
10650
|
}
|
|
10783
10651
|
const env = parseEnvInput(request.body?.env);
|
|
10784
10652
|
const updated = await setSeoMeta(connection, slug, {
|
|
@@ -10796,22 +10664,18 @@ async function wordpressRoutes(app, opts) {
|
|
|
10796
10664
|
return updated;
|
|
10797
10665
|
});
|
|
10798
10666
|
});
|
|
10799
|
-
app.post("/projects/:name/wordpress/pages/meta/bulk", async (request
|
|
10800
|
-
return withWordpressErrorHandling(
|
|
10801
|
-
const store = requireStore(
|
|
10802
|
-
if (!store) return;
|
|
10667
|
+
app.post("/projects/:name/wordpress/pages/meta/bulk", async (request) => {
|
|
10668
|
+
return withWordpressErrorHandling(async () => {
|
|
10669
|
+
const store = requireStore();
|
|
10803
10670
|
const project = resolveProject(app.db, request.params.name);
|
|
10804
|
-
const connection = requireConnection(store, project.name
|
|
10805
|
-
if (!connection) return;
|
|
10671
|
+
const connection = requireConnection(store, project.name);
|
|
10806
10672
|
const entries = request.body?.entries;
|
|
10807
10673
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
10808
|
-
|
|
10809
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10674
|
+
throw validationError("entries array is required and must not be empty");
|
|
10810
10675
|
}
|
|
10811
10676
|
for (const entry of entries) {
|
|
10812
10677
|
if (!entry.slug?.trim()) {
|
|
10813
|
-
|
|
10814
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10678
|
+
throw validationError("each entry must have a slug");
|
|
10815
10679
|
}
|
|
10816
10680
|
}
|
|
10817
10681
|
const env = parseEnvInput(request.body?.env);
|
|
@@ -10829,126 +10693,103 @@ async function wordpressRoutes(app, opts) {
|
|
|
10829
10693
|
return result;
|
|
10830
10694
|
});
|
|
10831
10695
|
});
|
|
10832
|
-
app.get("/projects/:name/wordpress/schema", async (request
|
|
10833
|
-
return withWordpressErrorHandling(
|
|
10834
|
-
const store = requireStore(
|
|
10835
|
-
if (!store) return;
|
|
10696
|
+
app.get("/projects/:name/wordpress/schema", async (request) => {
|
|
10697
|
+
return withWordpressErrorHandling(async () => {
|
|
10698
|
+
const store = requireStore();
|
|
10836
10699
|
const project = resolveProject(app.db, request.params.name);
|
|
10837
|
-
const connection = requireConnection(store, project.name
|
|
10838
|
-
if (!connection) return;
|
|
10700
|
+
const connection = requireConnection(store, project.name);
|
|
10839
10701
|
const slug = request.query?.slug?.trim();
|
|
10840
10702
|
if (!slug) {
|
|
10841
|
-
|
|
10842
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10703
|
+
throw validationError("slug is required");
|
|
10843
10704
|
}
|
|
10844
10705
|
const env = parseEnvInput(request.query?.env);
|
|
10845
10706
|
return getPageSchema(connection, slug, env);
|
|
10846
10707
|
});
|
|
10847
10708
|
});
|
|
10848
|
-
app.post("/projects/:name/wordpress/schema/manual", async (request
|
|
10849
|
-
return withWordpressErrorHandling(
|
|
10850
|
-
const store = requireStore(
|
|
10851
|
-
if (!store) return;
|
|
10709
|
+
app.post("/projects/:name/wordpress/schema/manual", async (request) => {
|
|
10710
|
+
return withWordpressErrorHandling(async () => {
|
|
10711
|
+
const store = requireStore();
|
|
10852
10712
|
const project = resolveProject(app.db, request.params.name);
|
|
10853
|
-
const connection = requireConnection(store, project.name
|
|
10854
|
-
if (!connection) return;
|
|
10713
|
+
const connection = requireConnection(store, project.name);
|
|
10855
10714
|
const slug = request.body?.slug?.trim();
|
|
10856
10715
|
const json = request.body?.json;
|
|
10857
10716
|
if (!slug || !json) {
|
|
10858
|
-
|
|
10859
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10717
|
+
throw validationError("slug and json are required");
|
|
10860
10718
|
}
|
|
10861
10719
|
const env = parseEnvInput(request.body?.env);
|
|
10862
10720
|
return buildManualSchemaUpdate(connection, slug, { type: request.body?.type, json }, env);
|
|
10863
10721
|
});
|
|
10864
10722
|
});
|
|
10865
|
-
app.post("/projects/:name/wordpress/schema/deploy", async (request
|
|
10866
|
-
return withWordpressErrorHandling(
|
|
10867
|
-
const store = requireStore(
|
|
10868
|
-
if (!store) return;
|
|
10723
|
+
app.post("/projects/:name/wordpress/schema/deploy", async (request) => {
|
|
10724
|
+
return withWordpressErrorHandling(async () => {
|
|
10725
|
+
const store = requireStore();
|
|
10869
10726
|
const project = resolveProject(app.db, request.params.name);
|
|
10870
|
-
const connection = requireConnection(store, project.name
|
|
10871
|
-
if (!connection) return;
|
|
10727
|
+
const connection = requireConnection(store, project.name);
|
|
10872
10728
|
const profile = request.body?.profile;
|
|
10873
10729
|
if (!profile?.business?.name || !profile?.pages || Object.keys(profile.pages).length === 0) {
|
|
10874
|
-
|
|
10875
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10730
|
+
throw validationError("profile with business.name and non-empty pages is required");
|
|
10876
10731
|
}
|
|
10877
10732
|
const env = parseEnvInput(request.body?.env);
|
|
10878
10733
|
return deploySchemaFromProfile(connection, profile, env);
|
|
10879
10734
|
});
|
|
10880
10735
|
});
|
|
10881
|
-
app.get("/projects/:name/wordpress/schema/status", async (request
|
|
10882
|
-
return withWordpressErrorHandling(
|
|
10883
|
-
const store = requireStore(
|
|
10884
|
-
if (!store) return;
|
|
10736
|
+
app.get("/projects/:name/wordpress/schema/status", async (request) => {
|
|
10737
|
+
return withWordpressErrorHandling(async () => {
|
|
10738
|
+
const store = requireStore();
|
|
10885
10739
|
const project = resolveProject(app.db, request.params.name);
|
|
10886
|
-
const connection = requireConnection(store, project.name
|
|
10887
|
-
if (!connection) return;
|
|
10740
|
+
const connection = requireConnection(store, project.name);
|
|
10888
10741
|
const env = parseEnvInput(request.query?.env);
|
|
10889
10742
|
return getSchemaStatus(connection, env);
|
|
10890
10743
|
});
|
|
10891
10744
|
});
|
|
10892
|
-
app.get("/projects/:name/wordpress/llms-txt", async (request
|
|
10893
|
-
return withWordpressErrorHandling(
|
|
10894
|
-
const store = requireStore(
|
|
10895
|
-
if (!store) return;
|
|
10745
|
+
app.get("/projects/:name/wordpress/llms-txt", async (request) => {
|
|
10746
|
+
return withWordpressErrorHandling(async () => {
|
|
10747
|
+
const store = requireStore();
|
|
10896
10748
|
const project = resolveProject(app.db, request.params.name);
|
|
10897
|
-
const connection = requireConnection(store, project.name
|
|
10898
|
-
if (!connection) return;
|
|
10749
|
+
const connection = requireConnection(store, project.name);
|
|
10899
10750
|
const env = parseEnvInput(request.query?.env);
|
|
10900
10751
|
return getLlmsTxt(connection, env);
|
|
10901
10752
|
});
|
|
10902
10753
|
});
|
|
10903
|
-
app.post("/projects/:name/wordpress/llms-txt/manual", async (request
|
|
10904
|
-
return withWordpressErrorHandling(
|
|
10905
|
-
const store = requireStore(
|
|
10906
|
-
if (!store) return;
|
|
10754
|
+
app.post("/projects/:name/wordpress/llms-txt/manual", async (request) => {
|
|
10755
|
+
return withWordpressErrorHandling(async () => {
|
|
10756
|
+
const store = requireStore();
|
|
10907
10757
|
const project = resolveProject(app.db, request.params.name);
|
|
10908
|
-
const connection = requireConnection(store, project.name
|
|
10909
|
-
if (!connection) return;
|
|
10758
|
+
const connection = requireConnection(store, project.name);
|
|
10910
10759
|
const content = request.body?.content;
|
|
10911
10760
|
if (!content) {
|
|
10912
|
-
|
|
10913
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10761
|
+
throw validationError("content is required");
|
|
10914
10762
|
}
|
|
10915
10763
|
const env = parseEnvInput(request.body?.env);
|
|
10916
10764
|
return buildManualLlmsTxtUpdate(connection, content, env);
|
|
10917
10765
|
});
|
|
10918
10766
|
});
|
|
10919
|
-
app.get("/projects/:name/wordpress/audit", async (request
|
|
10920
|
-
return withWordpressErrorHandling(
|
|
10921
|
-
const store = requireStore(
|
|
10922
|
-
if (!store) return;
|
|
10767
|
+
app.get("/projects/:name/wordpress/audit", async (request) => {
|
|
10768
|
+
return withWordpressErrorHandling(async () => {
|
|
10769
|
+
const store = requireStore();
|
|
10923
10770
|
const project = resolveProject(app.db, request.params.name);
|
|
10924
|
-
const connection = requireConnection(store, project.name
|
|
10925
|
-
if (!connection) return;
|
|
10771
|
+
const connection = requireConnection(store, project.name);
|
|
10926
10772
|
const env = parseEnvInput(request.query?.env);
|
|
10927
10773
|
return runAudit(connection, env);
|
|
10928
10774
|
});
|
|
10929
10775
|
});
|
|
10930
|
-
app.get("/projects/:name/wordpress/diff", async (request
|
|
10931
|
-
return withWordpressErrorHandling(
|
|
10932
|
-
const store = requireStore(
|
|
10933
|
-
if (!store) return;
|
|
10776
|
+
app.get("/projects/:name/wordpress/diff", async (request) => {
|
|
10777
|
+
return withWordpressErrorHandling(async () => {
|
|
10778
|
+
const store = requireStore();
|
|
10934
10779
|
const project = resolveProject(app.db, request.params.name);
|
|
10935
|
-
const connection = requireConnection(store, project.name
|
|
10936
|
-
if (!connection) return;
|
|
10780
|
+
const connection = requireConnection(store, project.name);
|
|
10937
10781
|
const slug = request.query?.slug?.trim();
|
|
10938
10782
|
if (!slug) {
|
|
10939
|
-
|
|
10940
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10783
|
+
throw validationError("slug is required");
|
|
10941
10784
|
}
|
|
10942
10785
|
return diffPageAcrossEnvironments(connection, slug);
|
|
10943
10786
|
});
|
|
10944
10787
|
});
|
|
10945
|
-
app.get("/projects/:name/wordpress/staging/status", async (request
|
|
10946
|
-
return withWordpressErrorHandling(
|
|
10947
|
-
const store = requireStore(
|
|
10948
|
-
if (!store) return;
|
|
10788
|
+
app.get("/projects/:name/wordpress/staging/status", async (request) => {
|
|
10789
|
+
return withWordpressErrorHandling(async () => {
|
|
10790
|
+
const store = requireStore();
|
|
10949
10791
|
const project = resolveProject(app.db, request.params.name);
|
|
10950
|
-
const connection = requireConnection(store, project.name
|
|
10951
|
-
if (!connection) return;
|
|
10792
|
+
const connection = requireConnection(store, project.name);
|
|
10952
10793
|
const plugins = await listActivePlugins(connection, "live");
|
|
10953
10794
|
return {
|
|
10954
10795
|
stagingConfigured: Boolean(connection.stagingUrl),
|
|
@@ -10958,34 +10799,28 @@ async function wordpressRoutes(app, opts) {
|
|
|
10958
10799
|
};
|
|
10959
10800
|
});
|
|
10960
10801
|
});
|
|
10961
|
-
app.post("/projects/:name/wordpress/staging/push", async (request
|
|
10962
|
-
return withWordpressErrorHandling(
|
|
10963
|
-
const store = requireStore(
|
|
10964
|
-
if (!store) return;
|
|
10802
|
+
app.post("/projects/:name/wordpress/staging/push", async (request) => {
|
|
10803
|
+
return withWordpressErrorHandling(async () => {
|
|
10804
|
+
const store = requireStore();
|
|
10965
10805
|
const project = resolveProject(app.db, request.params.name);
|
|
10966
|
-
const connection = requireConnection(store, project.name
|
|
10967
|
-
if (!connection) return;
|
|
10806
|
+
const connection = requireConnection(store, project.name);
|
|
10968
10807
|
if (!connection.stagingUrl) {
|
|
10969
|
-
|
|
10970
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10808
|
+
throw validationError("No staging URL configured for this project. Reconnect with --staging-url before using staging push.");
|
|
10971
10809
|
}
|
|
10972
10810
|
return buildManualStagingPush(connection);
|
|
10973
10811
|
});
|
|
10974
10812
|
});
|
|
10975
|
-
app.post("/projects/:name/wordpress/onboard", async (request
|
|
10976
|
-
return withWordpressErrorHandling(
|
|
10977
|
-
const store = requireStore(
|
|
10978
|
-
if (!store) return;
|
|
10813
|
+
app.post("/projects/:name/wordpress/onboard", async (request) => {
|
|
10814
|
+
return withWordpressErrorHandling(async () => {
|
|
10815
|
+
const store = requireStore();
|
|
10979
10816
|
const project = resolveProject(app.db, request.params.name);
|
|
10980
10817
|
const { url, username, appPassword, stagingUrl, profile, skipSchema, skipSubmit } = request.body ?? {};
|
|
10981
10818
|
if (!url || !username || !appPassword) {
|
|
10982
|
-
|
|
10983
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10819
|
+
throw validationError("url, username, and appPassword are required");
|
|
10984
10820
|
}
|
|
10985
10821
|
const defaultEnv = parseEnvInput(request.body?.defaultEnv, "defaultEnv") ?? (stagingUrl ? "staging" : "live");
|
|
10986
10822
|
if (defaultEnv === "staging" && !stagingUrl) {
|
|
10987
|
-
|
|
10988
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
10823
|
+
throw validationError('defaultEnv "staging" requires stagingUrl');
|
|
10989
10824
|
}
|
|
10990
10825
|
const steps = [];
|
|
10991
10826
|
let connection = null;
|
|
@@ -14532,6 +14367,26 @@ var ProviderExecutionGate = class {
|
|
|
14532
14367
|
}
|
|
14533
14368
|
}
|
|
14534
14369
|
};
|
|
14370
|
+
async function runWithConcurrency(items, limit, worker) {
|
|
14371
|
+
if (items.length === 0) return;
|
|
14372
|
+
const cap = Math.max(1, Math.min(limit, items.length));
|
|
14373
|
+
let cursor = 0;
|
|
14374
|
+
const next = async () => {
|
|
14375
|
+
while (true) {
|
|
14376
|
+
const idx = cursor++;
|
|
14377
|
+
if (idx >= items.length) return;
|
|
14378
|
+
await worker(items[idx]);
|
|
14379
|
+
}
|
|
14380
|
+
};
|
|
14381
|
+
await Promise.all(Array.from({ length: cap }, next));
|
|
14382
|
+
}
|
|
14383
|
+
var PROVIDER_FANOUT_DEFAULT = 8;
|
|
14384
|
+
function resolveProviderFanout() {
|
|
14385
|
+
const raw = process.env.CANONRY_PROVIDER_FANOUT;
|
|
14386
|
+
if (!raw) return PROVIDER_FANOUT_DEFAULT;
|
|
14387
|
+
const parsed = Number.parseInt(raw, 10);
|
|
14388
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : PROVIDER_FANOUT_DEFAULT;
|
|
14389
|
+
}
|
|
14535
14390
|
var JobRunner = class {
|
|
14536
14391
|
db;
|
|
14537
14392
|
registry;
|
|
@@ -14739,11 +14594,11 @@ var JobRunner = class {
|
|
|
14739
14594
|
}
|
|
14740
14595
|
}
|
|
14741
14596
|
};
|
|
14742
|
-
await
|
|
14597
|
+
await runWithConcurrency(apiProviders, resolveProviderFanout(), async (registeredProvider) => {
|
|
14743
14598
|
await Promise.all(projectKeywords.map(async (kw) => {
|
|
14744
14599
|
await processKeywordForProvider(registeredProvider, kw);
|
|
14745
14600
|
}));
|
|
14746
|
-
})
|
|
14601
|
+
});
|
|
14747
14602
|
for (const registeredProvider of browserProviders) {
|
|
14748
14603
|
for (const kw of projectKeywords) {
|
|
14749
14604
|
await processKeywordForProvider(registeredProvider, kw);
|
|
@@ -15361,6 +15216,21 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
15361
15216
|
projectsProcessed: allProjects.length,
|
|
15362
15217
|
domainsDiscovered: rows.length
|
|
15363
15218
|
});
|
|
15219
|
+
if (deps.enqueueAutoExtract) {
|
|
15220
|
+
const autoExtractProjects = allProjects.filter((p) => p.autoExtractBacklinks === 1);
|
|
15221
|
+
for (const p of autoExtractProjects) {
|
|
15222
|
+
try {
|
|
15223
|
+
deps.enqueueAutoExtract({ projectId: p.id, release });
|
|
15224
|
+
} catch (err) {
|
|
15225
|
+
log4.error("auto-extract.enqueue-failed", {
|
|
15226
|
+
syncId,
|
|
15227
|
+
release,
|
|
15228
|
+
projectId: p.id,
|
|
15229
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15230
|
+
});
|
|
15231
|
+
}
|
|
15232
|
+
}
|
|
15233
|
+
}
|
|
15364
15234
|
} catch (err) {
|
|
15365
15235
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
15366
15236
|
const finishedAt = deps.now().toISOString();
|
|
@@ -15404,6 +15274,7 @@ function computeSummary(rows) {
|
|
|
15404
15274
|
|
|
15405
15275
|
// src/backlink-extract.ts
|
|
15406
15276
|
import crypto23 from "crypto";
|
|
15277
|
+
import fs9 from "fs";
|
|
15407
15278
|
import { and as and12, desc as desc9, eq as eq23 } from "drizzle-orm";
|
|
15408
15279
|
var log5 = createLogger("BacklinkExtract");
|
|
15409
15280
|
function defaultDeps2() {
|
|
@@ -15432,6 +15303,11 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
15432
15303
|
if (!sync.vertexPath || !sync.edgesPath) {
|
|
15433
15304
|
throw new Error(`Release ${sync.release} is missing cached file paths`);
|
|
15434
15305
|
}
|
|
15306
|
+
if (!fs9.existsSync(sync.vertexPath) || !fs9.existsSync(sync.edgesPath)) {
|
|
15307
|
+
throw new Error(
|
|
15308
|
+
`Cache for release ${sync.release} is missing from disk (expected at ${sync.vertexPath}). The sync record exists in the database, but the ~16 GB dump was deleted or never present on this machine. Re-sync this release from the Backlinks admin page to restore the cache.`
|
|
15309
|
+
);
|
|
15310
|
+
}
|
|
15435
15311
|
const duckdb = deps.loadDuckdb();
|
|
15436
15312
|
const rows = await deps.queryBacklinks({
|
|
15437
15313
|
vertexPath: sync.vertexPath,
|
|
@@ -15946,7 +15822,7 @@ import crypto26 from "crypto";
|
|
|
15946
15822
|
import { eq as eq27 } from "drizzle-orm";
|
|
15947
15823
|
|
|
15948
15824
|
// src/agent/session.ts
|
|
15949
|
-
import
|
|
15825
|
+
import fs12 from "fs";
|
|
15950
15826
|
import path14 from "path";
|
|
15951
15827
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
15952
15828
|
import { registerBuiltInApiProviders } from "@mariozechner/pi-ai";
|
|
@@ -16048,7 +15924,7 @@ function buildAgentProvidersResponse(config) {
|
|
|
16048
15924
|
}
|
|
16049
15925
|
|
|
16050
15926
|
// src/agent/skill-paths.ts
|
|
16051
|
-
import
|
|
15927
|
+
import fs10 from "fs";
|
|
16052
15928
|
import path12 from "path";
|
|
16053
15929
|
import { fileURLToPath } from "url";
|
|
16054
15930
|
function resolveAeroSkillDir(pkgDir) {
|
|
@@ -16059,14 +15935,14 @@ function resolveAeroSkillDir(pkgDir) {
|
|
|
16059
15935
|
path12.join(here, "../../../../skills/aero")
|
|
16060
15936
|
];
|
|
16061
15937
|
for (const candidate of candidates) {
|
|
16062
|
-
if (
|
|
15938
|
+
if (fs10.existsSync(path12.join(candidate, "SKILL.md"))) return candidate;
|
|
16063
15939
|
}
|
|
16064
15940
|
throw new Error(`Aero skill not found. Searched:
|
|
16065
15941
|
${candidates.join("\n ")}`);
|
|
16066
15942
|
}
|
|
16067
15943
|
|
|
16068
15944
|
// src/agent/skill-tools.ts
|
|
16069
|
-
import
|
|
15945
|
+
import fs11 from "fs";
|
|
16070
15946
|
import path13 from "path";
|
|
16071
15947
|
import { Type } from "@sinclair/typebox";
|
|
16072
15948
|
var MAX_DOC_CHARS = 2e4;
|
|
@@ -16089,12 +15965,12 @@ function parseDescription(body) {
|
|
|
16089
15965
|
}
|
|
16090
15966
|
function scanSkillDocs(skillDir) {
|
|
16091
15967
|
const refsDir = path13.join(skillDir ?? resolveAeroSkillDir(), "references");
|
|
16092
|
-
if (!
|
|
15968
|
+
if (!fs11.existsSync(refsDir)) return [];
|
|
16093
15969
|
const entries = [];
|
|
16094
|
-
for (const file of
|
|
15970
|
+
for (const file of fs11.readdirSync(refsDir)) {
|
|
16095
15971
|
if (!file.endsWith(".md")) continue;
|
|
16096
15972
|
const filePath = path13.join(refsDir, file);
|
|
16097
|
-
const body =
|
|
15973
|
+
const body = fs11.readFileSync(filePath, "utf-8");
|
|
16098
15974
|
entries.push({
|
|
16099
15975
|
slug: file.replace(/\.md$/, ""),
|
|
16100
15976
|
description: parseDescription(body),
|
|
@@ -16138,7 +16014,7 @@ function buildReadSkillDocTool() {
|
|
|
16138
16014
|
});
|
|
16139
16015
|
}
|
|
16140
16016
|
const filePath = path13.join(skillDir, "references", `${match.slug}.md`);
|
|
16141
|
-
const content =
|
|
16017
|
+
const content = fs11.readFileSync(filePath, "utf-8");
|
|
16142
16018
|
if (content.length > MAX_DOC_CHARS) {
|
|
16143
16019
|
return textResult({
|
|
16144
16020
|
slug: match.slug,
|
|
@@ -16692,10 +16568,10 @@ function ensureBuiltinsRegistered() {
|
|
|
16692
16568
|
}
|
|
16693
16569
|
function loadAeroSystemPrompt(pkgDir) {
|
|
16694
16570
|
const skillDir = resolveAeroSkillDir(pkgDir);
|
|
16695
|
-
const skillBody =
|
|
16571
|
+
const skillBody = fs12.readFileSync(path14.join(skillDir, "SKILL.md"), "utf-8");
|
|
16696
16572
|
const soulPath = path14.join(skillDir, "soul.md");
|
|
16697
|
-
if (!
|
|
16698
|
-
const soulBody =
|
|
16573
|
+
if (!fs12.existsSync(soulPath)) return skillBody;
|
|
16574
|
+
const soulBody = fs12.readFileSync(soulPath, "utf-8");
|
|
16699
16575
|
return `${soulBody.trimEnd()}
|
|
16700
16576
|
|
|
16701
16577
|
---
|
|
@@ -18990,7 +18866,7 @@ async function createServer(opts) {
|
|
|
18990
18866
|
jobRunner.onRunCompleted = (runId, projectId) => runCoordinator.onRunCompleted(runId, projectId);
|
|
18991
18867
|
const snapshotService = new SnapshotService(registry);
|
|
18992
18868
|
const orphanedOpenClawDir = path15.join(os6.homedir(), ".openclaw-aero");
|
|
18993
|
-
if (
|
|
18869
|
+
if (fs13.existsSync(orphanedOpenClawDir)) {
|
|
18994
18870
|
app.log.warn(
|
|
18995
18871
|
{ path: orphanedOpenClawDir },
|
|
18996
18872
|
"OpenClaw gateway is no longer used. Remove ~/.openclaw-aero/ manually to reclaim the directory."
|
|
@@ -19314,7 +19190,26 @@ async function createServer(opts) {
|
|
|
19314
19190
|
};
|
|
19315
19191
|
},
|
|
19316
19192
|
onReleaseSyncRequested: (syncId, release) => {
|
|
19317
|
-
executeReleaseSync(opts.db, syncId, {
|
|
19193
|
+
executeReleaseSync(opts.db, syncId, {
|
|
19194
|
+
release,
|
|
19195
|
+
deps: {
|
|
19196
|
+
enqueueAutoExtract: ({ projectId, release: r }) => {
|
|
19197
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19198
|
+
const runId = crypto27.randomUUID();
|
|
19199
|
+
opts.db.insert(runs).values({
|
|
19200
|
+
id: runId,
|
|
19201
|
+
projectId,
|
|
19202
|
+
kind: RunKinds["backlink-extract"],
|
|
19203
|
+
status: RunStatuses.queued,
|
|
19204
|
+
trigger: RunTriggers.scheduled,
|
|
19205
|
+
createdAt: now
|
|
19206
|
+
}).run();
|
|
19207
|
+
executeBacklinkExtract(opts.db, runId, projectId, { release: r }).catch((err) => {
|
|
19208
|
+
app.log.error({ runId, projectId, err }, "Auto backlink extract failed");
|
|
19209
|
+
});
|
|
19210
|
+
}
|
|
19211
|
+
}
|
|
19212
|
+
}).catch((err) => {
|
|
19318
19213
|
app.log.error({ syncId, err }, "Common Crawl release sync failed");
|
|
19319
19214
|
});
|
|
19320
19215
|
},
|
|
@@ -19559,7 +19454,7 @@ async function createServer(opts) {
|
|
|
19559
19454
|
});
|
|
19560
19455
|
const dirname = path15.dirname(fileURLToPath2(import.meta.url));
|
|
19561
19456
|
const assetsDir = path15.join(dirname, "..", "assets");
|
|
19562
|
-
if (
|
|
19457
|
+
if (fs13.existsSync(assetsDir)) {
|
|
19563
19458
|
const indexPath = path15.join(assetsDir, "index.html");
|
|
19564
19459
|
const injectConfig = (html) => {
|
|
19565
19460
|
const clientConfig = {};
|
|
@@ -19578,8 +19473,8 @@ async function createServer(opts) {
|
|
|
19578
19473
|
index: false
|
|
19579
19474
|
});
|
|
19580
19475
|
const serveIndex = (_request, reply) => {
|
|
19581
|
-
if (
|
|
19582
|
-
const html =
|
|
19476
|
+
if (fs13.existsSync(indexPath)) {
|
|
19477
|
+
const html = fs13.readFileSync(indexPath, "utf-8");
|
|
19583
19478
|
return reply.type("text/html").send(injectConfig(html));
|
|
19584
19479
|
}
|
|
19585
19480
|
return reply.status(404).send({ error: "Dashboard not built" });
|
|
@@ -19599,8 +19494,8 @@ async function createServer(opts) {
|
|
|
19599
19494
|
if (basePath && !url.startsWith(basePath)) {
|
|
19600
19495
|
return reply.status(404).send({ error: "Not found", path: request.url });
|
|
19601
19496
|
}
|
|
19602
|
-
if (
|
|
19603
|
-
const html =
|
|
19497
|
+
if (fs13.existsSync(indexPath)) {
|
|
19498
|
+
const html = fs13.readFileSync(indexPath, "utf-8");
|
|
19604
19499
|
return reply.type("text/html").send(injectConfig(html));
|
|
19605
19500
|
}
|
|
19606
19501
|
return reply.status(404).send({ error: "Not found" });
|
|
@@ -19690,8 +19585,10 @@ export {
|
|
|
19690
19585
|
resolveProviderInput,
|
|
19691
19586
|
notificationEventSchema,
|
|
19692
19587
|
effectiveDomains,
|
|
19588
|
+
RunStatuses,
|
|
19693
19589
|
RunKinds,
|
|
19694
19590
|
determineAnswerMentioned,
|
|
19591
|
+
CcReleaseSyncStatuses,
|
|
19695
19592
|
reparseStoredResult2 as reparseStoredResult,
|
|
19696
19593
|
reparseStoredResult3 as reparseStoredResult2,
|
|
19697
19594
|
reparseStoredResult as reparseStoredResult3,
|