nibula 1.6.1 → 1.6.2

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
@@ -33,6 +33,7 @@ module.exports = function (eleventyConfig) {
33
33
  eleventyConfig.addPassthroughCopy("src/frontend/robots.txt");
34
34
 
35
35
  eleventyConfig.addPassthroughCopy({
36
+ // Bootstrap
36
37
  "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js": "js/bootstrap.bundle.min.js",
37
38
  "node_modules/bootstrap-icons/font/fonts": "css/fonts",
38
39
 
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ 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
+ ## [1.6.2] - 2026-08-07
9
+
10
+ ### Added
11
+ - `nib --version` (also `-v`, `v`, `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.
12
+
13
+ ### Fixed
14
+ [...le quattro voci di prima, invariate...]
15
+
16
+ ### Changed
17
+ - `docs/Styling with SCSS.md` now matches the template: the Nunjucks snippets include the `url` filter and the config file is named `.eleventy.js`.
18
+
8
19
  ## [1.6.1] - 2026-08-06
9
20
 
10
21
  ### 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: ['<script src="/js/bootstrap.bundle.min.js" defer></script>'],
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: ['<script src="/js/foundation.min.js" defer></script>'],
108
- eleventy: ['"node_modules/foundation-sites/dist/js/foundation.min.js": "js/foundation.min.js",'],
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
- '<script src="/js/uikit.min.js" defer></script>',
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 slashComment(content, line) {
254
- content = content.replace(new RegExp(`^([ \\t]*)// (${escapeRegex(line)})$`, 'gm'), '$1$2');
255
- return content.replace(new RegExp(`^([ \\t]*)(${escapeRegex(line)})$`, 'gm'), '$1// $2');
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, line) {
259
- return content.replace(new RegExp(`^([ \\t]*)// (${escapeRegex(line)})$`, 'gm'), '$1$2');
294
+ function slashUncomment(content, marker) {
295
+ return mapMarkedLines(content, marker, COMMENT_STYLE.SLASH, uncommentLine);
260
296
  }
261
297
 
262
- function njkComment(content, line) {
263
- content = content.split(`{# ${line} #}`).join(line);
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, line) {
268
- return content.split(`{# ${line} #}`).join(line);
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/modules/_global.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, `@import "../modules/frameworks/${fw}";`);
365
+ content = slashComment(content, `${SCSS_FRAMEWORK_PREFIX}${fw}`);
331
366
  });
332
367
  if (config.scss) {
333
- content = slashUncomment(content, `@import "../modules/frameworks/${config.scss}";`);
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(line => { content = njkComment(content, line); });
377
+ FRAMEWORKS[fw].njk.forEach(marker => { content = njkComment(content, marker); });
343
378
  });
344
- config.njk.forEach(line => { content = njkUncomment(content, line); });
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(line => { content = slashComment(content, line); });
387
+ FRAMEWORKS[fw].eleventy.forEach(marker => { content = slashComment(content, marker); });
353
388
  });
354
- config.eleventy.forEach(line => { content = slashUncomment(content, line); });
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);
@@ -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/modules/`)</small>
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.config.js
114
+ ### 3. eleventy.js
113
115
 
114
116
  ```javascript
115
117
  eleventyConfig.addPassthroughCopy({
@@ -128,7 +130,7 @@ eleventyConfig.addPassthroughCopy({
128
130
  });
129
131
  ```
130
132
 
131
- > ⚠️ Changes to `eleventy.config.js` are not picked up while the dev server is running — restart it.
133
+ > ⚠️ Changes to `eleventy.js` are not picked up while the dev server is running — restart it.
132
134
 
133
135
  ### Reducing bundle size
134
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nibula",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
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",
@@ -109,7 +109,6 @@
109
109
  ╚══════════════════╝#}
110
110
 
111
111
  {# Framework — uncomment the one you are using #}
112
-
113
112
  {# Bootstrap JS #}
114
113
  <script src="{{ '/js/bootstrap.bundle.min.js' | url }}" defer></script>
115
114
 
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 LOCAL_FLAG = 'NIBULA_LOCAL';
19
- const LOCAL_FLAG_VALUE = '1';
18
+ const GLOBAL_VERSION_FLAG = 'NIBULA_GLOBAL_VERSION';
20
19
  const TITLE_PADDING = 2;
21
20
 
22
- const TITLE_SUFFIX = process.env[LOCAL_FLAG] === LOCAL_FLAG_VALUE
23
- ? ` (local v${require('../../package.json').version})`
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
  }