@canonry/canonry 4.136.0 → 4.136.1
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/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -0
- package/assets/agent-workspace/skills/canonry/references/indexing.md +26 -12
- package/assets/assets/{AuditHistoryPanel-DKaiIlcO.js → AuditHistoryPanel-B73VrWNb.js} +1 -1
- package/assets/assets/{BacklinksPage-Z2_ws8c2.js → BacklinksPage-CLVMg_qx.js} +1 -1
- package/assets/assets/{ChartPrimitives-CF_ocuvj.js → ChartPrimitives-P81CbvWc.js} +1 -1
- package/assets/assets/{HistoryPage-DDKO8_D8.js → HistoryPage-IcDqWBYW.js} +1 -1
- package/assets/assets/ProjectPage-DTivvVIl.js +8 -0
- package/assets/assets/{RunRow-BhtCgyqA.js → RunRow-BTH3-Tml.js} +1 -1
- package/assets/assets/{RunsPage-B8Nw3wP-.js → RunsPage-B0k5wjFN.js} +1 -1
- package/assets/assets/{SettingsPage-DsmQhytl.js → SettingsPage-DGnT3Vdr.js} +1 -1
- package/assets/assets/{TrafficPage-COJs6eRH.js → TrafficPage-D79PC0bB.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BI6IwzMQ.js → TrafficSourceDetailPage-BYEVjfFm.js} +1 -1
- package/assets/assets/{arrow-left-C-lGPq-u.js → arrow-left-DCdFYJK-.js} +1 -1
- package/assets/assets/{extract-error-message-8h0hF1I1.js → extract-error-message-bM0aUBUI.js} +1 -1
- package/assets/assets/index-CMuA-uEN.js +210 -0
- package/assets/assets/index-CglduHCN.css +1 -0
- package/assets/assets/{trash-2-CnOZwYKe.js → trash-2-CEz2iXBv.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-PAR6P5H6.js → chunk-OESGFFUO.js} +132 -6
- package/dist/{chunk-PXEEWJRV.js → chunk-PIMWEIJ3.js} +51 -2
- package/dist/{chunk-3PFRVEWP.js → chunk-YMF35LN5.js} +4 -4
- package/dist/{chunk-TPVMHYHD.js → chunk-YQ2RXMU2.js} +205 -27
- package/dist/cli.js +184 -15
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-BMEJ5GFZ.js → intelligence-service-BKLR57RQ.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -10
- package/assets/assets/ProjectPage-hmNsqBov.js +0 -8
- package/assets/assets/index-B4ksyQXo.css +0 -1
- package/assets/assets/index-BoSfFv1t.js +0 -210
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
trafficConnectWordpressRequestSchema,
|
|
43
43
|
trafficEventKindSchema,
|
|
44
44
|
trafficSeriesGranularitySchema
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-PIMWEIJ3.js";
|
|
46
46
|
|
|
47
47
|
// src/config.ts
|
|
48
48
|
import fs from "fs";
|
|
@@ -1087,6 +1087,22 @@ var createClient = (config = {}) => {
|
|
|
1087
1087
|
var client = createClient(createConfig());
|
|
1088
1088
|
|
|
1089
1089
|
// ../api-client-generated/src/generated/sdk.gen.ts
|
|
1090
|
+
var postApiV1ProjectsByNameGoogleGscSitemapsSubmit = (options) => {
|
|
1091
|
+
return (options.client ?? client).post({
|
|
1092
|
+
security: [
|
|
1093
|
+
{
|
|
1094
|
+
scheme: "bearer",
|
|
1095
|
+
type: "http"
|
|
1096
|
+
}
|
|
1097
|
+
],
|
|
1098
|
+
url: "/api/v1/projects/{name}/google/gsc/sitemaps/submit",
|
|
1099
|
+
...options,
|
|
1100
|
+
headers: {
|
|
1101
|
+
"Content-Type": "application/json",
|
|
1102
|
+
...options.headers
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
};
|
|
1090
1106
|
var deleteApiV1ProjectsByName = (options) => {
|
|
1091
1107
|
return (options.client ?? client).delete({
|
|
1092
1108
|
security: [
|
|
@@ -5181,9 +5197,22 @@ var ApiClient = class {
|
|
|
5181
5197
|
})
|
|
5182
5198
|
);
|
|
5183
5199
|
}
|
|
5184
|
-
async gscSitemaps(project) {
|
|
5200
|
+
async gscSitemaps(project, params) {
|
|
5201
|
+
return this.invoke(
|
|
5202
|
+
() => getApiV1ProjectsByNameGoogleGscSitemaps({
|
|
5203
|
+
client: this.heyClient,
|
|
5204
|
+
path: { name: project },
|
|
5205
|
+
query: { sitemapIndex: params?.sitemapIndex }
|
|
5206
|
+
})
|
|
5207
|
+
);
|
|
5208
|
+
}
|
|
5209
|
+
async gscSubmitSitemaps(project, body) {
|
|
5185
5210
|
return this.invoke(
|
|
5186
|
-
() =>
|
|
5211
|
+
() => postApiV1ProjectsByNameGoogleGscSitemapsSubmit({
|
|
5212
|
+
client: this.heyClient,
|
|
5213
|
+
path: { name: project },
|
|
5214
|
+
body
|
|
5215
|
+
})
|
|
5187
5216
|
);
|
|
5188
5217
|
}
|
|
5189
5218
|
async gscDiscoverSitemaps(project) {
|
|
@@ -6149,6 +6178,92 @@ var gscCoverageHistoryInputSchema = z2.object({
|
|
|
6149
6178
|
project: projectNameSchema,
|
|
6150
6179
|
limit: z2.number().int().positive().max(500).optional()
|
|
6151
6180
|
});
|
|
6181
|
+
var gscSitemapsInputSchema = z2.object({
|
|
6182
|
+
project: projectNameSchema,
|
|
6183
|
+
sitemapIndex: z2.string().url().optional()
|
|
6184
|
+
});
|
|
6185
|
+
var gscSitemapsSubmitInputSchema = z2.union([
|
|
6186
|
+
z2.object({
|
|
6187
|
+
project: projectNameSchema,
|
|
6188
|
+
sitemapUrls: z2.array(z2.string().url()).min(1).max(50)
|
|
6189
|
+
}).strict(),
|
|
6190
|
+
z2.object({
|
|
6191
|
+
project: projectNameSchema,
|
|
6192
|
+
mode: z2.enum(["indexes", "all-files"])
|
|
6193
|
+
}).strict()
|
|
6194
|
+
]);
|
|
6195
|
+
async function submitGscSitemapsFromMcp(client2, input) {
|
|
6196
|
+
let sitemapUrls;
|
|
6197
|
+
if ("sitemapUrls" in input) {
|
|
6198
|
+
sitemapUrls = uniqueStrings(input.sitemapUrls);
|
|
6199
|
+
} else {
|
|
6200
|
+
const topLevel = await client2.gscSitemaps(input.project);
|
|
6201
|
+
if (input.mode === "indexes") {
|
|
6202
|
+
sitemapUrls = uniqueStrings(
|
|
6203
|
+
topLevel.preferredSubmissionUrls.length > 0 ? topLevel.preferredSubmissionUrls : topLevel.sitemaps.map((sitemap) => sitemap.path)
|
|
6204
|
+
);
|
|
6205
|
+
} else {
|
|
6206
|
+
const indexes = topLevel.sitemaps.filter((sitemap) => sitemap.isSitemapsIndex).map((sitemap) => sitemap.path);
|
|
6207
|
+
const expandedIndexUrls = [];
|
|
6208
|
+
for (let offset = 0; offset < indexes.length; offset += 4) {
|
|
6209
|
+
const children = await Promise.all(
|
|
6210
|
+
indexes.slice(offset, offset + 4).map(
|
|
6211
|
+
(sitemapIndex) => client2.gscSitemaps(input.project, { sitemapIndex })
|
|
6212
|
+
)
|
|
6213
|
+
);
|
|
6214
|
+
children.forEach((result, index) => {
|
|
6215
|
+
const sitemapIndex = indexes[offset + index];
|
|
6216
|
+
expandedIndexUrls.push(...result.sitemaps.length > 0 ? result.sitemaps.map((sitemap) => sitemap.path) : [sitemapIndex]);
|
|
6217
|
+
});
|
|
6218
|
+
}
|
|
6219
|
+
sitemapUrls = uniqueStrings([
|
|
6220
|
+
...topLevel.sitemaps.filter((sitemap) => !sitemap.isSitemapsIndex).map((sitemap) => sitemap.path),
|
|
6221
|
+
...expandedIndexUrls
|
|
6222
|
+
]);
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
if (sitemapUrls.length === 0) {
|
|
6226
|
+
throw new Error("No GSC sitemaps found. Submit an explicit sitemap URL first.");
|
|
6227
|
+
}
|
|
6228
|
+
const aggregate = {
|
|
6229
|
+
summary: { total: 0, accepted: 0, failed: 0 },
|
|
6230
|
+
results: []
|
|
6231
|
+
};
|
|
6232
|
+
for (let offset = 0; offset < sitemapUrls.length; offset += 50) {
|
|
6233
|
+
const batchSitemapUrls = sitemapUrls.slice(offset, offset + 50);
|
|
6234
|
+
try {
|
|
6235
|
+
const result = await client2.gscSubmitSitemaps(input.project, {
|
|
6236
|
+
sitemapUrls: batchSitemapUrls
|
|
6237
|
+
});
|
|
6238
|
+
aggregate.summary.total += result.summary.total;
|
|
6239
|
+
aggregate.summary.accepted += result.summary.accepted;
|
|
6240
|
+
aggregate.summary.failed += result.summary.failed;
|
|
6241
|
+
aggregate.results.push(...result.results);
|
|
6242
|
+
} catch (cause) {
|
|
6243
|
+
if (offset === 0) throw cause;
|
|
6244
|
+
const attempted = aggregate.summary.total + batchSitemapUrls.length;
|
|
6245
|
+
const remaining = sitemapUrls.length - attempted;
|
|
6246
|
+
throw new CliError({
|
|
6247
|
+
code: "GOOGLE_SITEMAP_SUBMISSION_PARTIAL",
|
|
6248
|
+
message: `Sitemap submission stopped after ${aggregate.summary.total} completed results; ${batchSitemapUrls.length} are unconfirmed and ${remaining} were not attempted.`,
|
|
6249
|
+
exitCode: cause instanceof CliError && cause.exitCode === 1 ? 1 : EXIT_SYSTEM_ERROR,
|
|
6250
|
+
details: {
|
|
6251
|
+
project: input.project,
|
|
6252
|
+
accepted: aggregate.summary.accepted,
|
|
6253
|
+
failed: aggregate.summary.failed,
|
|
6254
|
+
completed: aggregate.summary.total,
|
|
6255
|
+
attempted,
|
|
6256
|
+
unconfirmed: batchSitemapUrls.length,
|
|
6257
|
+
remaining,
|
|
6258
|
+
unconfirmedBatch: { index: Math.floor(offset / 50) + 1, sitemapUrls: batchSitemapUrls },
|
|
6259
|
+
partialResult: aggregate,
|
|
6260
|
+
cause: cause instanceof CliError ? { code: cause.code, message: cause.message, details: cause.details } : { message: cause instanceof Error ? cause.message : String(cause) }
|
|
6261
|
+
}
|
|
6262
|
+
});
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
return aggregate;
|
|
6266
|
+
}
|
|
6152
6267
|
var gaWindowInputSchema = z2.object({
|
|
6153
6268
|
project: projectNameSchema,
|
|
6154
6269
|
window: analyticsWindowSchema.optional()
|
|
@@ -6486,7 +6601,7 @@ var canonryMcpTools = [
|
|
|
6486
6601
|
description: "Get a Canonry project by name.",
|
|
6487
6602
|
access: "read",
|
|
6488
6603
|
tier: "core",
|
|
6489
|
-
inputSchema:
|
|
6604
|
+
inputSchema: gscSitemapsInputSchema,
|
|
6490
6605
|
annotations: readAnnotations(),
|
|
6491
6606
|
openApiOperations: ["GET /api/v1/projects/{name}"],
|
|
6492
6607
|
handler: (client2, input) => client2.getProject(input.project)
|
|
@@ -7048,10 +7163,21 @@ var canonryMcpTools = [
|
|
|
7048
7163
|
description: "Get sitemap data from Google Search Console for a Canonry project.",
|
|
7049
7164
|
access: "read",
|
|
7050
7165
|
tier: "gsc",
|
|
7051
|
-
inputSchema:
|
|
7166
|
+
inputSchema: gscSitemapsInputSchema,
|
|
7052
7167
|
annotations: readAnnotations(true),
|
|
7053
7168
|
openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/sitemaps"],
|
|
7054
|
-
handler: (client2, input) => client2.gscSitemaps(input.project)
|
|
7169
|
+
handler: (client2, input) => client2.gscSitemaps(input.project, { sitemapIndex: input.sitemapIndex })
|
|
7170
|
+
}),
|
|
7171
|
+
defineTool({
|
|
7172
|
+
name: "canonry_gsc_sitemaps_submit",
|
|
7173
|
+
title: "Submit GSC sitemaps",
|
|
7174
|
+
description: "Submit up to 50 explicit sitemap URLs, preferred sitemap indexes, or every standalone top-level file plus index child to Google Search Console for refetching. Parent indexes are not redundantly submitted in all-files mode. Google acceptance does not guarantee indexing.",
|
|
7175
|
+
access: "write",
|
|
7176
|
+
tier: "gsc",
|
|
7177
|
+
inputSchema: gscSitemapsSubmitInputSchema,
|
|
7178
|
+
annotations: writeAnnotations({ idempotentHint: false, openWorldHint: true }),
|
|
7179
|
+
openApiOperations: ["POST /api/v1/projects/{name}/google/gsc/sitemaps/submit"],
|
|
7180
|
+
handler: submitGscSitemapsFromMcp
|
|
7055
7181
|
}),
|
|
7056
7182
|
defineTool({
|
|
7057
7183
|
name: "canonry_ga_status",
|
|
@@ -1493,10 +1493,13 @@ var gscSiteListResponseDtoSchema = z9.object({
|
|
|
1493
1493
|
var gscSitemapContentDtoSchema = z9.object({
|
|
1494
1494
|
type: z9.string(),
|
|
1495
1495
|
submitted: z9.string(),
|
|
1496
|
-
|
|
1496
|
+
// Google still returns this for existing sitemap reads, but it is not a
|
|
1497
|
+
// reliable submission outcome. Retained only for response compatibility.
|
|
1498
|
+
indexed: z9.string().optional().describe("Deprecated compatibility field.")
|
|
1497
1499
|
});
|
|
1498
1500
|
var gscSitemapDtoSchema = z9.object({
|
|
1499
1501
|
path: z9.string(),
|
|
1502
|
+
parentSitemapUrl: z9.string().optional(),
|
|
1500
1503
|
lastSubmitted: z9.string().optional(),
|
|
1501
1504
|
isPending: z9.boolean().optional(),
|
|
1502
1505
|
isSitemapsIndex: z9.boolean().optional(),
|
|
@@ -1507,7 +1510,50 @@ var gscSitemapDtoSchema = z9.object({
|
|
|
1507
1510
|
contents: z9.array(gscSitemapContentDtoSchema).optional()
|
|
1508
1511
|
});
|
|
1509
1512
|
var gscSitemapListResponseDtoSchema = z9.object({
|
|
1510
|
-
sitemaps: z9.array(gscSitemapDtoSchema).default([])
|
|
1513
|
+
sitemaps: z9.array(gscSitemapDtoSchema).default([]),
|
|
1514
|
+
summary: z9.object({
|
|
1515
|
+
total: z9.number().int().nonnegative(),
|
|
1516
|
+
indexes: z9.number().int().nonnegative(),
|
|
1517
|
+
files: z9.number().int().nonnegative()
|
|
1518
|
+
}),
|
|
1519
|
+
preferredSubmissionUrls: z9.array(z9.string()).default([])
|
|
1520
|
+
});
|
|
1521
|
+
var gscSubmitSitemapsRequestDtoSchema = z9.object({
|
|
1522
|
+
sitemapUrls: z9.array(
|
|
1523
|
+
z9.string().url().refine(
|
|
1524
|
+
(value) => {
|
|
1525
|
+
const protocol = new URL(value).protocol;
|
|
1526
|
+
return protocol === "http:" || protocol === "https:";
|
|
1527
|
+
},
|
|
1528
|
+
{ message: "Sitemap URL must use http or https." }
|
|
1529
|
+
)
|
|
1530
|
+
).min(1).max(50)
|
|
1531
|
+
});
|
|
1532
|
+
var gscSubmitSitemapResultDtoSchema = z9.object({
|
|
1533
|
+
sitemapUrl: z9.string(),
|
|
1534
|
+
status: z9.enum(["accepted", "error"]),
|
|
1535
|
+
submittedAt: z9.string().optional(),
|
|
1536
|
+
error: z9.string().optional()
|
|
1537
|
+
});
|
|
1538
|
+
var gscSubmitSitemapsResponseDtoSchema = z9.object({
|
|
1539
|
+
summary: z9.object({
|
|
1540
|
+
total: z9.number().int().nonnegative(),
|
|
1541
|
+
accepted: z9.number().int().nonnegative(),
|
|
1542
|
+
failed: z9.number().int().nonnegative()
|
|
1543
|
+
}),
|
|
1544
|
+
results: z9.array(gscSubmitSitemapResultDtoSchema)
|
|
1545
|
+
});
|
|
1546
|
+
var gscDiscoverSitemapsResponseDtoSchema = z9.object({
|
|
1547
|
+
sitemaps: z9.array(gscSitemapDtoSchema),
|
|
1548
|
+
primarySitemapUrl: z9.string(),
|
|
1549
|
+
run: z9.object({
|
|
1550
|
+
id: z9.string(),
|
|
1551
|
+
projectId: z9.string(),
|
|
1552
|
+
kind: z9.string(),
|
|
1553
|
+
status: z9.string(),
|
|
1554
|
+
trigger: z9.string(),
|
|
1555
|
+
createdAt: z9.string()
|
|
1556
|
+
}).nullable()
|
|
1511
1557
|
});
|
|
1512
1558
|
|
|
1513
1559
|
// ../contracts/src/gbp.ts
|
|
@@ -7882,6 +7928,9 @@ export {
|
|
|
7882
7928
|
gscCoverageSnapshotDtoSchema,
|
|
7883
7929
|
gscSiteListResponseDtoSchema,
|
|
7884
7930
|
gscSitemapListResponseDtoSchema,
|
|
7931
|
+
gscSubmitSitemapsRequestDtoSchema,
|
|
7932
|
+
gscSubmitSitemapsResponseDtoSchema,
|
|
7933
|
+
gscDiscoverSitemapsResponseDtoSchema,
|
|
7885
7934
|
gbpAccountListResponseSchema,
|
|
7886
7935
|
gbpLocationDtoSchema,
|
|
7887
7936
|
gbpLocationListResponseSchema,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-OESGFFUO.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
siteAuditPages,
|
|
120
120
|
siteAuditSnapshots,
|
|
121
121
|
usageCounters
|
|
122
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-YQ2RXMU2.js";
|
|
123
123
|
import {
|
|
124
124
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
125
125
|
AGENT_PROVIDER_IDS,
|
|
@@ -194,7 +194,7 @@ import {
|
|
|
194
194
|
validationError,
|
|
195
195
|
winnabilityClassLabel,
|
|
196
196
|
withRetry
|
|
197
|
-
} from "./chunk-
|
|
197
|
+
} from "./chunk-PIMWEIJ3.js";
|
|
198
198
|
|
|
199
199
|
// src/telemetry.ts
|
|
200
200
|
import crypto from "crypto";
|
|
@@ -7066,7 +7066,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
7066
7066
|
return result;
|
|
7067
7067
|
}
|
|
7068
7068
|
async function backfillInsightsCommand(project, opts) {
|
|
7069
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
7069
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-BKLR57RQ.js");
|
|
7070
7070
|
const config = loadConfig();
|
|
7071
7071
|
const db = createClient(config.database);
|
|
7072
7072
|
migrate(db);
|
|
@@ -238,10 +238,13 @@ import {
|
|
|
238
238
|
gscCoverageSnapshotDtoSchema,
|
|
239
239
|
gscCoverageSummaryDtoSchema,
|
|
240
240
|
gscDeindexedRowSchema,
|
|
241
|
+
gscDiscoverSitemapsResponseDtoSchema,
|
|
241
242
|
gscPerformanceDailyDtoSchema,
|
|
242
243
|
gscSearchDataDtoSchema,
|
|
243
244
|
gscSiteListResponseDtoSchema,
|
|
244
245
|
gscSitemapListResponseDtoSchema,
|
|
246
|
+
gscSubmitSitemapsRequestDtoSchema,
|
|
247
|
+
gscSubmitSitemapsResponseDtoSchema,
|
|
245
248
|
gscUrlInspectionDtoSchema,
|
|
246
249
|
hasLocationLabel,
|
|
247
250
|
healthSnapshotDtoSchema,
|
|
@@ -365,7 +368,7 @@ import {
|
|
|
365
368
|
wordpressSchemaDeployResultDtoSchema,
|
|
366
369
|
wordpressSchemaStatusResultDtoSchema,
|
|
367
370
|
wordpressStatusDtoSchema
|
|
368
|
-
} from "./chunk-
|
|
371
|
+
} from "./chunk-PIMWEIJ3.js";
|
|
369
372
|
|
|
370
373
|
// src/intelligence-service.ts
|
|
371
374
|
import { eq as eq44, desc as desc21, asc as asc9, and as and35, ne as ne6, or as or10, inArray as inArray15, gte as gte12, lte as lte8 } from "drizzle-orm";
|
|
@@ -17487,7 +17490,9 @@ var SCHEMA_TABLE = {
|
|
|
17487
17490
|
GscDeindexedRowDto: gscDeindexedRowSchema,
|
|
17488
17491
|
GscPerformanceDailyDto: gscPerformanceDailyDtoSchema,
|
|
17489
17492
|
GscSearchDataDto: gscSearchDataDtoSchema,
|
|
17493
|
+
GscDiscoverSitemapsResponseDto: gscDiscoverSitemapsResponseDtoSchema,
|
|
17490
17494
|
GscSiteListResponseDto: gscSiteListResponseDtoSchema,
|
|
17495
|
+
GscSubmitSitemapsResponseDto: gscSubmitSitemapsResponseDtoSchema,
|
|
17491
17496
|
GscSitemapListResponseDto: gscSitemapListResponseDtoSchema,
|
|
17492
17497
|
GscUrlInspectionDto: gscUrlInspectionDtoSchema,
|
|
17493
17498
|
HealthSnapshotDto: healthSnapshotDtoSchema,
|
|
@@ -17910,6 +17915,34 @@ var routeCatalog = [
|
|
|
17910
17915
|
200: rawJsonResponse("OpenAPI document.", looseObjectSchema)
|
|
17911
17916
|
}
|
|
17912
17917
|
},
|
|
17918
|
+
{
|
|
17919
|
+
method: "post",
|
|
17920
|
+
path: "/api/v1/projects/{name}/google/gsc/sitemaps/submit",
|
|
17921
|
+
summary: "Submit GSC sitemaps",
|
|
17922
|
+
description: "Submits sitemap URLs to Google Search Console sequentially. An accepted result means Google accepted the submit/refetch request; it does not indicate indexing.",
|
|
17923
|
+
tags: ["google"],
|
|
17924
|
+
parameters: [nameParameter],
|
|
17925
|
+
requestBody: {
|
|
17926
|
+
required: true,
|
|
17927
|
+
content: {
|
|
17928
|
+
"application/json": {
|
|
17929
|
+
schema: {
|
|
17930
|
+
type: "object",
|
|
17931
|
+
required: ["sitemapUrls"],
|
|
17932
|
+
additionalProperties: false,
|
|
17933
|
+
properties: {
|
|
17934
|
+
sitemapUrls: { type: "array", minItems: 1, maxItems: 50, items: { type: "string", format: "uri", pattern: "^https?://" } }
|
|
17935
|
+
}
|
|
17936
|
+
}
|
|
17937
|
+
}
|
|
17938
|
+
}
|
|
17939
|
+
},
|
|
17940
|
+
responses: {
|
|
17941
|
+
200: jsonResponse("Per-sitemap submission results returned.", "GscSubmitSitemapsResponseDto"),
|
|
17942
|
+
400: errorResponse("Invalid sitemap request, property ownership, or OAuth scope."),
|
|
17943
|
+
404: errorResponse("Project or connection not found.")
|
|
17944
|
+
}
|
|
17945
|
+
},
|
|
17913
17946
|
{
|
|
17914
17947
|
method: "put",
|
|
17915
17948
|
path: "/api/v1/projects/{name}",
|
|
@@ -19487,7 +19520,15 @@ var routeCatalog = [
|
|
|
19487
19520
|
path: "/api/v1/projects/{name}/google/gsc/sitemaps",
|
|
19488
19521
|
summary: "List GSC sitemaps",
|
|
19489
19522
|
tags: ["google"],
|
|
19490
|
-
parameters: [
|
|
19523
|
+
parameters: [
|
|
19524
|
+
nameParameter,
|
|
19525
|
+
{
|
|
19526
|
+
name: "sitemapIndex",
|
|
19527
|
+
in: "query",
|
|
19528
|
+
description: "Optional owned sitemap-index URL. When present, returns that index's child entries.",
|
|
19529
|
+
schema: { type: "string", format: "uri", pattern: "^https?://" }
|
|
19530
|
+
}
|
|
19531
|
+
],
|
|
19491
19532
|
responses: {
|
|
19492
19533
|
200: jsonResponse("GSC sitemaps returned.", "GscSitemapListResponseDto"),
|
|
19493
19534
|
400: errorResponse("Invalid sitemap request."),
|
|
@@ -19501,8 +19542,7 @@ var routeCatalog = [
|
|
|
19501
19542
|
tags: ["google"],
|
|
19502
19543
|
parameters: [nameParameter],
|
|
19503
19544
|
responses: {
|
|
19504
|
-
|
|
19505
|
-
200: rawJsonResponse("Discovered sitemaps and queued run returned.", looseObjectSchema),
|
|
19545
|
+
200: jsonResponse("Discovered sitemaps and queued run returned.", "GscDiscoverSitemapsResponseDto"),
|
|
19506
19546
|
400: errorResponse("Invalid sitemap discovery request."),
|
|
19507
19547
|
404: errorResponse("Project or connection not found.")
|
|
19508
19548
|
}
|
|
@@ -23230,7 +23270,8 @@ function buildGbpSummary(input) {
|
|
|
23230
23270
|
// ../integration-google/src/constants.ts
|
|
23231
23271
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
23232
23272
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
23233
|
-
var GSC_SCOPE = "https://www.googleapis.com/auth/webmasters
|
|
23273
|
+
var GSC_SCOPE = "https://www.googleapis.com/auth/webmasters";
|
|
23274
|
+
var GSC_READONLY_SCOPE = "https://www.googleapis.com/auth/webmasters.readonly";
|
|
23234
23275
|
var INDEXING_SCOPE = "https://www.googleapis.com/auth/indexing";
|
|
23235
23276
|
var GSC_API_BASE = "https://www.googleapis.com/webmasters/v3";
|
|
23236
23277
|
var URL_INSPECTION_API = "https://searchconsole.googleapis.com/v1/urlInspection/index:inspect";
|
|
@@ -23480,7 +23521,7 @@ function gscClientLog(level, action, ctx) {
|
|
|
23480
23521
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
23481
23522
|
stream.write(JSON.stringify(entry) + "\n");
|
|
23482
23523
|
}
|
|
23483
|
-
async function
|
|
23524
|
+
async function gscFetchResponse(accessToken, url, opts) {
|
|
23484
23525
|
const method = opts?.method ?? "GET";
|
|
23485
23526
|
const headers = {
|
|
23486
23527
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -23506,26 +23547,50 @@ async function gscFetch(accessToken, url, opts) {
|
|
|
23506
23547
|
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
23507
23548
|
throw new GoogleApiError(`GSC API error (${res.status}): ${detail}`, res.status);
|
|
23508
23549
|
}
|
|
23509
|
-
return
|
|
23550
|
+
return res;
|
|
23551
|
+
}
|
|
23552
|
+
async function gscFetchJson(accessToken, url, opts) {
|
|
23553
|
+
const res = await gscFetchResponse(accessToken, url, opts);
|
|
23554
|
+
const text2 = await res.text();
|
|
23555
|
+
if (!text2) {
|
|
23556
|
+
throw new GoogleApiError("GSC API returned an empty response where JSON was expected", res.status);
|
|
23557
|
+
}
|
|
23558
|
+
try {
|
|
23559
|
+
return JSON.parse(text2);
|
|
23560
|
+
} catch {
|
|
23561
|
+
throw new GoogleApiError("GSC API returned invalid JSON", res.status);
|
|
23562
|
+
}
|
|
23510
23563
|
}
|
|
23511
23564
|
async function listSites(accessToken) {
|
|
23512
23565
|
validateAccessToken(accessToken);
|
|
23513
|
-
const data = await
|
|
23566
|
+
const data = await gscFetchJson(
|
|
23514
23567
|
accessToken,
|
|
23515
23568
|
`${GSC_API_BASE}/sites`
|
|
23516
23569
|
);
|
|
23517
23570
|
return data.siteEntry ?? [];
|
|
23518
23571
|
}
|
|
23519
|
-
async function listSitemaps(accessToken, siteUrl) {
|
|
23572
|
+
async function listSitemaps(accessToken, siteUrl, sitemapIndex) {
|
|
23520
23573
|
validateAccessToken(accessToken);
|
|
23521
23574
|
validateSiteUrl(siteUrl);
|
|
23575
|
+
if (sitemapIndex != null) validateUrl(sitemapIndex);
|
|
23522
23576
|
const encodedSiteUrl = encodeURIComponent(siteUrl);
|
|
23523
|
-
const
|
|
23577
|
+
const sitemapIndexQuery = sitemapIndex == null ? "" : `?sitemapIndex=${encodeURIComponent(sitemapIndex)}`;
|
|
23578
|
+
const data = await gscFetchJson(
|
|
23524
23579
|
accessToken,
|
|
23525
|
-
`${GSC_API_BASE}/sites/${encodedSiteUrl}/sitemaps`
|
|
23580
|
+
`${GSC_API_BASE}/sites/${encodedSiteUrl}/sitemaps${sitemapIndexQuery}`
|
|
23526
23581
|
);
|
|
23527
23582
|
return data.sitemap ?? [];
|
|
23528
23583
|
}
|
|
23584
|
+
async function submitSitemap(accessToken, siteUrl, sitemapUrl) {
|
|
23585
|
+
validateAccessToken(accessToken);
|
|
23586
|
+
validateSiteUrl(siteUrl);
|
|
23587
|
+
validateUrl(sitemapUrl);
|
|
23588
|
+
await gscFetchResponse(
|
|
23589
|
+
accessToken,
|
|
23590
|
+
`${GSC_API_BASE}/sites/${encodeURIComponent(siteUrl)}/sitemaps/${encodeURIComponent(sitemapUrl)}`,
|
|
23591
|
+
{ method: "PUT" }
|
|
23592
|
+
);
|
|
23593
|
+
}
|
|
23529
23594
|
async function fetchSearchAnalytics(accessToken, siteUrl, opts) {
|
|
23530
23595
|
validateAccessToken(accessToken);
|
|
23531
23596
|
validateSiteUrl(siteUrl);
|
|
@@ -23555,7 +23620,7 @@ async function fetchSearchAnalytics(accessToken, siteUrl, opts) {
|
|
|
23555
23620
|
requestBody.dimensionFilterGroups = filters;
|
|
23556
23621
|
}
|
|
23557
23622
|
const encodedSiteUrl = encodeURIComponent(siteUrl);
|
|
23558
|
-
const data = await
|
|
23623
|
+
const data = await gscFetchJson(
|
|
23559
23624
|
accessToken,
|
|
23560
23625
|
`${GSC_API_BASE}/sites/${encodedSiteUrl}/searchAnalytics/query`,
|
|
23561
23626
|
{ method: "POST", body: requestBody }
|
|
@@ -23572,7 +23637,7 @@ async function fetchSearchAnalytics(accessToken, siteUrl, opts) {
|
|
|
23572
23637
|
async function publishUrlNotification(accessToken, url, type = "URL_UPDATED") {
|
|
23573
23638
|
validateAccessToken(accessToken);
|
|
23574
23639
|
validateUrl(url);
|
|
23575
|
-
return
|
|
23640
|
+
return gscFetchJson(
|
|
23576
23641
|
accessToken,
|
|
23577
23642
|
`${INDEXING_API_BASE}/urlNotifications:publish`,
|
|
23578
23643
|
{
|
|
@@ -23585,7 +23650,7 @@ async function inspectUrl(accessToken, inspectionUrl, siteUrl) {
|
|
|
23585
23650
|
validateAccessToken(accessToken);
|
|
23586
23651
|
validateUrl(inspectionUrl);
|
|
23587
23652
|
validateSiteUrl(siteUrl);
|
|
23588
|
-
return
|
|
23653
|
+
return gscFetchJson(
|
|
23589
23654
|
accessToken,
|
|
23590
23655
|
URL_INSPECTION_API,
|
|
23591
23656
|
{
|
|
@@ -24795,6 +24860,33 @@ function scopesForConnectionType(type) {
|
|
|
24795
24860
|
return [GBP_SCOPE];
|
|
24796
24861
|
}
|
|
24797
24862
|
}
|
|
24863
|
+
function isSitemapOwnedByProperty(sitemapUrl, propertyId, canonicalDomain) {
|
|
24864
|
+
let sitemap;
|
|
24865
|
+
try {
|
|
24866
|
+
sitemap = new URL(sitemapUrl);
|
|
24867
|
+
} catch {
|
|
24868
|
+
return false;
|
|
24869
|
+
}
|
|
24870
|
+
if (sitemap.protocol !== "http:" && sitemap.protocol !== "https:") return false;
|
|
24871
|
+
if (/^sc-domain:/i.test(propertyId)) {
|
|
24872
|
+
const domain = propertyId.slice("sc-domain:".length).toLowerCase();
|
|
24873
|
+
const host = sitemap.hostname.toLowerCase();
|
|
24874
|
+
return host === domain || host.endsWith(`.${domain}`);
|
|
24875
|
+
}
|
|
24876
|
+
if (/^\d+$/.test(propertyId)) {
|
|
24877
|
+
const domain = normalizeProjectDomain(canonicalDomain);
|
|
24878
|
+
return sitemap.hostname.toLowerCase().replace(/^www\./, "") === domain;
|
|
24879
|
+
}
|
|
24880
|
+
try {
|
|
24881
|
+
const property = new URL(propertyId);
|
|
24882
|
+
if (property.protocol !== "http:" && property.protocol !== "https:") return false;
|
|
24883
|
+
if (sitemap.origin !== property.origin) return false;
|
|
24884
|
+
const prefix = property.pathname.endsWith("/") ? property.pathname : `${property.pathname}/`;
|
|
24885
|
+
return sitemap.pathname === property.pathname || sitemap.pathname.startsWith(prefix);
|
|
24886
|
+
} catch {
|
|
24887
|
+
return false;
|
|
24888
|
+
}
|
|
24889
|
+
}
|
|
24798
24890
|
function signState(payload, secret) {
|
|
24799
24891
|
return crypto19.createHmac("sha256", secret).update(payload).digest("hex");
|
|
24800
24892
|
}
|
|
@@ -25454,17 +25546,83 @@ async function googleRoutes(app, opts) {
|
|
|
25454
25546
|
}
|
|
25455
25547
|
const store = requireConnectionStore();
|
|
25456
25548
|
const project = resolveProject(app.db, request.params.name);
|
|
25549
|
+
const conn = store.getConnection(project.canonicalDomain, "gsc");
|
|
25550
|
+
if (!conn?.propertyId) {
|
|
25551
|
+
throw validationError('No GSC property configured for this connection. Set one with "canonry google set-property".');
|
|
25552
|
+
}
|
|
25553
|
+
const sitemapIndex = request.query.sitemapIndex;
|
|
25554
|
+
if (sitemapIndex && !isSitemapOwnedByProperty(sitemapIndex, conn.propertyId, project.canonicalDomain)) {
|
|
25555
|
+
throw validationError(`sitemapIndex must belong to the configured GSC property "${conn.propertyId}".`);
|
|
25556
|
+
}
|
|
25457
25557
|
try {
|
|
25458
25558
|
const { accessToken, propertyId } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
25459
25559
|
if (!propertyId) {
|
|
25460
25560
|
throw validationError('No GSC property configured for this connection. Set one with "canonry google set-property".');
|
|
25461
25561
|
}
|
|
25462
|
-
const
|
|
25463
|
-
|
|
25562
|
+
const entries = await listSitemaps(accessToken, propertyId, sitemapIndex);
|
|
25563
|
+
const sitemaps = sitemapIndex ? entries.map((sitemap) => ({ ...sitemap, parentSitemapUrl: sitemapIndex })) : entries;
|
|
25564
|
+
const indexes = sitemaps.filter((sitemap) => sitemap.isSitemapsIndex).length;
|
|
25565
|
+
return {
|
|
25566
|
+
sitemaps,
|
|
25567
|
+
summary: { total: sitemaps.length, indexes, files: sitemaps.length - indexes },
|
|
25568
|
+
preferredSubmissionUrls: indexes > 0 ? sitemaps.filter((sitemap) => sitemap.isSitemapsIndex).map((sitemap) => sitemap.path) : sitemaps.map((sitemap) => sitemap.path)
|
|
25569
|
+
};
|
|
25464
25570
|
} catch (err) {
|
|
25465
25571
|
throw gscErrorToAppError(err, "Failed to list Search Console sitemaps");
|
|
25466
25572
|
}
|
|
25467
25573
|
});
|
|
25574
|
+
app.post("/projects/:name/google/gsc/sitemaps/submit", async (request) => {
|
|
25575
|
+
const parsed = gscSubmitSitemapsRequestDtoSchema.safeParse(request.body ?? {});
|
|
25576
|
+
if (!parsed.success) {
|
|
25577
|
+
throw validationError("sitemapUrls must contain between 1 and 50 valid sitemap URLs");
|
|
25578
|
+
}
|
|
25579
|
+
const sitemapUrls = [...new Set(parsed.data.sitemapUrls)];
|
|
25580
|
+
const project = resolveProject(app.db, request.params.name);
|
|
25581
|
+
const store = requireConnectionStore();
|
|
25582
|
+
const conn = store.getConnection(project.canonicalDomain, "gsc");
|
|
25583
|
+
if (!conn) {
|
|
25584
|
+
throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
|
|
25585
|
+
}
|
|
25586
|
+
if (!conn.propertyId) {
|
|
25587
|
+
throw validationError('No GSC property configured for this connection. Set one with "canonry google set-property".');
|
|
25588
|
+
}
|
|
25589
|
+
const invalidUrls = sitemapUrls.filter((url) => !isSitemapOwnedByProperty(url, conn.propertyId, project.canonicalDomain));
|
|
25590
|
+
if (invalidUrls.length > 0) {
|
|
25591
|
+
throw validationError(`Sitemap URLs must belong to the configured GSC property "${conn.propertyId}". Invalid: ${invalidUrls.slice(0, 5).join(", ")}`);
|
|
25592
|
+
}
|
|
25593
|
+
if (!(conn.scopes ?? []).includes(GSC_SCOPE)) {
|
|
25594
|
+
throw validationError('This GSC connection has the read-only webmasters scope and cannot submit sitemaps. Reconnect with "canonry google connect" to grant the full webmasters scope.');
|
|
25595
|
+
}
|
|
25596
|
+
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
25597
|
+
if (!googleClientId || !googleClientSecret) {
|
|
25598
|
+
throw validationError("Google OAuth is not configured");
|
|
25599
|
+
}
|
|
25600
|
+
let accessToken;
|
|
25601
|
+
try {
|
|
25602
|
+
({ accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret));
|
|
25603
|
+
} catch (err) {
|
|
25604
|
+
throw gscErrorToAppError(err, "Failed to authorize Search Console sitemap submission");
|
|
25605
|
+
}
|
|
25606
|
+
const results = [];
|
|
25607
|
+
for (const sitemapUrl of sitemapUrls) {
|
|
25608
|
+
try {
|
|
25609
|
+
await submitSitemap(accessToken, conn.propertyId, sitemapUrl);
|
|
25610
|
+
const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25611
|
+
writeAuditLog(app.db, {
|
|
25612
|
+
projectId: project.id,
|
|
25613
|
+
actor: "api",
|
|
25614
|
+
action: "google.sitemap.submitted",
|
|
25615
|
+
entityType: "sitemap",
|
|
25616
|
+
entityId: sitemapUrl
|
|
25617
|
+
});
|
|
25618
|
+
results.push({ sitemapUrl, status: "accepted", submittedAt });
|
|
25619
|
+
} catch (err) {
|
|
25620
|
+
results.push({ sitemapUrl, status: "error", error: err instanceof Error ? err.message : String(err) });
|
|
25621
|
+
}
|
|
25622
|
+
}
|
|
25623
|
+
const accepted = results.filter((result) => result.status === "accepted").length;
|
|
25624
|
+
return { summary: { total: results.length, accepted, failed: results.length - accepted }, results };
|
|
25625
|
+
});
|
|
25468
25626
|
app.post("/projects/:name/google/gsc/discover-sitemaps", async (request) => {
|
|
25469
25627
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
25470
25628
|
if (!googleClientId || !googleClientSecret) {
|
|
@@ -25487,9 +25645,11 @@ async function googleRoutes(app, opts) {
|
|
|
25487
25645
|
throw gscErrorToAppError(err, "Failed to discover Search Console sitemaps");
|
|
25488
25646
|
}
|
|
25489
25647
|
if (sitemaps.length === 0) {
|
|
25490
|
-
throw validationError(
|
|
25648
|
+
throw validationError(
|
|
25649
|
+
`No sitemaps found for this GSC property. Submit one with "canonry google submit-sitemap ${request.params.name} <url>" first.`
|
|
25650
|
+
);
|
|
25491
25651
|
}
|
|
25492
|
-
const primary = sitemaps.find((s) =>
|
|
25652
|
+
const primary = sitemaps.find((s) => s.isSitemapsIndex) ?? sitemaps[0];
|
|
25493
25653
|
const sitemapUrl = primary.path;
|
|
25494
25654
|
store.updateConnection(project.canonicalDomain, "gsc", {
|
|
25495
25655
|
sitemapUrl,
|
|
@@ -42064,7 +42224,6 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
|
|
|
42064
42224
|
);
|
|
42065
42225
|
|
|
42066
42226
|
// ../api-routes/src/doctor/checks/google-auth.ts
|
|
42067
|
-
var REQUIRED_GSC_SCOPES = [GSC_SCOPE, INDEXING_SCOPE];
|
|
42068
42227
|
async function resolveAccessToken(ctx) {
|
|
42069
42228
|
if (!ctx.project) {
|
|
42070
42229
|
return { ok: false, output: skippedNoProject4() };
|
|
@@ -42339,8 +42498,19 @@ var scopesCheck2 = {
|
|
|
42339
42498
|
};
|
|
42340
42499
|
}
|
|
42341
42500
|
const granted = new Set(conn.scopes ?? []);
|
|
42342
|
-
const
|
|
42343
|
-
|
|
42501
|
+
const hasFullGscScope = granted.has(GSC_SCOPE);
|
|
42502
|
+
const hasReadOnlyGscScope = granted.has(GSC_READONLY_SCOPE);
|
|
42503
|
+
const hasIndexingScope = granted.has(INDEXING_SCOPE);
|
|
42504
|
+
if (!hasFullGscScope && !hasReadOnlyGscScope) {
|
|
42505
|
+
return {
|
|
42506
|
+
status: CheckStatuses.fail,
|
|
42507
|
+
code: "google.auth.required-scope-missing",
|
|
42508
|
+
summary: "Neither the full nor read-only Search Console scope is granted, so GSC reads will fail.",
|
|
42509
|
+
remediation: `Reconnect to grant a Search Console scope: \`canonry google connect ${ctx.project.name} --type gsc\`.`,
|
|
42510
|
+
details: { granted: [...granted], missing: [GSC_SCOPE, GSC_READONLY_SCOPE] }
|
|
42511
|
+
};
|
|
42512
|
+
}
|
|
42513
|
+
if (hasFullGscScope && hasIndexingScope) {
|
|
42344
42514
|
return {
|
|
42345
42515
|
status: CheckStatuses.ok,
|
|
42346
42516
|
code: "google.auth.scopes-ok",
|
|
@@ -42349,14 +42519,22 @@ var scopesCheck2 = {
|
|
|
42349
42519
|
details: { granted: [...granted] }
|
|
42350
42520
|
};
|
|
42351
42521
|
}
|
|
42352
|
-
|
|
42353
|
-
|
|
42522
|
+
if (!hasFullGscScope) {
|
|
42523
|
+
const missing = [GSC_SCOPE, ...hasIndexingScope ? [] : [INDEXING_SCOPE]];
|
|
42524
|
+
return {
|
|
42525
|
+
status: CheckStatuses.warn,
|
|
42526
|
+
code: "google.auth.sitemap-write-scope-missing",
|
|
42527
|
+
summary: hasIndexingScope ? "The read-only Search Console scope supports existing GSC reads, but sitemap submission is unavailable." : "The read-only Search Console scope supports existing GSC reads, but sitemap submission and the Indexing API are unavailable.",
|
|
42528
|
+
remediation: `Reconnect to grant the full Search Console webmasters scope: \`canonry google connect ${ctx.project.name} --type gsc\`.`,
|
|
42529
|
+
details: { granted: [...granted], missing }
|
|
42530
|
+
};
|
|
42531
|
+
}
|
|
42354
42532
|
return {
|
|
42355
|
-
status:
|
|
42356
|
-
code:
|
|
42357
|
-
summary:
|
|
42533
|
+
status: CheckStatuses.warn,
|
|
42534
|
+
code: "google.auth.indexing-scope-missing",
|
|
42535
|
+
summary: "Indexing API scope is not granted \u2014 `canonry google request-indexing` will fail.",
|
|
42358
42536
|
remediation: `Reconnect to grant missing scopes: \`canonry google connect ${ctx.project.name} --type gsc\`.`,
|
|
42359
|
-
details: { granted: [...granted], missing }
|
|
42537
|
+
details: { granted: [...granted], missing: [INDEXING_SCOPE] }
|
|
42360
42538
|
};
|
|
42361
42539
|
}
|
|
42362
42540
|
};
|