nibula 1.1.0 â 1.1.1
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/CHANGELOG.md +51 -0
- package/README.md +1 -1
- package/bin/create.js +82 -16
- package/docs/Backend.md +31 -23
- package/nginx.conf +75 -75
- package/package.json +1 -1
- package/src/backend/_core/index.js +267 -267
- package/src/backend/_core/init.js +52 -52
- package/src/backend/_core/modules/RateLimiter.js +58 -58
- package/src/backend/_core/modules/Response.js +59 -59
- package/src/backend/api/protected/example-protected.js +23 -23
- package/src/backend/api/public/example-public.js +24 -24
- package/src/backend/backend-node.service.example +30 -30
- package/src/backend/database/Database.js +46 -46
- package/src/backend/example.config.js +37 -37
- package/src/backend/package.json +18 -18
- package/src/frontend/.htaccess +51 -51
- package/src/frontend/assets/brand/favicon.svg +54 -54
- package/src/frontend/assets/brand/logo.svg +54 -54
- package/src/frontend/data/site.json +54 -54
- package/src/frontend/web.config +55 -55
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Nibula are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.1.0] - 2026-07-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Node.js backend** as an alternative to PHP, living side by side in
|
|
12
|
+
`src/backend/`: `_core/index.js` front controller (which is also the HTTP
|
|
13
|
+
server), `Response` and `RateLimiter` modules, a `mysql2` pool singleton
|
|
14
|
+
`Database.js`, `example.config.js`, and a `package.json` for the backend's
|
|
15
|
+
own dependencies. Same REST API as PHP: routing, `X-Api-Key` auth, CORS and
|
|
16
|
+
file-based rate limiting are identical.
|
|
17
|
+
- **Backend choice at scaffold time**: `nib new` now asks whether to use Node.js
|
|
18
|
+
or PHP, in addition to language and CSS framework.
|
|
19
|
+
- `backend/backend-node.service.example` â a ready-to-adapt **systemd** unit for
|
|
20
|
+
keeping the Node backend running on a server.
|
|
21
|
+
- `config.js` is generated from `example.config.js` at scaffold time (mirroring
|
|
22
|
+
the existing `config.php` generation).
|
|
23
|
+
- Deployment docs now cover running the Node backend with **`screen`** (quick /
|
|
24
|
+
small setups) and **systemd** (production), plus environment variables.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- **Composer is now required only when the PHP backend is chosen.** If you pick
|
|
28
|
+
Node, `composer install` is skipped entirely and the backend's npm
|
|
29
|
+
dependencies (`mysql2`) are installed instead.
|
|
30
|
+
- `.htaccess`, `web.config` and `nginx.conf` now cover **both** backends. On
|
|
31
|
+
nginx, `/api` goes to Node and automatically falls back to the PHP front
|
|
32
|
+
controller when Node is unreachable. On Apache/IIS the active backend is
|
|
33
|
+
selected by configuration (documented), since per-directory configs can't
|
|
34
|
+
health-check an upstream.
|
|
35
|
+
- `.gitignore` now also ignores `src/backend/config.js`,
|
|
36
|
+
`src/backend/node_modules/` and `src/backend/cache/`.
|
|
37
|
+
- Documentation (`README.md`, `docs/Backend.md`, `docs/Deploy.md`) updated:
|
|
38
|
+
backend examples now use JavaScript/Node as the reference, with the equivalent
|
|
39
|
+
PHP shown alongside and a note that PHP behaves identically.
|
|
40
|
+
|
|
41
|
+
### Notes
|
|
42
|
+
- Both backends are always scaffolded, so you can switch later without
|
|
43
|
+
re-creating the project. The PHP front controller only serves `.php` endpoint
|
|
44
|
+
files and the Node one only `.js`, so they coexist without conflict.
|
|
45
|
+
|
|
46
|
+
## [1.0.2]
|
|
47
|
+
|
|
48
|
+
- Previous release (PHP backend only).
|
|
49
|
+
|
|
50
|
+
[1.1.0]: https://github.com/Rhaastrake/Nibula/releases/tag/v1.1.0
|
|
51
|
+
[1.0.2]: https://github.com/Rhaastrake/Nibula/releases/tag/v1.0.2
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Building a website from scratch involves a lot of moving parts: templating, buil
|
|
|
21
21
|
- ðŠķ **Lightweight by default** â SCSS frameworks can be filtered so you ship only what you actually use
|
|
22
22
|
- ð **Open source** â free to use, free to modify, free to share
|
|
23
23
|
|
|
24
|
-

