@pagebridge/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/url-matcher.d.ts +22 -3
- package/dist/url-matcher.d.ts.map +1 -1
- package/dist/url-matcher.js +128 -109
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { GSCClient, type GSCClientOptions, type IndexStatusResult, type IndexVerdict, } from "./gsc-client.js";
|
|
2
2
|
export { SyncEngine, type SyncOptions, type SyncResult, type IndexStatusSyncResult, type SnapshotInsights, type PublishingImpact, type CannibalizationTarget, type DailyMetricPoint, } from "./sync-engine.js";
|
|
3
3
|
export { DecayDetector, defaultRules, type DecayRule, type DecaySignal, type QuietPeriodConfig, } from "./decay-detector.js";
|
|
4
|
-
export { URLMatcher, type MatchResult, type URLMatcherConfig, type UnmatchReason, type MatchDiagnostics, } from "./url-matcher.js";
|
|
4
|
+
export { URLMatcher, type MatchResult, type URLMatcherConfig, type ContentTypeUrlConfig, type UnmatchReason, type MatchDiagnostics, } from "./url-matcher.js";
|
|
5
5
|
export { TaskGenerator, type TaskGeneratorOptions, type QueryContext, } from "./task-generator.js";
|
|
6
6
|
export { QuickWinAnalyzer, type QuickWinQuery, type QuickWinConfig, } from "./quick-win-analyzer.js";
|
|
7
7
|
export { CtrAnomalyAnalyzer, EXPECTED_CTR_BY_POSITION, type CtrAnomaly, type CtrAnomalySeverity, type CtrAnomalyConfig, type InsightAlert, } from "./ctr-anomaly-analyzer.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EACL,wBAAwB,EACxB,KAAK,YAAY,GAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,uBAAuB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,qBAAqB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EACL,wBAAwB,EACxB,KAAK,YAAY,GAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,uBAAuB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,qBAAqB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/url-matcher.d.ts
CHANGED
|
@@ -14,17 +14,37 @@ export interface MatchResult {
|
|
|
14
14
|
matchedSlug?: string;
|
|
15
15
|
unmatchReason: UnmatchReason;
|
|
16
16
|
extractedSlug?: string;
|
|
17
|
+
matchedContentType?: string;
|
|
17
18
|
diagnostics?: MatchDiagnostics;
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Configuration for a single content type's URL structure
|
|
22
|
+
*/
|
|
23
|
+
export interface ContentTypeUrlConfig {
|
|
24
|
+
contentType: string;
|
|
25
|
+
pathPrefix?: string;
|
|
21
26
|
slugField: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* URLMatcher configuration with support for multiple content types and path structures
|
|
30
|
+
* @deprecated If using the old flat structure (contentTypes, slugField, pathPrefix),
|
|
31
|
+
* migrate to urlConfigs for more flexible URL path handling.
|
|
32
|
+
*/
|
|
33
|
+
export interface URLMatcherConfig {
|
|
34
|
+
/** New format: array of per-content-type URL configs (recommended) */
|
|
35
|
+
urlConfigs?: ContentTypeUrlConfig[];
|
|
36
|
+
/** @deprecated Use urlConfigs instead. Kept for backward compatibility. */
|
|
37
|
+
contentTypes?: string[];
|
|
38
|
+
/** @deprecated Use urlConfigs instead. Kept for backward compatibility. */
|
|
39
|
+
slugField?: string;
|
|
40
|
+
/** @deprecated Use urlConfigs instead. Kept for backward compatibility. */
|
|
22
41
|
pathPrefix?: string;
|
|
23
42
|
baseUrl: string;
|
|
24
43
|
}
|
|
25
44
|
export declare class URLMatcher {
|
|
26
45
|
private sanityClient;
|
|
27
46
|
private config;
|
|
47
|
+
private normalizedConfigs;
|
|
28
48
|
constructor(sanityClient: SanityClient, config: URLMatcherConfig);
|
|
29
49
|
matchUrls(gscUrls: string[]): Promise<MatchResult[]>;
|
|
30
50
|
/**
|
|
@@ -33,7 +53,6 @@ export declare class URLMatcher {
|
|
|
33
53
|
getAvailableSlugs(): Promise<string[]>;
|
|
34
54
|
private matchSingleUrl;
|
|
35
55
|
private normalizeUrl;
|
|
36
|
-
private extractSlug;
|
|
37
56
|
private extractSlugWithDiagnostics;
|
|
38
57
|
private escapeRegex;
|
|
39
58
|
private normalizeSlug;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-matcher.d.ts","sourceRoot":"","sources":["../src/url-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,mBAAmB,GACnB,sBAAsB,GACtB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,UAAU,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"url-matcher.d.ts","sourceRoot":"","sources":["../src/url-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,mBAAmB,GACnB,sBAAsB,GACtB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,UAAU,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACpC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AASD,qBAAa,UAAU;IAInB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,MAAM;IAJhB,OAAO,CAAC,iBAAiB,CAAyB;gBAGxC,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,gBAAgB;IAuB5B,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAuC1D;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAsB5C,OAAO,CAAC,cAAc;IA0GtB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,0BAA0B;IA2ClC,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CA8B5B"}
|
package/dist/url-matcher.js
CHANGED
|
@@ -1,130 +1,152 @@
|
|
|
1
1
|
export class URLMatcher {
|
|
2
2
|
sanityClient;
|
|
3
3
|
config;
|
|
4
|
+
normalizedConfigs;
|
|
4
5
|
constructor(sanityClient, config) {
|
|
5
6
|
this.sanityClient = sanityClient;
|
|
6
7
|
this.config = config;
|
|
8
|
+
// Normalize config: convert old format to new format if needed
|
|
9
|
+
if (config.urlConfigs) {
|
|
10
|
+
this.normalizedConfigs = config.urlConfigs;
|
|
11
|
+
}
|
|
12
|
+
else if (config.contentTypes) {
|
|
13
|
+
// Backward compatibility: convert old flat format to new format
|
|
14
|
+
console.warn("[URLMatcher] Deprecated: contentTypes, slugField, and pathPrefix are deprecated. " +
|
|
15
|
+
"Please use urlConfigs instead for more flexible URL path handling per content type.");
|
|
16
|
+
this.normalizedConfigs = config.contentTypes.map((contentType) => ({
|
|
17
|
+
contentType,
|
|
18
|
+
slugField: config.slugField || "slug",
|
|
19
|
+
pathPrefix: config.pathPrefix,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
throw new Error("URLMatcher requires either 'urlConfigs' (new format) or 'contentTypes' (deprecated format)");
|
|
24
|
+
}
|
|
7
25
|
}
|
|
8
26
|
async matchUrls(gscUrls) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
_type,
|
|
12
|
-
"${this.config.slugField}": ${this.config.slugField}.current,
|
|
13
|
-
_createdAt
|
|
14
|
-
}`;
|
|
15
|
-
const documents = await this.sanityClient.fetch(query, {
|
|
16
|
-
types: this.config.contentTypes,
|
|
17
|
-
});
|
|
18
|
-
const slugToDoc = new Map();
|
|
27
|
+
// Query documents for each content type with its configured slug field
|
|
28
|
+
const slugToDocMap = new Map();
|
|
19
29
|
const allSlugs = [];
|
|
20
|
-
for (const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
for (const urlConfig of this.normalizedConfigs) {
|
|
31
|
+
const query = `*[_type == $type]{
|
|
32
|
+
_id,
|
|
33
|
+
_type,
|
|
34
|
+
"${urlConfig.slugField}": ${urlConfig.slugField}.current,
|
|
35
|
+
_createdAt
|
|
36
|
+
}`;
|
|
37
|
+
const documents = await this.sanityClient.fetch(query, {
|
|
38
|
+
type: urlConfig.contentType,
|
|
39
|
+
});
|
|
40
|
+
for (const doc of documents) {
|
|
41
|
+
const slug = doc[urlConfig.slugField];
|
|
42
|
+
if (slug) {
|
|
43
|
+
const normalized = this.normalizeSlug(slug);
|
|
44
|
+
const key = `${urlConfig.contentType}:${normalized}`;
|
|
45
|
+
slugToDocMap.set(key, {
|
|
46
|
+
_id: doc._id,
|
|
47
|
+
_createdAt: doc._createdAt,
|
|
48
|
+
contentType: urlConfig.contentType,
|
|
49
|
+
});
|
|
50
|
+
allSlugs.push(normalized);
|
|
51
|
+
}
|
|
26
52
|
}
|
|
27
53
|
}
|
|
28
|
-
return gscUrls.map((url) => this.matchSingleUrl(url,
|
|
54
|
+
return gscUrls.map((url) => this.matchSingleUrl(url, slugToDocMap, allSlugs));
|
|
29
55
|
}
|
|
30
56
|
/**
|
|
31
57
|
* Get all available slugs from Sanity for diagnostic purposes
|
|
32
58
|
*/
|
|
33
59
|
async getAvailableSlugs() {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
const allSlugs = [];
|
|
61
|
+
for (const urlConfig of this.normalizedConfigs) {
|
|
62
|
+
const query = `*[_type == $type]{
|
|
63
|
+
"${urlConfig.slugField}": ${urlConfig.slugField}.current
|
|
64
|
+
}`;
|
|
65
|
+
const documents = await this.sanityClient.fetch(query, {
|
|
66
|
+
type: urlConfig.contentType,
|
|
67
|
+
});
|
|
68
|
+
const slugs = documents
|
|
69
|
+
.map((doc) => doc[urlConfig.slugField])
|
|
70
|
+
.filter((slug) => !!slug)
|
|
71
|
+
.map((slug) => this.normalizeSlug(slug));
|
|
72
|
+
allSlugs.push(...slugs);
|
|
73
|
+
}
|
|
74
|
+
return allSlugs;
|
|
44
75
|
}
|
|
45
|
-
matchSingleUrl(gscUrl,
|
|
76
|
+
matchSingleUrl(gscUrl, slugToDocMap, allSlugs) {
|
|
46
77
|
const normalized = this.normalizeUrl(gscUrl);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const withTrailing = slug + "/";
|
|
104
|
-
const addedTrailingMatch = slugToDoc.get(withTrailing);
|
|
105
|
-
if (addedTrailingMatch) {
|
|
106
|
-
return {
|
|
107
|
-
gscUrl,
|
|
108
|
-
sanityId: addedTrailingMatch._id,
|
|
109
|
-
confidence: "normalized",
|
|
110
|
-
matchedSlug: withTrailing,
|
|
111
|
-
unmatchReason: "matched",
|
|
112
|
-
extractedSlug: slug,
|
|
113
|
-
};
|
|
78
|
+
// Try to match against each content type's path prefix
|
|
79
|
+
for (const urlConfig of this.normalizedConfigs) {
|
|
80
|
+
const extractionResult = this.extractSlugWithDiagnostics(normalized, urlConfig.pathPrefix);
|
|
81
|
+
if (extractionResult.outsidePrefix) {
|
|
82
|
+
continue; // Try next content type
|
|
83
|
+
}
|
|
84
|
+
const slug = extractionResult.slug;
|
|
85
|
+
if (!slug) {
|
|
86
|
+
continue; // Try next content type
|
|
87
|
+
}
|
|
88
|
+
// Try exact match
|
|
89
|
+
const key = `${urlConfig.contentType}:${slug}`;
|
|
90
|
+
const exactMatch = slugToDocMap.get(key);
|
|
91
|
+
if (exactMatch) {
|
|
92
|
+
return {
|
|
93
|
+
gscUrl,
|
|
94
|
+
sanityId: exactMatch._id,
|
|
95
|
+
confidence: "exact",
|
|
96
|
+
matchedSlug: slug,
|
|
97
|
+
matchedContentType: urlConfig.contentType,
|
|
98
|
+
unmatchReason: "matched",
|
|
99
|
+
extractedSlug: slug,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Try without trailing slash
|
|
103
|
+
const withoutTrailing = slug.replace(/\/$/, "");
|
|
104
|
+
const keyWithoutTrailing = `${urlConfig.contentType}:${withoutTrailing}`;
|
|
105
|
+
const trailingMatch = slugToDocMap.get(keyWithoutTrailing);
|
|
106
|
+
if (trailingMatch) {
|
|
107
|
+
return {
|
|
108
|
+
gscUrl,
|
|
109
|
+
sanityId: trailingMatch._id,
|
|
110
|
+
confidence: "normalized",
|
|
111
|
+
matchedSlug: withoutTrailing,
|
|
112
|
+
matchedContentType: urlConfig.contentType,
|
|
113
|
+
unmatchReason: "matched",
|
|
114
|
+
extractedSlug: slug,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Try with trailing slash
|
|
118
|
+
const withTrailing = slug + "/";
|
|
119
|
+
const keyWithTrailing = `${urlConfig.contentType}:${withTrailing}`;
|
|
120
|
+
const addedTrailingMatch = slugToDocMap.get(keyWithTrailing);
|
|
121
|
+
if (addedTrailingMatch) {
|
|
122
|
+
return {
|
|
123
|
+
gscUrl,
|
|
124
|
+
sanityId: addedTrailingMatch._id,
|
|
125
|
+
confidence: "normalized",
|
|
126
|
+
matchedSlug: withTrailing,
|
|
127
|
+
matchedContentType: urlConfig.contentType,
|
|
128
|
+
unmatchReason: "matched",
|
|
129
|
+
extractedSlug: slug,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
114
132
|
}
|
|
115
|
-
// No match found
|
|
116
|
-
const
|
|
133
|
+
// No match found across any content type - return diagnostic info
|
|
134
|
+
const firstConfig = this.normalizedConfigs[0];
|
|
135
|
+
const firstExtractionResult = this.extractSlugWithDiagnostics(normalized, firstConfig?.pathPrefix);
|
|
136
|
+
const similarSlugs = this.findSimilarSlugs(firstExtractionResult.slug || "", allSlugs, 3);
|
|
117
137
|
return {
|
|
118
138
|
gscUrl,
|
|
119
139
|
sanityId: undefined,
|
|
120
140
|
confidence: "none",
|
|
121
|
-
unmatchReason:
|
|
122
|
-
|
|
141
|
+
unmatchReason: firstExtractionResult.outsidePrefix
|
|
142
|
+
? "outside_path_prefix"
|
|
143
|
+
: "no_matching_document",
|
|
144
|
+
extractedSlug: firstExtractionResult.slug,
|
|
123
145
|
diagnostics: {
|
|
124
146
|
normalizedUrl: normalized,
|
|
125
|
-
pathAfterPrefix:
|
|
126
|
-
configuredPrefix:
|
|
127
|
-
availableSlugsCount:
|
|
147
|
+
pathAfterPrefix: firstExtractionResult.pathAfterPrefix,
|
|
148
|
+
configuredPrefix: firstConfig?.pathPrefix ?? null,
|
|
149
|
+
availableSlugsCount: slugToDocMap.size,
|
|
128
150
|
similarSlugs,
|
|
129
151
|
},
|
|
130
152
|
};
|
|
@@ -141,16 +163,13 @@ export class URLMatcher {
|
|
|
141
163
|
return url.toLowerCase();
|
|
142
164
|
}
|
|
143
165
|
}
|
|
144
|
-
|
|
145
|
-
return this.extractSlugWithDiagnostics(normalizedUrl).slug;
|
|
146
|
-
}
|
|
147
|
-
extractSlugWithDiagnostics(normalizedUrl) {
|
|
166
|
+
extractSlugWithDiagnostics(normalizedUrl, pathPrefix) {
|
|
148
167
|
try {
|
|
149
168
|
const parsed = new URL(normalizedUrl);
|
|
150
169
|
let path = parsed.pathname;
|
|
151
170
|
// Check if the URL is outside the configured path prefix
|
|
152
|
-
if (
|
|
153
|
-
const prefixRegex = new RegExp(`^${this.escapeRegex(
|
|
171
|
+
if (pathPrefix) {
|
|
172
|
+
const prefixRegex = new RegExp(`^${this.escapeRegex(pathPrefix)}(/|$)`);
|
|
154
173
|
if (!prefixRegex.test(path)) {
|
|
155
174
|
return {
|
|
156
175
|
slug: undefined,
|
|
@@ -158,7 +177,7 @@ export class URLMatcher {
|
|
|
158
177
|
outsidePrefix: true,
|
|
159
178
|
};
|
|
160
179
|
}
|
|
161
|
-
path = path.replace(new RegExp(`^${this.escapeRegex(
|
|
180
|
+
path = path.replace(new RegExp(`^${this.escapeRegex(pathPrefix)}`), "");
|
|
162
181
|
}
|
|
163
182
|
const slug = path.replace(/^\/+|\/+$/g, "");
|
|
164
183
|
return {
|