@ainyc/canonry 4.60.1 → 4.60.2
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/dist/{chunk-X4ZZFZQZ.js → chunk-AUR7VMQF.js} +158 -64
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +8 -8
|
@@ -3542,6 +3542,71 @@ function buildPhases(input) {
|
|
|
3542
3542
|
// src/gsc-sync.ts
|
|
3543
3543
|
import crypto4 from "crypto";
|
|
3544
3544
|
import { eq as eq2, and as and2, sql as sql2 } from "drizzle-orm";
|
|
3545
|
+
|
|
3546
|
+
// src/gsc-inspect-paced.ts
|
|
3547
|
+
var INSPECT_BASE_DELAY_MS = 1e3;
|
|
3548
|
+
var INSPECT_PACING_JITTER_MS = 250;
|
|
3549
|
+
var INSPECT_MAX_RETRIES = 3;
|
|
3550
|
+
var INSPECT_MAX_BACKOFF_MS = 3e4;
|
|
3551
|
+
var INSPECT_FAILFAST_THRESHOLD = 5;
|
|
3552
|
+
function isRetryableGscInspectError(err) {
|
|
3553
|
+
if (err != null && typeof err === "object" && "status" in err) {
|
|
3554
|
+
if (err.status === 403) return true;
|
|
3555
|
+
}
|
|
3556
|
+
return isRetryableHttpError(err);
|
|
3557
|
+
}
|
|
3558
|
+
function defaultSleep(ms) {
|
|
3559
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3560
|
+
}
|
|
3561
|
+
async function inspectUrlsPaced(urls, cb, deps = {}) {
|
|
3562
|
+
const sleep3 = deps.sleep ?? defaultSleep;
|
|
3563
|
+
const jitter = deps.jitter ?? Math.random;
|
|
3564
|
+
let inspected = 0;
|
|
3565
|
+
let errors = 0;
|
|
3566
|
+
let consecutiveRetryableFailures = 0;
|
|
3567
|
+
for (let index = 0; index < urls.length; index++) {
|
|
3568
|
+
const url = urls[index];
|
|
3569
|
+
try {
|
|
3570
|
+
const result = await withRetry(() => cb.inspectOne(url), {
|
|
3571
|
+
maxRetries: INSPECT_MAX_RETRIES,
|
|
3572
|
+
baseDelayMs: INSPECT_BASE_DELAY_MS,
|
|
3573
|
+
maxDelayMs: INSPECT_MAX_BACKOFF_MS,
|
|
3574
|
+
isRetryable: isRetryableGscInspectError,
|
|
3575
|
+
sleep: sleep3,
|
|
3576
|
+
onRetry: ({ attempt, delayMs, err }) => deps.log?.info("inspect.retry", {
|
|
3577
|
+
url,
|
|
3578
|
+
attempt,
|
|
3579
|
+
delayMs: Math.round(delayMs),
|
|
3580
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3581
|
+
})
|
|
3582
|
+
});
|
|
3583
|
+
cb.onResult(url, result, index);
|
|
3584
|
+
inspected++;
|
|
3585
|
+
consecutiveRetryableFailures = 0;
|
|
3586
|
+
} catch (err) {
|
|
3587
|
+
errors++;
|
|
3588
|
+
cb.onError(url, err, index);
|
|
3589
|
+
if (isRetryableGscInspectError(err)) {
|
|
3590
|
+
consecutiveRetryableFailures++;
|
|
3591
|
+
if (consecutiveRetryableFailures >= INSPECT_FAILFAST_THRESHOLD) {
|
|
3592
|
+
deps.log?.error("inspect.circuit-break", {
|
|
3593
|
+
consecutiveFailures: consecutiveRetryableFailures,
|
|
3594
|
+
inspected,
|
|
3595
|
+
errors,
|
|
3596
|
+
remaining: urls.length - index - 1
|
|
3597
|
+
});
|
|
3598
|
+
return { inspected, errors, aborted: true, abortError: err };
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
if (index < urls.length - 1) {
|
|
3603
|
+
await sleep3(INSPECT_BASE_DELAY_MS + jitter() * INSPECT_PACING_JITTER_MS);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
return { inspected, errors, aborted: false };
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
// src/gsc-sync.ts
|
|
3545
3610
|
var log2 = createLogger("GscSync");
|
|
3546
3611
|
function formatDate(d) {
|
|
3547
3612
|
return d.toISOString().split("T")[0];
|
|
@@ -3570,6 +3635,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
3570
3635
|
if (!conn.propertyId) {
|
|
3571
3636
|
throw new Error('No GSC property selected. Use "canonry google properties" to list available sites, then set one with the API.');
|
|
3572
3637
|
}
|
|
3638
|
+
const propertyId = conn.propertyId;
|
|
3573
3639
|
let accessToken = conn.accessToken;
|
|
3574
3640
|
const expiresAt = conn.tokenExpiresAt ? new Date(conn.tokenExpiresAt).getTime() : 0;
|
|
3575
3641
|
if (Date.now() > expiresAt - 5 * 60 * 1e3) {
|
|
@@ -3631,35 +3697,53 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
3631
3697
|
}
|
|
3632
3698
|
const topPages = [...pageClicks.entries()].sort((a, b) => b[1] - a[1]).slice(0, 50).map(([page]) => page);
|
|
3633
3699
|
log2.info("inspect.start", { runId, projectId, urlCount: topPages.length });
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3700
|
+
const inspectOutcome = await inspectUrlsPaced(
|
|
3701
|
+
topPages,
|
|
3702
|
+
{
|
|
3703
|
+
inspectOne: (pageUrl) => inspectUrl(accessToken, pageUrl, propertyId),
|
|
3704
|
+
onResult: (pageUrl, result) => {
|
|
3705
|
+
const ir = result.inspectionResult;
|
|
3706
|
+
const idx = ir.indexStatusResult;
|
|
3707
|
+
const mob = ir.mobileUsabilityResult;
|
|
3708
|
+
const rich = ir.richResultsResult;
|
|
3709
|
+
const inspectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3710
|
+
db.insert(gscUrlInspections).values({
|
|
3711
|
+
id: crypto4.randomUUID(),
|
|
3712
|
+
projectId,
|
|
3713
|
+
syncRunId: runId,
|
|
3714
|
+
url: pageUrl,
|
|
3715
|
+
indexingState: idx?.indexingState ?? null,
|
|
3716
|
+
verdict: idx?.verdict ?? null,
|
|
3717
|
+
coverageState: idx?.coverageState ?? null,
|
|
3718
|
+
pageFetchState: idx?.pageFetchState ?? null,
|
|
3719
|
+
robotsTxtState: idx?.robotsTxtState ?? null,
|
|
3720
|
+
crawlTime: idx?.lastCrawlTime ?? null,
|
|
3721
|
+
lastCrawlResult: idx?.crawlResult ?? null,
|
|
3722
|
+
isMobileFriendly: mob?.verdict === "PASS" ? true : mob?.verdict === "FAIL" ? false : null,
|
|
3723
|
+
richResults: rich?.detectedItems?.map((d) => d.richResultType) ?? [],
|
|
3724
|
+
referringUrls: idx?.referringUrls ?? [],
|
|
3725
|
+
inspectedAt,
|
|
3726
|
+
createdAt: inspectedAt
|
|
3727
|
+
}).run();
|
|
3728
|
+
},
|
|
3729
|
+
onError: (pageUrl, err) => {
|
|
3730
|
+
log2.error("inspect.url-failed", { runId, projectId, url: pageUrl, error: err instanceof Error ? err.message : String(err) });
|
|
3731
|
+
}
|
|
3732
|
+
},
|
|
3733
|
+
{
|
|
3734
|
+
log: {
|
|
3735
|
+
info: (action, ctx) => log2.info(action, { runId, projectId, ...ctx }),
|
|
3736
|
+
error: (action, ctx) => log2.error(action, { runId, projectId, ...ctx })
|
|
3737
|
+
}
|
|
3662
3738
|
}
|
|
3739
|
+
);
|
|
3740
|
+
if (inspectOutcome.aborted) {
|
|
3741
|
+
log2.error("inspect.stopped-early", {
|
|
3742
|
+
runId,
|
|
3743
|
+
projectId,
|
|
3744
|
+
inspected: inspectOutcome.inspected,
|
|
3745
|
+
note: "URL inspection stopped early after sustained rate/access failures; search-analytics data was still saved"
|
|
3746
|
+
});
|
|
3663
3747
|
}
|
|
3664
3748
|
const allInspections = db.select().from(gscUrlInspections).where(eq2(gscUrlInspections.projectId, projectId)).all();
|
|
3665
3749
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
@@ -4057,6 +4141,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
4057
4141
|
if (!conn.propertyId) {
|
|
4058
4142
|
throw new Error('No GSC property selected. Use "canonry google properties" to list available sites, then set one.');
|
|
4059
4143
|
}
|
|
4144
|
+
const propertyId = conn.propertyId;
|
|
4060
4145
|
let accessToken = conn.accessToken;
|
|
4061
4146
|
const expiresAt = conn.tokenExpiresAt ? new Date(conn.tokenExpiresAt).getTime() : 0;
|
|
4062
4147
|
if (Date.now() > expiresAt - 5 * 60 * 1e3) {
|
|
@@ -4076,43 +4161,52 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
4076
4161
|
if (urls.length === 0) {
|
|
4077
4162
|
throw new Error("No URLs found in sitemap");
|
|
4078
4163
|
}
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4164
|
+
const { inspected, errors, aborted, abortError } = await inspectUrlsPaced(
|
|
4165
|
+
urls,
|
|
4166
|
+
{
|
|
4167
|
+
inspectOne: (pageUrl) => inspectUrl(accessToken, pageUrl, propertyId),
|
|
4168
|
+
onResult: (pageUrl, result, index) => {
|
|
4169
|
+
const ir = result.inspectionResult;
|
|
4170
|
+
const idx = ir.indexStatusResult;
|
|
4171
|
+
const mob = ir.mobileUsabilityResult;
|
|
4172
|
+
const rich = ir.richResultsResult;
|
|
4173
|
+
const inspectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4174
|
+
db.insert(gscUrlInspections).values({
|
|
4175
|
+
id: crypto6.randomUUID(),
|
|
4176
|
+
projectId,
|
|
4177
|
+
syncRunId: runId,
|
|
4178
|
+
url: pageUrl,
|
|
4179
|
+
indexingState: idx?.indexingState ?? null,
|
|
4180
|
+
verdict: idx?.verdict ?? null,
|
|
4181
|
+
coverageState: idx?.coverageState ?? null,
|
|
4182
|
+
pageFetchState: idx?.pageFetchState ?? null,
|
|
4183
|
+
robotsTxtState: idx?.robotsTxtState ?? null,
|
|
4184
|
+
crawlTime: idx?.lastCrawlTime ?? null,
|
|
4185
|
+
lastCrawlResult: idx?.crawlResult ?? null,
|
|
4186
|
+
isMobileFriendly: mob?.verdict === "PASS" ? true : mob?.verdict === "FAIL" ? false : null,
|
|
4187
|
+
richResults: rich?.detectedItems?.map((d) => d.richResultType) ?? [],
|
|
4188
|
+
referringUrls: idx?.referringUrls ?? [],
|
|
4189
|
+
inspectedAt,
|
|
4190
|
+
createdAt: inspectedAt
|
|
4191
|
+
}).run();
|
|
4192
|
+
log5.info("inspect.url-done", { runId, projectId, url: pageUrl, progress: `${index + 1}/${urls.length}` });
|
|
4193
|
+
},
|
|
4194
|
+
onError: (pageUrl, err) => {
|
|
4195
|
+
log5.error("inspect.url-failed", { runId, projectId, url: pageUrl, error: err instanceof Error ? err.message : String(err) });
|
|
4196
|
+
}
|
|
4197
|
+
},
|
|
4198
|
+
{
|
|
4199
|
+
log: {
|
|
4200
|
+
info: (action, ctx) => log5.info(action, { runId, projectId, ...ctx }),
|
|
4201
|
+
error: (action, ctx) => log5.error(action, { runId, projectId, ...ctx })
|
|
4202
|
+
}
|
|
4115
4203
|
}
|
|
4204
|
+
);
|
|
4205
|
+
if (aborted) {
|
|
4206
|
+
const detail = abortError instanceof Error ? abortError.message : String(abortError);
|
|
4207
|
+
throw new Error(
|
|
4208
|
+
`URL inspection aborted after ${INSPECT_FAILFAST_THRESHOLD} consecutive rate/access failures (likely GSC URL Inspection quota exhaustion or property access loss). Last error: ${detail}`
|
|
4209
|
+
);
|
|
4116
4210
|
}
|
|
4117
4211
|
const allInspections = db.select().from(gscUrlInspections).where(eq4(gscUrlInspections.projectId, projectId)).all();
|
|
4118
4212
|
const latestByUrl = /* @__PURE__ */ new Map();
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "4.60.
|
|
3
|
+
"version": "4.60.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -61,25 +61,25 @@
|
|
|
61
61
|
"@types/node-cron": "^3.0.11",
|
|
62
62
|
"tsup": "^8.5.1",
|
|
63
63
|
"tsx": "^4.19.0",
|
|
64
|
+
"@ainyc/canonry-api-routes": "0.0.0",
|
|
64
65
|
"@ainyc/canonry-api-client": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-contracts": "0.0.0",
|
|
66
66
|
"@ainyc/canonry-config": "0.0.0",
|
|
67
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
67
68
|
"@ainyc/canonry-db": "0.0.0",
|
|
68
|
-
"@ainyc/canonry-api-routes": "0.0.0",
|
|
69
69
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
71
70
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
72
71
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
73
|
-
"@ainyc/canonry-integration-
|
|
72
|
+
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
74
73
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
75
74
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
76
75
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
76
|
+
"@ainyc/canonry-integration-google-business-profile": "0.0.0",
|
|
77
77
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
78
78
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
79
|
-
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
80
79
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
81
|
-
"@ainyc/canonry-provider-
|
|
82
|
-
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
80
|
+
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
81
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
82
|
+
"@ainyc/canonry-provider-openai": "0.0.0"
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
85
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|