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/README.md +37 -10
- package/bin/cli.mjs +1 -1
- package/engine/scripts/prerender.js +50 -15
- package/engine/src/hooks/usePageMeta.js +8 -1
- package/init/VERSION +1 -1
- package/init/package-lock.json +371 -403
- package/init/package.json +9 -4
- package/init/scripts/prerender.js +50 -15
- package/init/src/App.jsx +7 -1
- package/init/src/AppLayout.jsx +8 -1
- package/init/src/hooks/usePageMeta.js +8 -1
- package/init/ssr.config.example.js +8 -0
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -99,6 +99,7 @@ after hydration.
|
|
|
99
99
|
| `bin/cli.mjs` | The `prestruct` executable (init/build/validate/clean). |
|
|
100
100
|
| `init/` | Project template `prestruct init` scaffolds, including `scripts/proxy.js` (VPS) and `proxy.worker.js` (Cloudflare Worker). |
|
|
101
101
|
| `example/` | Working demo site (8 routes), kept byte-identical to engine via `scripts/sync-engine.mjs`. |
|
|
102
|
+
| `packs/` | In-repo crashtest pack (`public/prestruct-proxy` v0.2.0) attacking the bot-proxy pair on both runtimes. |
|
|
102
103
|
| `docs/` | Full docs site, AGENTS-built with Pagefind. |
|
|
103
104
|
| `tests/` | vitest battery, including the engine sync contract test. |
|
|
104
105
|
|
|
@@ -118,31 +119,57 @@ CF Pages output; the two layers stack, they don't replace each other.
|
|
|
118
119
|
|
|
119
120
|
---
|
|
120
121
|
|
|
122
|
+
## Crash testing (in-repo pack)
|
|
123
|
+
|
|
124
|
+
`packs/public/prestruct-proxy` (pack v0.2.0) is this repo's own adversarial
|
|
125
|
+
pack for the bot-proxy pair above: four wire-level contracts (method matrix,
|
|
126
|
+
hostile-path fuzz, bot-render 302 fallback, refresh-secret strict parity),
|
|
127
|
+
run through the [crashtest](https://github.com/dhaupin/crashtest) harness
|
|
128
|
+
against both runtimes. Attack route names come from the host `moduleCtx`, so
|
|
129
|
+
the pack runs against any wiring. Latest results live in
|
|
130
|
+
[labs/hardening.md](./labs/hardening.md), the CI-comparable ledger in
|
|
131
|
+
[labs/crashledger.json](./labs/crashledger.json).
|
|
132
|
+
|
|
133
|
+
The pack travels with the checkout, not the npm tarball. Adopting it from
|
|
134
|
+
another repo is one line in that repo's `crashtest.config.js`:
|
|
135
|
+
`packs: ['/abs/path/to/prestruct/packs']` -- contract table and runbook in
|
|
136
|
+
[packs/README.md](./packs/README.md).
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
121
140
|
## Deploy
|
|
122
141
|
|
|
123
142
|
- Cloudflare Pages: [docs/guides/cloudflare.md](./docs/guides/cloudflare.md)
|
|
124
|
-
(`npm run build`, output `dist/`, Node
|
|
143
|
+
(`npm run build`, output `dist/`, Node 20+).
|
|
125
144
|
- Any static host works too: `dist/` is a plain static folder.
|
|
126
145
|
|
|
127
146
|
---
|
|
128
147
|
|
|
129
148
|
## Stack
|
|
130
149
|
|
|
131
|
-
- Vite 5+
|
|
150
|
+
- Vite 5+ (examples and the template battery build on Vite 6)
|
|
132
151
|
- React 18+
|
|
133
|
-
- React Router v6
|
|
134
|
-
|
|
152
|
+
- React Router v6 and v7 both work: v6 via the `./server.js` subpath, v7 via
|
|
153
|
+
the package-root fallback -- scaffold imports use the namespace+destructure
|
|
154
|
+
form so CJS v7 passes vite SSR (docs and generated HTML render from v7 live)
|
|
135
155
|
- Cloudflare Pages default; any static host otherwise
|
|
136
|
-
- Node
|
|
156
|
+
- Node 20+
|
|
137
157
|
|
|
138
158
|
---
|
|
139
159
|
|
|
140
160
|
## Publishing state
|
|
141
161
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
**prestruct is on npm: <https://www.npmjs.com/package/prestruct>** (1.1.0,
|
|
163
|
+
published via `publish.yml`, GitHub Actions OIDC trusted publishing with
|
|
164
|
+
npm provenance -- verify with `npm audit signatures`). Tags drive releases:
|
|
165
|
+
`v*` triggers npm publish plus the GitHub release archive (template zip with
|
|
166
|
+
a self-testing battery). `main` may be ahead of the latest tag; its version
|
|
167
|
+
pins are the next planned release.
|
|
168
|
+
|
|
169
|
+
Local install test from the registry tarball (35 files) passed: `init` never
|
|
170
|
+
clobbers, `build` produces full `dist/` (baked meta, sitemap, 404). The
|
|
171
|
+
release-body template inside `release.yml` was corrected in the v1.0.0
|
|
172
|
+
aftermath to match what the archive actually contains.
|
|
146
173
|
|
|
147
174
|
**Legal:** using prestruct means agreeing to [LICENSE](./LICENSE) and
|
|
148
175
|
[LEGAL.md](./LEGAL.md). Fair-Code: commercial SaaS use is restricted.
|
|
@@ -153,7 +180,7 @@ provenance is automatic and no stored npm token is needed.
|
|
|
153
180
|
|
|
154
181
|
- [AGENTS.md](./AGENTS.md) -- engineering history, gotchas, hard rules,
|
|
155
182
|
what's in/out of scope.
|
|
156
|
-
- [labs/prd.md](./labs/prd.md) -- roadmap.
|
|
183
|
+
- [labs/prd-prestruct.md](./labs/prd-prestruct.md) -- roadmap.
|
|
157
184
|
- [docs/](./docs/index.md) -- full site (SEO, routing, islands, proxy,
|
|
158
185
|
Cloudflare).
|
|
159
186
|
|
package/bin/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* bin/cli.mjs - prestruct CLI
|
|
4
4
|
* ============================
|
|
5
|
-
* Commands (spec'd in labs/prd.md Phase 2):
|
|
5
|
+
* Commands (spec'd in labs/prd-prestruct.md Phase 2):
|
|
6
6
|
*
|
|
7
7
|
* prestruct init [dir] Scaffold the template into dir (default: cwd).
|
|
8
8
|
* Never clobbers existing files.
|
|
@@ -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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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')
|
|
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
|
|
653
|
-
//
|
|
654
|
-
//
|
|
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.
|
|
664
|
-
fs.writeFileSync(path.join(p.
|
|
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
|
|
|
@@ -43,7 +43,14 @@
|
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
45
|
import { useEffect } from 'react'
|
|
46
|
-
import
|
|
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 }) {
|
package/init/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.1.0
|