nibula 1.6.1 → 2.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/.eleventy.js +6 -2
- package/CHANGELOG.md +83 -0
- package/bin/create.js +71 -36
- package/bin/delegate.js +2 -1
- package/bin/nibula.js +8 -0
- package/docs/Assistant CLI.md +7 -3
- package/docs/Backend.md +17 -0
- package/docs/Components.md +15 -14
- package/docs/Creating pages.md +13 -6
- package/docs/Deploy.md +10 -0
- package/docs/Head and SEO.md +11 -10
- package/docs/{Javascript.md → JavaScript.md} +2 -2
- package/docs/Project structure.md +63 -0
- package/docs/Styling with SCSS.md +11 -7
- package/nginx.conf +9 -7
- package/package.json +1 -1
- package/src/frontend/components/not-found.njk +4 -0
- package/src/frontend/layouts/base.njk +0 -1
- package/src/frontend/routes/404.njk +10 -0
- package/src/frontend/routes/index.njk +10 -0
- package/tools/cli/ui.js +6 -6
- package/tools/config/messages.json +0 -8
- package/tools/config/settings.json +0 -6
- package/tools/lib/pageActions.js +0 -4
- package/tools/lib/paths.js +0 -1
- package/tools/lib/validation.js +0 -1
- package/tools/res/templates/template.njk +4 -3
- package/src/frontend/404.njk +0 -17
- package/src/frontend/index.njk +0 -9
- package/src/frontend/layouts/page-components.njk +0 -14
- package/tools/lib/pageComponents.js +0 -88
- /package/src/frontend/{.htaccess → hosting/.htaccess} +0 -0
- /package/src/frontend/{web.config → hosting/web.config} +0 -0
- /package/src/frontend/{llms.njk → indexing/llms.njk} +0 -0
- /package/src/frontend/{robots.njk → indexing/robots.njk} +0 -0
- /package/src/frontend/{sitemap.njk → indexing/sitemap.njk} +0 -0
package/.eleventy.js
CHANGED
|
@@ -27,12 +27,16 @@ module.exports = function (eleventyConfig) {
|
|
|
27
27
|
copyRecursiveSync("src/backend", `${OUTPUT_DIR}/backend`);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
eleventyConfig.addPassthroughCopy(
|
|
31
|
-
|
|
30
|
+
eleventyConfig.addPassthroughCopy({
|
|
31
|
+
"src/frontend/hosting/.htaccess": ".htaccess",
|
|
32
|
+
"src/frontend/hosting/web.config": "web.config",
|
|
33
|
+
});
|
|
34
|
+
|
|
32
35
|
eleventyConfig.addPassthroughCopy("src/frontend/assets");
|
|
33
36
|
eleventyConfig.addPassthroughCopy("src/frontend/robots.txt");
|
|
34
37
|
|
|
35
38
|
eleventyConfig.addPassthroughCopy({
|
|
39
|
+
// Bootstrap
|
|
36
40
|
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js": "js/bootstrap.bundle.min.js",
|
|
37
41
|
"node_modules/bootstrap-icons/font/fonts": "css/fonts",
|
|
38
42
|
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,89 @@ All notable changes to Nibula are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.0.0] - 2026-08-07
|
|
9
|
+
|
|
10
|
+
### Breaking
|
|
11
|
+
|
|
12
|
+
Existing projects keep running the Nibula version they were built with. These
|
|
13
|
+
steps apply only when you run `npm install nibula@latest` inside a project. Do
|
|
14
|
+
them in one pass, before the next build.
|
|
15
|
+
|
|
16
|
+
- **`page-components.njk` no longer exists.** Its `elif` chain is replaced by
|
|
17
|
+
includes written directly in each route. For every page, move the includes
|
|
18
|
+
from its `elif` block into the matching file in `src/frontend/routes/`, then
|
|
19
|
+
delete `src/frontend/layouts/page-components.njk`.
|
|
20
|
+
- **Every route's layout becomes `base.njk`.** Change `layout: page-components.njk`
|
|
21
|
+
to `layout: base.njk` in the front matter of each file in `routes/`.
|
|
22
|
+
- **`index.njk` and `404.njk` move into `routes/`.** They sat at the root of
|
|
23
|
+
`src/frontend/`; every page now lives in one place.
|
|
24
|
+
- **`llms.njk`, `robots.njk` and `sitemap.njk` move into `src/frontend/indexing/`.**
|
|
25
|
+
Their `permalink` values are unchanged, so the published URLs stay the same.
|
|
26
|
+
- **`.htaccess` and `web.config` move into `src/frontend/hosting/`.** The build
|
|
27
|
+
still places them in the root of the output folder, so what you deploy is
|
|
28
|
+
unchanged. `nginx.conf` stays at the project root: it is installed on the
|
|
29
|
+
server by hand, not published with the site.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- `docs/Project structure.md` maps the whole project and lists where to go for
|
|
34
|
+
each kind of change. There was no overview of the layout anywhere, so the two
|
|
35
|
+
new folders — and the older ones — had to be inferred from the file tree.
|
|
36
|
+
- `src/frontend/components/not-found.njk` holds the 404 page markup, included by
|
|
37
|
+
`routes/404.njk`.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- **`llms.txt` is generated from page data instead of being hardcoded.** It
|
|
42
|
+
listed the homepage and nothing else, so every other page was invisible to AI
|
|
43
|
+
crawlers unless you edited the template by hand. It now walks the same
|
|
44
|
+
collection `sitemap.njk` does, reading each page's title and description from
|
|
45
|
+
`pages.json` and skipping drafts, the 404 page and anything marked `noindex`.
|
|
46
|
+
Creating a page with the assistant is enough to have it listed.
|
|
47
|
+
- **Components are included in the route that renders them.** Composition lived
|
|
48
|
+
in a single shared layout, so adding a page meant appending a branch to a file
|
|
49
|
+
that grew with every page, and reading one page's structure meant scrolling
|
|
50
|
+
past all the others. A route now includes what it needs, and what a page
|
|
51
|
+
contains is visible in the page itself.
|
|
52
|
+
- **The assistant no longer edits Nunjucks templates.** Creating, renaming and
|
|
53
|
+
removing a page used to rewrite `page-components.njk` with regular expressions
|
|
54
|
+
against the template's syntax. That step is gone; the assistant now touches
|
|
55
|
+
only files it fully controls.
|
|
56
|
+
- **Documentation catches up with the code.** `Head & SEO` opened by pointing at
|
|
57
|
+
`pages.json` and `{{ pages.* }}` for the global settings, which have lived in
|
|
58
|
+
`site.json` since 1.6.0, and still listed `legal.cookieControls`, removed in
|
|
59
|
+
the same release. `JavaScript` claimed `global.js` sits in the modules folder,
|
|
60
|
+
which stopped being true when it moved up a level — the surrounding example
|
|
61
|
+
already contradicted it. `Styling with SCSS` referred to the Eleventy config
|
|
62
|
+
as `eleventy.config.js`.
|
|
63
|
+
- New pages are scaffolded with a commented example include instead of a warning
|
|
64
|
+
not to edit the file.
|
|
65
|
+
|
|
66
|
+
### Removed
|
|
67
|
+
|
|
68
|
+
- `src/frontend/layouts/page-components.njk`, and with it the `pageComponents`
|
|
69
|
+
configuration block, its six log messages, and its entry in the project's
|
|
70
|
+
required-files check.
|
|
71
|
+
- The include of `404/_404.njk`, a component that was never shipped. It survived
|
|
72
|
+
only because of `ignore missing`, which silently swallowed it on every build.
|
|
73
|
+
|
|
74
|
+
### Notes
|
|
75
|
+
|
|
76
|
+
- `routes/` is no longer created on demand or removed when empty: with
|
|
77
|
+
`index.njk` and `404.njk` inside, it always exists. The same on-demand handling
|
|
78
|
+
still applies to `scss/pages/` and `js/pages/`.
|
|
79
|
+
- Writing markup directly in a route works and is fine for a simple page.
|
|
80
|
+
Components remain the recommendation when there is something to reuse, not a
|
|
81
|
+
requirement.
|
|
82
|
+
|
|
83
|
+
## [1.6.2] - 2026-08-07
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
- `nib --version` (also `-v`, `v`, `ver`, `version`) prints the version number on its own line. Inside a project it reports the project's own copy, which is the one actually running the command.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
- `docs/Styling with SCSS.md` now matches the template: the Nunjucks snippets include the `url` filter and the config file is named `.eleventy.js`.
|
|
90
|
+
|
|
8
91
|
## [1.6.1] - 2026-08-06
|
|
9
92
|
|
|
10
93
|
### Fixed
|
package/bin/create.js
CHANGED
|
@@ -33,6 +33,11 @@ const BACKEND = Object.freeze({
|
|
|
33
33
|
PHP: 'php',
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
const COMMENT_STYLE = Object.freeze({
|
|
37
|
+
SLASH: { open: '// ', close: '' },
|
|
38
|
+
NJK: { open: '{# ', close: ' #}' },
|
|
39
|
+
});
|
|
40
|
+
|
|
36
41
|
// ── CHOICES ──────────────────────────────────────────────────────────────────
|
|
37
42
|
|
|
38
43
|
const LANGUAGE_CHOICES = [
|
|
@@ -88,14 +93,16 @@ const BACKEND_SKIP_DIRS = new Set(['node_modules', 'cache', '.git']);
|
|
|
88
93
|
|
|
89
94
|
const ALL_FRAMEWORKS = Object.values(FRAMEWORK).filter(f => f !== FRAMEWORK.NONE);
|
|
90
95
|
|
|
96
|
+
const SCSS_FRAMEWORK_PREFIX = 'modules/frameworks/';
|
|
97
|
+
|
|
98
|
+
// Markers are substrings matched against whole lines, never full lines, so the
|
|
99
|
+
// surrounding syntax (Nunjucks url filter, passthrough copy mapping) can change
|
|
100
|
+
// without breaking the scaffolder.
|
|
91
101
|
const FRAMEWORKS = {
|
|
92
102
|
[FRAMEWORK.BOOTSTRAP]: {
|
|
93
103
|
scss: 'bootstrap',
|
|
94
|
-
njk: ['
|
|
95
|
-
eleventy: [
|
|
96
|
-
'"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js": "js/bootstrap.bundle.min.js",',
|
|
97
|
-
'"node_modules/bootstrap-icons/font/fonts": "css/fonts",',
|
|
98
|
-
],
|
|
104
|
+
njk: ['/js/bootstrap.bundle.min.js'],
|
|
105
|
+
eleventy: ['bootstrap/dist/js/bootstrap.bundle.min.js', 'bootstrap-icons/font/fonts'],
|
|
99
106
|
},
|
|
100
107
|
[FRAMEWORK.BULMA]: {
|
|
101
108
|
scss: 'bulma',
|
|
@@ -104,19 +111,13 @@ const FRAMEWORKS = {
|
|
|
104
111
|
},
|
|
105
112
|
[FRAMEWORK.FOUNDATION]: {
|
|
106
113
|
scss: 'foundation',
|
|
107
|
-
njk: ['
|
|
108
|
-
eleventy: ['
|
|
114
|
+
njk: ['/js/foundation.min.js'],
|
|
115
|
+
eleventy: ['foundation-sites/dist/js/foundation.min.js'],
|
|
109
116
|
},
|
|
110
117
|
[FRAMEWORK.UIKIT]: {
|
|
111
118
|
scss: 'uikit',
|
|
112
|
-
njk: [
|
|
113
|
-
|
|
114
|
-
'<script src="/js/uikit-icons.min.js" defer></script>',
|
|
115
|
-
],
|
|
116
|
-
eleventy: [
|
|
117
|
-
'"node_modules/uikit/dist/js/uikit.min.js": "js/uikit.min.js",',
|
|
118
|
-
'"node_modules/uikit/dist/js/uikit-icons.min.js": "js/uikit-icons.min.js",',
|
|
119
|
-
],
|
|
119
|
+
njk: ['/js/uikit.min.js', '/js/uikit-icons.min.js'],
|
|
120
|
+
eleventy: ['uikit/dist/js/uikit.min.js', 'uikit/dist/js/uikit-icons.min.js'],
|
|
120
121
|
},
|
|
121
122
|
[FRAMEWORK.NONE]: {
|
|
122
123
|
scss: null,
|
|
@@ -189,10 +190,6 @@ function logAdd(name) {
|
|
|
189
190
|
log(`${color.green}+${color.reset} ${name}`);
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
function escapeRegex(str) {
|
|
193
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
193
|
function copyRecursive(src, dest, exclude = []) {
|
|
197
194
|
const stat = fs.statSync(src);
|
|
198
195
|
if (stat.isDirectory()) {
|
|
@@ -250,22 +247,60 @@ function copyBackend(src, dest, backend) {
|
|
|
250
247
|
}
|
|
251
248
|
}
|
|
252
249
|
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
function splitLine(line) {
|
|
251
|
+
const body = line.trim();
|
|
252
|
+
if (!body) return { indent: line, body: '', trailing: '' };
|
|
253
|
+
|
|
254
|
+
const start = line.indexOf(body);
|
|
255
|
+
return {
|
|
256
|
+
indent: line.slice(0, start),
|
|
257
|
+
body,
|
|
258
|
+
trailing: line.slice(start + body.length),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function uncommentLine(line, style) {
|
|
263
|
+
const { indent, body, trailing } = splitLine(line);
|
|
264
|
+
if (!body.startsWith(style.open)) return line;
|
|
265
|
+
|
|
266
|
+
let inner = body.slice(style.open.length);
|
|
267
|
+
if (style.close) {
|
|
268
|
+
if (!inner.endsWith(style.close)) return line;
|
|
269
|
+
inner = inner.slice(0, -style.close.length);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return indent + inner + trailing;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function commentLine(line, style) {
|
|
276
|
+
const bare = uncommentLine(line, style);
|
|
277
|
+
const { indent, body, trailing } = splitLine(bare);
|
|
278
|
+
if (!body) return line;
|
|
279
|
+
|
|
280
|
+
return indent + style.open + body + style.close + trailing;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function mapMarkedLines(content, marker, style, transform) {
|
|
284
|
+
return content
|
|
285
|
+
.split('\n')
|
|
286
|
+
.map(line => (line.includes(marker) ? transform(line, style) : line))
|
|
287
|
+
.join('\n');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function slashComment(content, marker) {
|
|
291
|
+
return mapMarkedLines(content, marker, COMMENT_STYLE.SLASH, commentLine);
|
|
256
292
|
}
|
|
257
293
|
|
|
258
|
-
function slashUncomment(content,
|
|
259
|
-
return content
|
|
294
|
+
function slashUncomment(content, marker) {
|
|
295
|
+
return mapMarkedLines(content, marker, COMMENT_STYLE.SLASH, uncommentLine);
|
|
260
296
|
}
|
|
261
297
|
|
|
262
|
-
function njkComment(content,
|
|
263
|
-
content
|
|
264
|
-
return content.split(line).join(`{# ${line} #}`);
|
|
298
|
+
function njkComment(content, marker) {
|
|
299
|
+
return mapMarkedLines(content, marker, COMMENT_STYLE.NJK, commentLine);
|
|
265
300
|
}
|
|
266
301
|
|
|
267
|
-
function njkUncomment(content,
|
|
268
|
-
return content
|
|
302
|
+
function njkUncomment(content, marker) {
|
|
303
|
+
return mapMarkedLines(content, marker, COMMENT_STYLE.NJK, uncommentLine);
|
|
269
304
|
}
|
|
270
305
|
|
|
271
306
|
function installDependencies(backend) {
|
|
@@ -323,14 +358,14 @@ function installDependencies(backend) {
|
|
|
323
358
|
function applyFramework(framework) {
|
|
324
359
|
const config = FRAMEWORKS[framework];
|
|
325
360
|
|
|
326
|
-
const globalScssPath = path.join(targetDir, 'src/frontend/scss/
|
|
361
|
+
const globalScssPath = path.join(targetDir, 'src/frontend/scss/_global.scss');
|
|
327
362
|
if (fs.existsSync(globalScssPath)) {
|
|
328
363
|
let content = fs.readFileSync(globalScssPath, 'utf8');
|
|
329
364
|
ALL_FRAMEWORKS.forEach(fw => {
|
|
330
|
-
content = slashComment(content,
|
|
365
|
+
content = slashComment(content, `${SCSS_FRAMEWORK_PREFIX}${fw}`);
|
|
331
366
|
});
|
|
332
367
|
if (config.scss) {
|
|
333
|
-
content = slashUncomment(content,
|
|
368
|
+
content = slashUncomment(content, `${SCSS_FRAMEWORK_PREFIX}${config.scss}`);
|
|
334
369
|
}
|
|
335
370
|
fs.writeFileSync(globalScssPath, content);
|
|
336
371
|
}
|
|
@@ -339,9 +374,9 @@ function applyFramework(framework) {
|
|
|
339
374
|
if (fs.existsSync(baseNjkPath)) {
|
|
340
375
|
let content = fs.readFileSync(baseNjkPath, 'utf8');
|
|
341
376
|
ALL_FRAMEWORKS.forEach(fw => {
|
|
342
|
-
FRAMEWORKS[fw].njk.forEach(
|
|
377
|
+
FRAMEWORKS[fw].njk.forEach(marker => { content = njkComment(content, marker); });
|
|
343
378
|
});
|
|
344
|
-
config.njk.forEach(
|
|
379
|
+
config.njk.forEach(marker => { content = njkUncomment(content, marker); });
|
|
345
380
|
fs.writeFileSync(baseNjkPath, content);
|
|
346
381
|
}
|
|
347
382
|
|
|
@@ -349,9 +384,9 @@ function applyFramework(framework) {
|
|
|
349
384
|
if (fs.existsSync(eleventyPath)) {
|
|
350
385
|
let content = fs.readFileSync(eleventyPath, 'utf8');
|
|
351
386
|
ALL_FRAMEWORKS.forEach(fw => {
|
|
352
|
-
FRAMEWORKS[fw].eleventy.forEach(
|
|
387
|
+
FRAMEWORKS[fw].eleventy.forEach(marker => { content = slashComment(content, marker); });
|
|
353
388
|
});
|
|
354
|
-
config.eleventy.forEach(
|
|
389
|
+
config.eleventy.forEach(marker => { content = slashUncomment(content, marker); });
|
|
355
390
|
fs.writeFileSync(eleventyPath, content);
|
|
356
391
|
}
|
|
357
392
|
}
|
package/bin/delegate.js
CHANGED
|
@@ -10,6 +10,7 @@ const ENTRY_FILE = path.join('bin', 'nibula.js');
|
|
|
10
10
|
const PACKAGE_FILE = 'package.json';
|
|
11
11
|
const LOCAL_FLAG = 'NIBULA_LOCAL';
|
|
12
12
|
const LOCAL_FLAG_VALUE = '1';
|
|
13
|
+
const GLOBAL_VERSION_FLAG = 'NIBULA_GLOBAL_VERSION';
|
|
13
14
|
|
|
14
15
|
const globalVersion = require(`../${PACKAGE_FILE}`).version;
|
|
15
16
|
|
|
@@ -49,7 +50,7 @@ function delegateToLocal(projectRoot) {
|
|
|
49
50
|
const result = spawnSync(process.execPath, [localEntry, ...process.argv.slice(2)], {
|
|
50
51
|
stdio: 'inherit',
|
|
51
52
|
cwd: process.cwd(),
|
|
52
|
-
env: { ...process.env, [LOCAL_FLAG]: LOCAL_FLAG_VALUE },
|
|
53
|
+
env: { ...process.env, [LOCAL_FLAG]: LOCAL_FLAG_VALUE, [GLOBAL_VERSION_FLAG]: globalVersion },
|
|
53
54
|
});
|
|
54
55
|
|
|
55
56
|
process.exit(result.status ?? 0);
|
package/bin/nibula.js
CHANGED
|
@@ -293,6 +293,14 @@ async function main() {
|
|
|
293
293
|
usage(pkg.version);
|
|
294
294
|
break;
|
|
295
295
|
}
|
|
296
|
+
case 'version':
|
|
297
|
+
case 'v':
|
|
298
|
+
case 'ver':
|
|
299
|
+
case '-v':
|
|
300
|
+
case '--version': {
|
|
301
|
+
console.log(pkg.version);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
296
304
|
default:
|
|
297
305
|
console.error(`${color.red}\nUnknown command:${color.reset} ${cmd}`);
|
|
298
306
|
usage(pkg.version);
|
package/docs/Assistant CLI.md
CHANGED
|
@@ -33,7 +33,7 @@ For a page named `my-page`, the following files are created:
|
|
|
33
33
|
| `src/frontend/js/pages/myPage.js` | JS entry point |
|
|
34
34
|
| `src/frontend/routes/my-page.njk` | Nunjucks template |
|
|
35
35
|
|
|
36
|
-
It also adds
|
|
36
|
+
It also adds a stub entry in `pages.json`:
|
|
37
37
|
|
|
38
38
|
```json
|
|
39
39
|
"myPage": {
|
|
@@ -51,13 +51,17 @@ It also adds an `elif` block in `page-components.njk` and a stub entry in `pages
|
|
|
51
51
|
}
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
The route is created with an empty body and a commented example — you decide which components go in it. See **Components** DOC file.
|
|
55
|
+
|
|
54
56
|
## Remove page
|
|
55
57
|
|
|
56
|
-
Deletes all source files for the page
|
|
58
|
+
Deletes all source files for the page, cleans up the output directory, and removes the record from `pages.json`.
|
|
57
59
|
|
|
58
60
|
## Rename page
|
|
59
61
|
|
|
60
|
-
Renames all three source files, updates the `
|
|
62
|
+
Renames all three source files, updates the `permalink` in the route's front matter, and renames the record in `pages.json`. Nothing inside the record is touched — your SEO title, description and CDN links stay exactly as you wrote them.
|
|
63
|
+
|
|
64
|
+
The includes you wrote inside the route move with the file, since the route itself is renamed rather than rewritten.
|
|
61
65
|
|
|
62
66
|
## Configure output path
|
|
63
67
|
|
package/docs/Backend.md
CHANGED
|
@@ -42,6 +42,23 @@ src/backend/
|
|
|
42
42
|
> the matching `example.config.*`, and are git-ignored so your secrets stay
|
|
43
43
|
> local. If `config.*` is missing (e.g. after a fresh clone), copy the example.
|
|
44
44
|
|
|
45
|
+
### Installing Composer packages (PHP)
|
|
46
|
+
|
|
47
|
+
Backend dependencies live in `src/backend/_core`, next to `composer.json`.
|
|
48
|
+
Install from there, not from the project root:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd src/backend/_core
|
|
52
|
+
composer require vendor/package
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`_core/init.php` already requires Composer's autoloader, so an installed package
|
|
56
|
+
is usable in your endpoints straight away — there is no extra `require` to add.
|
|
57
|
+
|
|
58
|
+
Packages land in `src/backend/_core/vendor/`, which is git-ignored. After a fresh
|
|
59
|
+
clone that folder is missing and the backend won't start at all: run
|
|
60
|
+
`composer install` from `src/backend/_core` to restore it.
|
|
61
|
+
|
|
45
62
|
## How routing works
|
|
46
63
|
|
|
47
64
|
The file path inside `api/` maps directly to the URL. The `public` / `protected`
|
package/docs/Components.md
CHANGED
|
@@ -11,37 +11,38 @@ Create a new `.njk` file anywhere inside `src/frontend/components/`. You can org
|
|
|
11
11
|
```
|
|
12
12
|
src/frontend/components/
|
|
13
13
|
├── global/
|
|
14
|
-
├──
|
|
14
|
+
│ ├── header.njk
|
|
15
|
+
│ └── footer.njk
|
|
15
16
|
├── modals/
|
|
16
17
|
│ └── privacyModal.njk # You can move it to a modals/subfolder
|
|
18
|
+
├── not-found.njk
|
|
17
19
|
├── welcome.njk
|
|
18
20
|
```
|
|
19
21
|
|
|
20
22
|
## Include a component
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
Components are included in the page that renders them. Open the page's file in `src/frontend/routes/` and write the includes in its body
|
|
23
25
|
|
|
24
|
-
### page
|
|
26
|
+
### example-page.njk <small>(`src/frontend/routes/`)</small>
|
|
25
27
|
|
|
26
28
|
```js
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
---
|
|
30
|
+
title: "examplePage"
|
|
31
|
+
permalink: "/example-page/"
|
|
32
|
+
layout: base.njk
|
|
33
|
+
---
|
|
29
34
|
|
|
30
|
-
{% elif title == "examplePage" %}
|
|
31
35
|
{% include "example-component-1.njk" %}
|
|
32
36
|
{% include "subfolder/example-component-2.njk" %}
|
|
33
|
-
|
|
34
|
-
{% else %}
|
|
35
|
-
{% include "404/_404.njk" %}
|
|
36
|
-
{{ content | safe }}
|
|
37
|
-
{% endif %}
|
|
38
37
|
```
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
Components render in the order you list them. If a component lives in a subfolder, specify the relative path accordingly
|
|
40
|
+
|
|
41
|
+
Paths are resolved from `src/frontend/components/`, so you never write that part — `"welcome.njk"`, not `"components/welcome.njk"`
|
|
41
42
|
|
|
42
|
-
> ⚠️
|
|
43
|
+
> ⚠️ If you move or delete a component, update every route that includes it or the build will fail
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
Writing HTML directly in the route works too, and is perfectly fine for a short page. Components are the recommendation when there is something to reuse or when a page grows past a screenful — not a rule
|
|
45
46
|
|
|
46
47
|
## Nest components
|
|
47
48
|
|
package/docs/Creating pages.md
CHANGED
|
@@ -16,11 +16,16 @@ For a page named `my-page`:
|
|
|
16
16
|
## Adding content
|
|
17
17
|
|
|
18
18
|
1. Create a component in `src/frontend/components/` (e.g. `my-component.njk`)
|
|
19
|
-
2. Include it in `src/frontend/
|
|
19
|
+
2. Include it in the page's own file, `src/frontend/routes/my-page.njk`:
|
|
20
20
|
|
|
21
21
|
```njk
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
---
|
|
23
|
+
title: "myPage"
|
|
24
|
+
permalink: "/my-page/"
|
|
25
|
+
layout: base.njk
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
{% include "my-component.njk" %}
|
|
24
29
|
```
|
|
25
30
|
|
|
26
31
|
See **Components** DOC file for more info
|
|
@@ -36,8 +41,8 @@ To create a URL like `domain.it/about/team`, edit the `permalink` in `src/fronte
|
|
|
36
41
|
```njk
|
|
37
42
|
---
|
|
38
43
|
title: "team"
|
|
39
|
-
permalink: "about/team/"
|
|
40
|
-
layout:
|
|
44
|
+
permalink: "/about/team/"
|
|
45
|
+
layout: base.njk
|
|
41
46
|
---
|
|
42
47
|
```
|
|
43
48
|
|
|
@@ -67,4 +72,6 @@ The CLI creates a stub entry in `src/frontend/data/pages.json`. Fill it in:
|
|
|
67
72
|
"js": []
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
|
-
```
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The `seo.title` and `seo.description` you write here are also what the page shows up as in `llms.txt`, so a page left with the placeholder description will advertise itself that way to AI crawlers.
|
package/docs/Deploy.md
CHANGED
|
@@ -39,6 +39,16 @@ dependencies.
|
|
|
39
39
|
**Node backend:** Node.js 18+ and shell access to run a persistent process — a
|
|
40
40
|
VPS you control.
|
|
41
41
|
|
|
42
|
+
## Editing the server config
|
|
43
|
+
|
|
44
|
+
`.htaccess` and `web.config` are in the root of `out`, ready to upload. To change
|
|
45
|
+
a rule, edit the source in `src/frontend/hosting/` — a change made directly in
|
|
46
|
+
`out` is lost on the next build.
|
|
47
|
+
|
|
48
|
+
`nginx.conf` works differently: it sits in the project root and is **not** part
|
|
49
|
+
of `out`, because nginx reads no per-directory config. You install it on the
|
|
50
|
+
server by hand, as described below.
|
|
51
|
+
|
|
42
52
|
---
|
|
43
53
|
|
|
44
54
|
## Shared hosting (Apache)
|
package/docs/Head and SEO.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Head & SEO
|
|
2
2
|
|
|
3
|
-
Global settings live in `src/frontend/data/
|
|
3
|
+
Global settings live in `src/frontend/data/site.json` and are available everywhere via `{{ site.* }}`.
|
|
4
4
|
|
|
5
5
|
## Global fields
|
|
6
6
|
|
|
@@ -19,7 +19,6 @@ Global settings live in `src/frontend/data/pages.json` and are available everywh
|
|
|
19
19
|
"legal": {
|
|
20
20
|
"privacy": "",
|
|
21
21
|
"cookie": "",
|
|
22
|
-
"cookieControls": "",
|
|
23
22
|
"terms": "",
|
|
24
23
|
"copyright": {
|
|
25
24
|
"year": "2026",
|
|
@@ -41,7 +40,7 @@ Global settings live in `src/frontend/data/pages.json` and are available everywh
|
|
|
41
40
|
| `theme` | Site color scheme (`light` / `dark`) — sets `data-theme`, `data-bs-theme` and `color-scheme` |
|
|
42
41
|
| `theme_color` | Browser UI / PWA bar color — should match the chosen `theme` |
|
|
43
42
|
| `logo` | Path to the logo, also used as social image |
|
|
44
|
-
| `legal.privacy` / `cookie` / `
|
|
43
|
+
| `legal.privacy` / `cookie` / `terms` | Legal page URLs |
|
|
45
44
|
| `legal.copyright.year` / `text` | Footer copyright |
|
|
46
45
|
|
|
47
46
|
## Per-page SEO and CDN
|
|
@@ -68,10 +67,10 @@ Per-page settings live in `src/frontend/data/pages.json`, available via `{{ page
|
|
|
68
67
|
|
|
69
68
|
| Field | Purpose | If empty / absent |
|
|
70
69
|
|---|---|---|
|
|
71
|
-
| `seo.title` | Page title | Falls back to global `title` |
|
|
72
|
-
| `seo.description` | Meta description | Falls back to global `description` |
|
|
70
|
+
| `seo.title` | Page title, and the page's label in `llms.txt` | Falls back to global `title` |
|
|
71
|
+
| `seo.description` | Meta description, and the page's summary in `llms.txt` | Falls back to global `description` |
|
|
73
72
|
| `seo.keywords` | Meta keywords | Falls back to global `keywords` |
|
|
74
|
-
| `seo.noindex` | If `true`, emits `<meta name="robots" content="noindex, follow">` and drops the page from the sitemap | Page is indexable |
|
|
73
|
+
| `seo.noindex` | If `true`, emits `<meta name="robots" content="noindex, follow">` and drops the page from both the sitemap and `llms.txt` | Page is indexable |
|
|
75
74
|
| `seo.canonical` | Absolute canonical URL override | Computed as `url + page.url` |
|
|
76
75
|
| `cdn.css` / `cdn.js` | Extra per-page CDN links | No extra links loaded |
|
|
77
76
|
|
|
@@ -129,12 +128,14 @@ The three favicon tags are hardcoded in `base.njk`. To change the icons, replace
|
|
|
129
128
|
|
|
130
129
|
## AI & SEO bots
|
|
131
130
|
|
|
132
|
-
`llms.txt`, `robots.txt` and `sitemap.xml` are generated from `.njk` templates and are publicly reachable (they must not be blocked by the server config).
|
|
131
|
+
`llms.txt`, `robots.txt` and `sitemap.xml` are generated from `.njk` templates in `src/frontend/indexing/` and are publicly reachable (they must not be blocked by the server config).
|
|
133
132
|
|
|
134
133
|
| File | Purpose | Reachable at |
|
|
135
134
|
|---|---|---|
|
|
136
|
-
| `llms.njk` |
|
|
135
|
+
| `llms.njk` | Describes the site and lists its pages for AI models | `yoursite.com/llms.txt` |
|
|
137
136
|
| `robots.njk` | Controls search-engine crawling | `yoursite.com/robots.txt` |
|
|
138
|
-
| `sitemap.njk` | Lists indexable pages
|
|
137
|
+
| `sitemap.njk` | Lists indexable pages | `yoursite.com/sitemap.xml` |
|
|
139
138
|
|
|
140
|
-
|
|
139
|
+
`llms.njk` and `sitemap.njk` build their lists the same way: they walk every page, then skip drafts, the 404 page, and anything with `seo.noindex` set to `true`. Creating a page with the assistant is enough to have it appear in both — there is no separate list to maintain.
|
|
140
|
+
|
|
141
|
+
To customize the wording or the fixed sections, edit `src/frontend/indexing/llms.njk` or `src/frontend/indexing/robots.njk` directly.
|
|
@@ -31,7 +31,7 @@ That's what `global.js` is for. It lives in `src/frontend/js/`, every page entry
|
|
|
31
31
|
### global.js <small>(`src/frontend/js/`)</small>
|
|
32
32
|
|
|
33
33
|
```js
|
|
34
|
-
// import { initExampleModule } from './exampleModule.js';
|
|
34
|
+
// import { initExampleModule } from './modules/exampleModule.js';
|
|
35
35
|
|
|
36
36
|
function initGlobal() {
|
|
37
37
|
// initExampleModule();
|
|
@@ -50,7 +50,7 @@ Pages import it without curly braces, since there is nothing to export — the m
|
|
|
50
50
|
import '../global.js';
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Note
|
|
53
|
+
Note the difference in import paths. A page entry point sits inside `js/pages/`, so it reaches a module with `'../modules/exampleModule.js'`. `global.js` sits directly in `js/`, one level up, so the same module is `'./modules/exampleModule.js'`.
|
|
54
54
|
|
|
55
55
|
### A concrete example: the header menu
|
|
56
56
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Project Structure
|
|
2
|
+
|
|
3
|
+
> Examples use JavaScript, but everything applies equally to TypeScript. The only difference is the file extension (`.ts` instead of `.js`), that imports do **not** include the extension, and that paths use `src/frontend/ts/` instead of `src/frontend/js/`.
|
|
4
|
+
|
|
5
|
+
Nibula ships a fixed layout so you always know where to put your hands. Inside these folders you're free to organize as you like — subfolders in `components/`, `modules/` and `api/` are all fair game.
|
|
6
|
+
|
|
7
|
+
## The tree
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
your-project/
|
|
11
|
+
├── .eleventy.js # Eleventy configuration
|
|
12
|
+
├── nginx.conf # nginx site file
|
|
13
|
+
├── package.json
|
|
14
|
+
├── out/ # Build output — never edit, never commit
|
|
15
|
+
└── src/
|
|
16
|
+
├── backend/ # REST API — see docs/Backend.md
|
|
17
|
+
└── frontend/
|
|
18
|
+
├── assets/ # Images, fonts, brand files
|
|
19
|
+
└── brand/ # Favicons, logo etc
|
|
20
|
+
├── components/ # Reusable .njk fragments
|
|
21
|
+
│ └── global/ # Header and footer, included in every page
|
|
22
|
+
├── data/ # site.json and pages.json
|
|
23
|
+
├── hosting/ # .htaccess and web.config
|
|
24
|
+
├── indexing/ # llms, robots and sitemap templates
|
|
25
|
+
├── js/ # global.js, modules/, pages/
|
|
26
|
+
├── layouts/ # base.njk — the HTML skeleton every page extends
|
|
27
|
+
├── routes/ # One file per page
|
|
28
|
+
└── scss/ # _root.scss, _global.scss, modules/, pages/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Where things happen
|
|
32
|
+
|
|
33
|
+
| I want to... | Go to |
|
|
34
|
+
|---|---|
|
|
35
|
+
| Add a page | `nib cli` → creates the files in `routes/`, `scss/pages/`, `js/pages/` |
|
|
36
|
+
| Write the content of a page | `routes/` |
|
|
37
|
+
| Build a reusable block of markup | `components/` |
|
|
38
|
+
| Change the header or footer | `components/global/` |
|
|
39
|
+
| Style one page | `scss/pages/` |
|
|
40
|
+
| Style every page | `scss/_global.scss` |
|
|
41
|
+
| Define a variable used across the site | `scss/_root.scss` |
|
|
42
|
+
| Run a script on one page | `js/pages/` |
|
|
43
|
+
| Run a script everywhere | `js/global.js` |
|
|
44
|
+
| Change the site name, URL, author | `data/site.json` |
|
|
45
|
+
| Change a page's SEO | `data/pages.json` |
|
|
46
|
+
| Edit the HTML `<head>` | `layouts/base.njk` |
|
|
47
|
+
| Add an API endpoint | `backend/api/` |
|
|
48
|
+
|
|
49
|
+
## Two folders worth a note
|
|
50
|
+
|
|
51
|
+
**`out/`** is generated by every build. Editing anything in it means losing the change the next time you run `nib run` or `nib build`. Always edit the source in `src/` — including `.htaccess` and `web.config`, whose sources live in `src/frontend/hosting/`.
|
|
52
|
+
|
|
53
|
+
**`data/`** feeds the build but is never published: it holds the full page map, including pages you marked `noindex`, so it stays out of the output on purpose.
|
|
54
|
+
|
|
55
|
+
## What you can rearrange
|
|
56
|
+
|
|
57
|
+
Subfolders are allowed wherever content is yours to organize:
|
|
58
|
+
|
|
59
|
+
- `components/` — nest as deep as you like; includes take the path relative to `components/`
|
|
60
|
+
- `scss/modules/` and `js/modules/` — same idea, remember to update the relative import paths
|
|
61
|
+
- `backend/api/public/` and `protected/` — subfolders become part of the URL
|
|
62
|
+
|
|
63
|
+
The folders in the tree above are the part to leave alone: paths are wired into `.eleventy.js`, the build scripts and the assistant, so renaming one breaks the build.
|
|
@@ -31,7 +31,7 @@ body {
|
|
|
31
31
|
|
|
32
32
|
Instead of using `:root` in your custom modules or pages, the best thing to do is to centralize all your variables in a single file (that will be tree-shaken automatically by Sass)
|
|
33
33
|
|
|
34
|
-
### _root.scss <small>(`src/frontend/scss
|
|
34
|
+
### _root.scss <small>(`src/frontend/scss/`)</small>
|
|
35
35
|
```scss
|
|
36
36
|
$header-height: 10vh;
|
|
37
37
|
|
|
@@ -51,6 +51,8 @@ You can create subfolders if you want to refactor the structure, but be sure to
|
|
|
51
51
|
```scss
|
|
52
52
|
@use '../../root' as root;
|
|
53
53
|
|
|
54
|
+
@import '../../global';
|
|
55
|
+
|
|
54
56
|
body {
|
|
55
57
|
background-color: root.$primary;
|
|
56
58
|
}
|
|
@@ -97,19 +99,19 @@ To enable/disable them you have to modify 3 files around the project by just com
|
|
|
97
99
|
|
|
98
100
|
```html
|
|
99
101
|
{# Bootstrap JS #}
|
|
100
|
-
<script src="/js/bootstrap.bundle.min.js" defer></script>
|
|
102
|
+
<script src="{{ '/js/bootstrap.bundle.min.js' | url }}" defer></script>
|
|
101
103
|
|
|
102
104
|
{# Foundation JS #}
|
|
103
|
-
{# <script src="/js/foundation.min.js" defer></script> #}
|
|
105
|
+
{# <script src="{{ '/js/foundation.min.js' | url }}" defer></script> #}
|
|
104
106
|
|
|
105
107
|
{# UIkit JS #}
|
|
106
|
-
{# <script src="/js/uikit.min.js" defer></script> #}
|
|
107
|
-
{# <script src="/js/uikit-icons.min.js" defer></script> #}
|
|
108
|
+
{# <script src="{{ '/js/uikit.min.js' | url }}" defer></script> #}
|
|
109
|
+
{# <script src="{{ '/js/uikit-icons.min.js' | url }}" defer></script> #}
|
|
108
110
|
|
|
109
111
|
{# Bulma — no JS needed #}
|
|
110
112
|
```
|
|
111
113
|
|
|
112
|
-
### 3. eleventy.
|
|
114
|
+
### 3. .eleventy.js <small>(project root)</small>
|
|
113
115
|
|
|
114
116
|
```javascript
|
|
115
117
|
eleventyConfig.addPassthroughCopy({
|
|
@@ -128,7 +130,9 @@ eleventyConfig.addPassthroughCopy({
|
|
|
128
130
|
});
|
|
129
131
|
```
|
|
130
132
|
|
|
131
|
-
> ⚠️ Changes to
|
|
133
|
+
> ⚠️ Changes to `.eleventy.js` are not picked up while the dev server is running — restart it.
|
|
134
|
+
|
|
135
|
+
> These three files are set up for you when you pick a framework during `nib new`. You only need to edit them by hand if you change your mind later.
|
|
132
136
|
|
|
133
137
|
### Reducing bundle size
|
|
134
138
|
|
package/nginx.conf
CHANGED
|
@@ -29,7 +29,7 @@ server {
|
|
|
29
29
|
|
|
30
30
|
# HTTPS (443) configuration
|
|
31
31
|
server {
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
listen 443 ssl;
|
|
34
34
|
listen [::]:443 ssl;
|
|
35
35
|
|
|
@@ -40,13 +40,13 @@ server {
|
|
|
40
40
|
root /var/www/SITE_FOLDER;
|
|
41
41
|
index index.html;
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
# Certificate
|
|
44
44
|
ssl_certificate /etc/letsencrypt/live/YOUR_CERTIFICATE/fullchain.pem;
|
|
45
45
|
ssl_certificate_key /etc/letsencrypt/live/YOUR_CERTIFICATE/privkey.pem;
|
|
46
46
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
47
47
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
# Security headers
|
|
50
50
|
add_header X-Content-Type-Options "nosniff" always;
|
|
51
51
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
52
52
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
@@ -55,10 +55,12 @@ server {
|
|
|
55
55
|
# 31536000 once stable; "; includeSubDomains" needs HTTPS on all subdomains.
|
|
56
56
|
add_header Strict-Transport-Security "max-age=300" always;
|
|
57
57
|
|
|
58
|
+
# Hide Nginx version in error pages and Server HTTP header to avoid vulnerabilities
|
|
58
59
|
server_tokens off;
|
|
60
|
+
|
|
59
61
|
autoindex off;
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
# Blocked paths (redirect to 404.html)
|
|
62
64
|
error_page 403 404 /404.html;
|
|
63
65
|
|
|
64
66
|
location ^~ /backend/ {
|
|
@@ -77,7 +79,7 @@ server {
|
|
|
77
79
|
return 404;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
# API backend — Node primary, PHP fallback
|
|
81
83
|
location ^~ /api/ {
|
|
82
84
|
proxy_pass http://127.0.0.1:3000;
|
|
83
85
|
proxy_http_version 1.1;
|
|
@@ -102,12 +104,12 @@ server {
|
|
|
102
104
|
fastcgi_pass unix:/run/php/php-fpm.sock;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
|
|
107
|
+
# Static files
|
|
106
108
|
location / {
|
|
107
109
|
try_files $uri $uri/ $uri.html =404;
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
# Error page
|
|
111
113
|
location = /404.html {
|
|
112
114
|
internal;
|
|
113
115
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nibula",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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
|
"11ty",
|
package/tools/cli/ui.js
CHANGED
|
@@ -15,12 +15,14 @@ const BOX_VERTICAL = '\u2502';
|
|
|
15
15
|
const NEW_LINE = '\n';
|
|
16
16
|
const SPACE = ' ';
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
const LOCAL_FLAG_VALUE = '1';
|
|
18
|
+
const GLOBAL_VERSION_FLAG = 'NIBULA_GLOBAL_VERSION';
|
|
20
19
|
const TITLE_PADDING = 2;
|
|
21
20
|
|
|
22
|
-
const
|
|
23
|
-
|
|
21
|
+
const LOCAL_VERSION = require('../../package.json').version;
|
|
22
|
+
const GLOBAL_VERSION = process.env[GLOBAL_VERSION_FLAG];
|
|
23
|
+
|
|
24
|
+
const TITLE_SUFFIX = GLOBAL_VERSION && GLOBAL_VERSION !== LOCAL_VERSION
|
|
25
|
+
? ` (local v${LOCAL_VERSION})`
|
|
24
26
|
: '';
|
|
25
27
|
|
|
26
28
|
const PLAIN_TITLE = `${BOX_TITLE}${TITLE_SUFFIX}`;
|
|
@@ -55,8 +57,6 @@ function centeredTitle() {
|
|
|
55
57
|
const left = SPACE.repeat(Math.ceil(padding / 2));
|
|
56
58
|
const right = SPACE.repeat(Math.floor(padding / 2));
|
|
57
59
|
|
|
58
|
-
if (!TITLE_SUFFIX) return `${left}${BOX_TITLE}${right}`;
|
|
59
|
-
|
|
60
60
|
const suffix = `${color.dim}${TITLE_SUFFIX}${color.reset}${color[COLORS.box]}${color.bold}`;
|
|
61
61
|
return `${left}${BOX_TITLE}${suffix}${right}`;
|
|
62
62
|
}
|
|
@@ -26,14 +26,6 @@
|
|
|
26
26
|
"renameFailed": "[error] could not rename page files: {error}",
|
|
27
27
|
"deleteFailed": "[error] {path}: {error}"
|
|
28
28
|
},
|
|
29
|
-
"components": {
|
|
30
|
-
"fileMissing": "[skip] not found: {path}",
|
|
31
|
-
"anchorMissing": "[skip] no {anchor} anchor in {file}",
|
|
32
|
-
"blockAdded": "[updated] page block added for \"{name}\"",
|
|
33
|
-
"blockMissing": "[skip] page block for \"{name}\" not found",
|
|
34
|
-
"blockRemoved": "[cleaned] page block removed for \"{name}\"",
|
|
35
|
-
"blockRenamed": "[renamed] page block \"{source}\" \u2192 \"{destination}\""
|
|
36
|
-
},
|
|
37
29
|
"pagesData": {
|
|
38
30
|
"fileMissing": "[skip] not found: {path}",
|
|
39
31
|
"invalidStructure": "[error] {file} must contain an object of pages",
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"stylePages": "src/frontend/scss/pages",
|
|
14
14
|
"pagesData": "src/frontend/data/pages.json",
|
|
15
15
|
"siteData": "src/frontend/data/site.json",
|
|
16
|
-
"pageComponents": "src/frontend/layouts/page-components.njk",
|
|
17
16
|
"templates": "tools/res/templates"
|
|
18
17
|
},
|
|
19
18
|
"languages": {
|
|
@@ -94,11 +93,6 @@
|
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
},
|
|
97
|
-
"pageComponents": {
|
|
98
|
-
"anchor": "{% else %}",
|
|
99
|
-
"condition": "{% elif title == \"{camelName}\" %}",
|
|
100
|
-
"include": "{% include \"component-name.njk\" ignore missing %}"
|
|
101
|
-
},
|
|
102
96
|
"frontMatter": {
|
|
103
97
|
"title": "title: \"{camelName}\"",
|
|
104
98
|
"permalink": "permalink: \"/{pageName}/\""
|
package/tools/lib/pageActions.js
CHANGED
|
@@ -7,7 +7,6 @@ const { formatText } = require('./text');
|
|
|
7
7
|
const { languageSettings } = require('./project');
|
|
8
8
|
const { getPageArtifacts, pageExists } = require('./pageArtifacts');
|
|
9
9
|
const { readPagesData, addPageData, removePageData, renamePageData } = require('./pagesData');
|
|
10
|
-
const { addPageBlock, removePageBlock, renamePageBlock } = require('./pageComponents');
|
|
11
10
|
|
|
12
11
|
const FRONT_MATTER_PATTERNS = Object.freeze({
|
|
13
12
|
title: /^title:.*$/m,
|
|
@@ -62,7 +61,6 @@ function addPage(pageName) {
|
|
|
62
61
|
return;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
addPageBlock(pageName);
|
|
66
64
|
addPageData(pageName);
|
|
67
65
|
}
|
|
68
66
|
|
|
@@ -104,7 +102,6 @@ function renamePage(oldName, newName) {
|
|
|
104
102
|
return;
|
|
105
103
|
}
|
|
106
104
|
|
|
107
|
-
renamePageBlock(oldName, newName);
|
|
108
105
|
renamePageData(oldName, newName);
|
|
109
106
|
}
|
|
110
107
|
|
|
@@ -134,7 +131,6 @@ function removePage(pageName) {
|
|
|
134
131
|
}
|
|
135
132
|
}
|
|
136
133
|
|
|
137
|
-
removePageBlock(pageName);
|
|
138
134
|
removePageData(pageName);
|
|
139
135
|
|
|
140
136
|
const sourceDirectories = [source.style, source.script, source.route].map(file => path.dirname(file));
|
package/tools/lib/paths.js
CHANGED
|
@@ -46,7 +46,6 @@ const PATHS = Object.freeze({
|
|
|
46
46
|
get stylePages() { return resolveFromProject(settings.paths.stylePages); },
|
|
47
47
|
get siteData() { return resolveFromProject(settings.paths.siteData); },
|
|
48
48
|
get pagesData() { return resolveFromProject(settings.paths.pagesData); },
|
|
49
|
-
get pageComponents() { return resolveFromProject(settings.paths.pageComponents); },
|
|
50
49
|
get templates() { return resolveFromPackage(settings.paths.templates); },
|
|
51
50
|
});
|
|
52
51
|
|
package/tools/lib/validation.js
CHANGED
|
@@ -13,7 +13,6 @@ function requiredFiles() {
|
|
|
13
13
|
{ label: settings.paths.packageJson, path: PATHS.packageJson },
|
|
14
14
|
{ label: settings.paths.siteData, path: PATHS.siteData },
|
|
15
15
|
{ label: settings.paths.pagesData, path: PATHS.pagesData },
|
|
16
|
-
{ label: settings.paths.pageComponents, path: PATHS.pageComponents },
|
|
17
16
|
{ label: message('validation.templatesLabel'), path: PATHS.templates },
|
|
18
17
|
];
|
|
19
18
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: ""
|
|
3
3
|
permalink: ""
|
|
4
|
-
layout:
|
|
4
|
+
layout: base.njk
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- !IMPORTANT -->
|
|
8
|
-
<!--
|
|
9
|
-
|
|
8
|
+
<!-- To keep order, the best practice is to create a new component.njk file in components/ and include it here-->
|
|
9
|
+
|
|
10
|
+
{% include "component.njk" ignore missing %}
|
package/src/frontend/404.njk
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "404"
|
|
3
|
-
permalink: 404.html
|
|
4
|
-
layout: base.njk
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
<!-- !IMPORTANT -->
|
|
8
|
-
<!-- This is the only page that you need to modify statically -->
|
|
9
|
-
|
|
10
|
-
<div class="fade-in not-found">
|
|
11
|
-
<h1>Oops! Page not found</h1>
|
|
12
|
-
<a href="/">Return to homepage</a>
|
|
13
|
-
</div>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{# You can also add the includes you need here below
|
|
17
|
-
{% include "component.njk" %} #}
|
package/src/frontend/index.njk
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
layout: base.njk
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
<!-- NJK (HTML) components per page -->
|
|
6
|
-
|
|
7
|
-
{% if title == "homepage" %}
|
|
8
|
-
{% include "welcome.njk" ignore missing %}
|
|
9
|
-
|
|
10
|
-
{% else %}
|
|
11
|
-
<!-- You should not touch this else statement -->
|
|
12
|
-
{% include "404/_404.njk" %}
|
|
13
|
-
{{ content | safe }}
|
|
14
|
-
{% endif %}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const settings = require('../config/settings.json');
|
|
3
|
-
const { PATHS } = require('./paths');
|
|
4
|
-
const { exists, readText, writeText } = require('./files');
|
|
5
|
-
const { log } = require('./logger');
|
|
6
|
-
const { toCamelCase, formatText } = require('./text');
|
|
7
|
-
|
|
8
|
-
const EXTRA_BLANK_LINES_PATTERN = /\n\s*\n\s*\n/g;
|
|
9
|
-
const BLANK_LINES = '\n\n';
|
|
10
|
-
const ANCHOR = settings.pageComponents.anchor;
|
|
11
|
-
|
|
12
|
-
function blockPattern(camelName) {
|
|
13
|
-
return new RegExp(
|
|
14
|
-
`[ \\t]*\\{%\\s*elif\\s+title\\s*==\\s*"${camelName}"\\s*%\\}[\\s\\S]*?(?=[ \\t]*\\{%\\s*(?:elif|else|endif))`
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function conditionFor(camelName) {
|
|
19
|
-
return formatText(settings.pageComponents.condition, { camelName });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function readPageComponents() {
|
|
23
|
-
if (!exists(PATHS.pageComponents)) {
|
|
24
|
-
log('components.fileMissing', { path: PATHS.pageComponents });
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
return readText(PATHS.pageComponents);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function writePageComponents(content) {
|
|
31
|
-
writeText(PATHS.pageComponents, content);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function addPageBlock(pageName) {
|
|
35
|
-
const content = readPageComponents();
|
|
36
|
-
if (content === null) return;
|
|
37
|
-
|
|
38
|
-
const camelName = toCamelCase(pageName);
|
|
39
|
-
const condition = conditionFor(camelName);
|
|
40
|
-
if (content.includes(condition)) return;
|
|
41
|
-
|
|
42
|
-
if (!content.includes(ANCHOR)) {
|
|
43
|
-
log('components.anchorMissing', { anchor: ANCHOR, file: path.basename(PATHS.pageComponents) });
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const block = `${condition}\n${settings.pageComponents.include}\n\n`;
|
|
48
|
-
|
|
49
|
-
writePageComponents(content.replace(ANCHOR, `${block}${ANCHOR}`));
|
|
50
|
-
log('components.blockAdded', { name: camelName });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function removePageBlock(pageName) {
|
|
54
|
-
const content = readPageComponents();
|
|
55
|
-
if (content === null) return;
|
|
56
|
-
|
|
57
|
-
const camelName = toCamelCase(pageName);
|
|
58
|
-
if (!blockPattern(camelName).test(content)) {
|
|
59
|
-
log('components.blockMissing', { name: camelName });
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const updated = content
|
|
64
|
-
.replace(blockPattern(camelName), '')
|
|
65
|
-
.replace(EXTRA_BLANK_LINES_PATTERN, BLANK_LINES);
|
|
66
|
-
|
|
67
|
-
writePageComponents(updated);
|
|
68
|
-
log('components.blockRemoved', { name: camelName });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function renamePageBlock(oldName, newName) {
|
|
72
|
-
const content = readPageComponents();
|
|
73
|
-
if (content === null) return;
|
|
74
|
-
|
|
75
|
-
const oldCamelName = toCamelCase(oldName);
|
|
76
|
-
const newCamelName = toCamelCase(newName);
|
|
77
|
-
const oldCondition = conditionFor(oldCamelName);
|
|
78
|
-
|
|
79
|
-
if (!content.includes(oldCondition)) {
|
|
80
|
-
log('components.blockMissing', { name: oldCamelName });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
writePageComponents(content.replace(oldCondition, conditionFor(newCamelName)));
|
|
85
|
-
log('components.blockRenamed', { source: oldCamelName, destination: newCamelName });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
module.exports = { addPageBlock, removePageBlock, renamePageBlock };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|