prestruct 1.0.0 → 1.1.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/init/package.json CHANGED
@@ -1,16 +1,21 @@
1
1
  {
2
2
  "name": "prestruct",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Build-time SEO prerender layer for Vite + React + Cloudflare Pages",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "vite build && node scripts/inject-brand.js && node scripts/prerender.js",
8
8
  "test": "vitest",
9
9
  "test:run": "vitest run",
10
- "test:ui": "vitest --ui",
11
10
  "lint": "eslint . --ext js,mjs",
12
11
  "lint:fix": "eslint . --ext js,mjs --fix"
13
12
  },
13
+ "overrides": {
14
+ "tinypool": "^2.2.0"
15
+ },
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
14
19
  "peerDependencies": {
15
20
  "react": ">=18",
16
21
  "react-dom": ">=18",
@@ -20,8 +25,8 @@
20
25
  "devDependencies": {
21
26
  "@vitejs/plugin-react": "^4.3.1",
22
27
  "eslint": "^9.0.0",
23
- "vite": "^5.4.0",
24
- "vitest": "^2.0.0",
28
+ "vite": "^6.4.5",
29
+ "vitest": "^5.0.5",
25
30
  "react": "^18.3.1",
26
31
  "react-dom": "^18.3.1",
27
32
  "react-router-dom": "^6.30.1"
@@ -272,6 +272,30 @@ export function cleanCache({ cacheFile = enginePaths().cacheFile } = {}) {
272
272
 
273
273
  // -- Sitemap -------------------------------------------------------------------
274
274
 
275
+ //
276
+ // AI-crawler policy preset (aeo option):
277
+ // Search-visibility bots get explicit Allow groups; TRAINING-crawl preference
278
+ // is a separate choice via config.robots.aiBots ('allow' | 'block', default
279
+ // 'block'). Bot names are the token each vendor documents for its own robots
280
+ // product (2026: OpenAI OAI-SearchBot/GPTBot, Anthropic
281
+ // Claude-SearchBot/ClaudeBot, PerplexityBot, Apple Applebot/Applebot-Extended,
282
+ // Common Crawl CCBot, Google-Extended)
283
+ const AEO_SEARCH_BOTS = [
284
+ 'OAI-SearchBot', // OpenAI: surfaces sites in ChatGPT search
285
+ 'PerplexityBot', // Perplexity: search index, not model training
286
+ 'Claude-SearchBot', // Anthropic: Claude's search
287
+ 'Applebot', // Apple: Siri/Spotlight/Safari search
288
+ 'Googlebot', // Google Search incl. AI Overviews eligibility
289
+ 'Bingbot', // Bing / Copilot discovery
290
+ ]
291
+ const AEO_TRAINING_BOTS = [
292
+ 'GPTBot', // OpenAI: model training crawl
293
+ 'ClaudeBot', // Anthropic: model development crawl
294
+ 'Google-Extended', // Gemini/Vertex grounding (product token, no HTTP hits)
295
+ 'Applebot-Extended', // Apple: foundation model training
296
+ 'CCBot', // Common Crawl: open corpus crawlers
297
+ ]
298
+
275
299
  /**
276
300
  * Generate robots.txt when config.robots is opted in; otherwise null so the
277
301
  * author's own public/robots.txt is left untouched. The Sitemap: line points
@@ -290,16 +314,25 @@ export function generateRobots(config) {
290
314
  const cfgBody = typeof cfg === 'string' ? cfg : (cfg.body || '')
291
315
  const cfgSitemap = cfgBool || (cfg && typeof cfg === 'object' ? cfg.sitemap !== false : true)
292
316
  const siteUrl = (config.siteUrl || '').replace(/\/$/, '')
293
-
294
- if (cfgBool) {
295
- const lines = [
296
- 'User-agent: *',
297
- 'Allow: /',
298
- ]
299
- if (cfgSitemap && siteUrl) {
300
- lines.push('', `Sitemap: ${siteUrl}/sitemap.xml`)
317
+ const sitemapLine = cfgSitemap && siteUrl ? ['', `Sitemap: ${siteUrl}/sitemap.xml`] : []
318
+
319
+ // { aeo: true | {} } -> search bots allowed, training crawls blocked by
320
+ // default. { aeo: { aiBots: 'allow' } } flips training to Allow. aeo with
321
+ // an explicit body: the body is appended after the preset groups (letting
322
+ // owners add extra paths) only via body; the preset itself ignores body.
323
+ const aeo = !cfgBool && cfg && cfg.aeo
324
+ const aeoCli = aeo ? (aeo.aiBots === 'allow' ? 'allow' : 'block') : null
325
+
326
+ if (cfgBool || aeo) {
327
+ const lines = []
328
+ if (cfgBool) {
329
+ lines.push('User-agent: *', 'Allow: /')
330
+ } else {
331
+ for (const bot of AEO_SEARCH_BOTS) lines.push(`User-agent: ${bot}`, 'Allow: /', '')
332
+ const verdict = aeoCli === 'allow' ? 'Allow: /' : 'Disallow: /'
333
+ for (const bot of AEO_TRAINING_BOTS) lines.push(`User-agent: ${bot}`, verdict, '')
301
334
  }
302
- return lines.join('\n') + '\n'
335
+ return lines.concat(sitemapLine).join('\n').replace(/\n+$/, '\n')
303
336
  }
304
337
 
305
338
  // Custom body; sitemap pointer appended unless { sitemap: false }
@@ -649,19 +682,21 @@ export async function prerender(cfg = config, { force = false, root } = {}) {
649
682
  console.log('[prerender] ok /robots.txt')
650
683
  }
651
684
 
652
- // Cache stats -- static-sites-safe monitor: a JSON snapshot beside dist/
653
- // (Intentionally not a runtime endpoint; static hosts serve files, not
654
- // processes. Follows the PRD "static-sites-safe option preferred" note.)
685
+ // Cache stats -- static-sites-safe monitor: a JSON snapshot in the
686
+ // project's .prestruct/ dir, NOT dist/. Build telemetry must not become a
687
+ // deployed URL (issue 23): dist/ is the public artifact on every static
688
+ // host, and the old wall-clock generatedAt made two byte-identical
689
+ // builds differ. Same monitor, invisible to deploys, no opt-out needed
690
+ // because there is nothing to opt out of shipping.
655
691
  const stats = {
656
- generatedAt: new Date().toISOString(),
657
692
  routeCount: ROUTES.length,
658
693
  succeeded,
659
694
  cached: Object.keys(cache).length,
660
695
  force,
661
696
  routes: ROUTES.map(r => ({ path: r.path, cached: Boolean(cache[r.path]) })),
662
697
  }
663
- fs.mkdirSync(path.join(p.dist, '.prestruct'), { recursive: true })
664
- fs.writeFileSync(path.join(p.dist, '.prestruct', 'status.json'), JSON.stringify(stats, null, 2), 'utf-8')
698
+ fs.mkdirSync(path.join(p.cacheDir, '..'), { recursive: true })
699
+ fs.writeFileSync(path.join(p.root, '.prestruct', 'status.json'), JSON.stringify(stats, null, 2), 'utf-8')
665
700
 
666
701
  console.log(`[prerender] Done. ${succeeded}/${ROUTES.length} pages rendered.\n`)
667
702
 
package/init/src/App.jsx CHANGED
@@ -9,7 +9,13 @@
9
9
  * homepage with no error message. See AGENTS.md for the full explanation.
10
10
  */
11
11
 
12
- import { BrowserRouter } from 'react-router-dom'
12
+ import * as rrdNS from 'react-router-dom'
13
+ // Neither plain named imports nor a bare namespace destructure survive
14
+ // both majors (see AppLayout.jsx). Destructure from whichever side of
15
+ // the interop carries the exports object.
16
+ const rrd = rrdNS.default ?? rrdNS
17
+ const { BrowserRouter } = rrd
18
+
13
19
  import AppLayout from './AppLayout.jsx'
14
20
 
15
21
  export default function App() {
@@ -20,7 +20,14 @@
20
20
  * runs in Node where these globals don't exist.
21
21
  */
22
22
 
23
- import { Routes, Route, useLocation } from 'react-router-dom'
23
+ import * as rrdNS from 'react-router-dom'
24
+ // Neither plain named imports nor a bare namespace destructure survive
25
+ // both majors: v7 ships CJS (named imports rejected under vite SSR; the
26
+ // node interop namespace only reliably carries `default`), v6 is ESM
27
+ // with possibly no default. Destructure from whichever has the exports.
28
+ const rrd = rrdNS.default ?? rrdNS
29
+ const { Routes, Route, useLocation } = rrd
30
+
24
31
  import { useEffect } from 'react'
25
32
  import { mountIslands } from './islands.js'
26
33
  import { islands } from './AppIslands.jsx'
@@ -43,7 +43,14 @@
43
43
  */
44
44
 
45
45
  import { useEffect } from 'react'
46
- import { useLocation } from 'react-router-dom'
46
+ import * as rrdNS from 'react-router-dom'
47
+ // Neither plain named imports nor a bare namespace destructure survive
48
+ // both majors: v7 ships CJS (vite SSR rejects named imports; node interop
49
+ // gives a namespace whose only reliable export is `default` -- the exports
50
+ // object), while v6 is ESM with possibly no default. Destructure from
51
+ // whichever carries the exports.
52
+ const rrd = rrdNS.default ?? rrdNS
53
+ const { useLocation } = rrd
47
54
 
48
55
  /** Update the SPA head tags on every route change (see the module head doc). */
49
56
  export default function usePageMeta({ title, description, path, siteUrl }) {
@@ -46,6 +46,14 @@ export default {
46
46
  // robots: true, // minimal block (all agents + Sitemap line)
47
47
  // robots: "User-agent: *\nDisallow: /private", // custom body; sitemap auto-appended
48
48
  // robots: { body: "...", sitemap: false }, // custom body, no sitemap line
49
+ //
50
+ // AEO preset -- per-bot crawl policy from the 2026 vendor docs:
51
+ // robots: { aeo: true }, // search AI bots Allow, training crawls Disallow
52
+ // robots: { aeo: { aiBots: 'allow' } }, // same, but also allow training crawls
53
+ // Search group: OAI-SearchBot, PerplexityBot, Claude-SearchBot, Applebot,
54
+ // Googlebot, Bingbot. Training group: GPTBot, ClaudeBot, Google-Extended,
55
+ // Applebot-Extended, CCBot. Search access and training/data-use are separate
56
+ // choices; the Sitemap line is appended as usual.
49
57
 
50
58
  // === Routes ===
51
59
  // Each route gets prerendered to static HTML at build time.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prestruct",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "AI-first SEO prerender for Vite + React apps. Drop in, config, build.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -14,6 +14,9 @@
14
14
  "./prerender": "./engine/scripts/prerender.js",
15
15
  "./inject-brand": "./engine/scripts/inject-brand.js",
16
16
  "./validate-config": "./engine/scripts/validate-config.js",
17
+ "./hooks": "./engine/src/hooks/usePageMeta.js",
18
+ "./islands": "./engine/src/islands.js",
19
+ "./engine/*": "./engine/*",
17
20
  "./package.json": "./package.json"
18
21
  },
19
22
  "files": [
@@ -34,7 +37,10 @@
34
37
  "e2e": "node tests/e2e-fixture.mjs"
35
38
  },
36
39
  "engines": {
37
- "node": ">=18"
40
+ "node": ">=20"
41
+ },
42
+ "overrides": {
43
+ "tinypool": "^2.2.0"
38
44
  },
39
45
  "peerDependencies": {
40
46
  "react": ">=18",
@@ -45,12 +51,11 @@
45
51
  "devDependencies": {
46
52
  "express": "^4.22.3",
47
53
  "happy-dom": "^21.0.4",
48
- "puppeteer": "^22.15.0",
49
54
  "react": "^18.3.1",
50
55
  "react-dom": "^18.3.1",
51
56
  "react-router-dom": "^6.30.1",
52
- "vite": "^5.4.0",
53
- "vitest": "^2.1.0"
57
+ "vite": "^6.4.5",
58
+ "vitest": "^5.0.5"
54
59
  },
55
60
  "repository": {
56
61
  "type": "git",