@soloworks/smking-next 0.12.0 → 0.12.1

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,15 @@
1
1
  # @soloworks/smking-next
2
2
 
3
+ ## 0.12.1 — 2026-05-21
4
+
5
+ **Fix: `npx @soloworks/smking-next doctor` failed for all customers.**
6
+
7
+ ### Fixed
8
+
9
+ - `bin/install.ts` shipped as raw TypeScript on the assumption that Node 22+'s `--experimental-strip-types` would handle it. It doesn't — by design, Node excludes files under `node_modules/` from type-stripping (packages should ship compiled JS). Every `npx @soloworks/smking-next install|doctor` invocation failed with "Type-Stripping is not supported for files under node_modules" the moment a customer's install reached the doctor check. smking-wizard reproduced the bug 3× and filed ticket `dr_e0b7d8e924cd`.
10
+ - Renamed to `bin/install.mjs` with TypeScript syntax stripped (JSDoc type hints retained for editor / dev ergonomics). Pure ESM, no build step, no runtime type stripping needed.
11
+ - Source modules (`src/*.ts`) unchanged — Next.js's bundler handles them at customer build time. Only the standalone-executable bin script needed JS form.
12
+
3
13
  ## 0.12.0 — 2026-05-15
4
14
 
5
15
  **AI crawler + AI referral telemetry — `smkingProxy` Next.js middleware.**
package/README.md CHANGED
@@ -84,6 +84,71 @@ Content-Type: application/json
84
84
 
85
85
  Response: `{ revalidated: number, errors: number }`. `errors > 0` means some tags couldn't be revalidated (others still succeeded — partial-success delivery).
86
86
 
87
+ ## CMS rendering (optional, v0.11.0+)
88
+
89
+ The base install only wires AEO. If you author content in the smking dashboard's CMS and want to render it on your Next.js site, use the `<SmkingCms slug="…" />` Server Component.
90
+
91
+ The SDK **does not have a "CMS root" config** — you choose any URL prefix (`/blog`, `/knowledge`, `/shop/articles`) and wire your own route. The component takes a `slug` prop, fetches the published page from `${SMKING_BASE_URL}/api/v1/public/page?slug=…`, and renders the Tiptap ProseMirror JSON as `<article class="smk-cms">…</article>` via `@tiptap/static-renderer/pm/react`. SEO `<title>` / `<meta>` / `og:*` / canonical hoist into `<head>` automatically via React 19.
92
+
93
+ ### Catch-all route (handles flat + nested slugs)
94
+
95
+ smking CMS slugs can be nested — e.g. `blog/123`, `blog/seo/intro`. Use Next.js catch-all `[...slug]` (three dots, not single `[slug]`) so one route handles every depth:
96
+
97
+ ```tsx
98
+ // app/blog/[...slug]/page.tsx
99
+ import { SmkingCms } from "@soloworks/smking-next/cms";
100
+
101
+ export default async function Page({
102
+ params,
103
+ }: {
104
+ params: Promise<{ slug: string[] }>;
105
+ }) {
106
+ const { slug } = await params;
107
+ return (
108
+ <SmkingCms
109
+ apiKey={process.env.SMKING_API_KEY!}
110
+ slug={slug.join("/")} // ← array → "blog/seo/intro" matches dashboard slug format
111
+ />
112
+ );
113
+ }
114
+ ```
115
+
116
+ `[...slug]` accepts both flat (`/blog/hello` → `["hello"]`) and nested (`/blog/seo/intro` → `["seo", "intro"]`). The `.join("/")` reconstructs the dashboard slug string.
117
+
118
+ Single-bracket `[slug]` (without the three dots) **only matches one segment** — pick this if you know your slugs are always flat.
119
+
120
+ ### Markup contract for CSS
121
+
122
+ ```html
123
+ <article class="smk-cms" data-smking="cms">
124
+ <h1 class="smk-cms__title">…</h1>
125
+ <p>standard prose</p>
126
+ <h2>headings</h2>
127
+ <ul><li>lists</li></ul>
128
+ <blockquote>…</blockquote>
129
+ <pre><code>code blocks</code></pre>
130
+ <a href="…">links</a>
131
+ <img src="…" alt="…">
132
+ <div data-type="gallery"
133
+ data-layout="grid"
134
+ data-columns="3"
135
+ class="smk-gallery smk-gallery--grid">
136
+ <figure class="smk-gallery__item">
137
+ <img src="…" alt="…" loading="lazy">
138
+ <figcaption>optional</figcaption>
139
+ </figure>
140
+ </div>
141
+ </article>
142
+ ```
143
+
144
+ Standard nodes inherit your site's prose styling. Gallery is the only opinionated structure — provide CSS for `.smk-gallery` (typically a grid with `grid-template-columns: repeat(var(--smk-gallery-cols, 3), 1fr)` since the SDK injects `--smk-gallery-cols` inline).
145
+
146
+ ### Cache invalidation
147
+
148
+ CMS responses cache for 5 minutes by default via Next.js data cache (`tags: ["smking:cms_page:<slug>"]`). When you publish, rename, or archive a page in the dashboard, smking SaaS POSTs a signed webhook to `https://<your-site>/api/smking/webhook` (mount with one-line re-export — see Install). The handler verifies HMAC against `SMKING_WEBHOOK_SECRET` and calls `revalidateTag` so the next visitor reads fresh content.
149
+
150
+ If `SMKING_WEBHOOK_SECRET` is unset, the webhook route returns 503 and cache invalidation falls back to TTL-based expiry.
151
+
87
152
  ## Versions
