Skip to content

Repository files navigation

HagiLight

Reusable Astro components, integrations, and a Starlight plugin for HagiCode sites. This repository publishes three npm packages, all authored in TypeScript and shipped as ESM with generated .d.ts declarations. Astro components, Astro route entry points, styles, JSON, and images are shipped in their native formats.

flowchart LR
  A["@hagicode/hagilight"] --> C["@hagicode/hagilight-core"]
  S["@hagicode/hagilight-starlight"] --> C
Loading

Both feature packages depend on the matching version of the shared core and never on each other. Install core directly when your site imports a core entry point.

@hagicode/hagilight-core

Shared building blocks for plain Astro and Starlight sites. Requires astro ^6.0.7 || ^7.3.5; it has no Starlight dependency.

Import Provides
@hagicode/hagilight-core/links resolveSiteLinks() and the localized header/footer link catalog types
@hagicode/hagilight-core/favicon resolveFaviconHeadEntry() and getHagilightFaviconDataUri()
@hagicode/hagilight-core/seo Metadata, canonical head composition, and JSON-LD builders
@hagicode/hagilight-core/seo-schema seoSchema for optional seo frontmatter
@hagicode/hagilight-core/rss generateRssFeed() and resolveRssLocales()
@hagicode/hagilight-core/rss-ownership Coordination that keeps one integration as the RSS route owner
@hagicode/hagilight-core/promotions Typed promotion campaign loader
@hagicode/hagilight-core/Footer Localized footer with generated RSS and sitemap links
@hagicode/hagilight-core/Copyright, /PromotoBanner, /GoogleAnalytics, /Analytics51LA Astro components
@hagicode/hagilight-core/logo.png, /favicon.ico Brand assets

@hagicode/hagilight

Plain Astro integrations and components (astro ^6.0.7 || ^7.3.5):

Import Provides
@hagicode/hagilight/integration hagilight() (sitemap and robots.txt), hagilightRss(), hagilightFavicon(), and their option types
@hagicode/hagilight/SEOHead Canonical, Open Graph, Twitter, and JSON-LD head entries

@hagicode/hagilight-starlight

A Starlight plugin (@astrojs/starlight ^0.42.4, astro ^7.3.5) that adds the localized header, language chooser, and footer links; content-width toggle; custom 404 page; end-of-article HagiCode introduction; floating promotion banner; AI translation or authorship disclosures; SEO metadata and multilingual page discovery; RSS feeds; Google Analytics and 51LA; and the shared favicon.

// astro.config.mjs
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import hagilight from '@hagicode/hagilight-starlight';
import { locales } from '@hagicode/hagilight-starlight/locales';

export default defineConfig({
  site: 'https://docs.example.test',
  integrations: [starlight({ title: 'Docs', locales, plugins: [hagilight({ rss: { includeDocs: false } })] })],
});
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';
import { hagilightSchema } from '@hagicode/hagilight-starlight/schema';

export const collections = { docs: defineCollection({ loader: docsLoader(), schema: docsSchema({ extend: hagilightSchema }) }) };

HagilightStarlightOptions types every plugin option, so misspelled or mistyped options fail type-checking in astro.config.ts or JSDoc-checked configs; the plugin still validates options at runtime. Components such as @hagicode/hagilight-starlight/Header and /MarkdownContent remain importable for sites that compose their own Starlight overrides.

TypeScript usage

Every JavaScript entry point has a types export condition, so TypeScript resolves declarations with moduleResolution NodeNext, Node16, or Bundler without application-level module declarations. For example, type the module passed to hagilightRss({ getFeed }):

// src/rss-feed.ts
import type { RssFeedCallback } from '@hagicode/hagilight/integration';

const getFeed: RssFeedCallback = async ({ lang }) => ({
  title: 'Updates',
  description: `Updates in ${lang}`,
  items: [{ title: 'Release notes', link: '/blog/release/', date: new Date() }],
});
export default getFeed;

Local development

Run commands from the repository root:

npm install
npm run build              # tsc -b: core, then @hagicode/hagilight and the Starlight package
npm run typecheck          # build, check Astro route entry points, and check test/types fixtures
npm test                   # builds first, then runs node --test
npm run build:example      # builds packages and both examples, then verifies their output
npm run pack:check         # verifies all three tarballs, exports, and version alignment
npm run integration:installed  # installs tarballs into isolated core, Astro, and Starlight consumers

