@lownoise-studio/rendershield 0.3.1 → 1.0.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +58 -32
  2. package/CONTRIBUTING.md +41 -0
  3. package/README.md +209 -144
  4. package/SECURITY.md +25 -0
  5. package/dist/cli.d.ts +3 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +27 -19
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/build.d.ts +2 -0
  10. package/dist/commands/build.d.ts.map +1 -0
  11. package/dist/commands/build.js +10 -9
  12. package/dist/commands/build.js.map +1 -1
  13. package/dist/commands/init.d.ts +2 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/verify.d.ts +19 -0
  16. package/dist/commands/verify.d.ts.map +1 -0
  17. package/dist/commands/verify.js +57 -64
  18. package/dist/commands/verify.js.map +1 -1
  19. package/dist/core/generateRobots.d.ts +3 -0
  20. package/dist/core/generateRobots.d.ts.map +1 -0
  21. package/dist/core/generateSitemap.d.ts +3 -0
  22. package/dist/core/generateSitemap.d.ts.map +1 -0
  23. package/dist/core/generateWorker.d.ts +3 -0
  24. package/dist/core/generateWorker.d.ts.map +1 -0
  25. package/dist/core/generateWorker.js +75 -75
  26. package/dist/core/loadConfig.d.ts +3 -0
  27. package/dist/core/loadConfig.d.ts.map +1 -0
  28. package/dist/core/loadConfig.js +68 -25
  29. package/dist/core/loadConfig.js.map +1 -1
  30. package/dist/core/loadMarkdown.d.ts +3 -0
  31. package/dist/core/loadMarkdown.d.ts.map +1 -0
  32. package/dist/core/loadMarkdown.js +4 -3
  33. package/dist/core/loadMarkdown.js.map +1 -1
  34. package/dist/core/renderHtml.d.ts +3 -0
  35. package/dist/core/renderHtml.d.ts.map +1 -0
  36. package/dist/core/renderHtml.js +25 -10
  37. package/dist/core/renderHtml.js.map +1 -1
  38. package/dist/core/validateOutput.d.ts +28 -0
  39. package/dist/core/validateOutput.d.ts.map +1 -0
  40. package/dist/core/validateOutput.js +7 -1
  41. package/dist/core/validateOutput.js.map +1 -1
  42. package/dist/errors.d.ts +14 -0
  43. package/dist/errors.d.ts.map +1 -0
  44. package/dist/errors.js +24 -0
  45. package/dist/errors.js.map +1 -0
  46. package/dist/index.d.ts +20 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +19 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/types.d.ts +53 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +1 -1
  53. package/dist/types.js.map +1 -1
  54. package/docs/deploy-cloudflare.md +40 -14
  55. package/package.json +24 -2
  56. package/src/cli.ts +86 -75
  57. package/src/commands/build.ts +199 -185
  58. package/src/commands/verify.ts +266 -236
  59. package/src/core/generateWorker.ts +97 -97
  60. package/src/core/loadConfig.ts +261 -173
  61. package/src/core/loadMarkdown.ts +9 -3
  62. package/src/core/renderHtml.ts +36 -12
  63. package/src/core/validateOutput.ts +335 -328
  64. package/src/errors.ts +48 -0
  65. package/src/index.ts +40 -0
  66. package/src/types.ts +4 -1
@@ -37,6 +37,12 @@ At a high level:
37
37
  - Known crawlers are routed to prerendered HTML files
38
38
  - All other traffic passes through to your SPA unchanged
39
39
 
40
+ Every response from the Worker includes an **x-rendershield** header so routing is observable:
41
+
42
+ - **pass-through** — request was not rewritten (human or path not in rewrite bases)
43
+ - **bot-hit** — bot request was rewritten and prerendered HTML was served
44
+ - **bot-fallback** — bot request was rewritten but the origin returned non-200; Worker fell back to the SPA
45
+
40
46
  If prerendered output is missing or incomplete, RenderShield fails the build.
41
47
 
42
48
  ---
@@ -104,7 +110,7 @@ The Worker must be able to fetch prerendered files.
104
110
 
105
111
  You need a static origin that serves paths like:
106
112
 
107
- - /content/example/index.html
113
+ - /blog/example/index.html (or your configured route base)
108
114
  - /sitemap.xml
109
115
  - /robots.txt
110
116
 
@@ -132,36 +138,56 @@ Submit the sitemap URL in Google Search Console.
132
138
 
133
139
  ## Verification
134
140
 
135
- RenderShield includes a verify command to guide testing.
141
+ ### Production verification (recommended)
142
+
143
+ After deploying the Worker and hosting prerendered output, run:
144
+
145
+ ```bash
146
+ rendershield verify --prod https://yourdomain.com
147
+ ```
148
+
149
+ This command:
136
150
 
