nibula 2.0.2 → 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/.eleventy.js CHANGED
@@ -1,17 +1,30 @@
1
- const esbuild = require("esbuild");
2
- const glob = require("glob");
1
+ const { RenderPlugin } = require("@11ty/eleventy");
2
+ const markdownItAnchor = require("markdown-it-anchor");
3
+ const markdownItAttrs = require("markdown-it-attrs");
3
4
  const Image = require("@11ty/eleventy-img");
4
5
  const fs = require("fs");
5
6
  const path = require("path");
6
7
 
7
8
  const OUTPUT_DIR = "out";
9
+ const TEMPLATE_ENGINE = "njk";
10
+ const ANCHOR_LEVELS = [2, 3, 4];
11
+ const ANCHOR_PERMALINK_SYMBOL = "#";
12
+ const ANCHOR_PERMALINK_PLACEMENT = "after";
13
+
14
+ const MARKDOWN_BASE_PATH = "src/frontend/components/";
8
15
 
9
16
  module.exports = function (eleventyConfig) {
10
17
 
18
+ // ---------------------------------------------------------------------------
19
+ // Utilities
20
+ // ---------------------------------------------------------------------------
21
+
11
22
  function copyRecursiveSync(src, dest) {
12
23
  if (!fs.existsSync(src)) return;
13
- if (src.includes('.git')) return;
24
+ if (src.includes(".git")) return;
25
+
14
26
  const stat = fs.statSync(src);
27
+
15
28
  if (stat.isDirectory()) {
16
29
  fs.mkdirSync(dest, { recursive: true });
17
30
  for (const child of fs.readdirSync(src)) {
@@ -23,18 +36,47 @@ module.exports = function (eleventyConfig) {
23
36
  }
24
37
  }
25
38
 
39
+ // ---------------------------------------------------------------------------
40
+ // Plugins & markdown
41
+ // ---------------------------------------------------------------------------
42
+
43
+ eleventyConfig.addPlugin(RenderPlugin);
44
+
45
+ eleventyConfig.amendLibrary("md", (markdownLibrary) => {
46
+ markdownLibrary
47
+ .use(markdownItAnchor, {
48
+ level: ANCHOR_LEVELS,
49
+ permalink: markdownItAnchor.permalink.linkInsideHeader({
50
+ symbol: ANCHOR_PERMALINK_SYMBOL,
51
+ placement: ANCHOR_PERMALINK_PLACEMENT,
52
+ }),
53
+ })
54
+ .use(markdownItAttrs);
55
+ });
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Events
59
+ // ---------------------------------------------------------------------------
60
+
26
61
  eleventyConfig.on("eleventy.before", () => {
27
62
  copyRecursiveSync("src/backend", `${OUTPUT_DIR}/backend`);
28
63
  });
29
64
 
65
+ // ---------------------------------------------------------------------------
66
+ // Passthrough copy
67
+ // ---------------------------------------------------------------------------
68
+
69
+ // Project assets
70
+ eleventyConfig.addPassthroughCopy("src/frontend/assets");
71
+ eleventyConfig.addPassthroughCopy("src/frontend/robots.txt");
72
+
73
+ // Hosting files
30
74
  eleventyConfig.addPassthroughCopy({
31
75
  "src/frontend/hosting/.htaccess": ".htaccess",
32
76
  "src/frontend/hosting/web.config": "web.config",
33
77
  });
34
-
35
- eleventyConfig.addPassthroughCopy("src/frontend/assets");
36
- eleventyConfig.addPassthroughCopy("src/frontend/robots.txt");
37
78
 
79
+ // node_modules dependencies
38
80
  eleventyConfig.addPassthroughCopy({
39
81
  // Bootstrap
40
82
  "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js": "js/bootstrap.bundle.min.js",
@@ -50,8 +92,14 @@ module.exports = function (eleventyConfig) {
50
92
  // Bulma — CSS only, no JS passthrough needed
51
93
  });
52
94
 
95
+ // ---------------------------------------------------------------------------
96
+ // Shortcodes
97
+ // ---------------------------------------------------------------------------
98
+
99
+ eleventyConfig.addFilter("markdownPath", (name) => `${MARKDOWN_BASE_PATH}${name}`);
100
+
53
101
  eleventyConfig.addShortcode("image", async function (src, alt) {
54
- let metadata = await Image(src, {
102
+ const metadata = await Image(src, {
55
103
  widths: [320, 480, 720, 1280, 1920, 2048, 2560, 3840, 4096, 7680],
56
104
  formats: ["webp", "jpeg"],
57
105
  outputDir: `${OUTPUT_DIR}/assets/images/`,
@@ -66,15 +114,27 @@ module.exports = function (eleventyConfig) {
66
114
  });
67
115
  });
68
116
 
117
+ // ---------------------------------------------------------------------------
118
+ // Watch targets & dev server
119
+ // ---------------------------------------------------------------------------
120
+
69
121
  eleventyConfig.addWatchTarget("./src/frontend/scss");
70
122
  eleventyConfig.addWatchTarget("./src/frontend/routes");
71
123
  eleventyConfig.addWatchTarget("./src/frontend/data");
124
+ eleventyConfig.addWatchTarget("./src/frontend/components");
72
125
 
73
126
  eleventyConfig.setServerOptions({
74
- watch: [`${OUTPUT_DIR}/js/**/*.js`]
127
+ watch: [`${OUTPUT_DIR}/js/**/*.js`],
75
128
  });
76
129
 
130
+ // ---------------------------------------------------------------------------
131
+ // Directory configuration
132
+ // ---------------------------------------------------------------------------
133
+
77
134
  return {
135
+ markdownTemplateEngine: TEMPLATE_ENGINE,
136
+ htmlTemplateEngine: TEMPLATE_ENGINE,
137
+
78
138
  dir: {
79
139
  input: "src/frontend",
80
140
  output: OUTPUT_DIR,
package/CHANGELOG.md CHANGED
@@ -5,6 +5,85 @@ 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.1.0] - 2026-08-17
9
+
10
+ ### Added
11
+ - **The assistant asks which layout a new page should use.** Any `.njk` file in
12
+ `src/frontend/layouts/` is offered in an arrow-key menu, and the chosen one is
13
+ written into the route's front matter. Before, every page was created with
14
+ `base.njk` and the documentation had to remind you to change it by hand — the
15
+ assistant wrote a value the reader was then expected to correct. With a single
16
+ layout in the folder the menu is skipped, so nothing changes for a project that
17
+ has not added one; with none, the front matter is left untouched.
18
+ - `askChoice` in `tools/cli/prompt.js`: the arrow-key menu the scaffolder already
19
+ used, available to the assistant as well.
20
+ - **Markdown files can be rendered inside a page.** `markdown-it-anchor` and
21
+ `markdown-it-attrs` are installed and preconfigured, and Eleventy's
22
+ `RenderPlugin` is registered, so a `.md` file in `src/frontend/components/`
23
+ can be pulled into a route with `renderFile`. Headings from level 2 down get
24
+ an `id` and a `#` link, and `{.class}` applies a CSS class inline — markdown
25
+ produces plain HTML, so without a way to set classes the output carries none of
26
+ the project's framework styling.
27
+ - A `markdownPath` filter that prefixes the components folder, so a route writes
28
+ `"your-file.md" | markdownPath` and not the full path from the project root.
29
+ `renderFile` resolves paths from the working directory, unlike `include`, which
30
+ resolves them from the components folder.
31
+ - The template new pages are created from carries a commented `renderFile`
32
+ example next to the component one, with the data argument included:
33
+ `renderFile` does not inherit the page context, so a `.md` that reads `site.*`
34
+ renders those values empty unless the data is passed explicitly.
35
+
36
+ ### Changed
37
+ - **`tools/cli/prompt.js` creates a readline interface per question** instead of
38
+ holding one open for the whole session. The session-wide interface kept its own
39
+ keypress listener on stdin, which would have fought with the raw-mode listener
40
+ the arrow-key menu needs: both would have received every key, so arrows moved
41
+ the readline cursor and Enter fired its `line` event.
42
+ - **`markdownTemplateEngine` and `htmlTemplateEngine` are set to Nunjucks.**
43
+ Eleventy defaults `.md` files to Liquid, so a markdown file containing
44
+ `{{ site.title }}` was processed by a different template engine than every
45
+ other file in the project — working for the simple cases and silently not for
46
+ the rest.
47
+ - `src/frontend/components` is now a watch target. Eleventy tracks `.njk`
48
+ includes as template dependencies, but a `.md` read through `renderFile` is
49
+ invisible to it, so editing one changed nothing until the route itself was
50
+ saved.
51
+ - `.eleventy.js` is grouped into labelled sections, with plugins and markdown
52
+ configuration ahead of the build event and the passthrough copies.
53
+ - **Documentation is written for readers who are new to this.** The docs assumed
54
+ vocabulary a beginner does not have yet. `Deploy.md` opens with a question that
55
+ routes the reader by backend, and explains what shared hosting, a VPS, systemd,
56
+ an HTTPS certificate and `screen` actually are; the Node section now separates
57
+ trying the backend out from setting it up to survive a reboot, which read as
58
+ five equal steps before. `Head and SEO.md` starts from what the `<head>` is and
59
+ why there are two data files, and drops the unexplained jargon — canonical,
60
+ JSON-LD, noindex. `Components.md` documents markdown from what Markdown is,
61
+ with each part of the `renderFile` line explained rather than assumed.
62
+ - `Project structure.md` lists `components/` as the home of `.md` content, and
63
+ `Creating pages.md` describes both commented examples a new route carries.
64
+
65
+ ### Fixed
66
+ - `Deploy.md` told the reader to open the Nginx site file with `nano` and how to
67
+ save it, without ever saying what to paste into it. The step now names the
68
+ edited `nginx.conf` as the source.
69
+
70
+ ### Removed
71
+ - **The dead language switch in the scaffolder.** `bin/create.js` commented and
72
+ uncommented a `glob.sync` line for the JavaScript and TypeScript entry points,
73
+ but that line has not existed in `.eleventy.js` since bundling moved to
74
+ `tools/buildJs.js`, so the two markers matched nothing and the function ran as
75
+ a no-op. Entry points are collected from both language folders and resolved
76
+ against the filesystem, which makes the switch unnecessary: the folder that
77
+ does not exist contributes nothing.
78
+ - Unused `esbuild` and `glob` imports from `.eleventy.js`. Both are still real
79
+ dependencies of `tools/buildJs.js`, which is where they are used.
80
+
81
+ ### Notes
82
+ - The markdown setup lands in newly created projects only. To adopt it in an
83
+ existing one, install `markdown-it-anchor` and `markdown-it-attrs` and copy the
84
+ plugin block, the `markdownPath` filter and the two template engine settings
85
+ from the current `.eleventy.js`.
86
+
8
87
  ## [2.0.2] - 2026-08-08
9
88
 
10
89
  ### Fixed
package/bin/create.js CHANGED
@@ -126,13 +126,6 @@ const FRAMEWORKS = {
126
126
  },
127
127
  };
128
128
 
129
- // ── LANGUAGE CONFIG ───────────────────────────────────────────────────────────
130
-
131
- const LANGUAGE_ELEVENTY = Object.freeze({
132
- jsEntry: 'const entryPoints = glob.sync("src/frontend/js/pages/*.js");',
133
- tsEntry: 'const entryPoints = glob.sync("src/frontend/ts/pages/*.ts");',
134
- });
135
-
136
129
  // ── GENERATED FILE CONTENTS ───────────────────────────────────────────────────
137
130
 
138
131
  const GITIGNORE_CONTENT = `
@@ -170,6 +163,8 @@ const PROJECT_PACKAGE = {
170
163
  'bulma': '^1.0.4',
171
164
  'foundation-sites': '^6.9.0',
172
165
  'glob': '^13.0.6',
166
+ 'markdown-it-anchor': '^9.2.1',
167
+ 'markdown-it-attrs': '^5.0.1',
173
168
  'uikit': '^3.25.13',
174
169
  },
175
170
  devDependencies: {
@@ -391,23 +386,6 @@ function applyFramework(framework) {
391
386
  }
392
387
  }
393
388
 
394
- function applyLanguage(language) {
395
- const eleventyPath = path.join(targetDir, '.eleventy.js');
396
- if (!fs.existsSync(eleventyPath)) return;
397
-
398
- let content = fs.readFileSync(eleventyPath, 'utf8');
399
-
400
- if (language === LANGUAGE.TYPESCRIPT) {
401
- content = slashComment(content, LANGUAGE_ELEVENTY.jsEntry);
402
- content = slashUncomment(content, LANGUAGE_ELEVENTY.tsEntry);
403
- } else {
404
- content = slashUncomment(content, LANGUAGE_ELEVENTY.jsEntry);
405
- content = slashComment(content, LANGUAGE_ELEVENTY.tsEntry);
406
- }
407
-
408
- fs.writeFileSync(eleventyPath, content);
409
- }
410
-
411
389
  // ── UI ────────────────────────────────────────────────────────────────────────
412
390
 
413
391
  function askChoice(question, choices) {
@@ -527,7 +505,6 @@ async function init() {
527
505
  }
528
506
 
529
507
  applyFramework(framework);
530
- applyLanguage(language);
531
508
 
532
509
  installDependencies(backend);
533
510
 
@@ -58,6 +58,82 @@ A component can include other components. This is useful for breaking complex se
58
58
 
59
59
  > The same path rules apply: if the included component is in a subfolder, specify the full relative path.
60
60
 
61
+ ## Markdown files
62
+
63
+ ### What is Markdown
64
+
65
+ Markdown (`.md`) is a way to write formatted text without HTML tags. You write
66
+ `**bold**` instead of `<strong>bold</strong>`, and a line starting with `-`
67
+ becomes a list item. For a page that is mostly text — an article, a privacy
68
+ policy, a changelog — it is much faster than writing the markup by hand.
69
+
70
+ ### Write the file
71
+
72
+ Create a `.md` file in `src/frontend/components/`, next to your `.njk` ones.
73
+
74
+ #### article.md <small>(`src/frontend/components/`)</small>
75
+
76
+ ```markdown
77
+ ## Section title
78
+
79
+ Regular paragraph text. **Bold** and *italic* work as usual.
80
+
81
+ - First item
82
+ - Second item
83
+ ```
84
+
85
+ ### Show it in a page
86
+
87
+ Open the page's file in `src/frontend/routes/` and add this line to its body:
88
+
89
+ ```njk
90
+ {% renderFile "article.md" | markdownPath, { site: site } %}
91
+ ```
92
+
93
+ Three parts to it:
94
+
95
+ | Part | What it does |
96
+ |---|---|
97
+ | `renderFile` | turns the Markdown into HTML and puts it in the page |
98
+ | `markdownPath` | fills in the `src/frontend/components/` part of the path for you |
99
+ | `{ site: site }` | hands the file your site settings — see below |
100
+
101
+ Subfolders work the same way as with components:
102
+ `"legal/privacy.md" | markdownPath`
103
+
104
+ ### Why `{ site: site }`
105
+
106
+ A `.md` file can use site values, exactly like a component:
107
+
108
+ ```markdown
109
+ Welcome to {{ site.title }}
110
+ ```
111
+
112
+ But a Markdown file does not automatically see them. Anything the file needs has
113
+ to be listed in the curly braces:
114
+
115
+ ```njk
116
+ {% renderFile "article.md" | markdownPath, { site: site, pages: pages } %}
117
+ ```
118
+
119
+ If you forget, nothing breaks — the value simply comes out empty. So if a
120
+ `{{ site.something }}` disappears from your page, this is why.
121
+
122
+ ### Extras you get for free
123
+
124
+ **Links to a section.** Every `##` and `###` heading gets an anchor, so you can
125
+ link straight to it: `yoursite.com/my-page/#section-title`.
126
+
127
+ **CSS classes.** Markdown produces plain HTML with no classes, so your framework's
128
+ styling doesn't reach it. Add a class in curly braces after the element:
129
+
130
+ ```markdown
131
+ ## Section title {.text-center}
132
+ ```
133
+
134
+ > ⚠️ If the `.md` file is missing or the name is misspelled, the build stops with
135
+ > an error. That is on purpose: silently skipping content you asked for is worse.
136
+
61
137
  ## Global components
62
138
 
63
139
  Header and footer live in `src/frontend/components/global/` and are automatically included in every page via `base.njk`. Edit them to change the site-wide layout
@@ -148,49 +224,11 @@ All values defined in `src/frontend/data/site.json` are globally available in ev
148
224
  <img src="{{ site.logo }}" alt="{{ site.title }}">
149
225
  ```
150
226
 
227
+ > In a `.md` file rendered with `renderFile`, these values only work if you passed
228
+ > `site` in the curly braces — see *Why `{ site: site }`* above.
229
+
151
230
  ## Your own data files
152
231
 
153
232
  `site.json` isn't special: **any** `.json` file you drop in `src/frontend/data/` becomes a global variable, and the variable takes the name of the file.
154
233
 
155
- Create `test.json`, put anything you want inside it, and every key is reachable as `test.yourKey` — no import, no configuration, no restart.
156
-
157
- ### test.json <small>(`src/frontend/data/`)</small>
158
- ```json
159
- {
160
- "testMessage": "This is a test"
161
- }
162
- ```
163
-
164
- ### Usage in any `.njk` file
165
- ```njk
166
- <p>{{ test.testMessage }}</p>
167
- ```
168
-
169
- Renders as:
170
- ```html
171
- <p>This is a test</p>
172
- ```
173
-
174
- That's the whole rule: **file name = variable name, keys inside = what you write after the dot.** `site.json` gives you `site.title` for exactly the same reason.
175
-
176
- Values can be anything JSON allows, so a list lets you loop instead of copying markup:
177
-
178
- ```json
179
- {
180
- "testMessage": "This is a test",
181
- "links": [
182
- { "label": "Home", "url": "/" },
183
- { "label": "Contact", "url": "/contact/" }
184
- ]
185
- }
186
- ```
187
-
188
- ```njk
189
- {% for link in test.links %}
190
- <a href="{{ link.url }}">{{ link.label }}</a>
191
- {% endfor %}
192
- ```
193
-
194
- Subfolders create nested names: `data/shop/products.json` becomes `{{ shop.products }}`.
195
-
196
- > ⚠️ Don't reuse a name that already exists. Another file named `site.json` in a subfolder overwrites the original one silently, with no error to point you at the cause.
234
+ Create `test.json`, put anything you want inside it, and every key is reachable as `test.yourKey` — no import, no configuration, no restart.
@@ -17,7 +17,9 @@ It also adds a stub entry in `src/frontend/data/pages.json` for the page's SEO a
17
17
 
18
18
  ## Adding content
19
19
 
20
- The route is created empty. Write your HTML in its body, or include components — see the **Components** DOC file.
20
+ The route is created empty, with two commented examples: one for including a
21
+ `.njk` component, one for rendering a `.md` file. Uncomment the one you need or
22
+ write your HTML directly in the body — see the **Components** DOC file.
21
23
 
22
24
  ## URL and title
23
25