@soloworks/smking-next 0.18.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @soloworks/smking-next
2
2
 
3
+ ## 0.20.0 — 2026-06-02
4
+
5
+ **`<SmkingCms>` now emits the structured-data engine's JSON-LD `@graph` inline.**
6
+
7
+ CMS pages get a full multi-entity `@graph` (BlogPosting / Organization /
8
+ WebSite / BreadcrumbList / FAQPage, `@id`-linked) generated by the smking
9
+ structured-data engine and served via `/api/v1/public/page`. `<SmkingCms>`
10
+ inlines it as `<script type="application/ld+json">` — JSON-LD is valid in
11
+ `<body>`, so no head hoisting needed (unlike `<title>`/`<meta>`, which
12
+ React 19 still hoists for you). The `safeJson` escaper (`</` + U+2028/U+2029)
13
+ is extracted into a shared `lib/safe-json.ts` that `<SmkingAEO>` imports too.
14
+
15
+ No customer code change — `<SmkingCms>` picks up the new `jsonLd` field
16
+ automatically once a page is republished under the engine. Bump your
17
+ constraint to `^0.20`.
18
+
19
+ ## 0.19.0 — 2026-05-29
20
+
21
+ **CMS base path reported via heartbeat — dashboard "View page" resolves real URLs without per-page visits.**
22
+
23
+ `getAeoContent()`'s heartbeat (`sdk_meta`) now sends `cms_base_path` from
24
+ `SMKING_CMS_PATH` (default `/blog`). smking stores it site-level and builds
25
+ "View page" links as `domain + base path + slug` — a page resolves the
26
+ moment any page on the site is crawled, instead of waiting for that specific
27
+ page to be visited (supersedes 0.18.0's per-slug URL reporting). Set
28
+ `SMKING_CMS_PATH` if your CMS catch-all is mounted somewhere other than
29
+ `/blog`.
30
+
3
31
  ## 0.18.0 — 2026-05-28
4
32
 
5
33
  **CMS live-URL reporting — `<SmkingCms>` now tells the dashboard each page's real public URL.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soloworks/smking-next",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "AI-native SEO (AEO) for Next.js — auto-inject JSON-LD, FAQ, AI summary, and SEO metadata so AI crawlers (ChatGPT, Perplexity, Google AI) can cite your pages.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/sillyleo/smking/tree/main/packages/smking-next",
@@ -1,5 +1,6 @@
1
1
  import type { DiscoverParams } from "../types";
2
2
  import { getAeoContent } from "../lib/client";
3
+ import { safeJson } from "../lib/safe-json";
3
4
 
4
5
  const SR_ONLY_STYLE: React.CSSProperties = {
5
6
  position: "absolute",
@@ -118,17 +119,3 @@ export async function SmkingAEO(props: SmkingAEOProps) {
118
119
  </>
119
120
  );
120
121
  }
121
-
122
- /**
123
- * Escape JSON for safe `<script>` embedding. Covers the three values
124
- * that can break out:
125
- * - `</` (any `</script>`-like sequence)
126
- * - U+2028 / U+2029 (treated as line terminators inside a JS string
127
- * literal, which would corrupt the embedded JSON)
128
- */
129
- function safeJson(value: Record<string, unknown>): string {
130
- return JSON.stringify(value)
131
- .replace(/<\//g, "<\\/")
132
- .replace(/\u2028/g, "\\u2028")
133
- .replace(/\u2029/g, "\\u2029");
134
- }
@@ -1,4 +1,5 @@
1
1
  import { getCmsPage } from "../lib/cms-client";
2
+ import { safeJson } from "../lib/safe-json";
2
3
  import type { CmsParams } from "../types";
3
4
 
4
5
  /**
@@ -72,6 +73,17 @@ export async function SmkingCms(props: CmsParams) {
72
73
  <link rel="canonical" href={seo.canonicalUrl} data-smking="cms" />
73
74
  )}
74
75
 
76
+ {/* Engine-precomputed @graph. JSON-LD is valid in <body>, so no
77
+ head hoisting needed (unlike title/meta above). */}
78
+ {data.page.jsonLd && (
79
+ <script
80
+ type="application/ld+json"
81
+ data-smking="cms"
82
+ // eslint-disable-next-line react/no-danger -- escaped via safeJson; engine output is schema-bounded
83
+ dangerouslySetInnerHTML={{ __html: safeJson(data.page.jsonLd) }}
84
+ />
85
+ )}
86
+
75
87
  {/* Raw passthrough — bodyHtml is SaaS-emitted Plate output (Tailwind
76
88
  utility + slate-* hooks + data-slate-* attrs all intact). No
77
89
  article shell, no canonical CSS injection: what the dashboard
package/src/lib/client.ts CHANGED
@@ -110,7 +110,11 @@ export async function getAeoContent(
110
110
  sdk_version: SDK_VERSION,
111
111
  app_env: process.env.NODE_ENV ?? null,
112
112
  host: safeHostname(url),
113
+ // Site-level CMS base path → smking dashboard "View page" URLs.
114
+ // Default "/blog"; override with SMKING_CMS_PATH (wizard sets it).
115
+ cms_base_path: process.env.SMKING_CMS_PATH ?? "/blog",
113
116
  },
117
+
114
118
  }),
115
119
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
116
120
  next: {
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Escape an object for safe `<script type="application/ld+json">`
3
+ * embedding. Covers the sequences that can break out of / corrupt the
4
+ * script:
5
+ * - `</` (any `</script>`-like sequence)
6
+ * - U+2028 / U+2029 (JS string-literal line terminators)
7
+ *
8
+ * The U+2028 / U+2029 patterns are built via `new RegExp("\\u2028", ...)`
9
+ * (escaped-backslash strings) so this source file stays pure ASCII — no
10
+ * invisible line-separator characters to corrupt on edit.
11
+ *
12
+ * Shared by `<SmkingAEO>` and `<SmkingCms>`. Engine output is
13
+ * schema-bounded, but this guard is mandatory defence-in-depth — never
14
+ * inline JSON-LD without it.
15
+ */
16
+ export function safeJson(value: Record<string, unknown>): string {
17
+ return JSON.stringify(value)
18
+ .replace(/<\//g, "<\\/")
19
+ .replace(new RegExp("\\u2028", "g"), "\\u2028")
20
+ .replace(new RegExp("\\u2029", "g"), "\\u2029");
21
+ }
package/src/types.ts CHANGED
@@ -191,6 +191,12 @@ export interface CmsPage {
191
191
  excerpt?: string | null;
192
192
  featuredImageUrl?: string | null;
193
193
  publishedAt: string | null;
194
+ /**
195
+ * v0.M1+ engine-precomputed schema.org @graph. `<SmkingCms>` inlines it
196
+ * as `<script type="application/ld+json">`. Null until the page is
197
+ * republished under the structured-data engine.
198
+ */
199
+ jsonLd?: Record<string, unknown> | null;
194
200
  }
195
201
 
196
202
  /**