88
153
 
89
154
  See [CHANGELOG.md](./CHANGELOG.md). Aligned with `smking/laravel` for SaaS-side parity.
@@ -2,8 +2,14 @@
2
2
  /**
3
3
  * `npx @soloworks/smking-next [install|doctor] [--json]`
4
4
  *
5
- * Two subcommands share this entry point (Node 22+ strips TS at runtime, so
6
- * the package ships .ts source directly — no build step):
5
+ * Two subcommands share this entry point. The file ships as plain ESM
6
+ * (`.mjs`) since Node's `--experimental-strip-types` is excluded from
7
+ * files under `node_modules` by design — so `bin/install.ts` (the
8
+ * previous v0.12.0 layout) fails with "Type-Stripping is not supported
9
+ * for files under node_modules" the moment a customer runs `npx
10
+ * @soloworks/smking-next`. Source modules (`src/*.ts`) are still TS —
11
+ * Next.js's bundler handles them at customer build time. Only this
12
+ * standalone-executable bin script needed to drop TS.
7
13
  *
8
14
  * - **install** (default) — One-shot scaffold for the three takeover
9
15
  * drop-in files (sitemap.ts / robots.ts / llms.txt route). Idempotent:
@@ -19,36 +25,32 @@
19
25
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
20
26
  import { join } from "node:path";
21
27
 
22
- // ── install (既有功能) ──────────────────────────────────────────
28
+ // ── install (file scaffold) ─────────────────────────────────────
23
29
 
24
- interface FileSpec {
25
- path: string; // relative to detected app root
26
- content: string;
27
- label: string;
28
- }
30
+ /**
31
+ * @typedef {{ path: string, content: string, label: string }} FileSpec
32
+ */
29
33
 
