@ainyc/canonry 5.0.1 → 5.1.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/dist/{chunk-GMNZNLVX.js → chunk-ZWVXUPLP.js} +118 -3
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +8 -8
|
@@ -6286,7 +6286,7 @@ function nonBlank(value) {
|
|
|
6286
6286
|
return trimmed ? trimmed : void 0;
|
|
6287
6287
|
}
|
|
6288
6288
|
function resolveBuildCommit(env = process.env) {
|
|
6289
|
-
const embedded = true ? "
|
|
6289
|
+
const embedded = true ? "2c66fea459e20ff285096090dbc6a6ee788c4c00" : void 0;
|
|
6290
6290
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
6291
6291
|
}
|
|
6292
6292
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -6342,6 +6342,30 @@ function buildRunCompletedProps(input) {
|
|
|
6342
6342
|
if (input.location) props.location = input.location;
|
|
6343
6343
|
return props;
|
|
6344
6344
|
}
|
|
6345
|
+
function buildSiteAuditCompletedProps(input) {
|
|
6346
|
+
const props = {
|
|
6347
|
+
status: input.status,
|
|
6348
|
+
durationMs: Date.now() - input.startTime
|
|
6349
|
+
};
|
|
6350
|
+
if (input.trigger) props.trigger = input.trigger;
|
|
6351
|
+
const domainHash = hashDomain(input.canonicalDomain ?? null);
|
|
6352
|
+
if (domainHash) props.domainHash = domainHash;
|
|
6353
|
+
const crawl = input.crawl;
|
|
6354
|
+
if (!crawl) return props;
|
|
6355
|
+
props.complete = crawl.complete;
|
|
6356
|
+
if (crawl.termination) props.termination = crawl.termination;
|
|
6357
|
+
props.pagesDiscovered = crawl.pagesDiscovered;
|
|
6358
|
+
props.pagesFetched = crawl.pagesFetched;
|
|
6359
|
+
props.pagesAudited = crawl.pagesAudited;
|
|
6360
|
+
props.pagesErrored = crawl.pagesErrored;
|
|
6361
|
+
if (crawl.pagesAudited > 0 && typeof crawl.aggregateScore === "number" && Number.isFinite(crawl.aggregateScore)) {
|
|
6362
|
+
props.aggregateScore = crawl.aggregateScore;
|
|
6363
|
+
}
|
|
6364
|
+
props.pageBudget = crawl.pageBudget;
|
|
6365
|
+
props.checkDeadLinks = crawl.checkDeadLinks;
|
|
6366
|
+
if (crawl.checkDeadLinks) props.deadLinksFound = crawl.deadLinksFound;
|
|
6367
|
+
return props;
|
|
6368
|
+
}
|
|
6345
6369
|
|
|
6346
6370
|
// src/provider-execution-gate.ts
|
|
6347
6371
|
var ProviderExecutionGate = class {
|
|
@@ -6610,7 +6634,14 @@ var JobRunner = class {
|
|
|
6610
6634
|
this.registry = registry;
|
|
6611
6635
|
}
|
|
6612
6636
|
recoverStaleRuns() {
|
|
6613
|
-
const stale = this.db.select({
|
|
6637
|
+
const stale = this.db.select({
|
|
6638
|
+
id: runs.id,
|
|
6639
|
+
projectId: runs.projectId,
|
|
6640
|
+
kind: runs.kind,
|
|
6641
|
+
status: runs.status,
|
|
6642
|
+
trigger: runs.trigger,
|
|
6643
|
+
startedAt: runs.startedAt
|
|
6644
|
+
}).from(runs).where(inArray(runs.status, ["running", "queued"])).all();
|
|
6614
6645
|
if (stale.length === 0) return;
|
|
6615
6646
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6616
6647
|
for (const run of stale) {
|
|
@@ -6628,6 +6659,35 @@ var JobRunner = class {
|
|
|
6628
6659
|
});
|
|
6629
6660
|
if (!recovered) continue;
|
|
6630
6661
|
log2.warn("run.recovered-stale", { runId: run.id, previousStatus: run.status });
|
|
6662
|
+
if (run.kind === RunKinds["site-audit"]) this.trackRecoveredSiteAudit(run);
|
|
6663
|
+
}
|
|
6664
|
+
}
|
|
6665
|
+
/**
|
|
6666
|
+
* A crawl the process died under never reaches `executeSiteAudit`'s terminal
|
|
6667
|
+
* telemetry, so without this every interrupted audit vanishes from the data.
|
|
6668
|
+
* Emitted after the recovery transaction commits. `durationMs` runs from the
|
|
6669
|
+
* crawl's start to this recovery, so it includes the downtime and is an upper
|
|
6670
|
+
* bound; a run that was still queued has no start and reports 0.
|
|
6671
|
+
*
|
|
6672
|
+
* The source is set explicitly: recovery runs during server construction,
|
|
6673
|
+
* before `canonry serve` switches the process source to `cli-server`.
|
|
6674
|
+
*/
|
|
6675
|
+
trackRecoveredSiteAudit(run) {
|
|
6676
|
+
try {
|
|
6677
|
+
const startedAt = run.startedAt ? Date.parse(run.startedAt) : Number.NaN;
|
|
6678
|
+
const project = this.db.select({ canonicalDomain: projects.canonicalDomain }).from(projects).where(eq2(projects.id, run.projectId)).get();
|
|
6679
|
+
trackEvent(
|
|
6680
|
+
"site_audit.completed",
|
|
6681
|
+
buildSiteAuditCompletedProps({
|
|
6682
|
+
status: "failed",
|
|
6683
|
+
startTime: Number.isFinite(startedAt) ? startedAt : Date.now(),
|
|
6684
|
+
trigger: run.trigger,
|
|
6685
|
+
canonicalDomain: project?.canonicalDomain ?? null
|
|
6686
|
+
}),
|
|
6687
|
+
{ errorCode: "SERVER_RESTARTED", source: "cli-server" }
|
|
6688
|
+
);
|
|
6689
|
+
} catch (err) {
|
|
6690
|
+
log2.warn("telemetry.recovered-site-audit-failed", { runId: run.id, error: describeError(err) });
|
|
6631
6691
|
}
|
|
6632
6692
|
}
|
|
6633
6693
|
async executeRun(runId, projectId, providerOverride, locationOverride) {
|
|
@@ -7149,6 +7209,7 @@ var JobRunner = class {
|
|
|
7149
7209
|
if (existingRun.kind === "answer-visibility" && runTrigger !== "probe" && totalSnapshotsInserted > 0 && !this.hasPriorActivation(projectId, runId)) {
|
|
7150
7210
|
trackEvent("activation.completed", {
|
|
7151
7211
|
flowVersion: ONBOARDING_FLOW_VERSION,
|
|
7212
|
+
kind: "answer_visibility",
|
|
7152
7213
|
status: finalStatus,
|
|
7153
7214
|
providerCountBucket: bucketOnboardingCount(executionContext.providerCount),
|
|
7154
7215
|
queryCountBucket: bucketOnboardingCount(executionContext.queryCount),
|
|
@@ -10031,7 +10092,7 @@ function buildDiscoveryInsightTitle(input) {
|
|
|
10031
10092
|
|
|
10032
10093
|
// src/execute-site-audit.ts
|
|
10033
10094
|
import crypto19 from "crypto";
|
|
10034
|
-
import { and as and13, eq as eq15, sql as sql6 } from "drizzle-orm";
|
|
10095
|
+
import { and as and13, eq as eq15, ne as ne3, sql as sql6 } from "drizzle-orm";
|
|
10035
10096
|
import { runSiteCrawl } from "@canonry/aeo-audit";
|
|
10036
10097
|
|
|
10037
10098
|
// src/site-audit-root.ts
|
|
@@ -11108,10 +11169,35 @@ function deadLinkCheckedCount(edges, pages) {
|
|
|
11108
11169
|
}
|
|
11109
11170
|
return new Set([...edges].filter((edge) => edge.type === "anchor" && edge.classification === "internal" && attemptedUrls.has(edge.to)).map((edge) => edge.to)).size;
|
|
11110
11171
|
}
|
|
11172
|
+
function trackSiteAuditOutcome(db, input) {
|
|
11173
|
+
try {
|
|
11174
|
+
const errorCode = input.status === "cancelled" ? "RUN_CANCELLED" : input.status === "failed" ? "UNKNOWN" : void 0;
|
|
11175
|
+
trackEvent(
|
|
11176
|
+
"site_audit.completed",
|
|
11177
|
+
buildSiteAuditCompletedProps(input),
|
|
11178
|
+
errorCode ? { errorCode } : void 0
|
|
11179
|
+
);
|
|
11180
|
+
const pagesAudited = input.crawl?.pagesAudited ?? 0;
|
|
11181
|
+
if (pagesAudited === 0 || input.trigger === RunTriggers.probe) return;
|
|
11182
|
+
const prior = db.select({ id: siteAuditSnapshots.id }).from(siteAuditSnapshots).where(and13(eq15(siteAuditSnapshots.projectId, input.projectId), ne3(siteAuditSnapshots.runId, input.runId))).limit(1).get();
|
|
11183
|
+
if (prior) return;
|
|
11184
|
+
trackEvent("activation.completed", {
|
|
11185
|
+
flowVersion: ONBOARDING_FLOW_VERSION,
|
|
11186
|
+
kind: "site_health",
|
|
11187
|
+
status: input.status,
|
|
11188
|
+
pagesAuditedBucket: bucketOnboardingCount(pagesAudited)
|
|
11189
|
+
});
|
|
11190
|
+
} catch (error) {
|
|
11191
|
+
log13.warn("telemetry.failed", { runId: input.runId, projectId: input.projectId, error: describeError(error) });
|
|
11192
|
+
}
|
|
11193
|
+
}
|
|
11111
11194
|
async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
11112
11195
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11113
11196
|
const claim = db.update(runs).set({ status: "running", startedAt }).where(and13(eq15(runs.id, runId), eq15(runs.projectId, projectId), eq15(runs.status, "queued"))).run();
|
|
11114
11197
|
if (claim.changes === 0) return;
|
|
11198
|
+
const startTime = Date.now();
|
|
11199
|
+
const trigger = db.select({ trigger: runs.trigger }).from(runs).where(eq15(runs.id, runId)).get()?.trigger ?? null;
|
|
11200
|
+
let canonicalDomain = null;
|
|
11115
11201
|
const attemptNumber = (db.select({ value: sql6`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq15(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
|
|
11116
11202
|
const attemptId = crypto19.randomUUID();
|
|
11117
11203
|
db.insert(siteCrawlAttempts).values({
|
|
@@ -11391,6 +11477,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
11391
11477
|
try {
|
|
11392
11478
|
const project = db.select().from(projects).where(eq15(projects.id, projectId)).get();
|
|
11393
11479
|
if (!project) throw new Error(`Project not found: ${projectId}`);
|
|
11480
|
+
canonicalDomain = project.canonicalDomain;
|
|
11394
11481
|
const homepageUrl = toHomepageUrl(project.canonicalDomain);
|
|
11395
11482
|
const maxPages = clampSiteAuditLimit(opts.maxPages ?? opts.limit);
|
|
11396
11483
|
const maxEdges = clampSiteAuditEdgeLimit(opts.maxEdges);
|
|
@@ -11610,6 +11697,26 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
11610
11697
|
throw new Error(`Site audit run was no longer running before publication: ${runId}`);
|
|
11611
11698
|
}
|
|
11612
11699
|
log13.info("completed", { runId, projectId, status: terminalStatus, complete: crawlSummary.complete, pages: crawlSummary.pagesObserved });
|
|
11700
|
+
trackSiteAuditOutcome(db, {
|
|
11701
|
+
runId,
|
|
11702
|
+
projectId,
|
|
11703
|
+
status: crawlSummary.complete ? "completed" : "partial",
|
|
11704
|
+
startTime,
|
|
11705
|
+
trigger,
|
|
11706
|
+
canonicalDomain,
|
|
11707
|
+
crawl: {
|
|
11708
|
+
complete: crawlSummary.complete,
|
|
11709
|
+
termination: crawlSummary.terminationReason,
|
|
11710
|
+
pagesDiscovered: crawlSummary.pagesDiscovered,
|
|
11711
|
+
pagesFetched: crawlSummary.pagesFetched,
|
|
11712
|
+
pagesAudited: crawlSummary.auditRollup.auditedPages,
|
|
11713
|
+
pagesErrored: errorCount,
|
|
11714
|
+
aggregateScore: crawlSummary.auditRollup.aggregateScore,
|
|
11715
|
+
pageBudget: maxPages,
|
|
11716
|
+
checkDeadLinks: opts.checkDeadLinks ?? false,
|
|
11717
|
+
deadLinksFound
|
|
11718
|
+
}
|
|
11719
|
+
});
|
|
11613
11720
|
} catch (error) {
|
|
11614
11721
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11615
11722
|
const cancelled = isCancelled(error, opts.signal);
|
|
@@ -11628,6 +11735,14 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
11628
11735
|
}).where(and13(eq15(runs.id, runId), eq15(runs.status, "running"))).run();
|
|
11629
11736
|
});
|
|
11630
11737
|
log13.error("failed", { runId, projectId, cancelled, error: message });
|
|
11738
|
+
trackSiteAuditOutcome(db, {
|
|
11739
|
+
runId,
|
|
11740
|
+
projectId,
|
|
11741
|
+
status: cancelled ? "cancelled" : "failed",
|
|
11742
|
+
startTime,
|
|
11743
|
+
trigger,
|
|
11744
|
+
canonicalDomain
|
|
11745
|
+
});
|
|
11631
11746
|
throw error;
|
|
11632
11747
|
}
|
|
11633
11748
|
}
|
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": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
|
|
6
6
|
"keywords": [
|
|
@@ -87,29 +87,29 @@
|
|
|
87
87
|
"tsup": "^8.5.1",
|
|
88
88
|
"tsx": "^4.19.0",
|
|
89
89
|
"@ainyc/canonry-web": "0.0.0",
|
|
90
|
-
"@ainyc/canonry-api-
|
|
90
|
+
"@ainyc/canonry-api-client": "0.0.0",
|
|
91
91
|
"@ainyc/canonry-config": "0.0.0",
|
|
92
92
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
93
|
-
"@ainyc/canonry-api-
|
|
93
|
+
"@ainyc/canonry-api-routes": "0.0.0",
|
|
94
94
|
"@ainyc/canonry-db": "0.0.0",
|
|
95
95
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
96
96
|
"@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
|
|
97
97
|
"@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
|
|
98
|
+
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
98
99
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
100
|
+
"@ainyc/canonry-integration-google": "0.0.0",
|
|
99
101
|
"@ainyc/canonry-integration-google-ads": "0.0.0",
|
|
100
102
|
"@ainyc/canonry-integration-google-business-profile": "0.0.0",
|
|
101
|
-
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
102
103
|
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
103
|
-
"@ainyc/canonry-integration-google": "0.0.0",
|
|
104
104
|
"@ainyc/canonry-integration-google-tag-manager": "0.0.0",
|
|
105
|
-
"@ainyc/canonry-integration-openai-ads": "0.0.0",
|
|
106
105
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
107
|
-
"@ainyc/canonry-
|
|
106
|
+
"@ainyc/canonry-integration-openai-ads": "0.0.0",
|
|
108
107
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
108
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
109
109
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
110
110
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
111
|
-
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
112
111
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
112
|
+
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
113
113
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
114
114
|
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
115
115
|
},
|