@growth-labs/seo 0.1.5 → 0.2.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/README.md +142 -54
- package/dist/bindings.d.ts +127 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +11 -0
- package/dist/bindings.js.map +1 -0
- package/dist/cron/prune-aeo-r2.d.ts +36 -0
- package/dist/cron/prune-aeo-r2.d.ts.map +1 -0
- package/dist/cron/prune-aeo-r2.js +94 -0
- package/dist/cron/prune-aeo-r2.js.map +1 -0
- package/dist/durable-objects/aeo-revalidation-coord.d.ts +69 -0
- package/dist/durable-objects/aeo-revalidation-coord.d.ts.map +1 -0
- package/dist/durable-objects/aeo-revalidation-coord.js +177 -0
- package/dist/durable-objects/aeo-revalidation-coord.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -14
- package/dist/index.js.map +1 -1
- package/dist/middleware/seo.d.ts +44 -4
- package/dist/middleware/seo.d.ts.map +1 -1
- package/dist/middleware/seo.js +237 -41
- package/dist/middleware/seo.js.map +1 -1
- package/dist/options.d.ts +1293 -6
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +238 -1
- package/dist/options.js.map +1 -1
- package/dist/routes/apple-news.d.ts +4 -0
- package/dist/routes/apple-news.d.ts.map +1 -0
- package/dist/routes/apple-news.js +28 -0
- package/dist/routes/apple-news.js.map +1 -0
- package/dist/routes/llms-full.d.ts +4 -0
- package/dist/routes/llms-full.d.ts.map +1 -0
- package/dist/routes/llms-full.js +29 -0
- package/dist/routes/llms-full.js.map +1 -0
- package/dist/routes/revalidate.d.ts +16 -0
- package/dist/routes/revalidate.d.ts.map +1 -0
- package/dist/routes/revalidate.js +243 -0
- package/dist/routes/revalidate.js.map +1 -0
- package/dist/routes/rss.d.ts.map +1 -1
- package/dist/routes/rss.js +4 -1
- package/dist/routes/rss.js.map +1 -1
- package/dist/routes/sitemap-markdown.d.ts +4 -0
- package/dist/routes/sitemap-markdown.d.ts.map +1 -0
- package/dist/routes/sitemap-markdown.js +32 -0
- package/dist/routes/sitemap-markdown.js.map +1 -0
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/aeo-summary.d.ts +35 -0
- package/dist/utils/aeo-summary.d.ts.map +1 -0
- package/dist/utils/aeo-summary.js +141 -0
- package/dist/utils/aeo-summary.js.map +1 -0
- package/dist/utils/aeo-twin-emitter.d.ts +79 -0
- package/dist/utils/aeo-twin-emitter.d.ts.map +1 -0
- package/dist/utils/aeo-twin-emitter.js +99 -0
- package/dist/utils/aeo-twin-emitter.js.map +1 -0
- package/dist/utils/aeo.d.ts +62 -12
- package/dist/utils/aeo.d.ts.map +1 -1
- package/dist/utils/aeo.js +187 -26
- package/dist/utils/aeo.js.map +1 -1
- package/dist/utils/apple-news-anf.d.ts +38 -0
- package/dist/utils/apple-news-anf.d.ts.map +1 -0
- package/dist/utils/apple-news-anf.js +120 -0
- package/dist/utils/apple-news-anf.js.map +1 -0
- package/dist/utils/apple-news-rss.d.ts +31 -0
- package/dist/utils/apple-news-rss.d.ts.map +1 -0
- package/dist/utils/apple-news-rss.js +103 -0
- package/dist/utils/apple-news-rss.js.map +1 -0
- package/dist/utils/content-filter.d.ts +52 -0
- package/dist/utils/content-filter.d.ts.map +1 -0
- package/dist/utils/content-filter.js +75 -0
- package/dist/utils/content-filter.js.map +1 -0
- package/dist/utils/crawler-class.d.ts +39 -0
- package/dist/utils/crawler-class.d.ts.map +1 -0
- package/dist/utils/crawler-class.js +127 -0
- package/dist/utils/crawler-class.js.map +1 -0
- package/dist/utils/effective-auth.d.ts +28 -0
- package/dist/utils/effective-auth.d.ts.map +1 -0
- package/dist/utils/effective-auth.js +33 -0
- package/dist/utils/effective-auth.js.map +1 -0
- package/dist/utils/fcrdns.d.ts +73 -0
- package/dist/utils/fcrdns.d.ts.map +1 -0
- package/dist/utils/fcrdns.js +219 -0
- package/dist/utils/fcrdns.js.map +1 -0
- package/dist/utils/fresh-layer.d.ts +53 -0
- package/dist/utils/fresh-layer.d.ts.map +1 -0
- package/dist/utils/fresh-layer.js +147 -0
- package/dist/utils/fresh-layer.js.map +1 -0
- package/dist/utils/index.d.ts +14 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +14 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/json-ld/article.d.ts +13 -1
- package/dist/utils/json-ld/article.d.ts.map +1 -1
- package/dist/utils/json-ld/article.js +37 -8
- package/dist/utils/json-ld/article.js.map +1 -1
- package/dist/utils/llms-full.d.ts +29 -0
- package/dist/utils/llms-full.d.ts.map +1 -0
- package/dist/utils/llms-full.js +67 -0
- package/dist/utils/llms-full.js.map +1 -0
- package/dist/utils/meta.d.ts +4 -1
- package/dist/utils/meta.d.ts.map +1 -1
- package/dist/utils/meta.js +25 -2
- package/dist/utils/meta.js.map +1 -1
- package/dist/utils/sitemap-markdown.d.ts +24 -0
- package/dist/utils/sitemap-markdown.d.ts.map +1 -0
- package/dist/utils/sitemap-markdown.js +57 -0
- package/dist/utils/sitemap-markdown.js.map +1 -0
- package/dist/utils/staleness.d.ts +27 -0
- package/dist/utils/staleness.d.ts.map +1 -0
- package/dist/utils/staleness.js +46 -0
- package/dist/utils/staleness.js.map +1 -0
- package/dist/utils/validation.d.ts +41 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +78 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +13 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ContentItem } from '../types.js';
|
|
2
|
+
export type RenderBody = (item: ContentItem) => string | Promise<string>;
|
|
3
|
+
export interface EmitAeoTwinsOptions {
|
|
4
|
+
items: ContentItem[];
|
|
5
|
+
publisherName: string;
|
|
6
|
+
schemaType: string;
|
|
7
|
+
/** Resolver that returns the article body in markdown. Required; the emitter
|
|
8
|
+
* can't synthesize content. Consumers typically produce this from their CMS. */
|
|
9
|
+
renderBody: RenderBody;
|
|
10
|
+
/** Map item.url → primary twin URL. Default: append '.md'. */
|
|
11
|
+
twinUrl?: (articleUrl: string) => string;
|
|
12
|
+
/** Predicate applied after the default members filter. Default: always true. */
|
|
13
|
+
include?: (item: ContentItem) => boolean;
|
|
14
|
+
/** Emit a summary twin alongside the primary. Default true. */
|
|
15
|
+
summaryTwin?: boolean;
|
|
16
|
+
/** Wrap semantic sections in <!-- aeo:section --> markers. Default true. */
|
|
17
|
+
ragChunkMarkers?: boolean;
|
|
18
|
+
/** Stale-hash metadata mode. */
|
|
19
|
+
stalenessCheck?: 'content-hash' | 'dateModified' | 'none';
|
|
20
|
+
/** Called when the summary generator falls back to tier 4. Used for build telemetry. */
|
|
21
|
+
onSummaryMinimalFallback?: (item: ContentItem) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface EmittedTwin {
|
|
24
|
+
/** URL path this file will be served at (used by the writer to derive the filesystem path). */
|
|
25
|
+
urlPath: string;
|
|
26
|
+
/** Full URL (for frontmatter). */
|
|
27
|
+
url: string;
|
|
28
|
+
/** File body. */
|
|
29
|
+
content: string;
|
|
30
|
+
/** Content-type for HTTP response headers when this file is served. */
|
|
31
|
+
contentType: string;
|
|
32
|
+
/** If this emission is the primary twin for an item, the item is populated. */
|
|
33
|
+
item?: ContentItem;
|
|
34
|
+
/** If this emission is a summary twin, the corresponding primary URL. */
|
|
35
|
+
primaryUrl?: string;
|
|
36
|
+
/** Primary twins carry a stable content hash for staleness validation; absent on summaries. */
|
|
37
|
+
contentHash?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface EmitAeoTwinsResult {
|
|
40
|
+
twins: EmittedTwin[];
|
|
41
|
+
/** Map of item.url → contentHash. Persist to disk for staleness validation. */
|
|
42
|
+
contentHashes: Map<string, string>;
|
|
43
|
+
/** Number of items filtered out (either by access rule or consumer predicate). */
|
|
44
|
+
skipped: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Compute all twin files for a given contentProvider output, without touching
|
|
48
|
+
* the filesystem. Callers (index.ts at astro:build:done time) write the returned
|
|
49
|
+
* twins to disk and record content hashes for later staleness validation.
|
|
50
|
+
*
|
|
51
|
+
* Filtering:
|
|
52
|
+
* - Members items excluded unconditionally (via forMarkdownTwin).
|
|
53
|
+
* - Consumer-supplied `include` predicate applied after the access filter.
|
|
54
|
+
*
|
|
55
|
+
* For each surviving item we emit:
|
|
56
|
+
* - Primary twin at twinUrl(item.url).
|
|
57
|
+
* - Summary twin at <primary>.summary.md (when summaryTwin: true).
|
|
58
|
+
*
|
|
59
|
+
* Aliases (spec twinAliases) are NOT emitted as static files in v7 — they're
|
|
60
|
+
* middleware-only redirects. Static-mode twins live at the primary URL only.
|
|
61
|
+
*/
|
|
62
|
+
export declare function emitAeoTwins(options: EmitAeoTwinsOptions): Promise<EmitAeoTwinsResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Default twinUrl: strip trailing slashes and append '.md'.
|
|
65
|
+
* `/article/midway/` -> `/article/midway.md`
|
|
66
|
+
* `/article/midway` -> `/article/midway.md`
|
|
67
|
+
*/
|
|
68
|
+
declare function defaultTwinUrl(articleUrl: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Extract the URL-path portion of an absolute or relative URL.
|
|
71
|
+
* Used by the caller to derive the filesystem write path under dist/client/.
|
|
72
|
+
*/
|
|
73
|
+
declare function urlPath(url: string): string;
|
|
74
|
+
export declare const _internals: {
|
|
75
|
+
defaultTwinUrl: typeof defaultTwinUrl;
|
|
76
|
+
urlPath: typeof urlPath;
|
|
77
|
+
};
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=aeo-twin-emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aeo-twin-emitter.d.ts","sourceRoot":"","sources":["../../src/utils/aeo-twin-emitter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAQ9C,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AAExE,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB;qFACiF;IACjF,UAAU,EAAE,UAAU,CAAA;IACtB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAA;IACxC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAA;IACxC,+DAA+D;IAC/D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,gCAAgC;IAChC,cAAc,CAAC,EAAE,cAAc,GAAG,cAAc,GAAG,MAAM,CAAA;IACzD,wFAAwF;IACxF,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAA;CACtD;AAED,MAAM,WAAW,WAAW;IAC3B,+FAA+F;IAC/F,OAAO,EAAE,MAAM,CAAA;IACf,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAA;IACX,iBAAiB;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+FAA+F;IAC/F,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,+EAA+E;IAC/E,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuE5F;AAID;;;;GAIG;AACH,iBAAS,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,iBAAS,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOpC;AAED,eAAO,MAAM,UAAU;;;CAGtB,CAAA"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { generateAeoMarkdown } from './aeo.js';
|
|
2
|
+
import { generateSummaryTwin } from './aeo-summary.js';
|
|
3
|
+
import { forMarkdownTwin } from './content-filter.js';
|
|
4
|
+
import { computeContentHash } from './staleness.js';
|
|
5
|
+
/**
|
|
6
|
+
* Compute all twin files for a given contentProvider output, without touching
|
|
7
|
+
* the filesystem. Callers (index.ts at astro:build:done time) write the returned
|
|
8
|
+
* twins to disk and record content hashes for later staleness validation.
|
|
9
|
+
*
|
|
10
|
+
* Filtering:
|
|
11
|
+
* - Members items excluded unconditionally (via forMarkdownTwin).
|
|
12
|
+
* - Consumer-supplied `include` predicate applied after the access filter.
|
|
13
|
+
*
|
|
14
|
+
* For each surviving item we emit:
|
|
15
|
+
* - Primary twin at twinUrl(item.url).
|
|
16
|
+
* - Summary twin at <primary>.summary.md (when summaryTwin: true).
|
|
17
|
+
*
|
|
18
|
+
* Aliases (spec twinAliases) are NOT emitted as static files in v7 — they're
|
|
19
|
+
* middleware-only redirects. Static-mode twins live at the primary URL only.
|
|
20
|
+
*/
|
|
21
|
+
export async function emitAeoTwins(options) {
|
|
22
|
+
const { items, publisherName, schemaType, renderBody, twinUrl = defaultTwinUrl, include = () => true, summaryTwin = true, ragChunkMarkers = true, stalenessCheck = 'content-hash', onSummaryMinimalFallback, } = options;
|
|
23
|
+
const filtered = forMarkdownTwin(items).filter(include);
|
|
24
|
+
const skipped = items.length - filtered.length;
|
|
25
|
+
const twins = [];
|
|
26
|
+
const contentHashes = new Map();
|
|
27
|
+
for (const item of filtered) {
|
|
28
|
+
const primaryUrl = twinUrl(item.url);
|
|
29
|
+
const primaryUrlPath = urlPath(primaryUrl);
|
|
30
|
+
const body = await renderBody(item);
|
|
31
|
+
const contentHash = stalenessCheck === 'content-hash' ? await computeContentHash(item, body) : undefined;
|
|
32
|
+
if (contentHash)
|
|
33
|
+
contentHashes.set(item.url, contentHash);
|
|
34
|
+
const summaryUrl = summaryTwin ? `${primaryUrl}.summary.md` : undefined;
|
|
35
|
+
const aeoOpts = {
|
|
36
|
+
publisherName,
|
|
37
|
+
schemaType,
|
|
38
|
+
content: body,
|
|
39
|
+
ragChunkMarkers,
|
|
40
|
+
canonical: item.url,
|
|
41
|
+
twinUrl: primaryUrl,
|
|
42
|
+
summaryUrl,
|
|
43
|
+
contentHash,
|
|
44
|
+
};
|
|
45
|
+
const primaryContent = generateAeoMarkdown(item, aeoOpts);
|
|
46
|
+
twins.push({
|
|
47
|
+
urlPath: primaryUrlPath,
|
|
48
|
+
url: primaryUrl,
|
|
49
|
+
content: primaryContent,
|
|
50
|
+
contentType: 'text/markdown; charset=utf-8',
|
|
51
|
+
item,
|
|
52
|
+
contentHash,
|
|
53
|
+
});
|
|
54
|
+
if (summaryTwin && summaryUrl) {
|
|
55
|
+
const summary = generateSummaryTwin(item, {
|
|
56
|
+
publisherName,
|
|
57
|
+
schemaType,
|
|
58
|
+
content: body,
|
|
59
|
+
fullUrl: primaryUrl,
|
|
60
|
+
onMinimalFallback: onSummaryMinimalFallback,
|
|
61
|
+
});
|
|
62
|
+
twins.push({
|
|
63
|
+
urlPath: urlPath(summaryUrl),
|
|
64
|
+
url: summaryUrl,
|
|
65
|
+
content: summary.markdown,
|
|
66
|
+
contentType: 'text/markdown; charset=utf-8',
|
|
67
|
+
primaryUrl,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { twins, contentHashes, skipped };
|
|
72
|
+
}
|
|
73
|
+
// ─── Defaults ───
|
|
74
|
+
/**
|
|
75
|
+
* Default twinUrl: strip trailing slashes and append '.md'.
|
|
76
|
+
* `/article/midway/` -> `/article/midway.md`
|
|
77
|
+
* `/article/midway` -> `/article/midway.md`
|
|
78
|
+
*/
|
|
79
|
+
function defaultTwinUrl(articleUrl) {
|
|
80
|
+
return `${articleUrl.replace(/\/+$/, '')}.md`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Extract the URL-path portion of an absolute or relative URL.
|
|
84
|
+
* Used by the caller to derive the filesystem write path under dist/client/.
|
|
85
|
+
*/
|
|
86
|
+
function urlPath(url) {
|
|
87
|
+
try {
|
|
88
|
+
return new URL(url).pathname;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Relative URL — assume it's already a path.
|
|
92
|
+
return url.startsWith('/') ? url : `/${url}`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export const _internals = {
|
|
96
|
+
defaultTwinUrl,
|
|
97
|
+
urlPath,
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=aeo-twin-emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aeo-twin-emitter.js","sourceRoot":"","sources":["../../src/utils/aeo-twin-emitter.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/aeo.d.ts
CHANGED
|
@@ -1,16 +1,66 @@
|
|
|
1
|
+
import type { ContentItem } from '../types.js';
|
|
2
|
+
export interface GenerateAeoMarkdownOptions {
|
|
3
|
+
/** Publisher organization name for the frontmatter `publisher` field. */
|
|
4
|
+
publisherName: string;
|
|
5
|
+
/** Schema.org type (e.g. 'Article', 'NewsArticle'). */
|
|
6
|
+
schemaType: string;
|
|
7
|
+
/** Rendered article body in clean markdown (no HTML chrome). */
|
|
8
|
+
content: string;
|
|
9
|
+
/** If true, wraps semantic sections in `<!-- aeo:section start/end -->` comments. */
|
|
10
|
+
ragChunkMarkers?: boolean;
|
|
11
|
+
/** Canonical HTML URL to reference from the twin frontmatter. Defaults to item.url. */
|
|
12
|
+
canonical?: string;
|
|
13
|
+
/** The primary twin URL (what this file will be served at). Stored in frontmatter as `url`. */
|
|
14
|
+
twinUrl?: string;
|
|
15
|
+
/** Optional summary-twin URL to cross-link from the full twin frontmatter. */
|
|
16
|
+
summaryUrl?: string;
|
|
17
|
+
/** Pre-computed SHA-256 content hash for staleness validation. */
|
|
18
|
+
contentHash?: string;
|
|
19
|
+
}
|
|
1
20
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
21
|
+
* Cheap token count estimate: 1 token ≈ 4 characters. Used for the `x-markdown-tokens`
|
|
22
|
+
* response header and the `tokens` frontmatter field.
|
|
4
23
|
*/
|
|
5
24
|
export declare function estimateTokenCount(text: string): number;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Generate an AEO markdown twin for a ContentItem.
|
|
27
|
+
*
|
|
28
|
+
* Produces: frontmatter (YAML) + body. When `ragChunkMarkers: true`, inserts
|
|
29
|
+
* `<!-- aeo:section start="<slug>" -->` / `<!-- aeo:section end="<slug>" -->` pairs
|
|
30
|
+
* around semantic sections (identified by `## ` headings) to guide RAG retrievers
|
|
31
|
+
* toward boundaries we control.
|
|
32
|
+
*
|
|
33
|
+
* Sanitization:
|
|
34
|
+
* - Slugs are forced to `[a-z0-9-]{1,64}`; empty results fall back to `section-N`.
|
|
35
|
+
* - Any author-supplied `<!--` / `-->` in the body is escaped (`<!--` / `-->`)
|
|
36
|
+
* before wrapping, so forged markers can't close real ones.
|
|
37
|
+
*/
|
|
38
|
+
export declare function generateAeoMarkdown(item: ContentItem, options: GenerateAeoMarkdownOptions): string;
|
|
39
|
+
/**
|
|
40
|
+
* Emit a value safely for YAML. Unquoted when possible; quoted (with escapes) when
|
|
41
|
+
* the value would otherwise parse ambiguously.
|
|
42
|
+
*/
|
|
43
|
+
declare function yamlValue(v: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Wrap each `##` section in `<!-- aeo:section start="..." -->` / `<!-- end -->`.
|
|
46
|
+
* Content before the first `##` is wrapped as `lede`; content after the last `##`
|
|
47
|
+
* terminates with an end marker for that section.
|
|
48
|
+
*/
|
|
49
|
+
declare function wrapSectionMarkers(body: string): string;
|
|
50
|
+
/** Strict slug sanitization — [a-z0-9-]{1,64}, no leading/trailing/repeated dashes. */
|
|
51
|
+
declare function sanitizeSlug(text: string): string;
|
|
52
|
+
declare function uniqueSlug(base: string, used: Set<string>): string;
|
|
53
|
+
/**
|
|
54
|
+
* Escape any author-supplied `<!--` / `-->` in the body so forged markers can't
|
|
55
|
+
* close real ones. Applied BEFORE wrapping (spec "Marker sanitization").
|
|
56
|
+
*/
|
|
57
|
+
declare function escapeHtmlComments(body: string): string;
|
|
58
|
+
export declare const _internals: {
|
|
59
|
+
sanitizeSlug: typeof sanitizeSlug;
|
|
60
|
+
uniqueSlug: typeof uniqueSlug;
|
|
61
|
+
escapeHtmlComments: typeof escapeHtmlComments;
|
|
62
|
+
wrapSectionMarkers: typeof wrapSectionMarkers;
|
|
63
|
+
yamlValue: typeof yamlValue;
|
|
64
|
+
};
|
|
65
|
+
export {};
|
|
16
66
|
//# sourceMappingURL=aeo.d.ts.map
|
package/dist/utils/aeo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aeo.d.ts","sourceRoot":"","sources":["../../src/utils/aeo.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"aeo.d.ts","sourceRoot":"","sources":["../../src/utils/aeo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAI9C,MAAM,WAAW,0BAA0B;IAC1C,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAA;IACrB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,0BAA0B,GACjC,MAAM,CAKR;AAqED;;;GAGG;AACH,iBAAS,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAUpC;AAID;;;;GAIG;AACH,iBAAS,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA0ChD;AAED,uFAAuF;AACvF,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ1C;AAED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAK3D;AAED;;;GAGG;AACH,iBAAS,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAID,eAAO,MAAM,UAAU;;;;;;CAMtB,CAAA"}
|
package/dist/utils/aeo.js
CHANGED
|
@@ -1,38 +1,199 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Cheap token count estimate: 1 token ≈ 4 characters. Used for the `x-markdown-tokens`
|
|
3
|
+
* response header and the `tokens` frontmatter field.
|
|
4
4
|
*/
|
|
5
5
|
export function estimateTokenCount(text) {
|
|
6
6
|
return Math.ceil(text.length / 4);
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Generate an AEO markdown twin for a ContentItem.
|
|
10
|
+
*
|
|
11
|
+
* Produces: frontmatter (YAML) + body. When `ragChunkMarkers: true`, inserts
|
|
12
|
+
* `<!-- aeo:section start="<slug>" -->` / `<!-- aeo:section end="<slug>" -->` pairs
|
|
13
|
+
* around semantic sections (identified by `## ` headings) to guide RAG retrievers
|
|
14
|
+
* toward boundaries we control.
|
|
15
|
+
*
|
|
16
|
+
* Sanitization:
|
|
17
|
+
* - Slugs are forced to `[a-z0-9-]{1,64}`; empty results fall back to `section-N`.
|
|
18
|
+
* - Any author-supplied `<!--` / `-->` in the body is escaped (`<!--` / `-->`)
|
|
19
|
+
* before wrapping, so forged markers can't close real ones.
|
|
20
|
+
*/
|
|
21
|
+
export function generateAeoMarkdown(item, options) {
|
|
22
|
+
const frontmatter = buildFrontmatter(item, options);
|
|
23
|
+
const safeBody = escapeHtmlComments(options.content);
|
|
24
|
+
const body = options.ragChunkMarkers ? wrapSectionMarkers(safeBody) : safeBody;
|
|
25
|
+
return `---\n${frontmatter}\n---\n\n${body}`;
|
|
26
|
+
}
|
|
27
|
+
// ─── Frontmatter ───
|
|
28
|
+
function buildFrontmatter(item, options) {
|
|
29
|
+
const canonical = options.canonical ?? item.url;
|
|
30
|
+
const twinUrl = options.twinUrl ?? item.url;
|
|
31
|
+
const firstImage = Array.isArray(item.image) ? item.image[0] : item.image;
|
|
32
|
+
const authorEntries = (item.authors ?? []).map((a) => {
|
|
33
|
+
const o = { name: a.name };
|
|
34
|
+
if (a.url)
|
|
35
|
+
o.url = a.url;
|
|
36
|
+
if (a.jobTitle)
|
|
37
|
+
o.jobTitle = a.jobTitle;
|
|
38
|
+
if (a.knowsAbout?.length)
|
|
39
|
+
o.knowsAbout = a.knowsAbout;
|
|
40
|
+
if (a.sameAs?.length)
|
|
41
|
+
o.sameAs = a.sameAs;
|
|
42
|
+
return o;
|
|
43
|
+
});
|
|
44
|
+
const alternateLanguages = (item.alternateLocales ?? []).map((l) => ({
|
|
45
|
+
lang: l.lang,
|
|
46
|
+
url: l.url,
|
|
47
|
+
}));
|
|
48
|
+
const lines = [];
|
|
49
|
+
lines.push(yamlScalar('title', item.title));
|
|
15
50
|
if (item.description)
|
|
16
|
-
|
|
51
|
+
lines.push(yamlScalar('description', item.description));
|
|
52
|
+
lines.push(yamlScalar('url', twinUrl));
|
|
53
|
+
lines.push(yamlScalar('canonical', canonical));
|
|
17
54
|
if (item.datePublished)
|
|
18
|
-
|
|
55
|
+
lines.push(yamlScalar('datePublished', item.datePublished));
|
|
19
56
|
if (item.dateModified)
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
57
|
+
lines.push(yamlScalar('dateModified', item.dateModified));
|
|
58
|
+
if (authorEntries.length) {
|
|
59
|
+
lines.push('author:');
|
|
60
|
+
for (const a of authorEntries) {
|
|
61
|
+
lines.push(` - name: ${yamlValue(a.name)}`);
|
|
62
|
+
if (a.url)
|
|
63
|
+
lines.push(` url: ${yamlValue(a.url)}`);
|
|
64
|
+
if (a.jobTitle)
|
|
65
|
+
lines.push(` jobTitle: ${yamlValue(a.jobTitle)}`);
|
|
66
|
+
if (a.knowsAbout) {
|
|
67
|
+
lines.push(' knowsAbout:');
|
|
68
|
+
for (const k of a.knowsAbout)
|
|
69
|
+
lines.push(` - ${yamlValue(k)}`);
|
|
70
|
+
}
|
|
71
|
+
if (a.sameAs) {
|
|
72
|
+
lines.push(' sameAs:');
|
|
73
|
+
for (const s of a.sameAs)
|
|
74
|
+
lines.push(` - ${yamlValue(s)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
23
77
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
78
|
+
lines.push(yamlScalar('publisher', options.publisherName));
|
|
79
|
+
if (firstImage)
|
|
80
|
+
lines.push(yamlScalar('image', firstImage));
|
|
81
|
+
lines.push(yamlScalar('type', options.schemaType));
|
|
82
|
+
if (item.locale)
|
|
83
|
+
lines.push(yamlScalar('language', item.locale));
|
|
84
|
+
if (item.audio)
|
|
85
|
+
lines.push(yamlScalar('audio', item.audio.url));
|
|
86
|
+
if (alternateLanguages.length) {
|
|
87
|
+
lines.push('alternateLanguages:');
|
|
88
|
+
for (const l of alternateLanguages) {
|
|
89
|
+
lines.push(` - lang: ${yamlValue(l.lang)}`);
|
|
90
|
+
lines.push(` url: ${yamlValue(l.url)}`);
|
|
27
91
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
92
|
+
}
|
|
93
|
+
if (options.contentHash)
|
|
94
|
+
lines.push(yamlScalar('contentHash', options.contentHash));
|
|
95
|
+
const tokens = estimateTokenCount(options.content);
|
|
96
|
+
lines.push(`tokens: ${tokens}`);
|
|
97
|
+
if (options.summaryUrl)
|
|
98
|
+
lines.push(yamlScalar('summaryUrl', options.summaryUrl));
|
|
99
|
+
return lines.join('\n');
|
|
100
|
+
}
|
|
101
|
+
function yamlScalar(key, value) {
|
|
102
|
+
return `${key}: ${yamlValue(value)}`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Emit a value safely for YAML. Unquoted when possible; quoted (with escapes) when
|
|
106
|
+
* the value would otherwise parse ambiguously.
|
|
107
|
+
*/
|
|
108
|
+
function yamlValue(v) {
|
|
109
|
+
// Whitelisted forms that can be emitted unquoted even though they contain `:`.
|
|
110
|
+
const isUrl = /^https?:\/\//.test(v);
|
|
111
|
+
const isDate = /^\d{4}-\d{2}-\d{2}/.test(v);
|
|
112
|
+
// Always quote if contains chars with YAML special meaning (excluding `:` which
|
|
113
|
+
// is handled separately below), or if starts with a character that could be misread.
|
|
114
|
+
const needsQuotes = /^[\s-]/.test(v) || /[#{}[\],&*?|>!%@`'"\n\r]/.test(v) || (v.includes(':') && !isUrl && !isDate);
|
|
115
|
+
if (!needsQuotes)
|
|
116
|
+
return v;
|
|
117
|
+
return `"${v.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
|
|
118
|
+
}
|
|
119
|
+
// ─── RAG chunk markers ───
|
|
120
|
+
/**
|
|
121
|
+
* Wrap each `##` section in `<!-- aeo:section start="..." -->` / `<!-- end -->`.
|
|
122
|
+
* Content before the first `##` is wrapped as `lede`; content after the last `##`
|
|
123
|
+
* terminates with an end marker for that section.
|
|
124
|
+
*/
|
|
125
|
+
function wrapSectionMarkers(body) {
|
|
126
|
+
const lines = body.split('\n');
|
|
127
|
+
const out = [];
|
|
128
|
+
let currentSlug = null;
|
|
129
|
+
const usedSlugs = new Set();
|
|
130
|
+
let ordinal = 0;
|
|
131
|
+
let sawContent = false;
|
|
132
|
+
// Open with `lede` if we hit body content before any heading.
|
|
133
|
+
const openSection = (slug) => {
|
|
134
|
+
out.push(`<!-- aeo:section start="${slug}" -->`);
|
|
135
|
+
currentSlug = slug;
|
|
136
|
+
};
|
|
137
|
+
const closeSection = () => {
|
|
138
|
+
if (currentSlug) {
|
|
139
|
+
out.push(`<!-- aeo:section end="${currentSlug}" -->`);
|
|
140
|
+
currentSlug = null;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
const headingMatch = /^##\s+(.+)$/.exec(line);
|
|
145
|
+
if (headingMatch) {
|
|
146
|
+
closeSection();
|
|
147
|
+
ordinal++;
|
|
148
|
+
const slug = sanitizeSlug(headingMatch[1]) || `section-${ordinal}`;
|
|
149
|
+
const unique = uniqueSlug(slug, usedSlugs);
|
|
150
|
+
usedSlugs.add(unique);
|
|
151
|
+
openSection(unique);
|
|
152
|
+
out.push(line);
|
|
153
|
+
sawContent = true;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!currentSlug && line.trim() !== '' && !sawContent) {
|
|
157
|
+
openSection('lede');
|
|
158
|
+
usedSlugs.add('lede');
|
|
159
|
+
sawContent = true;
|
|
160
|
+
}
|
|
161
|
+
out.push(line);
|
|
162
|
+
}
|
|
163
|
+
closeSection();
|
|
164
|
+
return out.join('\n');
|
|
165
|
+
}
|
|
166
|
+
/** Strict slug sanitization — [a-z0-9-]{1,64}, no leading/trailing/repeated dashes. */
|
|
167
|
+
function sanitizeSlug(text) {
|
|
168
|
+
const lowered = text.toLowerCase();
|
|
169
|
+
const ascii = lowered.normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
|
|
170
|
+
const cleaned = ascii
|
|
171
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
172
|
+
.replace(/^-+|-+$/g, '')
|
|
173
|
+
.slice(0, 64);
|
|
174
|
+
return cleaned;
|
|
175
|
+
}
|
|
176
|
+
function uniqueSlug(base, used) {
|
|
177
|
+
if (!used.has(base))
|
|
178
|
+
return base;
|
|
179
|
+
let n = 2;
|
|
180
|
+
while (used.has(`${base}-${n}`))
|
|
181
|
+
n++;
|
|
182
|
+
return `${base}-${n}`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Escape any author-supplied `<!--` / `-->` in the body so forged markers can't
|
|
186
|
+
* close real ones. Applied BEFORE wrapping (spec "Marker sanitization").
|
|
187
|
+
*/
|
|
188
|
+
function escapeHtmlComments(body) {
|
|
189
|
+
return body.replace(/<!--/g, '<!--').replace(/-->/g, '-->');
|
|
37
190
|
}
|
|
191
|
+
// ─── Exports for testing ───
|
|
192
|
+
export const _internals = {
|
|
193
|
+
sanitizeSlug,
|
|
194
|
+
uniqueSlug,
|
|
195
|
+
escapeHtmlComments,
|
|
196
|
+
wrapSectionMarkers,
|
|
197
|
+
yamlValue,
|
|
198
|
+
};
|
|
38
199
|
//# sourceMappingURL=aeo.js.map
|
package/dist/utils/aeo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aeo.js","sourceRoot":"","sources":["../../src/utils/aeo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"aeo.js","sourceRoot":"","sources":["../../src/utils/aeo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ContentItem } from '../types.js';
|
|
2
|
+
export interface GenerateAnfOptions {
|
|
3
|
+
channelId: string;
|
|
4
|
+
byline?: string;
|
|
5
|
+
language?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AnfComponent {
|
|
8
|
+
role: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export type AnfLayoutMap = Record<string, Record<string, unknown>>;
|
|
12
|
+
export type AnfStyleMap = Record<string, Record<string, unknown>>;
|
|
13
|
+
export interface AnfDocument {
|
|
14
|
+
version: string;
|
|
15
|
+
identifier: string;
|
|
16
|
+
title: string;
|
|
17
|
+
subtitle?: string;
|
|
18
|
+
language: string;
|
|
19
|
+
layout: Record<string, unknown>;
|
|
20
|
+
documentStyle?: Record<string, unknown>;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
components: AnfComponent[];
|
|
23
|
+
componentLayouts?: AnfLayoutMap;
|
|
24
|
+
componentStyles?: AnfStyleMap;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate an Apple News Format document for a ContentItem. The returned value
|
|
28
|
+
* is a JSON-serializable object; consumers submit it as the `article.json` part
|
|
29
|
+
* of a News Publisher multipart POST.
|
|
30
|
+
*
|
|
31
|
+
* The generated document uses a conservative default layout (7-column grid),
|
|
32
|
+
* standard component styles, and a body composed of: title, byline, hero image,
|
|
33
|
+
* then one body component per paragraph-separated block of the article's
|
|
34
|
+
* description. Consumers producing real articles should replace the body
|
|
35
|
+
* components with their rendered content before submitting.
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateAppleNewsAnf(item: ContentItem, options: GenerateAnfOptions): AnfDocument;
|
|
38
|
+
//# sourceMappingURL=apple-news-anf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apple-news-anf.d.ts","sourceRoot":"","sources":["../../src/utils/apple-news-anf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAiB9C,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAGjB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AAID,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAClE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAEjE,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,UAAU,EAAE,YAAY,EAAE,CAAA;IAC1B,gBAAgB,CAAC,EAAE,YAAY,CAAA;IAC/B,eAAe,CAAC,EAAE,WAAW,CAAA;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAsFhG"}
|