@hagicode/hagilight 0.18.0 → 0.19.1-dev.184.1.830a4c6
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 +12 -7
- package/SEOHead.astro +5 -0
- package/dist/integration.d.ts +12 -0
- package/dist/integration.js +47 -1
- package/dist/llms.d.ts +3 -0
- package/dist/llms.js +7 -0
- package/dist/site-metadata.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -112,7 +112,8 @@ The build warning reads `Hagilight SEO image <address> was kept as declared: its
|
|
|
112
112
|
`hagilight()` writes `/llms.txt` after the build, following the [llms.txt](https://llmstxt.org/) layout: the site name as the H1, the home page description as a blockquote summary, and one H2 section of links per top-level path segment. It is generated from the built HTML, so it works for plain Astro and Starlight sites alike and needs no content collection.
|
|
113
113
|
|
|
114
114
|
- Only one language is listed: the Astro `i18n.defaultLocale`, then the home page language. The home pages of the other languages go under `## Optional`.
|
|
115
|
-
- Pages are left out when they are `noindex`,
|
|
115
|
+
- Pages are left out when they are `noindex`, meta-refresh redirects, 404 pages, or declare a canonical URL for another page.
|
|
116
|
+
- A page that redirects as soon as it loads with a script can opt out with `<meta name="hagilight:llms" content="exclude">` in its `<head>`. Only the redirect page should carry it; a normal page keeps its entry.
|
|
116
117
|
- Each link carries the page's meta description. A description repeated on three or more pages is treated as a site-wide default and left out of the links.
|
|
117
118
|
- Section order is Overview first, then by page count. A section lists 12 links (Overview 36) and the file lists at most 120.
|
|
118
119
|
- An `llms.txt` that the site already provides, as a public file or a page, is never overwritten. `hagilight({ enabled: false })` skips generation too.
|
|
@@ -120,8 +121,8 @@ The build warning reads `Hagilight SEO image <address> was kept as declared: its
|
|
|
120
121
|
```js
|
|
121
122
|
hagilight({
|
|
122
123
|
llms: {
|
|
123
|
-
title: 'Example Docs', // default: the home page og:site_name
|
|
124
|
-
summary: 'What this site is.', // default: the home page description
|
|
124
|
+
title: 'Example Docs', // default: the home page og:site_name; a configured value always wins
|
|
125
|
+
summary: 'What this site is.', // default: the home page description; a configured value always wins
|
|
125
126
|
lang: 'en-US', // default: i18n.defaultLocale
|
|
126
127
|
maxLinksPerSection: 60, // default: 12
|
|
127
128
|
maxLinks: 200, // default: 120
|
|
@@ -164,7 +165,7 @@ Pass `llms: false` (or `{ enabled: false }`) to opt out. The file is only as goo
|
|
|
164
165
|
"llms": "/llms.txt"
|
|
165
166
|
},
|
|
166
167
|
"analytics": { "googleAnalytics": true, "fiftyOneLa": false, "posthog": true },
|
|
167
|
-
"features": { "seo": true, "promoto": true }
|
|
168
|
+
"features": { "seo": true, "promoto": true, "consent": true }
|
|
168
169
|
}
|
|
169
170
|
```
|
|
170
171
|
|
|
@@ -178,7 +179,7 @@ Pass `llms: false` (or `{ enabled: false }`) to opt out. The file is only as goo
|
|
|
178
179
|
|
|
179
180
|
## PostHog
|
|
180
181
|
|
|
181
|
-
`hagilight()` injects PostHog into the head of every page of a production build, with no analytics option, enabled flag, or project key. It uses `phc_qiVVihsL925ovhPYNdLJWz4k7aoKVMwYCevgZPE6PER5`, API host `https://us.
|
|
182
|
+
`hagilight()` injects PostHog into the head of every page of a production build, with no analytics option, enabled flag, or project key. It uses `phc_qiVVihsL925ovhPYNdLJWz4k7aoKVMwYCevgZPE6PER5`, API host `https://t.hagicode.com` (managed proxy), UI host `https://us.posthog.com`, person profiles `identified_only`, and SDK defaults `2026-05-30`, the same as the Starlight and Docusaurus plugins. Sites with a strict script or connect policy must allow `t.hagicode.com`. `astro dev` pages emit no loader. Injection does not depend on the `enabled` option, which controls only the sitemap and `robots.txt`.
|
|
182
183
|
|
|
183
184
|
| PostHog input | Behavior |
|
|
184
185
|
| --- | --- |
|
|
@@ -194,7 +195,7 @@ hagilight({ analytics: { posthog: { apiKey: 'phc_Custom123' } } });
|
|
|
194
195
|
hagilight({ analytics: { posthog: { enabled: false } } });
|
|
195
196
|
```
|
|
196
197
|
|
|
197
|
-
The built-in key belongs to HagiCode, so a site that omits `analytics` sends its traffic to HagiCode's project. The loader initializes once per document. SDK defaults own navigation tracking
|
|
198
|
+
The built-in key belongs to HagiCode, so a site that omits `analytics` sends its traffic to HagiCode's project. The loader initializes once per document. SDK defaults own navigation tracking. Hagilight also registers the site context once after initialization (`site_id` from `links.siteId` or the site URL's hostname, `site_framework` `astro`, `hagilight_version`, `locale` from `<html lang>`, and `page_type`), sets `capture_exceptions`, and sends `cta_click` for marked links through a click listener that works without Google Analytics; it adds no other capture calls. `page_type` is `other` unless a page sets it: pass the `pageType` prop to `SEOHead`, which emits `<meta name="hagilight:page-type">`, or render that meta element yourself. Choose one initialization owner: a site that already renders `@hagicode/hagilight-core/PostHog` or another PostHog snippet should remove it or set `enabled: false`. When `@astrojs/starlight` is registered, this integration injects nothing and the Starlight plugin owns PostHog. The `analytics.posthog` flag in `hagilight.metadata.json` is `true` when the built pages contain the loader.
|
|
198
199
|
|
|
199
200
|
## Body images
|
|
200
201
|
|
|
@@ -211,4 +212,8 @@ hagilight({ contentImages: { enabled: false } });
|
|
|
211
212
|
|
|
212
213
|
Sites that do not use this integration can import the plugin from `@hagicode/hagilight-core` directly; its README describes the options and processor setup.
|
|
213
214
|
|
|
214
|
-
See the [repository documentation](https://github.com/HagiCode-org/hagilight#readme) for RSS configuration, feed callback types, and integration behavior.
|
|
215
|
+
See the [repository documentation](https://github.com/HagiCode-org/hagilight#readme) for RSS configuration, feed callback types, and integration behavior.
|
|
216
|
+
|
|
217
|
+
### Consent
|
|
218
|
+
|
|
219
|
+
`analytics.consent` (`enabled`, `privacyUrl`, `policyVersion`) applies to the PostHog this integration injects. With consent on (the default) the head carries an inert descriptor, the page mounts the popup and gate, and PostHog starts only after a grant. `enabled: false` injects PostHog immediately as before. Layouts that compose `<PostHog />`, `<GoogleAnalytics />` or `<Analytics51LA />` by hand pass `consent` to each component and render `<CookieConsent providers={...} />`.
|
package/SEOHead.astro
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
OG_IMAGE_WIDTH,
|
|
17
17
|
classifyOgImage,
|
|
18
18
|
} from '@hagicode/hagilight-core/og-image';
|
|
19
|
+
import { POSTHOG_PAGE_TYPES, type PostHogPageType } from '@hagicode/hagilight-core/posthog-events';
|
|
19
20
|
|
|
20
21
|
interface Props {
|
|
21
22
|
page?: {
|
|
@@ -37,6 +38,8 @@ interface Props {
|
|
|
37
38
|
ogType?: string;
|
|
38
39
|
organization?: DefaultOrganizationOptions;
|
|
39
40
|
defaultStructuredData?: boolean;
|
|
41
|
+
/** The PostHog page type for this page. Emitted as `<meta name="hagilight:page-type">` when in the vocabulary. */
|
|
42
|
+
pageType?: PostHogPageType;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
const {
|
|
@@ -50,6 +53,7 @@ const {
|
|
|
50
53
|
ogType,
|
|
51
54
|
organization,
|
|
52
55
|
defaultStructuredData = true,
|
|
56
|
+
pageType,
|
|
53
57
|
} = Astro.props;
|
|
54
58
|
const site = configuredSite?.toString();
|
|
55
59
|
const currentPageUrl = site
|
|
@@ -131,6 +135,7 @@ const defaultValues = buildDefaultStructuredData({
|
|
|
131
135
|
---
|
|
132
136
|
|
|
133
137
|
{composedHead.map(({ tag: Tag, attrs, content }) => <Tag {...attrs} set:html={content} />)}
|
|
138
|
+
{(POSTHOG_PAGE_TYPES as readonly string[]).includes(pageType ?? '') && <meta name="hagilight:page-type" content={pageType} />}
|
|
134
139
|
{[...defaultValues, ...dataValues].map((value) => (
|
|
135
140
|
<script type="application/ld+json" set:html={serializeJsonLd(value)} />
|
|
136
141
|
))}
|
package/dist/integration.d.ts
CHANGED
|
@@ -43,6 +43,18 @@ export interface HagilightOptions {
|
|
|
43
43
|
}
|
|
44
44
|
export interface HagilightAnalyticsOptions {
|
|
45
45
|
posthog?: HagilightPostHogOptions;
|
|
46
|
+
/**
|
|
47
|
+
* Strict prior consent for the PostHog this integration injects. On by default; `enabled: false` injects PostHog
|
|
48
|
+
* immediately as before. Components composed by hand take their own `consent` prop.
|
|
49
|
+
*/
|
|
50
|
+
consent?: HagilightConsentOptions;
|
|
51
|
+
}
|
|
52
|
+
export interface HagilightConsentOptions {
|
|
53
|
+
enabled?: boolean;
|
|
54
|
+
/** An absolute HTTP(S) URL or a site-relative path for the privacy link. */
|
|
55
|
+
privacyUrl?: string;
|
|
56
|
+
/** Defaults to `1`. 1 to 32 letters, digits, dots, underscores or hyphens. */
|
|
57
|
+
policyVersion?: string;
|
|
46
58
|
}
|
|
47
59
|
export interface HagilightPostHogOptions {
|
|
48
60
|
/** Defaults to true when the provider options are omitted, otherwise to whether `apiKey` is set. */
|
package/dist/integration.js
CHANGED
|
@@ -6,6 +6,8 @@ import { resolveBuildInfo } from '@hagicode/hagilight-core/build-info-resolver';
|
|
|
6
6
|
import { checkContentImageRegistration, registerContentImages } from '@hagicode/hagilight-core/content-images';
|
|
7
7
|
import { resolveFaviconHeadEntry } from '@hagicode/hagilight-core/favicon';
|
|
8
8
|
import { DEFAULT_POSTHOG_API_KEY, POSTHOG_API_KEY_PATTERN, posthogInitScript } from '@hagicode/hagilight-core/posthog';
|
|
9
|
+
import { consentHeadScript } from '@hagicode/hagilight-core/consent-mount';
|
|
10
|
+
import { validatePolicyVersion, validatePrivacyUrl } from '@hagicode/hagilight-core/consent-markup';
|
|
9
11
|
import { resolveRssLocales } from '@hagicode/hagilight-core/rss';
|
|
10
12
|
import { resolvePlainAstroRssOwner } from '@hagicode/hagilight-core/rss-ownership';
|
|
11
13
|
import { resolveSiteMetadataOption } from '@hagicode/hagilight-core/site-metadata';
|
|
@@ -49,6 +51,33 @@ function resolvePostHogApiKey(analytics) {
|
|
|
49
51
|
function integrationName(integration) {
|
|
50
52
|
return isRecord(integration) ? integration.name : undefined;
|
|
51
53
|
}
|
|
54
|
+
const PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
55
|
+
/** The plain-Astro site context: `site_id` is the configured site's hostname, when there is one. */
|
|
56
|
+
/** Consent for the injected PostHog: on by default, and the core validators name any invalid option. */
|
|
57
|
+
function resolveConsentOptions(analytics, posthogEnabled) {
|
|
58
|
+
const consent = isRecord(analytics) ? analytics.consent : undefined;
|
|
59
|
+
if (consent !== undefined && !isRecord(consent)) {
|
|
60
|
+
throw new TypeError('Hagilight analytics consent option must be an object.');
|
|
61
|
+
}
|
|
62
|
+
if (consent?.enabled !== undefined && typeof consent.enabled !== 'boolean') {
|
|
63
|
+
throw new TypeError('Hagilight analytics consent enabled option must be a boolean.');
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
enabled: posthogEnabled && (consent?.enabled ?? true),
|
|
67
|
+
privacyUrl: validatePrivacyUrl(consent?.privacyUrl),
|
|
68
|
+
policyVersion: validatePolicyVersion(consent?.policyVersion),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function postHogSiteContext(site) {
|
|
72
|
+
let siteId;
|
|
73
|
+
try {
|
|
74
|
+
siteId = new URL(site).hostname || undefined;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
siteId = undefined;
|
|
78
|
+
}
|
|
79
|
+
return { siteId, framework: 'astro', version: PACKAGE_VERSION };
|
|
80
|
+
}
|
|
52
81
|
function resolveDiscoverySite(site) {
|
|
53
82
|
const message = 'Hagilight sitemap and robots require an absolute HTTP(S) Astro site URL. Set `site` in astro.config.mjs.';
|
|
54
83
|
let parsed;
|
|
@@ -91,6 +120,7 @@ export function hagilight({ enabled = true, rss, contentImages, llms, metadata,
|
|
|
91
120
|
throw new TypeError('Hagilight content images enabled option must be a boolean.');
|
|
92
121
|
}
|
|
93
122
|
const posthogApiKey = resolvePostHogApiKey(analytics);
|
|
123
|
+
const consent = resolveConsentOptions(analytics, posthogApiKey !== undefined);
|
|
94
124
|
const llmsOptions = resolveLlmsOptions(llms);
|
|
95
125
|
const metadataOptions = resolveSiteMetadataOption(metadata);
|
|
96
126
|
const contentImagesEnabled = typeof contentImages === 'boolean' ? contentImages : contentImages?.enabled !== false;
|
|
@@ -128,7 +158,23 @@ export function hagilight({ enabled = true, rss, contentImages, llms, metadata,
|
|
|
128
158
|
// site gets PostHog from the Starlight plugin's generated Head instead.
|
|
129
159
|
const hasStarlight = (config.integrations ?? []).some((integration) => integrationName(integration) === '@astrojs/starlight');
|
|
130
160
|
if (posthogApiKey && command === 'build' && !hasStarlight) {
|
|
131
|
-
|
|
161
|
+
if (consent.enabled) {
|
|
162
|
+
// The loader travels inside an inert descriptor; the shared mount places the popup and starts PostHog
|
|
163
|
+
// and its click sink only after a grant.
|
|
164
|
+
const loader = posthogInitScript(posthogApiKey, postHogSiteContext(config.site));
|
|
165
|
+
injectScript('head-inline', consentHeadScript({
|
|
166
|
+
posthog: { apiKey: posthogApiKey, loader },
|
|
167
|
+
config: { providers: ['posthog'], privacyUrl: consent.privacyUrl, policyVersion: consent.policyVersion },
|
|
168
|
+
}));
|
|
169
|
+
const mountModule = fileURLToPath(import.meta.resolve('@hagicode/hagilight-core/consent-mount'));
|
|
170
|
+
injectScript('page', `import { mountConsent } from ${JSON.stringify(mountModule)};\nmountConsent();`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
injectScript('head-inline', posthogInitScript(posthogApiKey, postHogSiteContext(config.site)));
|
|
174
|
+
// The click sink is independent of Google Analytics; it stays silent until window.posthog exists.
|
|
175
|
+
const clickSink = fileURLToPath(import.meta.resolve('@hagicode/hagilight-core/posthog-events'));
|
|
176
|
+
injectScript('page', `import { installPostHogClickTracking } from ${JSON.stringify(clickSink)};\ninstallPostHogClickTracking();`);
|
|
177
|
+
}
|
|
132
178
|
}
|
|
133
179
|
if (!enabled)
|
|
134
180
|
return;
|
package/dist/llms.d.ts
CHANGED
|
@@ -33,6 +33,9 @@ export interface BuildLlmsTxtOptions {
|
|
|
33
33
|
}
|
|
34
34
|
export declare const DEFAULT_MAX_LINKS_PER_SECTION = 12;
|
|
35
35
|
export declare const DEFAULT_MAX_LINKS = 120;
|
|
36
|
+
/** `<meta name="hagilight:llms" content="exclude">` keeps an immediate-redirect page out of llms.txt. */
|
|
37
|
+
export declare const LLMS_EXCLUDE_META_NAME = "hagilight:llms";
|
|
38
|
+
export declare const LLMS_EXCLUDE_META_VALUE = "exclude";
|
|
36
39
|
/**
|
|
37
40
|
* Read the facts `llms.txt` needs from one built page. Returns `undefined` for pages that should
|
|
38
41
|
* not be listed: `noindex`, instant redirects, 404 pages, and pages whose canonical URL points
|
package/dist/llms.js
CHANGED
|
@@ -8,6 +8,9 @@ export const DEFAULT_MAX_LINKS_PER_SECTION = 12;
|
|
|
8
8
|
export const DEFAULT_MAX_LINKS = 120;
|
|
9
9
|
const OVERVIEW_LIMIT_FACTOR = 3;
|
|
10
10
|
const GENERIC_DESCRIPTION_MIN_PAGES = 3;
|
|
11
|
+
/** `<meta name="hagilight:llms" content="exclude">` keeps an immediate-redirect page out of llms.txt. */
|
|
12
|
+
export const LLMS_EXCLUDE_META_NAME = 'hagilight:llms';
|
|
13
|
+
export const LLMS_EXCLUDE_META_VALUE = 'exclude';
|
|
11
14
|
const LOCALE_SEGMENT = /^[a-z]{2,3}(?:-[a-z0-9]{2,8})?$/iu;
|
|
12
15
|
const ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ' };
|
|
13
16
|
function decodeEntities(value) {
|
|
@@ -74,6 +77,10 @@ export function parsePageMeta(html, pageUrl) {
|
|
|
74
77
|
return undefined;
|
|
75
78
|
if (metas.some((meta) => meta['http-equiv']?.toLowerCase() === 'refresh'))
|
|
76
79
|
return undefined;
|
|
80
|
+
// A page that redirects as soon as it loads (for example an inline script) opts out explicitly.
|
|
81
|
+
if (metas.some((meta) => meta.name?.toLowerCase() === LLMS_EXCLUDE_META_NAME
|
|
82
|
+
&& meta.content?.trim().toLowerCase() === LLMS_EXCLUDE_META_VALUE))
|
|
83
|
+
return undefined;
|
|
77
84
|
const canonicalTag = [...head.matchAll(/<link\b[^>]*>/giu)]
|
|
78
85
|
.map(([tag]) => attributes(tag))
|
|
79
86
|
.find((link) => link.rel?.toLowerCase().split(/\s+/u).includes('canonical'));
|
package/dist/site-metadata.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { CONSENT_DESCRIPTOR_ID } from '@hagicode/hagilight-core/consent-markup';
|
|
9
10
|
import { buildSiteMetadata, detectAnalyticsLoaders, SITE_METADATA_PATH, } from '@hagicode/hagilight-core/site-metadata';
|
|
10
11
|
import { findDiscoveryOutputs, writeSiteMetadata } from '@hagicode/hagilight-core/site-metadata-writer';
|
|
11
12
|
import { parseHeadMeta } from './llms.js';
|
|
@@ -97,6 +98,8 @@ export function writeAstroSiteMetadata({ config, outDir, pages, build, options,
|
|
|
97
98
|
build,
|
|
98
99
|
discovery: findDiscoveryOutputs(outDir),
|
|
99
100
|
analytics: detectAnalyticsLoaders(pageHtml),
|
|
101
|
+
// The popup is mounted exactly when a built page carries its config descriptor.
|
|
102
|
+
features: { consent: pageHtml.some((html) => html.includes(CONSENT_DESCRIPTOR_ID)) },
|
|
100
103
|
});
|
|
101
104
|
writeSiteMetadata({
|
|
102
105
|
outDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/hagilight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1-dev.184.1.830a4c6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Astro integrations and components for HagiCode sites",
|
|
6
6
|
"repository": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@astrojs/sitemap": "^3.7.4",
|
|
43
|
-
"@hagicode/hagilight-core": "0.
|
|
43
|
+
"@hagicode/hagilight-core": "0.19.1-dev.184.1.830a4c6"
|
|
44
44
|
},
|
|
45
45
|
"keywords": [
|
|
46
46
|
"hagicode",
|