30
- const FILES: FileSpec[] = [
34
+ /** @type {FileSpec[]} */
35
+ const FILES = [
31
36
  {
32
37
  path: "sitemap.ts",
33
38
  label: "sitemap.ts → /sitemap.xml",
34
- content:
35
- 'export { default } from "@soloworks/smking-next/sitemap";\n',
39
+ content: 'export { default } from "@soloworks/smking-next/sitemap";\n',
36
40
  },
37
41
  {
38
42
  path: "robots.ts",
39
43
  label: "robots.ts → /robots.txt",
40
- content:
41
- 'export { default } from "@soloworks/smking-next/robots";\n',
44
+ content: 'export { default } from "@soloworks/smking-next/robots";\n',
42
45
  },
43
46
  {
44
47
  path: "llms.txt/route.ts",
45
48
  label: "llms.txt/route.ts → /llms.txt",
46
- content:
47
- 'export { GET } from "@soloworks/smking-next/llms-txt";\n',
49
+ content: 'export { GET } from "@soloworks/smking-next/llms-txt";\n',
48
50
  },
49
51
  ];
50
52
 
51
- function detectAppDir(): string | null {
53
+ function detectAppDir() {
52
54
  const candidates = ["app", "src/app"];
53
55
  for (const c of candidates) {
54
56
  if (existsSync(c)) return c;
@@ -56,7 +58,7 @@ function detectAppDir(): string | null {
56
58
  return null;
57
59
  }
58
60
 
59
- function runInstall(): number {
61
+ function runInstall() {
60
62
  const appDir = detectAppDir();
61
63
  if (!appDir) {
62
64
  console.error(
@@ -90,15 +92,18 @@ function runInstall(): number {
90
92
  return 0;
91
93
  }
92
94
 
93
- // ── doctor (新增) ──────────────────────────────────────────────
95
+ // ── doctor (self-check) ─────────────────────────────────────────
94
96
 
95
- interface DoctorCheck {
96
- name: string;
97
- status: "pass" | "fail" | "info";
98
- detail: string;
99
- }
97
+ /**
98
+ * @typedef {{ name: string, status: "pass" | "fail" | "info", detail: string }} DoctorCheck
99
+ */
100
100
 
101
- function checkEnv(name: string, prefix?: string): DoctorCheck {
101
+ /**
102
+ * @param {string} name
103
+ * @param {string} [prefix]
104
+ * @returns {DoctorCheck}
105
+ */
106
+ function checkEnv(name, prefix) {
102
107
  const value = process.env[name];
103
108
  if (!value) {
104
109
  return {
@@ -121,7 +126,11 @@ function checkEnv(name: string, prefix?: string): DoctorCheck {
121
126
  };
122
127
  }
123
128
 
124
- function checkLayoutUsage(appDir: string): DoctorCheck {
129
+ /**
130
+ * @param {string} appDir
131
+ * @returns {DoctorCheck}
132
+ */
133
+ function checkLayoutUsage(appDir) {
125
134
  // Mirror Next.js's layout file resolution. Order matters — TS > JS to
126
135
  // match what Next.js itself would pick.
127
136
  const candidates = [
@@ -140,9 +149,6 @@ function checkLayoutUsage(appDir: string): DoctorCheck {
140
149
  }
141
150
 
142
151
  const content = readFileSync(layoutPath, "utf-8");
143
- // Substring check is intentional rather than AST parsing. A customer
144
- // commenting it out or aliasing the import shows up as fail/info either
145
- // way, and AST adds a TypeScript parser dependency for trivial value.
146
152
  if (!content.includes("SmkingAEO")) {
147
153
  return {
148
154
  name: "<SmkingAEO /> in root layout",
@@ -157,7 +163,10 @@ function checkLayoutUsage(appDir: string): DoctorCheck {
157
163
  };
158
164
  }
159
165
 
160
- async function checkApiReachable(): Promise<DoctorCheck> {
166
+ /**
167
+ * @returns {Promise<DoctorCheck>}
168
+ */
169
+ async function checkApiReachable() {
161
170
  const apiKey = process.env.SMKING_API_KEY;
162
171
  const baseUrl = process.env.SMKING_BASE_URL;
163
172
  if (!apiKey || !baseUrl) {
@@ -222,16 +231,21 @@ async function checkApiReachable(): Promise<DoctorCheck> {
222
231
  }
223
232
  }
224
233
 
225
- async function runDoctor(jsonOutput: boolean): Promise<number> {
234
+ /**
235
+ * @param {boolean} jsonOutput
236
+ * @returns {Promise<number>}
237
+ */
238
+ async function runDoctor(jsonOutput) {
226
239
  const appDir = detectAppDir();
227
- const checks: DoctorCheck[] = [
240
+ /** @type {DoctorCheck[]} */
241
+ const checks = [
228
242
  checkEnv("SMKING_API_KEY", "pk_"),
229
243
  checkEnv("SMKING_BASE_URL", "http"),
230
244
  appDir
231
245
  ? checkLayoutUsage(appDir)
232
246
  : {
233
247
  name: "<SmkingAEO /> in root layout",
234
- status: "info" as const,
248
+ status: "info",
235
249
  detail: "no app/ or src/app/ directory found — skipped",
236
250
  },
237
251
  await checkApiReachable(),
@@ -272,7 +286,7 @@ async function runDoctor(jsonOutput: boolean): Promise<number> {
272
286
 
273
287
  // ── Entry dispatcher ────────────────────────────────────────────
274
288
 
275
- async function main(): Promise<number> {
289
+ async function main() {
276
290
  const subcommand = process.argv[2];
277
291
  const jsonOutput = process.argv.includes("--json");
278
292
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soloworks/smking-next",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
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",
@@ -41,7 +41,7 @@
41
41
  }
42
42
  },
43
43
  "bin": {
44
- "smking-next": "./bin/install.ts"
44
+ "smking-next": "./bin/install.mjs"
45
45
  },
46
46
  "files": [
47
47
  "src",