create-wordjs 1.14.1 → 2.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 +21 -11
- package/index.js +64 -10
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -31,11 +31,12 @@ That single command takes you from nothing to the browser install wizard:
|
|
|
31
31
|
clickable URL:
|
|
32
32
|
|
|
33
33
|
```
|
|
34
|
-
→ https://localhost:3000/install
|
|
34
|
+
→ https://localhost:3000/install#token=…
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Open the URL, pick your database
|
|
38
|
-
|
|
37
|
+
Open the URL, pick your database, create your admin account, and you're in. The wizard offers
|
|
38
|
+
**SQLite** (zero config, the default), **PostgreSQL** and **MySQL/MariaDB** — all three are certified
|
|
39
|
+
in CI — plus a pure-JS *SQLite (legacy / WASM)* fallback for hosts where the native binary can't load.
|
|
39
40
|
|
|
40
41
|
## Requirements
|
|
41
42
|
|
|
@@ -46,9 +47,12 @@ account, and you're in.
|
|
|
46
47
|
| Option | Description |
|
|
47
48
|
| --- | --- |
|
|
48
49
|
| `--zip <path-or-url>` | Use a local release ZIP (or a direct ZIP URL) instead of querying the GitHub API. Handy offline or when rate-limited. |
|
|
49
|
-
| `--version <tag>` | Install a specific release (e.g. `--version
|
|
50
|
+
| `--version <tag>` | Install a specific release (e.g. `--version v2.1.0`) instead of the latest. |
|
|
50
51
|
| `--http` | Serve plain HTTP instead of self-signed HTTPS (sets `WORDJS_HTTP=1`). |
|
|
51
52
|
| `--no-start` | Scaffold and install dependencies only — start the server yourself later. |
|
|
53
|
+
| `--yes`, `-y` | Skip the confirmation prompt (required when `upgrade` runs non-interactively). |
|
|
54
|
+
| `--force` | (`upgrade`) Re-apply even if the site is already on the target version. |
|
|
55
|
+
| `--no-install` | (`upgrade`) Swap the code only; skip `npm run release:install`. |
|
|
52
56
|
| `-h`, `--help` | Show usage. |
|
|
53
57
|
|
|
54
58
|
Separate-mode options:
|
|
@@ -68,8 +72,10 @@ Separate-mode options:
|
|
|
68
72
|
cd .. && npx create-wordjs@latest upgrade my-site # or run it from inside: npx create-wordjs@latest upgrade .
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
Downloads the newest release and replaces the app code while **preserving your data**: the
|
|
72
|
-
|
|
75
|
+
Downloads the newest release and replaces the app code while **preserving your data**: the database
|
|
76
|
+
directory (`backend/data`), `backend/uploads/`, `wordjs-config.json`, `.env`, gateway secrets
|
|
77
|
+
(`gateway/gateway-config.json`) and any user-installed plugins survive. It asks for confirmation
|
|
78
|
+
before touching an existing install — on a non-interactive shell it refuses unless you pass `--yes`.
|
|
73
79
|
Restart the server afterwards (schema migrations run automatically on the next start).
|
|
74
80
|
|
|
75
81
|
## Separate mode (multi-machine)
|
|
@@ -95,9 +101,11 @@ npx create-wordjs@latest join frontend --gateway 10.0.0.1 --token <t> --ca-hash
|
|
|
95
101
|
```
|
|
96
102
|
|
|
97
103
|
Each `join` downloads the release, enrolls against the gateway (the token authorizes exactly one
|
|
98
|
-
certificate signing; it is burned afterwards
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
certificate signing; it is burned afterwards, and the ones `gateway` printed also expire after 120
|
|
105
|
+
minutes — mint more on the gateway with `node scripts/cluster.js token <backend|frontend>`, which
|
|
106
|
+
defaults to a 60-minute TTL and takes `--ttl <minutes>`), then starts the service, which registers
|
|
107
|
+
with the gateway over mTLS. Browse `https://<gateway>:3000` when all three are up. `join` machines
|
|
108
|
+
need `openssl` on the PATH. Full details, port matrix and the manual (source-checkout) procedure:
|
|
101
109
|
[documentation/separate-mode.md](https://github.com/jaimemartinez/wordjs/blob/main/documentation/separate-mode.md).
|
|
102
110
|
|
|
103
111
|
## Good to know
|
|
@@ -111,14 +119,16 @@ gateway over mTLS. Browse `https://<gateway>:3000` when all three are up. `join`
|
|
|
111
119
|
- **GitHub rate limit / offline**: the release lookup uses the unauthenticated GitHub API. If it
|
|
112
120
|
is rate-limited or you're offline, download `wordjs-v*.zip` from the
|
|
113
121
|
[releases page](https://github.com/jaimemartinez/wordjs/releases) and run
|
|
114
|
-
`npx create-wordjs@latest my-site --zip ./wordjs-
|
|
122
|
+
`npx create-wordjs@latest my-site --zip ./wordjs-v2.1.0.zip`.
|
|
115
123
|
- **Existing directories**: the target directory must not exist (or must be empty) — the tool
|
|
116
124
|
refuses to overwrite anything.
|
|
117
125
|
|
|
118
126
|
## What gets created
|
|
119
127
|
|
|
120
128
|
A ready-to-run WordJS bundle: backend (pre-compiled to `dist/`), frontend (pre-built `.next`),
|
|
121
|
-
gateway, bundled plugins and
|
|
129
|
+
gateway, the bundled plugins and the four bundled themes (`circuito`, `default`, `gaceta`,
|
|
130
|
+
`vergel`). Marketplace plugins are **not** in the bundle — they ship as separate release assets and
|
|
131
|
+
are installed from the admin. Secrets (JWT, DB password, install token) are generated
|
|
122
132
|
locally during install — nothing sensitive ships in the bundle. See `INSTALL.md` inside the
|
|
123
133
|
scaffolded directory for the manual steps and `documentation/deployment.md` for production
|
|
124
134
|
deployment.
|
package/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 1. Downloads the latest pre-compiled WordJS release ZIP from GitHub (no build step needed).
|
|
11
11
|
* 2. Extracts it into <dir> and installs the runtime dependencies (npm run release:install).
|
|
12
12
|
* 3. Generates a one-time install token and starts the server (npm run start:mono) with it,
|
|
13
|
-
* printing a clickable https://localhost:3000/install
|
|
13
|
+
* printing a clickable https://localhost:3000/install#token=… URL — the browser install
|
|
14
14
|
* wizard takes it from there (pick SQLite/PostgreSQL, create your admin, done).
|
|
15
15
|
*
|
|
16
16
|
* Plain Node, no TypeScript. Only runtime dependency: adm-zip (ZIP extraction).
|
|
@@ -49,7 +49,7 @@ Usage:
|
|
|
49
49
|
|
|
50
50
|
Options:
|
|
51
51
|
--zip <path-or-url> Use a local release ZIP (or a direct ZIP URL) instead of asking GitHub.
|
|
52
|
-
--version <tag> Install/upgrade to a specific release tag (e.g.
|
|
52
|
+
--version <tag> Install/upgrade to a specific release tag (e.g. v2.1.0) instead of the latest.
|
|
53
53
|
--http Serve plain HTTP instead of self-signed HTTPS (sets WORDJS_HTTP=1). (create)
|
|
54
54
|
--no-start Scaffold + install dependencies only; don't start the server.
|
|
55
55
|
--yes, -y Skip the confirmation prompt (required when upgrading non-interactively).
|
|
@@ -65,7 +65,7 @@ Options:
|
|
|
65
65
|
|
|
66
66
|
Examples:
|
|
67
67
|
npx create-wordjs@latest my-site
|
|
68
|
-
npx create-wordjs@latest my-site --version
|
|
68
|
+
npx create-wordjs@latest my-site --version v2.1.0
|
|
69
69
|
npx create-wordjs@latest upgrade # from inside your site directory
|
|
70
70
|
npx create-wordjs@latest upgrade ./my-site --yes
|
|
71
71
|
|
|
@@ -103,7 +103,7 @@ function parseArgs(argv) {
|
|
|
103
103
|
const a = argv[i];
|
|
104
104
|
if (a === '-h' || a === '--help') { console.log(HELP); process.exit(0); }
|
|
105
105
|
else if (a === '--zip') { opts.zip = argv[++i] || fail('--zip needs a value (path or URL to a wordjs-*.zip).'); }
|
|
106
|
-
else if (a === '--version') { opts.version = argv[++i] || fail('--version needs a value (a release tag, e.g.
|
|
106
|
+
else if (a === '--version') { opts.version = argv[++i] || fail('--version needs a value (a release tag, e.g. v2.1.0).'); }
|
|
107
107
|
else if (a === '--http') opts.http = true;
|
|
108
108
|
else if (a === '--no-start') opts.start = false;
|
|
109
109
|
else if (a === '--yes' || a === '-y') opts.yes = true;
|
|
@@ -130,7 +130,7 @@ function parseArgs(argv) {
|
|
|
130
130
|
else if (opts.mode === 'join') opts.dir = opts.role ? `wordjs-${opts.role}` : 'wordjs-node';
|
|
131
131
|
else fail('Please specify a directory for your new site.', 'Example: npx create-wordjs@latest my-site');
|
|
132
132
|
}
|
|
133
|
-
if (opts.version && /^\d/.test(opts.version)) opts.version = 'v' + opts.version; // accept "1.0
|
|
133
|
+
if (opts.version && /^\d/.test(opts.version)) opts.version = 'v' + opts.version; // accept "2.1.0" for "v2.1.0"
|
|
134
134
|
return opts;
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -181,6 +181,41 @@ async function githubJson(url) {
|
|
|
181
181
|
try { return JSON.parse(body); } catch { fail('GitHub returned an unparsable response.', 'Try again, or use --zip <path-to-zip>.'); }
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// NAME THE ASSET WE WANT; DO NOT TAKE THE FIRST ONE THAT LOOKS RIGHT.
|
|
185
|
+
//
|
|
186
|
+
// The core bundle is not alone on the release: the same release carries all 31 marketplace plugin
|
|
187
|
+
// zips, and `wordjs-*.zip` is a shape, not an identity. A plugin slug beginning with `wordjs-` would
|
|
188
|
+
// sort ahead of the bundle in the assets array and this installer would download a plugin and try to
|
|
189
|
+
// boot it as a site. Nothing today collides, which is exactly when it is cheap to fix.
|
|
190
|
+
//
|
|
191
|
+
// release.yml names the bundle after the tag (`wordjs-v2.0.0.zip`), so ask for that by name. The
|
|
192
|
+
// loose match survives only as a fallback — for older releases, and so a rename in the workflow
|
|
193
|
+
// degrades gracefully instead of failing hard.
|
|
194
|
+
//
|
|
195
|
+
// BUT THE FALLBACK IS THE OLD RULE, so it cannot be allowed to guess. Taking the first loose match
|
|
196
|
+
// would reinstate exactly the bug the exact match was added to fix, on every path where the
|
|
197
|
+
// tag-named asset is absent (a workflow_dispatch build, a rename, any earlier release). The loose
|
|
198
|
+
// shape is therefore used ONLY when it is unambiguous: exactly one candidate. Two or more means we
|
|
199
|
+
// would be choosing which file is the site, and choosing wrong installs a plugin as a site — so we
|
|
200
|
+
// refuse and say so, and `--zip` is right there. Fail closed, never guess.
|
|
201
|
+
//
|
|
202
|
+
// Exported (below) so it can be exercised directly: it is the one piece of release resolution that is
|
|
203
|
+
// pure, and testing it through the network call would mean testing a copy of it instead.
|
|
204
|
+
function pickBundleAsset(assets, tagName) {
|
|
205
|
+
const list = Array.isArray(assets) ? assets : [];
|
|
206
|
+
const wanted = `wordjs-${tagName}.zip`.toLowerCase();
|
|
207
|
+
const exact = list.find((a) => String(a && a.name || '').toLowerCase() === wanted);
|
|
208
|
+
if (exact) return exact;
|
|
209
|
+
const loose = looseBundleCandidates(list);
|
|
210
|
+
return loose.length === 1 ? loose[0] : null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Every asset matching the loose `wordjs-*.zip` shape — used to explain an ambiguous refusal. */
|
|
214
|
+
function looseBundleCandidates(assets) {
|
|
215
|
+
const list = Array.isArray(assets) ? assets : [];
|
|
216
|
+
return list.filter((a) => /^wordjs-.*\.zip$/i.test(a && a.name || ''));
|
|
217
|
+
}
|
|
218
|
+
|
|
184
219
|
async function resolveReleaseAsset(tag) {
|
|
185
220
|
const url = tag
|
|
186
221
|
? `https://api.github.com/repos/${REPO}/releases/tags/${encodeURIComponent(tag)}`
|
|
@@ -190,8 +225,17 @@ async function resolveReleaseAsset(tag) {
|
|
|
190
225
|
fail(tag ? `No release found for tag "${tag}".` : `No releases found for ${REPO}.`,
|
|
191
226
|
`See https://github.com/${REPO}/releases for available versions, or pass --zip <path-or-url>.`);
|
|
192
227
|
}
|
|
193
|
-
const asset = (release.assets
|
|
194
|
-
if (!asset)
|
|
228
|
+
const asset = pickBundleAsset(release.assets, release.tag_name);
|
|
229
|
+
if (!asset) {
|
|
230
|
+
// Say WHICH of the two refusals this is: "there is no bundle" and "there are several and I
|
|
231
|
+
// will not guess" need different answers from whoever is reading.
|
|
232
|
+
const candidates = looseBundleCandidates(release.assets).map((a) => a.name);
|
|
233
|
+
if (candidates.length > 1) {
|
|
234
|
+
fail(`Release ${release.tag_name} has no asset named wordjs-${release.tag_name}.zip, and ${candidates.length} others match wordjs-*.zip: ${candidates.join(', ')}.`,
|
|
235
|
+
'Refusing to guess which one is the site bundle — pass --zip <path-or-url> with the one you want.');
|
|
236
|
+
}
|
|
237
|
+
fail(`Release ${release.tag_name} has no wordjs-*.zip asset.`, 'Pass --zip <path-or-url> instead.');
|
|
238
|
+
}
|
|
195
239
|
return { name: asset.name, url: asset.browser_download_url, tag: release.tag_name };
|
|
196
240
|
}
|
|
197
241
|
|
|
@@ -685,7 +729,7 @@ async function main() {
|
|
|
685
729
|
console.log(` cd ${opts.dir}`);
|
|
686
730
|
console.log(` npm run start:mono${opts.http ? ' (with WORDJS_HTTP=1 in the environment for plain HTTP)' : ''}`);
|
|
687
731
|
console.log('');
|
|
688
|
-
console.log(` The console will print your one-time install URL (${proto}://localhost:3000/install
|
|
732
|
+
console.log(` The console will print your one-time install URL (${proto}://localhost:3000/install#token=…).`);
|
|
689
733
|
console.log(line + '\n');
|
|
690
734
|
return;
|
|
691
735
|
}
|
|
@@ -699,7 +743,12 @@ async function main() {
|
|
|
699
743
|
console.log(`\n${line}`);
|
|
700
744
|
console.log('✅ WordJS is ready — finish setup in your browser:');
|
|
701
745
|
console.log('');
|
|
702
|
-
|
|
746
|
+
// The token rides in the URL FRAGMENT, not a `?token=` query string: a fragment is never sent to
|
|
747
|
+
// any server, so this bootstrap secret stays out of access/proxy logs and out of the `Referer` of
|
|
748
|
+
// every sub-resource the install page loads. The wizard reads `#token=` (and still accepts a
|
|
749
|
+
// legacy `?token=`) and scrubs it from the address bar. Keep in sync with the backend's own
|
|
750
|
+
// banner in backend/src/core/install-token.ts.
|
|
751
|
+
console.log(` → ${proto}://localhost:3000/install#token=${token}`);
|
|
703
752
|
console.log('');
|
|
704
753
|
console.log(' • The server is starting below — give it ~15–30 seconds, then open the URL.');
|
|
705
754
|
if (!opts.http) {
|
|
@@ -730,4 +779,9 @@ async function main() {
|
|
|
730
779
|
child.on('exit', (code) => process.exit(code == null ? 0 : code));
|
|
731
780
|
}
|
|
732
781
|
|
|
733
|
-
|
|
782
|
+
// Run only when invoked as the CLI, so the pure helpers above can be required and exercised.
|
|
783
|
+
if (require.main === module) {
|
|
784
|
+
main().catch((e) => fail(e && e.message ? e.message : String(e)));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
module.exports = { pickBundleAsset };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-wordjs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Create a WordJS site with one command — the self-hosted CMS where third-party plugins run in an OS-isolated process with per-capability permission grants. SSR/SEO out of the box, SQLite by default, no PHP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jaime Martinez (https://github.com/jaimemartinez)",
|
|
@@ -46,6 +46,9 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"adm-zip": "^0.
|
|
49
|
+
"adm-zip": "^0.6.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"test": "node --test test/*.test.js"
|
|
50
53
|
}
|
|
51
54
|
}
|