137
- ### Worker execution test
151
+ - Fetches the URL as Googlebot
152
+ - Asserts the response has **x-rendershield: bot-hit** (proving the Worker served prerendered HTML)
153
+ - Validates metadata, JSON-LD, and article content
154
+ - Exits with code 1 if the header is missing, is `bot-fallback`, or the contract fails
138
155
 
139
- Run:
156
+ If it passes, crawlers are receiving the prerendered HTML.
140
157
 
141
- curl -I -H "User-Agent: GPTBot" https://yourdomain.com/content/example
158
+ ### Manual checks (optional)
142
159
 
143
- If debug headers are enabled, you should see headers similar to:
160
+ Check the response header:
161
+
162
+ ```bash
163
+ curl -I -H "User-Agent: Googlebot" https://yourdomain.com/blog/example
164
+ ```
165
+
166
+ You should see **x-rendershield: bot-hit**. If debug headers are enabled in config, you may also see:
144
167
 
145
168
  - X-Bot-Detected: true
146
169
  - X-Prerender: true
147
- - X-Final-Path: /content/example/index.html
170
+ - X-Final-Path: /blog/example/index.html
171
+
172
+ If **x-rendershield** is missing or **bot-fallback**:
148
173
 
149
- If these headers are missing:
150
174
  - The Worker route may not be attached
151
175
  - The Cloudflare proxy may be disabled
152
- - DNS may still point to a different origin
153
-
154
- ---
176
+ - The prerendered origin may be returning non-200 for that path
155
177
 
156
- ## Content comparison
178
+ ### Content comparison
157
179
 
158
180
  Human request (SPA response):
159
181
 
160
- curl -s https://yourdomain.com/content/example | grep -i "<title>"
182
+ ```bash
183
+ curl -s https://yourdomain.com/blog/example | grep -i "<title>"
184
+ ```
161
185
 
162
186
  Crawler request (prerendered HTML):
163
187
 
164
- curl -s -H "User-Agent: Googlebot" https://yourdomain.com/content/example | grep -i "<title>"
188
+ ```bash
189
+ curl -s -H "User-Agent: Googlebot" https://yourdomain.com/blog/example | grep -i "<title>"
190
+ ```
165
191
 
166
192
  The crawler response should contain the prerendered, route-specific title.
167
193
 
package/package.json CHANGED
@@ -1,12 +1,33 @@
1
1
  {
2
2
  "name": "@lownoise-studio/rendershield",
3
- "version": "0.3.1",
3
+ "version": "1.0.0",
4
4
  "description": "Boring bot-aware prerendering: real HTML for bots, SPA for humans.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "engines": {
8
+ "node": ">=18"
9
+ },
10
+ "keywords": [
11
+ "prerender",
12
+ "seo",
13
+ "bots",
14
+ "spa",
15
+ "static-site",
16
+ "cloudflare-workers",
17
+ "markdown"
18
+ ],
7
19
  "bin": {
8
20
  "rendershield": "dist/cli.js"
9
21
  },
22
+ "main": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
10
31
  "scripts": {
11
32
  "build": "tsc -p tsconfig.json",
12
33
  "dev": "node --enable-source-maps dist/cli.js",
@@ -19,10 +40,11 @@
19
40
  "dist/**",
20
41
  "src/**",
21
42
  "docs/**",
22
- "content/**",
23
43
  "DEPLOY.md",
24
44
  "README.md",
25
45
  "CHANGELOG.md",
46
+ "CONTRIBUTING.md",
47
+ "SECURITY.md",
26
48
  "LICENSE"
27
49
  ],
