@hagicode/hagilight 0.14.1-dev.150.1.68cde77 → 0.14.1-dev.151.1.c2d06ef

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 CHANGED
@@ -118,6 +118,26 @@ Pass `llms: false` (or `{ enabled: false }`) to opt out. The file is only as goo
118
118
  - Hosts that drop dot-directories need an explicit opt-in. GitHub Pages' Jekyll step skips `.well-known` unless the published root contains an empty `.nojekyll` file.
119
119
  - When the Starlight plugin is also registered with metadata enabled, the Starlight plugin writes the file, and this integration defers to it. Registration order does not change the output.
120
120
 
121
+ ## PostHog
122
+
123
+ `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.i.posthog.com`, and SDK defaults `2026-05-30`, the same as the Starlight and Docusaurus plugins. `astro dev` pages emit no loader. Injection does not depend on the `enabled` option, which controls only the sitemap and `robots.txt`.
124
+
125
+ | PostHog input | Behavior |
126
+ | --- | --- |
127
+ | `analytics` omitted, `analytics: {}`, or `posthog` omitted | Enabled with the supplied default key |
128
+ | `posthog: {}` | Disabled, like an empty provider in the Starlight plugin |
129
+ | `posthog: { apiKey: 'phc_Custom123' }` | Enabled with the custom key |
130
+ | `posthog: { enabled: true, apiKey: 'phc_Custom123' }` | Enabled with the custom key |
131
+ | `posthog: { enabled: true }` | Configuration error: `apiKey` required |
132
+ | `posthog: { enabled: false }` | Disabled |
133
+
134
+ ```js
135
+ hagilight({ analytics: { posthog: { apiKey: 'phc_Custom123' } } });
136
+ hagilight({ analytics: { posthog: { enabled: false } } });
137
+ ```
138
+
139
+ 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 adds no capture calls. 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.
140
+
121
141
  ## Body images
122
142
 
123
143
  `hagilight()` registers the content-image plugin from `@hagicode/hagilight-core/content-images`. Markdown and MDX body images then get `loading="lazy"`, `decoding="async"`, and the class `hagilight-content-image`, with the author's values kept. This registration does not depend on `enabled`, so `hagilight({ enabled: false })` still registers the plugin.
@@ -34,6 +34,21 @@ export interface HagilightOptions {
34
34
  * and generates metadata, the Starlight plugin writes the file.
35
35
  */
36
36
  metadata?: boolean | HagilightMetadataOptions;
37
+ /**
38
+ * Analytics providers this integration injects into every page head of a production build.
39
+ * Only PostHog is injected here; Starlight sites configure it through the Starlight plugin,
40
+ * which owns the head, so this integration adds nothing when `@astrojs/starlight` is registered.
41
+ */
42
+ analytics?: HagilightAnalyticsOptions;
43
+ }
44
+ export interface HagilightAnalyticsOptions {
45
+ posthog?: HagilightPostHogOptions;
46
+ }
47
+ export interface HagilightPostHogOptions {
48
+ /** Defaults to true when the provider options are omitted, otherwise to whether `apiKey` is set. */
49
+ enabled?: boolean;
50
+ /** Project key, `phc_` followed by ASCII letters or digits. Defaults to the HagiCode project key. */
51
+ apiKey?: string;
37
52
  }
