@xingwangzhe/stalux 1.25.0 → 1.25.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.25.0",
3
+ "version": "1.25.2",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -76,6 +76,7 @@ const contentTitle =
76
76
  : effectiveTitle;
77
77
  const resolvedNoindex = Boolean(siteConfig.noindex) || isNotFoundPage || Boolean(props.noindex);
78
78
  const resolvedNofollow = Boolean(siteConfig.nofollow) || isNotFoundPage;
79
+ const staluxVersion = "1.25.2";
79
80
 
80
81
  const openGraphConfig: Record<string, unknown> = {
81
82
  basic: {
@@ -146,6 +147,9 @@ const extendedLink = [
146
147
  const extendedMeta = [
147
148
  { name: "viewport", content: "width=device-width, initial-scale=1" },
148
149
  { name: "color-scheme", content: "dark" },
150
+ { name: "generator", content: `Stalux ${staluxVersion}` },
151
+ { name: "theme", content: "Stalux" },
152
+ { name: "stalux-version", content: staluxVersion },
149
153
  ...(!resolvedNoindex
150
154
  ? [
151
155
  {
package/src/index.ts CHANGED
@@ -147,6 +147,7 @@ function getRoutes(baseDir: URL): RouteEntry[] {
147
147
  { pattern: "/atom.xml", entrypoint: resolvePage("./pages/atom.xml.ts") },
148
148
  { pattern: "/llms.txt", entrypoint: resolvePage("./pages/llms.txt.ts") },
149
149
  { pattern: "/llms-full.txt", entrypoint: resolvePage("./pages/llms-full.txt.ts") },
150
+ { pattern: "/openapi.json", entrypoint: resolvePage("./pages/openapi.json.ts") },
150
151
  {
151
152
  pattern: "/api/post.abbrlink.json",
152
153
  entrypoint: resolvePage("./pages/api/post.abbrlink.json.ts"),
@@ -10,6 +10,9 @@ export const GET: APIRoute = async (context) => {
10
10
  const site = getSite(config, context.site?.toString());
11
11
  const text = await renderAboutMd(site);
12
12
  return new Response(text, {
13
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
13
+ headers: {
14
+ "Content-Type": "text/markdown; charset=utf-8",
15
+ Vary: "Accept, Accept-Encoding",
16
+ },
14
17
  });
15
18
  };
@@ -19,6 +19,9 @@ export const GET: APIRoute = async (context) => {
19
19
  const { t } = createTranslator(lang);
20
20
  const text = await renderArchivesMd(site, exportMd, t);
21
21
  return new Response(text, {
22
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
22
+ headers: {
23
+ "Content-Type": "text/markdown; charset=utf-8",
24
+ Vary: "Accept, Accept-Encoding",
25
+ },
23
26
  });
24
27
  };
@@ -29,6 +29,9 @@ export const GET: APIRoute = async (context) => {
29
29
  const site = getSite(config, context.site?.toString());
30
30
  const text = renderTaxonomyPageMd(category, posts, site, "categories", exportMd);
31
31
  return new Response(text, {
32
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
32
+ headers: {
33
+ "Content-Type": "text/markdown; charset=utf-8",
34
+ Vary: "Accept, Accept-Encoding",
35
+ },
33
36
  });
34
37
  };
@@ -22,6 +22,9 @@ export const GET: APIRoute = async (context) => {
22
22
  const map = buildTaxonomyMap(posts, "categories");
23
23
  const text = renderTaxonomyListMd(map, site, "categories", t("ai.allCategories"));
24
24
  return new Response(text, {
25
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
25
+ headers: {
26
+ "Content-Type": "text/markdown; charset=utf-8",
27
+ Vary: "Accept, Accept-Encoding",
28
+ },
26
29
  });
27
30
  };
@@ -13,6 +13,9 @@ export const GET: APIRoute = async (context) => {
13
13
  const { t } = createTranslator(lang);
14
14
  const text = await renderIndexMd(config, site, t);
15
15
  return new Response(text, {
16
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
16
+ headers: {
17
+ "Content-Type": "text/markdown; charset=utf-8",
18
+ Vary: "Accept, Accept-Encoding",
19
+ },
17
20
  });
18
21
  };
@@ -13,6 +13,9 @@ export const GET: APIRoute = async (context) => {
13
13
  const { t } = createTranslator(lang);
14
14
  const text = renderLinksMd(config, site, t);
15
15
  return new Response(text, {
16
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
16
+ headers: {
17
+ "Content-Type": "text/markdown; charset=utf-8",
18
+ Vary: "Accept, Accept-Encoding",
19
+ },
17
20
  });
18
21
  };
@@ -20,6 +20,7 @@ export const GET: APIRoute = async (context) => {
20
20
  return new Response(text, {
21
21
  headers: {
22
22
  "Content-Type": "text/plain; charset=utf-8",
23
+ Vary: "Accept, Accept-Encoding",
23
24
  },
24
25
  });
25
26
  };
@@ -21,6 +21,7 @@ export const GET: APIRoute = async (context) => {
21
21
  return new Response(text, {
22
22
  headers: {
23
23
  "Content-Type": "text/plain; charset=utf-8",
24
+ Vary: "Accept, Accept-Encoding",
24
25
  },
25
26
  });
26
27
  };
@@ -0,0 +1,77 @@
1
+ import type { APIRoute } from "astro";
2
+
3
+ export const prerender = true;
4
+
5
+ const responseSchema = {
6
+ type: "array",
7
+ items: { type: "object", additionalProperties: true },
8
+ };
9
+
10
+ export const GET: APIRoute = async ({ site }) => {
11
+ const server = site?.toString().replace(/\/$/, "") || "";
12
+ const document = {
13
+ openapi: "3.1.0",
14
+ info: {
15
+ title: "Stalux Blog Read API",
16
+ version: "1.0.0",
17
+ description:
18
+ "Read-only machine-readable endpoints for discovering and retrieving published Stalux blog content.",
19
+ },
20
+ servers: server ? [{ url: server }] : undefined,
21
+ paths: {
22
+ "/api/posts.json": {
23
+ get: {
24
+ operationId: "listPublishedPosts",
25
+ summary: "List published posts",
26
+ description: "Returns metadata for every published post, newest first.",
27
+ responses: {
28
+ "200": {
29
+ description: "Published post metadata",
30
+ content: { "application/json": { schema: responseSchema } },
31
+ },
32
+ },
33
+ },
34
+ },
35
+ "/api/post.abbrlink.json": {
36
+ get: {
37
+ operationId: "listPostIdentifiers",
38
+ summary: "List post identifiers",
39
+ description: "Returns the title and stable abbrlink for every published post.",
40
+ responses: {
41
+ "200": {
42
+ description: "Published post identifiers",
43
+ content: { "application/json": { schema: responseSchema } },
44
+ },
45
+ },
46
+ },
47
+ },
48
+ "/posts/{abbrlink}.md": {
49
+ get: {
50
+ operationId: "readPostMarkdown",
51
+ summary: "Read one post as Markdown",
52
+ description: "Returns the normalized Markdown source for a published post.",
53
+ parameters: [
54
+ {
55
+ name: "abbrlink",
56
+ in: "path",
57
+ required: true,
58
+ description: "Stable post identifier returned by the post index.",
59
+ schema: { type: "string" },
60
+ },
61
+ ],
62
+ responses: {
63
+ "200": {
64
+ description: "Markdown post content",
65
+ content: { "text/markdown": { schema: { type: "string" } } },
66
+ },
67
+ "404": { description: "Post not found" },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ };
73
+
74
+ return new Response(JSON.stringify(document, null, 2), {
75
+ headers: { "Content-Type": "application/json; charset=utf-8" },
76
+ });
77
+ };
@@ -98,6 +98,7 @@ export const GET: APIRoute = async ({ props }) => {
98
98
  status: 200,
99
99
  headers: {
100
100
  "Content-Type": "text/markdown; charset=utf-8",
101
+ Vary: "Accept, Accept-Encoding",
101
102
  },
102
103
  });
103
104
  };
@@ -29,6 +29,9 @@ export const GET: APIRoute = async (context) => {
29
29
  const site = getSite(config, context.site?.toString());
30
30
  const text = renderTaxonomyPageMd(tag, posts, site, "tags", exportMd);
31
31
  return new Response(text, {
32
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
32
+ headers: {
33
+ "Content-Type": "text/markdown; charset=utf-8",
34
+ Vary: "Accept, Accept-Encoding",
35
+ },
33
36
  });
34
37
  };
@@ -22,6 +22,9 @@ export const GET: APIRoute = async (context) => {
22
22
  const map = buildTaxonomyMap(posts, "tags");
23
23
  const text = renderTaxonomyListMd(map, site, "tags", t("ai.allTags"));
24
24
  return new Response(text, {
25
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
25
+ headers: {
26
+ "Content-Type": "text/markdown; charset=utf-8",
27
+ Vary: "Accept, Accept-Encoding",
28
+ },
26
29
  });
27
30
  };
@@ -9,6 +9,9 @@ export const GET: APIRoute = async () => {
9
9
 
10
10
  const text = await renderWordsMd();
11
11
  return new Response(text, {
12
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
12
+ headers: {
13
+ "Content-Type": "text/markdown; charset=utf-8",
14
+ Vary: "Accept, Accept-Encoding",
15
+ },
13
16
  });
14
17
  };
@@ -468,6 +468,38 @@ export async function renderLlmsTxt(config: ConfigMap, site: string): Promise<st
468
468
  lines.push(buildMdLink("llms-full.txt", `${site}/llms-full.txt`, t("ai.llmsFullTxtDesc")));
469
469
  lines.push("");
470
470
 
471
+ // Developer resources and agent guidance. Keep these links predictable so an
472
+ // agent can discover the read API without scraping the HTML application shell.
473
+ lines.push("## Developer resources");
474
+ lines.push(
475
+ buildMdLink(
476
+ "OpenAPI specification",
477
+ `${site}/openapi.json`,
478
+ "Machine-readable description of the read-only blog API.",
479
+ ),
480
+ );
481
+ lines.push(
482
+ buildMdLink(
483
+ "Published post index",
484
+ `${site}/api/posts.json`,
485
+ "JSON metadata for published posts, including stable abbrlink identifiers.",
486
+ ),
487
+ );
488
+ lines.push(
489
+ buildMdLink(
490
+ "Post identifier index",
491
+ `${site}/api/post.abbrlink.json`,
492
+ "JSON mapping of published post titles to stable identifiers.",
493
+ ),
494
+ );
495
+ lines.push("");
496
+
497
+ lines.push("## When to use this site");
498
+ lines.push(
499
+ "Use the read API or Markdown endpoints when you need to search, cite, summarize, or retrieve the author's published technical articles. Start with `/openapi.json` or `/api/posts.json`, then fetch a specific `/posts/{abbrlink}.md` resource. The endpoints are read-only and do not require authentication.",
500
+ );
501
+ lines.push("");
502
+
471
503
  // Optional
472
504
  lines.push(`## ${t("ai.optional")}`);
473
505
  lines.push(buildMdLink(t("ai.rss"), `${site}/rss.xml`));