28
50
  "repository": {
package/src/cli.ts CHANGED
@@ -1,75 +1,86 @@
1
- #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
- import { cmdInit } from "./commands/init.js";
4
- import { cmdBuild } from "./commands/build.js";
5
- import { cmdVerify } from "./commands/verify.js";
6
-
7
- const require = createRequire(import.meta.url);
8
- const pkg = require("../package.json") as { version?: string };
9
- const VERSION = pkg.version ?? "0.0.0";
10
-
11
- function printHelp() {
12
- console.log(`
13
- RenderShield v${VERSION} — boring bot-aware prerendering.
14
-
15
- Usage:
16
- rendershield init
17
- rendershield build
18
- rendershield verify [--prod <url>]
19
-
20
- verify Print curl commands for local/build output.
21
- verify --prod Fetch URL as bot and human; verify bot sees full HTML and contract fields.
22
- Requires x-rendershield: bot-hit from the Worker.
23
-
24
- Notes:
25
- - Config file: rendershield.config.json
26
- - Content: content/<collection>/**/*.md (frontmatter required)
27
- - Output: dist-prerender/
28
- `);
29
- }
30
-
31
- async function main() {
32
- const args = process.argv.slice(2);
33
- const cmd = args[0]?.trim();
34
-
35
- if (!cmd || cmd === "-h" || cmd === "--help") {
36
- printHelp();
37
- process.exit(0);
38
- }
39
- if (cmd === "-V" || cmd === "--version") {
40
- console.log(VERSION);
41
- process.exit(0);
42
- }
43
-
44
- try {
45
- if (cmd === "init") {
46
- await cmdInit();
47
- return;
48
- }
49
- if (cmd === "build") {
50
- await cmdBuild();
51
- return;
52
- }
53
- if (cmd === "verify") {
54
- const verifyArgs = args.slice(1);
55
- if (verifyArgs.includes("-h") || verifyArgs.includes("--help")) {
56
- printHelp();
57
- process.exit(0);
58
- }
59
- const prodIdx = verifyArgs.indexOf("--prod");
60
- const prodUrl = prodIdx >= 0 && verifyArgs[prodIdx + 1] ? verifyArgs[prodIdx + 1].trim() : undefined;
61
- await cmdVerify(undefined, prodUrl ? { prodUrl } : undefined);
62
- return;
63
- }
64
-
65
- console.error(`Unknown command: ${cmd}\n`);
66
- printHelp();
67
- process.exit(1);
68
- } catch (err: unknown) {
69
- const msg = err instanceof Error ? err.message : String(err);
70
- console.error(`\nRenderShield error: ${msg}\n`);
71
- process.exit(1);
72
- }
73
- }
74
-
75
- main();
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { cmdInit } from "./commands/init.js";
4
+ import { cmdBuild } from "./commands/build.js";
5
+ import { cmdVerify } from "./commands/verify.js";
6
+ import { formatCliError, renderShieldError } from "./errors.js";
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const pkg = require("../package.json") as { version?: string };
10
+ const VERSION = pkg.version ?? "0.0.0";
11
+
12
+ function printHelp() {
13
+ console.log(`
14
+ RenderShield v${VERSION} — boring bot-aware prerendering.
15
+
16
+ Usage:
17
+ rendershield init
18
+ rendershield build
19
+ rendershield verify [--prod <url>]
20
+
21
+ verify Print curl commands for local/build output.
22
+ verify --prod Fetch URL as bot and human; verify bot sees full HTML and contract fields.
23
+ Requires x-rendershield: bot-hit from the Worker.
24
+
25
+ Notes:
26
+ - Config file: rendershield.config.json
27
+ - Content: content/<collection>/**/*.md (frontmatter required)
28
+ - Output: dist-prerender/
29
+ `);
30
+ }
31
+
32
+ async function main() {
33
+ const args = process.argv.slice(2);
34
+ const cmd = args[0]?.trim();
35
+
36
+ if (!cmd || cmd === "-h" || cmd === "--help") {
37
+ printHelp();
38
+ process.exit(0);
39
+ }
40
+ if (cmd === "-V" || cmd === "--version") {
41
+ console.log(VERSION);
42
+ process.exit(0);
43
+ }
44
+
45
+ try {
46
+ if (cmd === "init") {
47
+ await cmdInit();
48
+ return;
49
+ }
50
+ if (cmd === "build") {
51
+ await cmdBuild();
52
+ return;
53
+ }
54
+ if (cmd === "verify") {
55
+ const verifyArgs = args.slice(1);
56
+ if (verifyArgs.includes("-h") || verifyArgs.includes("--help")) {
57
+ printHelp();
58
+ process.exit(0);
59
+ }
60
+ const prodIdx = verifyArgs.indexOf("--prod");
61
+ if (prodIdx >= 0) {
62
+ const prodUrl = verifyArgs[prodIdx + 1]?.trim();
63
+ if (!prodUrl || prodUrl.startsWith("-")) {
64
+ throw renderShieldError(
65
+ "CLI_INVALID_ARGS",
66
+ "verify --prod requires a URL. Example: rendershield verify --prod https://example.com/blog/hello-world"
67
+ );
68
+ }
69
+ await cmdVerify(undefined, { prodUrl });
70
+ return;
71
+ }
72
+ await cmdVerify();
73
+ return;
74
+ }
75
+
76
+ console.error(`Unknown command: ${cmd}\n`);
77
+ printHelp();
78
+ process.exit(1);
79
+ } catch (err: unknown) {
80
+ const msg = formatCliError(err);
81
+ console.error(`\nRenderShield error: ${msg}\n`);
82
+ process.exit(1);
83
+ }
84
+ }
85
+
86
+ main();