|
|
25
25
|

|
|
26
26
|

|
|
27
27
|
|
package/bin/create.js
CHANGED
|
@@ -36,7 +36,7 @@ const BACKEND = Object.freeze({
|
|
|
36
36
|
// ââ CHOICES ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
37
37
|
|
|
38
38
|
const LANGUAGE_CHOICES = [
|
|
39
|
-
{ label: 'JavaScript (
|
|
39
|
+
{ label: 'JavaScript (recomended)', value: LANGUAGE.JAVASCRIPT },
|
|
40
40
|
{ label: 'TypeScript', value: LANGUAGE.TYPESCRIPT },
|
|
41
41
|
];
|
|
42
42
|
|
|
@@ -53,6 +53,12 @@ const BACKEND_CHOICES = [
|
|
|
53
53
|
{ label: 'PHP (index.php â Composer dependencies)', value: BACKEND.PHP },
|
|
54
54
|
];
|
|
55
55
|
|
|
56
|
+
// Runtime dependency added to the ROOT package.json when the Node backend is
|
|
57
|
+
// chosen, so it lands in the root node_modules (never in src/backend).
|
|
58
|
+
const NODE_BACKEND_DEPENDENCIES = {
|
|
59
|
+
mysql2: '^3.11.0',
|
|
60
|
+
};
|
|
61
|
+
|
|
56
62
|
// ââ COPY CONFIG âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
57
63
|
|
|
58
64
|
const MANDATORY_COPY = [
|
|
@@ -72,6 +78,14 @@ const CREATE_DIRS = [
|
|
|
72
78
|
'src/frontend/_routes',
|
|
73
79
|
];
|
|
74
80
|
|
|
81
|
+
// Backend files that belong to exactly one backend, matched by basename.
|
|
82
|
+
// Everything else (migrations, .htaccess, web.config, README, ...) is shared.
|
|
83
|
+
const NODE_ONLY_FILES = new Set(['package.json', 'backend-node.service.example']);
|
|
84
|
+
const PHP_ONLY_FILES = new Set(['composer.json', 'composer.lock']);
|
|
85
|
+
const PHP_ONLY_DIRS = new Set(['vendor']);
|
|
86
|
+
// Runtime artifacts that must never be copied from the template, either way.
|
|
87
|
+
const BACKEND_SKIP_DIRS = new Set(['node_modules', 'cache', '.git']);
|
|
88
|
+
|
|
75
89
|
// ââ FRAMEWORK CONFIG ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
76
90
|
|
|
77
91
|
const ALL_FRAMEWORKS = Object.values(FRAMEWORK).filter(f => f !== FRAMEWORK.NONE);
|
|
@@ -128,7 +142,6 @@ src/backend/_core/vendor/
|
|
|
128
142
|
out/
|
|
129
143
|
src/backend/config.php
|
|
130
144
|
src/backend/config.js
|
|
131
|
-
src/backend/node_modules/
|
|
132
145
|
src/backend/cache/
|
|
133
146
|
`;
|
|
134
147
|
|
|
@@ -198,6 +211,48 @@ function copyRecursive(src, dest, exclude = []) {
|
|
|
198
211
|
}
|
|
199
212
|
}
|
|
200
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Decide whether a backend entry belongs to the chosen backend.
|
|
216
|
+
* Returns false for entries that must be SKIPPED.
|
|
217
|
+
*/
|
|
218
|
+
function backendEntryKept(basename, isDir, backend) {
|
|
219
|
+
// Never copy runtime artifacts from the template.
|
|
220
|
+
if (isDir && BACKEND_SKIP_DIRS.has(basename)) return false;
|
|
221
|
+
|
|
222
|
+
if (backend === BACKEND.NODE) {
|
|
223
|
+
// Node project: drop every PHP artifact.
|
|
224
|
+
if (basename.endsWith('.php')) return false;
|
|
225
|
+
if (PHP_ONLY_FILES.has(basename)) return false;
|
|
226
|
+
if (isDir && PHP_ONLY_DIRS.has(basename)) return false;
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// PHP project: drop every Node artifact.
|
|
231
|
+
if (basename.endsWith('.js')) return false;
|
|
232
|
+
if (NODE_ONLY_FILES.has(basename)) return false;
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Copy src/backend into the project keeping only the chosen backend's files.
|
|
238
|
+
* Shared files (SQL migrations, .htaccess, web.config, README, ...) are kept
|
|
239
|
+
* for both. Empty directories left behind by filtering are not created.
|
|
240
|
+
*/
|
|
241
|
+
function copyBackend(src, dest, backend) {
|
|
242
|
+
const stat = fs.statSync(src);
|
|
243
|
+
if (stat.isDirectory()) {
|
|
244
|
+
for (const child of fs.readdirSync(src)) {
|
|
245
|
+
const childSrc = path.join(src, child);
|
|
246
|
+
const isDir = fs.statSync(childSrc).isDirectory();
|
|
247
|
+
if (!backendEntryKept(child, isDir, backend)) continue;
|
|
248
|
+
copyBackend(childSrc, path.join(dest, child), backend);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
252
|
+
fs.copyFileSync(src, dest);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
201
256
|
function slashComment(content, line) {
|
|
202
257
|
content = content.replace(new RegExp(`^([ \\t]*)// (${escapeRegex(line)})$`, 'gm'), '$1$2');
|
|
203
258
|
return content.replace(new RegExp(`^([ \\t]*)(${escapeRegex(line)})$`, 'gm'), '$1// $2');
|
|
@@ -217,8 +272,7 @@ function njkUncomment(content, line) {
|
|
|
217
272
|
}
|
|
218
273
|
|
|
219
274
|
function installDependencies(backend) {
|
|
220
|
-
const
|
|
221
|
-
const backendCore = path.join(backendRoot, '_core');
|
|
275
|
+
const backendCore = path.join(targetDir, 'src', 'backend', '_core');
|
|
222
276
|
|
|
223
277
|
log(`${color.blue}\n>> Installing Node modules...${color.reset}`);
|
|
224
278
|
const npm = spawnSync('npm', ['install'], {
|
|
@@ -234,17 +288,11 @@ function installDependencies(backend) {
|
|
|
234
288
|
return false;
|
|
235
289
|
}
|
|
236
290
|
|
|
237
|
-
//
|
|
291
|
+
// Node backend: its runtime deps were added to the ROOT package.json and
|
|
292
|
+
// installed above into the root node_modules â there is no separate install
|
|
293
|
+
// inside src/backend, and Composer is never run.
|
|
238
294
|
if (backend === BACKEND.NODE) {
|
|
239
|
-
|
|
240
|
-
log(`\n${color.blue}>> Installing Node backend modules...${color.reset}`);
|
|
241
|
-
spawnSync('npm', ['install'], {
|
|
242
|
-
cwd: backendRoot,
|
|
243
|
-
stdio: 'inherit',
|
|
244
|
-
shell: process.platform === 'win32',
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
return true; // Composer is never run for the Node backend
|
|
295
|
+
return true;
|
|
248
296
|
}
|
|
249
297
|
|
|
250
298
|
// --- PHP backend: install Composer dependencies (if present) ---
|
|
@@ -389,11 +437,19 @@ async function init() {
|
|
|
389
437
|
const src = path.join(templateDir, target);
|
|
390
438
|
const dest = path.join(targetDir, target);
|
|
391
439
|
if (!fs.existsSync(src)) continue;
|
|
392
|
-
|
|
393
|
-
|
|
440
|
+
|
|
441
|
+
if (target === 'src/backend') {
|
|
442
|
+
// Copy only the chosen backend's files (the other backend is omitted).
|
|
443
|
+
copyBackend(src, dest, backend);
|
|
444
|
+
} else {
|
|
445
|
+
const exclude = target === 'src/frontend' ? FRONTEND_EXCLUDE[language] : [];
|
|
446
|
+
copyRecursive(src, dest, exclude);
|
|
447
|
+
}
|
|
394
448
|
logAdd(target);
|
|
395
449
|
}
|
|
396
450
|
|
|
451
|
+
// Generate the local config only for the chosen backend. The other backend's
|
|
452
|
+
// example file was not copied, so its guard below is simply skipped.
|
|
397
453
|
const configDest = path.join(targetDir, 'src/backend/config.php');
|
|
398
454
|
const configExample = path.join(targetDir, 'src/backend/example.config.php');
|
|
399
455
|
if (!fs.existsSync(configDest) && fs.existsSync(configExample)) {
|
|
@@ -408,7 +464,17 @@ async function init() {
|
|
|
408
464
|
logAdd('src/backend/config.js');
|
|
409
465
|
}
|
|
410
466
|
|
|
467
|
+
// Build the project package.json. Clone the shared dependency maps so we
|
|
468
|
+
// never mutate the PROJECT_PACKAGE constant.
|
|
411
469
|
const pkg = { ...PROJECT_PACKAGE };
|
|
470
|
+
pkg.dependencies = { ...PROJECT_PACKAGE.dependencies };
|
|
471
|
+
pkg.devDependencies = { ...PROJECT_PACKAGE.devDependencies };
|
|
472
|
+
|
|
473
|
+
// Node backend deps live in the ROOT node_modules â add them to root deps
|
|
474
|
+
// so `npm install` installs them there (never in src/backend).
|
|
475
|
+
if (backend === BACKEND.NODE) {
|
|
476
|
+
pkg.dependencies = { ...pkg.dependencies, ...NODE_BACKEND_DEPENDENCIES };
|
|
477
|
+
}
|
|
412
478
|
|
|
413
479
|
if (language === LANGUAGE.TYPESCRIPT) {
|
|
414
480
|
const tsSrc = path.join(templateDir, 'tsconfig.json');
|
package/docs/Backend.md
CHANGED
|
@@ -27,43 +27,51 @@ When you run `nib new`, the scaffolder asks three questions: language, CSS
|
|
|
27
27
|
framework, and **backend**. Pick Node.js or PHP.
|
|
28
28
|
|
|
29
29
|
- **If you pick Node**, Composer is never run â you get an npm-only setup, and
|
|
30
|
-
the backend's
|
|
31
|
-
|
|
32
|
-
- **If you pick PHP**, Composer dependencies are installed as before
|
|
30
|
+
the backend's dependency (`mysql2`, used only if you touch the database) is
|
|
31
|
+
added to the **root** `package.json` and installed into the root `node_modules`.
|
|
32
|
+
- **If you pick PHP**, Composer dependencies are installed as before, and no Node
|
|
33
|
+
backend files are added.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
files
|
|
35
|
+
**Only the backend you choose is copied into the project.** A Node project
|
|
36
|
+
contains no PHP files; a PHP project contains no `.js` backend files. Shared,
|
|
37
|
+
backend-agnostic files (SQL migrations, `.htaccess`, `web.config`) are always
|
|
38
|
+
included.
|
|
37
39
|
|
|
38
40
|
## Structure
|
|
39
41
|
|
|
42
|
+
The structure below shows both backends for reference; **your project will have
|
|
43
|
+
only one side of each pair** (plus the shared files).
|
|
44
|
+
|
|
40
45
|
```
|
|
41
46
|
src/backend/
|
|
42
47
|
âââ api/
|
|
43
48
|
â âââ public/ # Endpoints accessible without an API key
|
|
44
49
|
â âââ protected/ # Endpoints requiring the X-Api-Key header
|
|
45
50
|
âââ _core/ # Framework internals (routing, modules) â do not edit
|
|
46
|
-
â âââ index.php # PHP front controller
|
|
47
|
-
â âââ index.js # Node front controller (
|
|
51
|
+
â âââ index.php # PHP front controller (PHP project)
|
|
52
|
+
â âââ index.js # Node front controller + server (Node project)
|
|
48
53
|
â âââ init.php / init.js
|
|
49
|
-
â âââ modules/ # Response, RateLimiter
|
|
54
|
+
â âââ modules/ # Response, RateLimiter
|
|
50
55
|
âââ database/
|
|
51
|
-
â âââ Database.php # PDO singleton
|
|
52
|
-
â âââ Database.js # mysql2 pool singleton
|
|
53
|
-
â âââ migrations/
|
|
54
|
-
âââ package.json # Node backend deps + start script
|
|
55
|
-
âââ example.config.php # PHP
|
|
56
|
-
âââ example.config.js # Node
|
|
57
|
-
âââ config.php #
|
|
58
|
-
âââ config.js #
|
|
56
|
+
â âââ Database.php # PDO singleton (PHP project)
|
|
57
|
+
â âââ Database.js # mysql2 pool singleton (Node project)
|
|
58
|
+
â âââ migrations/ # shared
|
|
59
|
+
âââ package.json # Node project only â backend deps + start script
|
|
60
|
+
âââ example.config.php # PHP project only â versioned template
|
|
61
|
+
âââ example.config.js # Node project only â versioned template
|
|
62
|
+
âââ config.php # PHP project only â generated on setup, never commit
|
|
63
|
+
âââ config.js # Node project only â generated on setup, never commit
|
|
59
64
|
```
|
|
60
65
|
|
|
61
|
-
>
|
|
62
|
-
>
|
|
63
|
-
>
|
|
64
|
-
>
|
|
65
|
-
|
|
66
|
-
>
|
|
66
|
+
> `config.php` / `config.js` are generated automatically by `nib new` (a copy of
|
|
67
|
+
> the matching `example.config.*`) and are git-ignored, so your secrets stay
|
|
68
|
+
> local. The `example.config.*` file is versioned as a secret-free reference. If
|
|
69
|
+
> `config.*` is ever missing (e.g. after a fresh clone), copy the example to it.
|
|
70
|
+
|
|
71
|
+
> **Where do the Node backend's dependencies live?** In the **root**
|
|
72
|
+
> `node_modules`, not in `src/backend` â so your source tree stays clean (no
|
|
73
|
+
> `src/backend/node_modules` locally). On the server you install them into
|
|
74
|
+
> `out/backend` instead; see `docs/Deploy.md`.
|
|
67
75
|
|
|
68
76
|
## Key difference: how each backend runs
|
|
69
77
|
|
package/nginx.conf
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
# Example nginx directives for a Nibula site â covers BOTH backends.
|
|
2
|
-
#
|
|
3
|
-
# /api is served by Node (index.js) if it is running on 127.0.0.1:3000; if Node
|
|
4
|
-
# is unreachable, nginx falls back to the PHP front controller automatically.
|
|
5
|
-
# So the same config works whether you deployed the Node backend or the PHP one:
|
|
6
|
-
# - Node backend running -> Node answers /api
|
|
7
|
-
# - only PHP deployed -> Node is down -> falls back to PHP-FPM
|
|
8
|
-
#
|
|
9
|
-
# This is NOT a ready-to-use file: paste these directives inside your own
|
|
10
|
-
# server { } block (the one with server_name, listen and SSL) and set `root`
|
|
11
|
-
# to your `out` folder.
|
|
12
|
-
|
|
13
|
-
# Hide the nginx version from response headers
|
|
14
|
-
server_tokens off;
|
|
15
|
-
|
|
16
|
-
# Force HTTPS on future requests (applies site-wide, including /api)
|
|
17
|
-
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
18
|
-
|
|
19
|
-
root /var/www/your-site/out;
|
|
20
|
-
index index.html;
|
|
21
|
-
|
|
22
|
-
# Disable directory listing
|
|
23
|
-
autoindex off;
|
|
24
|
-
|
|
25
|
-
error_page 403 404 /404.html;
|
|
26
|
-
location = /404.html {
|
|
27
|
-
internal;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
# Never expose the backend source directory over HTTP.
|
|
31
|
-
location ^~ /backend/ {
|
|
32
|
-
return 404;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
# Block server config files anywhere in the tree (.htaccess, web.config).
|
|
36
|
-
location ~* (^|/)(\.htaccess|web\.config)$ {
|
|
37
|
-
return 404;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
# --- /api: Node primary, PHP fallback ---------------------------------------
|
|
41
|
-
# Try the Node backend first. If it is down (502/504), retry through the PHP
|
|
42
|
-
# front controller. Security headers for API responses are set by the backend.
|
|
43
|
-
location ^~ /api/ {
|
|
44
|
-
proxy_pass http://127.0.0.1:3000;
|
|
45
|
-
proxy_http_version 1.1;
|
|
46
|
-
proxy_set_header Host $host;
|
|
47
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
48
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
49
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
50
|
-
|
|
51
|
-
proxy_intercept_errors on;
|
|
52
|
-
error_page 502 504 = @php_backend;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
location @php_backend {
|
|
56
|
-
include fastcgi_params;
|
|
57
|
-
fastcgi_param SCRIPT_FILENAME $document_root/backend/_core/index.php;
|
|
58
|
-
|
|
59
|
-
# Adjust the PHP-FPM socket to your distro:
|
|
60
|
-
# Debian / Ubuntu : unix:/run/php/php8.3-fpm.sock
|
|
61
|
-
# RHEL / Fedora : unix:/run/php-fpm/php-fpm.sock
|
|
62
|
-
# TCP fallback : 127.0.0.1:9000
|
|
63
|
-
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
# Serve static files, fall back to the 404 page.
|
|
67
|
-
# A location with add_header does not inherit server-level headers,
|
|
68
|
-
# so Strict-Transport-Security is repeated here.
|
|
69
|
-
location / {
|
|
70
|
-
add_header X-Content-Type-Options "nosniff" always;
|
|
71
|
-
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
72
|
-
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
73
|
-
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
74
|
-
try_files $uri $uri/ /404.html;
|
|
75
|
-
}
|
|
1
|
+
# Example nginx directives for a Nibula site â covers BOTH backends.
|
|
2
|
+
#
|
|
3
|
+
# /api is served by Node (index.js) if it is running on 127.0.0.1:3000; if Node
|
|
4
|
+
# is unreachable, nginx falls back to the PHP front controller automatically.
|
|
5
|
+
# So the same config works whether you deployed the Node backend or the PHP one:
|
|
6
|
+
# - Node backend running -> Node answers /api
|
|
7
|
+
# - only PHP deployed -> Node is down -> falls back to PHP-FPM
|
|
8
|
+
#
|
|
9
|
+
# This is NOT a ready-to-use file: paste these directives inside your own
|
|
10
|
+
# server { } block (the one with server_name, listen and SSL) and set `root`
|
|
11
|
+
# to your `out` folder.
|
|
12
|
+
|
|
13
|
+
# Hide the nginx version from response headers
|
|
14
|
+
server_tokens off;
|
|
15
|
+
|
|
16
|
+
# Force HTTPS on future requests (applies site-wide, including /api)
|
|
17
|
+
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
18
|
+
|
|
19
|
+
root /var/www/your-site/out;
|
|
20
|
+
index index.html;
|
|
21
|
+
|
|
22
|
+
# Disable directory listing
|
|
23
|
+
autoindex off;
|
|
24
|
+
|
|
25
|
+
error_page 403 404 /404.html;
|
|
26
|
+
location = /404.html {
|
|
27
|
+
internal;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Never expose the backend source directory over HTTP.
|
|
31
|
+
location ^~ /backend/ {
|
|
32
|
+
return 404;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Block server config files anywhere in the tree (.htaccess, web.config).
|
|
36
|
+
location ~* (^|/)(\.htaccess|web\.config)$ {
|
|
37
|
+
return 404;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# --- /api: Node primary, PHP fallback ---------------------------------------
|
|
41
|
+
# Try the Node backend first. If it is down (502/504), retry through the PHP
|
|
42
|
+
# front controller. Security headers for API responses are set by the backend.
|
|
43
|
+
location ^~ /api/ {
|
|
44
|
+
proxy_pass http://127.0.0.1:3000;
|
|
45
|
+
proxy_http_version 1.1;
|
|
46
|
+
proxy_set_header Host $host;
|
|
47
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
48
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
49
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
50
|
+
|
|
51
|
+
proxy_intercept_errors on;
|
|
52
|
+
error_page 502 504 = @php_backend;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
location @php_backend {
|
|
56
|
+
include fastcgi_params;
|
|
57
|
+
fastcgi_param SCRIPT_FILENAME $document_root/backend/_core/index.php;
|
|
58
|
+
|
|
59
|
+
# Adjust the PHP-FPM socket to your distro:
|
|
60
|
+
# Debian / Ubuntu : unix:/run/php/php8.3-fpm.sock
|
|
61
|
+
# RHEL / Fedora : unix:/run/php-fpm/php-fpm.sock
|
|
62
|
+
# TCP fallback : 127.0.0.1:9000
|
|
63
|
+
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Serve static files, fall back to the 404 page.
|
|
67
|
+
# A location with add_header does not inherit server-level headers,
|
|
68
|
+
# so Strict-Transport-Security is repeated here.
|
|
69
|
+
location / {
|
|
70
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
71
|
+
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
72
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
73
|
+
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
74
|
+
try_files $uri $uri/ /404.html;
|
|
75
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nibula",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "A beginner-friendly static site generator built on Eleventy that stays close to vanilla HTML, CSS and JS â with a page-management CLI, an optional PHP backend, and SEO files generated for you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"static-site-generator",
|