Sources live in packages/*/src/*.ts and compile to the ignored packages/*/dist/ directories; .astro components and route files import the built dist/*.js modules, so build before running examples from a clean checkout. The release workflow stamps one version into all three packages and the core dependency of both feature packages (scripts/release.mjs stamp), then scripts/publish.mjs publishes core first, followed by the plain-Astro and Starlight packages.

Sitemap and robots.txt for plain Astro

Register the plain-Astro integration in astro.config.mjs (importing a component alone cannot register an Astro integration):

import { defineConfig } from 'astro/config';
import { hagilight } from '@hagicode/hagilight/integration';

export default defineConfig({
  site: 'https://example.test',
  integrations: [hagilight()],
});

With an absolute HTTP(S) site, Astro's sitemap integration generates sitemap-index.xml and Hagilight generates robots.txt with an absolute Sitemap: URL to that index. For base: '/manual/', verify https://example.test/manual/sitemap-index.xml and the sitemap entries under https://example.test/manual/; the localized Footer sitemap link uses that same base-aware path. Set hagilight({ enabled: false }) to disable both generated outputs. An existing @astrojs/sitemap integration or Starlight owns sitemap generation instead; a consumer-owned public/robots.txt or src/pages/robots.txt.* remains authoritative.

If you need a site-specific crawl policy, own public/robots.txt and include the absolute URL of the deployed sitemap index:

User-agent: *
Allow: /
Disallow: /private/
Sitemap: https://example.test/manual/sitemap-index.xml

Hagilight does not generate llms.txt. To publish AI discovery links, add your own public/llms.txt with only the localized pages you want to expose:

# Example documentation

- [English](https://example.test/manual/)
- [简体中文](https://example.test/manual/zh-CN/)

If you do not want AI discovery, do not add an llms.txt file. Deployments under a base path must still serve /robots.txt at the origin root, because crawlers look for that exact URL.

Localized RSS for plain Astro

Plain Astro sites can keep owning RSS routes and call generateRssFeed from @hagicode/hagilight-core/rss, or opt in to generated routes and Footer links with hagilightRss from @hagicode/hagilight/integration:

import { defineConfig } from 'astro/config';
import { hagilightRss } from '@hagicode/hagilight/integration';

const locales = {
  root: { label: 'English', lang: 'en-US' },
  'zh-CN': { label: '简体中文', lang: 'zh-CN' },
};

export default defineConfig({
  site: 'https://example.test',
  integrations: [
    hagilightRss({ locales, getFeed: './src/rss-feed.ts' }),
  ],
});

The getFeed path is resolved relative to the Astro project root. Its module must default-export an RssFeedCallback that receives { route, lang } for each configured locale and returns { title, description, items }. Items use the same title, link, optional description, and optional date/pubDate shape accepted by generateRssFeed. The integration requires an absolute HTTP(S) site URL, a nonempty Starlight-shaped locales map, and a valid callback module and result.

The integration prerenders /rss.xml, /rss.en.xml, and /rss.<language>.xml for each configured non-English language. /rss.xml and /rss.en.xml use the configured English callback result. If no English locale is configured, both are valid empty English feeds; Hagilight does not invoke a different language's callback or borrow its content. A generated filename conflicting with a page or public file fails the build rather than replacing consumer-owned output. Removing the integration leaves route and Footer behavior unchanged.

Generated route and item URLs honor Astro's base, for example base: '/manual/' generates /manual/rss.xml and resolves relative item links under https://example.test/manual/. The core Footer gets the default feed link and, on configured non-English pages, a current-language link from request-local integration context. Explicit links.rssFeedUrl, links.rssLocaleFeedUrl, link overrides, and existing removal options remain authoritative.

When both Hagilight integrations are active, Starlight remains the sole RSS owner if its RSS generation is enabled; hagilightRss() then adds neither routes nor Footer URLs. Both packages coordinate through @hagicode/hagilight-core/rss-ownership. Having both packages installed does not enable either integration. If the Starlight integration is present with RSS explicitly disabled while hagilightRss() is also enabled, setup fails with an ownership diagnostic; remove one integration or enable Starlight RSS instead.

Desktop viewport regression baseline

Install the Chromium browser once with npx playwright install chromium, then run npm run test:viewport from the Hagilight repository root. The command builds and serves both example sites locally before checking these routes at 1536 x 864, 1920 x 1080, and 2560 x 1440 CSS viewport pixels:

Example Routes Layout states
Core Astro /, /zh-CN/ Header actions, bounded feature cards, internal code-block scrolling, and document overflow
Starlight /zh-CN/, /zh-Hant/ Header, sidebar, populated page outline, narrow and wide reading widths, and the open language chooser

The suite blocks external requests, disables optional analytics only for its test build, waits for fonts, and disables animation. On failure it reports the route, viewport, and affected region, then saves a screenshot; CI retains these under .ci-artifacts/viewport/.

These dimensions are Chromium CSS viewport pixels at device scale factor 1. They do not emulate macOS display scaling, physical device pixels, Safari, or other browser typography; a real-device/browser pass remains complementary.

About

Shared Astro components and Starlight plugin for HagiCode documentation sites — localized footer, header, locale chooser, copyright, promotion banner, analytics, and AI-disclosure support.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages