prestruct 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +159 -2
- package/bin/cli.mjs +212 -0
- package/engine/scripts/inject-brand.js +430 -0
- package/engine/scripts/prerender.js +726 -0
- package/engine/scripts/validate-config.js +117 -0
- package/engine/src/hooks/usePageMeta.js +78 -0
- package/engine/src/islands.js +129 -0
- package/engine/vite.config.js +18 -0
- package/init/VERSION +1 -0
- package/init/ecosystem.cjs +45 -0
- package/init/eslint.config.js +30 -0
- package/init/index.html +42 -0
- package/init/package-lock.json +3183 -0
- package/init/package.json +29 -0
- package/init/proxy.package.json +21 -0
- package/init/public/_headers +31 -0
- package/init/public/_redirects +16 -0
- package/init/scripts/inject-brand.js +430 -0
- package/init/scripts/prerender.js +726 -0
- package/init/scripts/prestruct-proxy.service +47 -0
- package/init/scripts/proxy.js +373 -0
- package/init/scripts/proxy.worker.js +217 -0
- package/init/scripts/validate-config.js +117 -0
- package/init/src/App.jsx +21 -0
- package/init/src/AppIslands.jsx +39 -0
- package/init/src/AppLayout.jsx +66 -0
- package/init/src/hooks/usePageMeta.js +78 -0
- package/init/src/islands.js +129 -0
- package/init/src/main.jsx +35 -0
- package/init/ssr.config.example.js +151 -0
- package/init/vite.config.js +18 -0
- package/init/vitest.config.js +14 -0
- package/init/wrangler.toml +32 -0
- package/package.json +70 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Creadev.org (AUTHOR: dhaupin)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,160 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Prestruct
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Build-time SEO prerender for Vite + React.** Validate your `ssr.config.js`,
|
|
4
|
+
build, inject meta, prerender every route to static HTML. No framework
|
|
5
|
+
migration, no runtime SSR.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# In an existing Vite + React project:
|
|
13
|
+
npm i -D prestruct
|
|
14
|
+
npx prestruct init # never clobbers existing files
|
|
15
|
+
cp ssr.config.example.js ssr.config.js # then edit siteUrl + routes
|
|
16
|
+
npx prestruct build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Build runs the whole pipeline: validate -> `vite build` -> inject meta ->
|
|
20
|
+
prerender routes. Output lands in `dist/` with one `index.html` per route,
|
|
21
|
+
`sitemap.xml`, and `404.html`.
|
|
22
|
+
|
|
23
|
+
**Live demo:** https://prestruct.creadev.org (built with prestruct; source in
|
|
24
|
+
`example/`).
|
|
25
|
+
|
|
26
|
+
Prefer raw npm scripts over the CLI? The `init/` template scaffolds
|
|
27
|
+
`scripts/prerender.js` + `scripts/inject-brand.js` you can wire into
|
|
28
|
+
`package.json` directly -- see [docs/guides/cli.md](./docs/guides/cli.md).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Config
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
// ssr.config.js
|
|
36
|
+
export default {
|
|
37
|
+
siteUrl: 'https://yoursite.com',
|
|
38
|
+
siteName: 'Your Site',
|
|
39
|
+
routes: [
|
|
40
|
+
{
|
|
41
|
+
path: '/',
|
|
42
|
+
priority: '1.0',
|
|
43
|
+
meta: {
|
|
44
|
+
title: 'Home | Your Site',
|
|
45
|
+
description: 'Homepage description (150-160 chars).',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{ path: '/about', priority: '0.8', meta: { title: 'About | Your Site' } },
|
|
49
|
+
],
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Full reference (platform, islands, JSON-LD, `fetchRoutes()`, deployId footer,
|
|
54
|
+
`PRESTRUCT_STRICT`): [docs/guides/config.md](./docs/guides/config.md).
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## CLI
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx prestruct init # scaffold template files, never clobber
|
|
62
|
+
npx prestruct build # validate -> vite build -> inject -> prerender
|
|
63
|
+
npx prestruct validate # check ssr.config.js; exit 1 on bad config
|
|
64
|
+
npx prestruct clean # clear the prerender route cache
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Incremental builds: `.prestruct/cache/` stores rendered HTML; unchanged routes
|
|
68
|
+
are skipped on rebuild unless the shell changes. Fix a config mistake silently?
|
|
69
|
+
Rebuild with a validation pass first (`npx prestruct validate` prints errors
|
|
70
|
+
line-by-line).
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Architecture
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
ssr.config.js
|
|
78
|
+
|
|
|
79
|
+
v
|
|
80
|
+
prestruct build (or npm run build in the template)
|
|
81
|
+
|
|
|
82
|
+
|- validate structured [prestruct] diagnostics
|
|
83
|
+
|- vite build JS/CSS bundles in dist/assets/
|
|
84
|
+
|- inject-brand meta + OG + JSON-LD into dist/index.html
|
|
85
|
+
`- prerender vite.ssrLoadModule per route -> static HTML
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Static HTML is served directly at deploy time (Cloudflare Pages default). No
|
|
89
|
+
runtime SSR. Client-only dynamic content mounts into `<pre-island>` elements
|
|
90
|
+
after hydration.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Repo layout (this monorepos the npm package)
|
|
95
|
+
|
|
96
|
+
| Path | Purpose |
|
|
97
|
+
|------|---------|
|
|
98
|
+
| `engine/` | Single source of truth for `prerender.js`, `inject-brand.js`, `validate-config.js`, islands + usePageMeta. |
|
|
99
|
+
| `bin/cli.mjs` | The `prestruct` executable (init/build/validate/clean). |
|
|
100
|
+
| `init/` | Project template `prestruct init` scaffolds, including `scripts/proxy.js` (VPS) and `proxy.worker.js` (Cloudflare Worker). |
|
|
101
|
+
| `example/` | Working demo site (8 routes), kept byte-identical to engine via `scripts/sync-engine.mjs`. |
|
|
102
|
+
| `docs/` | Full docs site, AGENTS-built with Pagefind. |
|
|
103
|
+
| `tests/` | vitest battery, including the engine sync contract test. |
|
|
104
|
+
|
|
105
|
+
If you touch `engine/`, run `node scripts/sync-engine.mjs` or CI will fail the
|
|
106
|
+
byte-identical check. Details in [AGENTS.md](./AGENTS.md).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Bot proxy (optional, additive)
|
|
111
|
+
|
|
112
|
+
Prerendered HTML covers every route in `ssr.config.js`. Everything else serves
|
|
113
|
+
the SPA shell. The optional render proxy intercepts bots and serves live
|
|
114
|
+
Puppeteer HTML for the rest -- deploy it on a cheap VPS
|
|
115
|
+
([README-proxy.md](./README-proxy.md), Option A: Node + Puppeteer) or as a
|
|
116
|
+
Cloudflare Worker with Browser Rendering (Option B). Humans always get static
|
|
117
|
+
CF Pages output; the two layers stack, they don't replace each other.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Deploy
|
|
122
|
+
|
|
123
|
+
- Cloudflare Pages: [docs/guides/cloudflare.md](./docs/guides/cloudflare.md)
|
|
124
|
+
(`npm run build`, output `dist/`, Node 18+).
|
|
125
|
+
- Any static host works too: `dist/` is a plain static folder.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Stack
|
|
130
|
+
|
|
131
|
+
- Vite 5+
|
|
132
|
+
- React 18+
|
|
133
|
+
- React Router v6 (v7 works -- engine falls back to the package root when the
|
|
134
|
+
`./server.js` subpath is absent)
|
|
135
|
+
- Cloudflare Pages default; any static host otherwise
|
|
136
|
+
- Node 18+
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Publishing state
|
|
141
|
+
|
|
142
|
+
`package.json` is at 1.0.0 but **not tagged and not published**. `publish.yml`
|
|
143
|
+
(OIDC trusted publishing on `v*` tags) is wired and dormant -- it
|
|
144
|
+
fires nothing until the first tag exists, by owner decision. When it fires,
|
|
145
|
+
provenance is automatic and no stored npm token is needed.
|
|
146
|
+
|
|
147
|
+
**Legal:** using prestruct means agreeing to [LICENSE](./LICENSE) and
|
|
148
|
+
[LEGAL.md](./LEGAL.md). Fair-Code: commercial SaaS use is restricted.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Read next
|
|
153
|
+
|
|
154
|
+
- [AGENTS.md](./AGENTS.md) -- engineering history, gotchas, hard rules,
|
|
155
|
+
what's in/out of scope.
|
|
156
|
+
- [labs/prd.md](./labs/prd.md) -- roadmap.
|
|
157
|
+
- [docs/](./docs/index.md) -- full site (SEO, routing, islands, proxy,
|
|
158
|
+
Cloudflare).
|
|
159
|
+
|
|
160
|
+
_No runtime deps added. Config is the API._
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bin/cli.mjs - prestruct CLI
|
|
4
|
+
* ============================
|
|
5
|
+
* Commands (spec'd in labs/prd.md Phase 2):
|
|
6
|
+
*
|
|
7
|
+
* prestruct init [dir] Scaffold the template into dir (default: cwd).
|
|
8
|
+
* Never clobbers existing files.
|
|
9
|
+
* prestruct build Validate config, run vite build, inject meta,
|
|
10
|
+
* prerender routes -- full pipeline from ssr.config.js.
|
|
11
|
+
* prestruct validate Load + validate ssr.config.js; exit 1 on bad config.
|
|
12
|
+
* prestruct help This text.
|
|
13
|
+
*
|
|
14
|
+
* The CLI runs inside the USER PROJECT: config comes from <workdir>/ssr.config.js,
|
|
15
|
+
* vite/react/react-router-dom resolve from the project's own node_modules
|
|
16
|
+
* (peerDependencies), and build output lands in <workdir>/dist.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'fs'
|
|
20
|
+
import path from 'path'
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from 'url'
|
|
22
|
+
import { execSync } from 'child_process'
|
|
23
|
+
|
|
24
|
+
process.on('unhandledRejection', (e) => console.error('[prestruct] unhandled rejection:', e && (e.stack || e)))
|
|
25
|
+
|
|
26
|
+
const cliDir = path.dirname(fileURLToPath(import.meta.url))
|
|
27
|
+
const packageRoot = path.resolve(cliDir, '..')
|
|
28
|
+
|
|
29
|
+
const { validateConfig, formatDiagnostics } = await import(
|
|
30
|
+
pathToFileURL(path.join(packageRoot, 'engine/scripts/validate-config.js'))
|
|
31
|
+
)
|
|
32
|
+
const { injectBrand } = await import(
|
|
33
|
+
pathToFileURL(path.join(packageRoot, 'engine/scripts/inject-brand.js'))
|
|
34
|
+
)
|
|
35
|
+
const { prerender, cleanCache, enginePaths } = await import(
|
|
36
|
+
pathToFileURL(path.join(packageRoot, 'engine/scripts/prerender.js'))
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const HELP = `
|
|
40
|
+
prestruct -- build-time SEO prerender for Vite + React
|
|
41
|
+
|
|
42
|
+
prestruct init [dir] Scaffold the template into dir (default: cwd).
|
|
43
|
+
Existing files are never clobbered.
|
|
44
|
+
prestruct build [--force] [--clean]
|
|
45
|
+
Full pipeline: validate -> vite build -> inject
|
|
46
|
+
meta -> prerender routes. --force rerenders all
|
|
47
|
+
routes; --clean clears the incremental cache first.
|
|
48
|
+
prestruct validate Validate ssr.config.js (exit 1 on bad config).
|
|
49
|
+
prestruct clean Clear the prerender route cache.
|
|
50
|
+
prestruct help This text.
|
|
51
|
+
|
|
52
|
+
Config lives in ssr.config.js -- copy ssr.config.example.js to start.
|
|
53
|
+
`
|
|
54
|
+
|
|
55
|
+
function findWorkdir(args) {
|
|
56
|
+
// Explicit workdir wins; otherwise walk up from cwd until ssr.config.js is found
|
|
57
|
+
const [first] = args
|
|
58
|
+
if (first && !first.startsWith('-')) {
|
|
59
|
+
return path.resolve(first)
|
|
60
|
+
}
|
|
61
|
+
let dir = process.cwd()
|
|
62
|
+
while (dir !== path.dirname(dir)) {
|
|
63
|
+
if (fs.existsSync(path.join(dir, 'ssr.config.js'))) return dir
|
|
64
|
+
dir = path.dirname(dir)
|
|
65
|
+
}
|
|
66
|
+
return process.cwd()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function loadConfig(workdir, { required = true } = {}) {
|
|
70
|
+
const configPath = path.join(workdir, 'ssr.config.js')
|
|
71
|
+
if (!fs.existsSync(configPath)) {
|
|
72
|
+
if (required) {
|
|
73
|
+
console.error(`[prestruct] ssr.config.js not found in ${workdir}`)
|
|
74
|
+
console.error('[prestruct] Copy ssr.config.example.js to ssr.config.js and edit it.')
|
|
75
|
+
}
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return (await import(pathToFileURL(configPath))).default
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(`[prestruct] ssr.config.js failed to load: ${err.message}`)
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function report(config) {
|
|
87
|
+
// Returns the ok BOOLEAN (not an object) -- callers must not destructure it.
|
|
88
|
+
const { ok, errors } = validateConfig(config)
|
|
89
|
+
if (!ok) formatDiagnostics(errors).forEach(line => console.error(line))
|
|
90
|
+
return ok
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// -- init ----------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
function cmdInit(args) {
|
|
96
|
+
const target = path.resolve(args.find(a => !a.startsWith('-')) || '.')
|
|
97
|
+
const templateRoot = path.join(packageRoot, 'init')
|
|
98
|
+
if (!fs.existsSync(templateRoot)) {
|
|
99
|
+
console.error('[prestruct] template missing from installed package')
|
|
100
|
+
process.exit(1)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let created = 0, skipped = 0
|
|
104
|
+
function walk(dir) {
|
|
105
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
106
|
+
if (entry === 'node_modules' || entry === 'dist' || entry === '.prestruct') continue
|
|
107
|
+
// .npmignore belongs to the PACKAGE tarball build (excludes init's local
|
|
108
|
+
// node_modules/dist from npm pack), not to scaffolded apps. Shipping it
|
|
109
|
+
// into a user project would silently prune THEIR files if they ever
|
|
110
|
+
// npm-pack their own package.
|
|
111
|
+
if (entry === '.npmignore') continue
|
|
112
|
+
const src = path.join(dir, entry)
|
|
113
|
+
const dest = path.join(target, path.relative(templateRoot, src))
|
|
114
|
+
const st = fs.statSync(src)
|
|
115
|
+
if (st.isDirectory()) {
|
|
116
|
+
walk(src)
|
|
117
|
+
} else if (fs.existsSync(dest)) {
|
|
118
|
+
skipped++
|
|
119
|
+
console.log(` skip ${path.relative(process.cwd(), dest)} (exists)`)
|
|
120
|
+
} else {
|
|
121
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
122
|
+
fs.copyFileSync(src, dest)
|
|
123
|
+
created++
|
|
124
|
+
console.log(` create ${path.relative(process.cwd(), dest)}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
walk(templateRoot)
|
|
129
|
+
|
|
130
|
+
console.log(`\n[prestruct] init done: ${created} created, ${skipped} skipped.`)
|
|
131
|
+
console.log('[prestruct] Next: cp ssr.config.example.js ssr.config.js, edit routes/siteUrl,')
|
|
132
|
+
console.log('[prestruct] add "prestruct build" (or npm run build) as your build step.')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// -- build ---------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
async function cmdBuild(args) {
|
|
138
|
+
const workdir = findWorkdir(args)
|
|
139
|
+
const config = await loadConfig(workdir)
|
|
140
|
+
if (!config) process.exit(1)
|
|
141
|
+
const configOk = report(config)
|
|
142
|
+
if (!configOk && process.env.PRESTRUCT_STRICT === '1') {
|
|
143
|
+
console.error('[prestruct] PRESTRUCT_STRICT=1 -- failing on config diagnostics')
|
|
144
|
+
process.exit(1)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const run = (cmd) => {
|
|
148
|
+
console.log(`[prestruct] $ ${cmd}`)
|
|
149
|
+
execSync(cmd, { cwd: workdir, stdio: 'inherit' })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
run('npx --no-install vite build')
|
|
153
|
+
injectBrand(config, path.join(workdir, 'dist'))
|
|
154
|
+
|
|
155
|
+
// Forward --force/--clean through to the engine's incremental prerender.
|
|
156
|
+
// Found live by tarball e2e: without this pass-through, engine accepted the
|
|
157
|
+
// force flag but CLI silently dropped it -- "cached / (route)" under --force,
|
|
158
|
+
// which shipped stale static HTML and made users reach for rm -rf.
|
|
159
|
+
const force = args.includes('--force') || args.includes('-f')
|
|
160
|
+
if (args.includes('--clean')) {
|
|
161
|
+
const p = enginePaths(path.resolve(workdir))
|
|
162
|
+
cleanCache({ cacheFile: p.cacheFile })
|
|
163
|
+
console.log('[prestruct] prerender cache cleared (--clean)')
|
|
164
|
+
}
|
|
165
|
+
const result = await prerender(config, { root: workdir, force })
|
|
166
|
+
// Engine prerender() returns { total, succeeded, failed, failures }.
|
|
167
|
+
// Live-fire (2026-10): a route that fails to render still exits 0 -- the
|
|
168
|
+
// broken deploy rides through green. Match the engine's new contract:
|
|
169
|
+
// failures are a failed build.
|
|
170
|
+
if (result && result.failed > 0) {
|
|
171
|
+
console.error(`[prestruct] prerender failed for ${result.failed}/${result.total} route(s) -- failing build`)
|
|
172
|
+
process.exit(1)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// -- validate / clean ----------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
async function cmdValidate(args) {
|
|
179
|
+
const workdir = findWorkdir(args)
|
|
180
|
+
const config = await loadConfig(workdir)
|
|
181
|
+
if (!config) process.exit(1)
|
|
182
|
+
const ok = report(config)
|
|
183
|
+
if (ok) console.log(`[prestruct] ssr.config.js is valid (${(config.routes || []).length} routes)`)
|
|
184
|
+
else process.exit(1)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function cmdClean(args) {
|
|
188
|
+
const workdir = findWorkdir(args)
|
|
189
|
+
const p = enginePaths(path.resolve(workdir))
|
|
190
|
+
cleanCache({ cacheFile: p.cacheFile })
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// -- dispatch ------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
const [command, ...args] = process.argv.slice(2)
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
switch (command || 'help') {
|
|
199
|
+
case 'init': cmdInit(args); break
|
|
200
|
+
case 'build': await cmdBuild(args); break
|
|
201
|
+
case 'validate': await cmdValidate(args); break
|
|
202
|
+
case 'clean': cmdClean(args); break
|
|
203
|
+
case 'help': console.log(HELP); break
|
|
204
|
+
default:
|
|
205
|
+
console.error(`[prestruct] unknown command: ${command}`)
|
|
206
|
+
console.log(HELP)
|
|
207
|
+
process.exit(1)
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error(`[prestruct] ${err.message}`)
|
|
211
|
+
process.exit(1)
|
|
212
|
+
}
|