38
53
  export interface HagilightMetadataOptions {
39
54
  /** Defaults to `true`. */
@@ -74,6 +89,6 @@ export type HagilightFaviconOptions = FaviconOptions;
74
89
  * owns sitemap generation, a consumer-owned robots page or public file takes
75
90
  * precedence, and an enabled Starlight Hagilight integration owns RSS feeds.
76
91
  */
77
- export declare function hagilight({ enabled, rss, contentImages, llms, metadata }?: HagilightOptions): AstroIntegration;
92
+ export declare function hagilight({ enabled, rss, contentImages, llms, metadata, analytics }?: HagilightOptions): AstroIntegration;
78
93
  /** Inject the bundled HagiCode favicon unless the site head already declares an icon. */
79
94
  export declare function hagilightFavicon(options?: HagilightFaviconOptions): AstroIntegration;
@@ -5,6 +5,7 @@ import sitemap from '@astrojs/sitemap';
5
5
  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
+ import { DEFAULT_POSTHOG_API_KEY, POSTHOG_API_KEY_PATTERN, posthogInitScript } from '@hagicode/hagilight-core/posthog';
8
9
  import { resolveRssLocales } from '@hagicode/hagilight-core/rss';
9
10
  import { resolvePlainAstroRssOwner } from '@hagicode/hagilight-core/rss-ownership';
10
11
  import { resolveSiteMetadataOption } from '@hagicode/hagilight-core/site-metadata';
@@ -21,6 +22,30 @@ const routeEntrypoint = (name) => fileURLToPath(new URL(name, packageRoot));
21
22
  function isRecord(value) {
22
23
  return value !== null && typeof value === 'object' && !Array.isArray(value);
23
24
  }
25
+ function resolvePostHogApiKey(analytics) {
26
+ if (analytics !== undefined && !isRecord(analytics)) {
27
+ throw new TypeError('Hagilight analytics option must be an object.');
28
+ }
29
+ const posthog = analytics?.posthog;
30
+ if (posthog === undefined)
31
+ return DEFAULT_POSTHOG_API_KEY;
32
+ if (!isRecord(posthog)) {
33
+ throw new TypeError('Hagilight PostHog analytics options must be an object.');
34
+ }
35
+ if (posthog.enabled !== undefined && typeof posthog.enabled !== 'boolean') {
36
+ throw new TypeError('Hagilight PostHog analytics enabled option must be a boolean.');
37
+ }
38
+ if (!(posthog.enabled ?? Boolean(posthog.apiKey)))
39
+ return undefined;
40
+ const apiKey = posthog.apiKey;
41
+ if (typeof apiKey !== 'string' || !apiKey.trim()) {
42
+ throw new Error('Hagilight PostHog analytics is enabled but its apiKey is missing.');
43
+ }
44
+ if (!POSTHOG_API_KEY_PATTERN.test(apiKey)) {
45
+ throw new Error('Hagilight PostHog analytics apiKey has an invalid format.');
46
+ }
47
+ return apiKey;
48
+ }
24
49
  function integrationName(integration) {
25
50
  return isRecord(integration) ? integration.name : undefined;
26
51
  }
@@ -55,7 +80,7 @@ const BUILTIN_RSS_FEED_SOURCE = BUILTIN_RSS_FEED.toString();
55
80
  * owns sitemap generation, a consumer-owned robots page or public file takes
56
81
  * precedence, and an enabled Starlight Hagilight integration owns RSS feeds.
57
82
  */
58
- export function hagilight({ enabled = true, rss, contentImages, llms, metadata } = {}) {
83
+ export function hagilight({ enabled = true, rss, contentImages, llms, metadata, analytics } = {}) {
59
84
  if (typeof enabled !== 'boolean') {
60
85
  throw new TypeError('Hagilight sitemap and robots enabled option must be a boolean.');
61
86
  }
@@ -65,6 +90,7 @@ export function hagilight({ enabled = true, rss, contentImages, llms, metadata }
65
90
  if (isRecord(contentImages) && contentImages.enabled !== undefined && typeof contentImages.enabled !== 'boolean') {
66
91
  throw new TypeError('Hagilight content images enabled option must be a boolean.');
67
92
  }
93
+ const posthogApiKey = resolvePostHogApiKey(analytics);
68
94
  const llmsOptions = resolveLlmsOptions(llms);
69
95
  const metadataOptions = resolveSiteMetadataOption(metadata);
70
96
  const contentImagesEnabled = typeof contentImages === 'boolean' ? contentImages : contentImages?.enabled !== false;
@@ -87,7 +113,7 @@ export function hagilight({ enabled = true, rss, contentImages, llms, metadata }
87
113
  return {
88
114
  name: '@hagicode/hagilight:discovery',
89
115
  hooks: {
90
- 'astro:config:setup'({ config, injectRoute, addMiddleware, updateConfig, logger }) {
116
+ 'astro:config:setup'({ command, config, injectRoute, injectScript, addMiddleware, updateConfig, logger }) {
91
117
  // Integrations added by this hook go in one update, in order: the sitemap first, then the
92
118
  // metadata finalizer, so the finalizer's build hook runs after the sitemap has written its index.
93
119
  let sitemapNeeded = false;
@@ -98,6 +124,12 @@ export function hagilight({ enabled = true, rss, contentImages, llms, metadata }
98
124
  metadataPeerToken = resolveSiteMetadataOwner() === 'starlight' ? expectSiteMetadataReport() : undefined;
99
125
  updateConfig({ vite: { plugins: [createBuildInfoPlugin(buildInfo)] } });
100
126
  addMiddleware({ entrypoint: fileURLToPath(new URL('./build-info-middleware.js', import.meta.url)), order: 'pre' });
127
+ // Independent of `enabled`. The shared script initializes once per document, and a Starlight
128
+ // site gets PostHog from the Starlight plugin's generated Head instead.
129
+ const hasStarlight = (config.integrations ?? []).some((integration) => integrationName(integration) === '@astrojs/starlight');
130
+ if (posthogApiKey && command === 'build' && !hasStarlight) {
131
+ injectScript('head-inline', posthogInitScript(posthogApiKey));
132
+ }
101
133
  if (!enabled)
102
134
  return;
103
135
  resolveDiscoverySite(config.site);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/hagilight",
3
- "version": "0.14.1-dev.150.1.68cde77",
3
+ "version": "0.14.1-dev.151.1.c2d06ef",
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.14.1-dev.150.1.68cde77"
43
+ "@hagicode/hagilight-core": "0.14.1-dev.151.1.c2d06ef"
44
44
  },
45
45
  "keywords": [
46
46
  "hagicode",