create-berna-stencil 2.6.0 → 2.6.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.
Files changed (179) hide show
  1. package/.gitignore +5 -4
  2. package/README.md +1 -6
  3. package/_tools/assistant.js +92 -88
  4. package/_tools/modules/constants.js +23 -0
  5. package/_tools/modules/pageComponents.js +77 -0
  6. package/_tools/modules/updateData.js +25 -44
  7. package/_tools/modules/updateOutputPath.js +59 -63
  8. package/_tools/modules/updatePage.js +131 -104
  9. package/_tools/modules/utils.js +16 -19
  10. package/_tools/modules/validation.js +20 -0
  11. package/bin/create.js +75 -13
  12. package/package.json +50 -65
  13. package/src/backend/_core/vendor/composer/autoload_static.php +10 -10
  14. package/src/backend/_core/vendor/composer/installed.json +6 -6
  15. package/src/backend/_core/vendor/composer/installed.php +2 -2
  16. package/src/backend/_core/vendor/graham-campbell/result-type/.gitattributes +9 -0
  17. package/src/backend/_core/vendor/graham-campbell/result-type/.github/CODE_OF_CONDUCT.md +132 -0
  18. package/src/backend/_core/vendor/graham-campbell/result-type/.github/CONTRIBUTING.md +31 -0
  19. package/src/backend/_core/vendor/graham-campbell/result-type/.github/FUNDING.yml +2 -0
  20. package/src/backend/_core/vendor/graham-campbell/result-type/.github/SECURITY.md +14 -0
  21. package/src/backend/_core/vendor/graham-campbell/result-type/.github/workflows/stale.yml +11 -0
  22. package/src/backend/_core/vendor/graham-campbell/result-type/.github/workflows/tests.yml +40 -0
  23. package/src/backend/_core/vendor/graham-campbell/result-type/CHANGELOG.md +53 -0
  24. package/src/backend/_core/vendor/graham-campbell/result-type/LICENSE +21 -21
  25. package/src/backend/_core/vendor/graham-campbell/result-type/README.md +42 -0
  26. package/src/backend/_core/vendor/graham-campbell/result-type/composer.json +33 -33
  27. package/src/backend/_core/vendor/graham-campbell/result-type/phpunit.xml.dist +13 -0
  28. package/src/backend/_core/vendor/graham-campbell/result-type/src/Error.php +121 -121
  29. package/src/backend/_core/vendor/graham-campbell/result-type/src/Result.php +69 -69
  30. package/src/backend/_core/vendor/graham-campbell/result-type/src/Success.php +120 -120
  31. package/src/backend/_core/vendor/graham-campbell/result-type/tests/ResultTest.php +95 -0
  32. package/src/backend/_core/vendor/phpoption/phpoption/.gitattributes +13 -0
  33. package/src/backend/_core/vendor/phpoption/phpoption/.github/CODE_OF_CONDUCT.md +132 -0
  34. package/src/backend/_core/vendor/phpoption/phpoption/.github/CONTRIBUTING.md +30 -0
  35. package/src/backend/_core/vendor/phpoption/phpoption/.github/FUNDING.yml +2 -0
  36. package/src/backend/_core/vendor/phpoption/phpoption/.github/SECURITY.md +14 -0
  37. package/src/backend/_core/vendor/phpoption/phpoption/.github/workflows/static.yml +40 -0
  38. package/src/backend/_core/vendor/phpoption/phpoption/.github/workflows/tests.yml +40 -0
  39. package/src/backend/_core/vendor/phpoption/phpoption/LICENSE +200 -200
  40. package/src/backend/_core/vendor/phpoption/phpoption/Makefile +17 -0
  41. package/src/backend/_core/vendor/phpoption/phpoption/README.md +201 -0
  42. package/src/backend/_core/vendor/phpoption/phpoption/composer.json +50 -50
  43. package/src/backend/_core/vendor/phpoption/phpoption/phpstan-baseline.neon +44 -0
  44. package/src/backend/_core/vendor/phpoption/phpoption/phpstan.neon.dist +7 -0
  45. package/src/backend/_core/vendor/phpoption/phpoption/phpunit.xml.dist +13 -0
  46. package/src/backend/_core/vendor/phpoption/phpoption/src/PhpOption/LazyOption.php +175 -175
  47. package/src/backend/_core/vendor/phpoption/phpoption/src/PhpOption/None.php +136 -136
  48. package/src/backend/_core/vendor/phpoption/phpoption/src/PhpOption/Option.php +434 -434
  49. package/src/backend/_core/vendor/phpoption/phpoption/src/PhpOption/Some.php +169 -169
  50. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/EnsureTest.php +72 -0
  51. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/LazyOptionTest.php +357 -0
  52. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/NoneTest.php +153 -0
  53. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/OptionTest.php +166 -0
  54. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/SomeTest.php +194 -0
  55. package/src/backend/_core/vendor/phpoption/phpoption/tests/bootstrap.php +8 -0
  56. package/src/backend/_core/vendor/phpoption/phpoption/vendor-bin/phpstan/composer.json +8 -0
  57. package/src/backend/_core/vendor/symfony/polyfill-ctype/Ctype.php +232 -232
  58. package/src/backend/_core/vendor/symfony/polyfill-ctype/LICENSE +19 -19
  59. package/src/backend/_core/vendor/symfony/polyfill-ctype/README.md +12 -12
  60. package/src/backend/_core/vendor/symfony/polyfill-ctype/bootstrap.php +50 -50
  61. package/src/backend/_core/vendor/symfony/polyfill-ctype/bootstrap80.php +46 -46
  62. package/src/backend/_core/vendor/symfony/polyfill-ctype/composer.json +38 -38
  63. package/src/backend/_core/vendor/symfony/polyfill-mbstring/LICENSE +19 -19
  64. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Mbstring.php +1077 -1077
  65. package/src/backend/_core/vendor/symfony/polyfill-mbstring/README.md +13 -13
  66. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php +119 -119
  67. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php +1397 -1397
  68. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php +5 -5
  69. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php +1489 -1489
  70. package/src/backend/_core/vendor/symfony/polyfill-mbstring/bootstrap.php +171 -171
  71. package/src/backend/_core/vendor/symfony/polyfill-mbstring/bootstrap80.php +167 -167
  72. package/src/backend/_core/vendor/symfony/polyfill-mbstring/composer.json +39 -39
  73. package/src/backend/_core/vendor/symfony/polyfill-php80/LICENSE +19 -19
  74. package/src/backend/_core/vendor/symfony/polyfill-php80/Php80.php +115 -115
  75. package/src/backend/_core/vendor/symfony/polyfill-php80/PhpToken.php +106 -106
  76. package/src/backend/_core/vendor/symfony/polyfill-php80/README.md +25 -25
  77. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php +31 -31
  78. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php +16 -16
  79. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php +20 -20
  80. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php +16 -16
  81. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php +16 -16
  82. package/src/backend/_core/vendor/symfony/polyfill-php80/bootstrap.php +42 -42
  83. package/src/backend/_core/vendor/symfony/polyfill-php80/composer.json +37 -37
  84. package/src/backend/_core/vendor/vlucas/phpdotenv/.editorconfig +15 -0
  85. package/src/backend/_core/vendor/vlucas/phpdotenv/.gitattributes +15 -0
  86. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/CODE_OF_CONDUCT.md +132 -0
  87. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/CONTRIBUTING.md +30 -0
  88. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/FUNDING.yml +2 -0
  89. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/SECURITY.md +14 -0
  90. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/workflows/static.yml +40 -0
  91. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/workflows/tests.yml +70 -0
  92. package/src/backend/_core/vendor/vlucas/phpdotenv/LICENSE +30 -30
  93. package/src/backend/_core/vendor/vlucas/phpdotenv/Makefile +17 -0
  94. package/src/backend/_core/vendor/vlucas/phpdotenv/README.md +370 -0
  95. package/src/backend/_core/vendor/vlucas/phpdotenv/UPGRADING.md +196 -0
  96. package/src/backend/_core/vendor/vlucas/phpdotenv/composer.json +60 -60
  97. package/src/backend/_core/vendor/vlucas/phpdotenv/phpstan-baseline.neon +157 -0
  98. package/src/backend/_core/vendor/vlucas/phpdotenv/phpstan.neon.dist +7 -0
  99. package/src/backend/_core/vendor/vlucas/phpdotenv/phpunit.xml.dist +13 -0
  100. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Dotenv.php +267 -267
  101. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php +12 -12
  102. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidEncodingException.php +12 -12
  103. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidFileException.php +12 -12
  104. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidPathException.php +12 -12
  105. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/ValidationException.php +12 -12
  106. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Loader/Loader.php +48 -48
  107. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Loader/LoaderInterface.php +20 -20
  108. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Loader/Resolver.php +65 -65
  109. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Entry.php +59 -59
  110. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php +299 -299
  111. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Lexer.php +58 -58
  112. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Lines.php +127 -127
  113. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Parser.php +53 -53
  114. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/ParserInterface.php +19 -19
  115. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Value.php +88 -88
  116. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/AdapterInterface.php +15 -15
  117. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ApacheAdapter.php +89 -89
  118. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ArrayAdapter.php +80 -80
  119. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php +88 -88
  120. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/GuardedWriter.php +85 -85
  121. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ImmutableWriter.php +110 -110
  122. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiReader.php +48 -48
  123. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiWriter.php +64 -64
  124. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/PutenvAdapter.php +91 -91
  125. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReaderInterface.php +17 -17
  126. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReplacingWriter.php +104 -104
  127. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php +88 -88
  128. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/WriterInterface.php +27 -27
  129. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/AdapterRepository.php +107 -107
  130. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/RepositoryBuilder.php +272 -272
  131. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/RepositoryInterface.php +51 -51
  132. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/File/Paths.php +44 -44
  133. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/File/Reader.php +81 -81
  134. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/FileStore.php +72 -72
  135. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/StoreBuilder.php +141 -141
  136. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/StoreInterface.php +17 -17
  137. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/StringStore.php +37 -37
  138. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Util/Regex.php +112 -112
  139. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Util/Str.php +108 -108
  140. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Validator.php +207 -207
  141. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/DotenvTest.php +387 -0
  142. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Loader/LoaderTest.php +86 -0
  143. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/EntryParserTest.php +234 -0
  144. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LexerTest.php +40 -0
  145. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LinesTest.php +53 -0
  146. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/ParserTest.php +98 -0
  147. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ArrayAdapterTest.php +57 -0
  148. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/EnvConstAdapterTest.php +75 -0
  149. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/PutenvAdapterTest.php +52 -0
  150. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ServerConstAdapterTest.php +75 -0
  151. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/RepositoryTest.php +305 -0
  152. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Store/StoreTest.php +141 -0
  153. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/ValidatorTest.php +479 -0
  154. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/.env +5 -0
  155. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/assertions.env +18 -0
  156. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/booleans.env +33 -0
  157. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/commented.env +15 -0
  158. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/empty.env +1 -0
  159. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/example.env +1 -0
  160. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/exported.env +7 -0
  161. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/immutable.env +1 -0
  162. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/integers.env +17 -0
  163. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/large.env +2 -0
  164. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multibyte.env +3 -0
  165. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multiline.env +14 -0
  166. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multiple.env +4 -0
  167. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/mutable.env +1 -0
  168. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/nested.env +15 -0
  169. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/quoted.env +11 -0
  170. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/specialchars.env +8 -0
  171. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/unicodevarnames.env +2 -0
  172. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/utf8-with-bom-encoding.env +3 -0
  173. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/windows.env +1 -0
  174. package/src/backend/_core/vendor/vlucas/phpdotenv/vendor-bin/phpstan/composer.json +15 -0
  175. package/src/backend/{config.example.php → config.php} +4 -13
  176. package/src/frontend/data/site.json +44 -44
  177. package/_tools/buildJs.js +0 -28
  178. package/_tools/cleanOutput.js +0 -20
  179. package/_tools/modules/updateIncludes.js +0 -81
package/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
- node_modules/
2
- src/backend/_core/vendor/
3
- out/
4
- src/backend/config.php
1
+
2
+ node_modules/
3
+ src/backend/_core/vendor/
4
+ out/
5
+ src/backend/config.php
package/README.md CHANGED
@@ -16,7 +16,7 @@ Because it keeps you close to the fundamentals instead of hiding them, you spend
16
16
  - 📁 **Scalable structure** — a clean, opinionated project layout that grows with your needs
17
17
  - 🌍 **Open source** — free to use, free to modify, free to share
18
18
 
19
- ![Version](https://img.shields.io/badge/version-2.6.0-blue)
19
+ ![Version](https://img.shields.io/badge/version-2.6.1-blue)
20
20
  ![License](https://img.shields.io/badge/license-Apache--2.0-blue)
21
21
  ![Eleventy](https://img.shields.io/badge/11ty-v3.1.2-black)
22
22
 
@@ -37,11 +37,6 @@ Because it keeps you close to the fundamentals instead of hiding them, you spend
37
37
  cd your-project
38
38
  ```
39
39
 
40
- * Install all the node_modules
41
- ```bash
42
- npm install
43
- ```
44
-
45
40
  * Run the command to launch the live server and build the site, then visit localhost:8080:
46
41
  ```bash
47
42
  npm run serve
@@ -1,146 +1,150 @@
1
1
  const readline = require('readline');
2
- const { addPage, removePage, renamePage } = require('./modules/updatePage');
3
- const { updateOutputPath, getCurrentOutputPath } = require('./modules/updateOutputPath');
4
2
 
5
- const c = {
6
- reset: "\x1b[0m",
7
- bold: "\x1b[1m",
8
- dim: "\x1b[2m",
9
- red: "\x1b[31m",
10
- green: "\x1b[32m",
11
- yellow: "\x1b[33m",
12
- magenta: "\x1b[35m",
13
- cyan: "\x1b[36m"
3
+ const { addPage, removePage, renamePage, pageExists } = require('./modules/updatePage');
4
+ const { updateOutputPath, getCurrentOutputPath } = require('./modules/updateOutputPath');
5
+ const { validatePageName, validateOutputPath } = require('./modules/validation');
6
+ const { toKebabCase } = require('./modules/utils');
7
+
8
+ const color = {
9
+ reset: '\x1b[0m',
10
+ bold: '\x1b[1m',
11
+ dim: '\x1b[2m',
12
+ red: '\x1b[31m',
13
+ green: '\x1b[32m',
14
+ yellow: '\x1b[33m',
15
+ magenta: '\x1b[35m',
16
+ cyan: '\x1b[36m',
14
17
  };
15
18
 
16
- const readerInterface = readline.createInterface({
19
+ const rl = readline.createInterface({
17
20
  input: process.stdin,
18
21
  output: process.stdout,
19
- terminal: true
22
+ terminal: true,
20
23
  });
21
24
 
22
- const PROTECTED_PAGES = ['homepage', '404'];
23
- const MAX_NAME_LENGTH = 50;
24
-
25
- function toKebabCase(str) {
26
- return str.trim().toLowerCase()
27
- .replace(/[^a-z0-9\s_-]/g, '')
28
- .replace(/[\s_]+/g, '-')
29
- .replace(/-+/g, '-')
30
- .replace(/^-+|-+$/g, '');
31
- }
32
-
33
- function validatePageName(name) {
34
- if (!name) return 'Invalid name.';
35
- if (name.length > MAX_NAME_LENGTH) return `Name must be ${MAX_NAME_LENGTH} characters or fewer.`;
36
- if (!/^[a-z0-9-]+$/.test(name)) return 'Page name can only contain lowercase letters, numbers, and hyphens.';
37
- if (/^\d/.test(name)) return 'Page name cannot start with a number.';
38
- if (PROTECTED_PAGES.includes(name)) return `"${name}" is a protected page name.`;
39
- return null;
40
- }
41
-
42
- function validateOutputPath(input) {
43
- if (!input.trim()) return 'Invalid path.';
44
- if (input.includes('..')) return 'Path cannot contain "..".';
45
- if (/[<>|?*"']/.test(input)) return 'Path contains invalid characters.';
46
- return null;
25
+ function sanitizeInput(value) {
26
+ return (value ?? '').replace(/[\x00-\x1F\x7F]/g, '').trim();
47
27
  }
48
28
 
49
- function sanitizeInput(str) {
50
- return str.replace(/[\x00-\x1F\x7F]/g, '').trim();
29
+ function ask(prompt) {
30
+ return new Promise((resolve) => {
31
+ const onClose = () => resolve(null);
32
+ rl.once('close', onClose);
33
+ rl.question(prompt, (answer) => {
34
+ rl.off('close', onClose);
35
+ resolve(sanitizeInput(answer));
36
+ });
37
+ });
51
38
  }
52
39
 
53
- function ask(prompt) {
54
- return new Promise(resolve =>
55
- readerInterface.question(prompt, answer => resolve(sanitizeInput(answer)))
56
- );
40
+ async function confirm(prompt) {
41
+ const answer = await ask(`${prompt} ${color.dim}[y/N]${color.reset} `);
42
+ return /^y(es)?$/i.test((answer ?? '').trim());
57
43
  }
58
44
 
59
45
  async function askPageName(prompt) {
60
46
  const raw = await ask(prompt);
47
+ if (raw === null) return null;
48
+
61
49
  const name = toKebabCase(raw);
62
50
  const error = validatePageName(name);
63
51
  if (error) {
64
- console.log(`\n${c.red}✖ ${error}${c.reset}`);
52
+ console.log(`\n${color.red}✖ ${error}${color.reset}`);
65
53
  return null;
66
54
  }
67
55
  return name;
68
56
  }
69
57
 
70
- async function handleCreateRequest() {
71
- const name = await askPageName(`\n${c.green}❯${c.reset} Enter the name of the new page: `);
72
- if (name) addPage(name, null);
58
+ async function handleCreate() {
59
+ const name = await askPageName(`\n${color.green}❯${color.reset} Name of the new page: `);
60
+ if (name) addPage(name);
73
61
  }
74
62
 
75
- async function handleRemoveRequest() {
76
- const name = await askPageName(`\n${c.red}❯${c.reset} Enter the name of the page to remove: `);
77
- if (name) removePage(name);
63
+ async function handleRemove() {
64
+ const name = await askPageName(`\n${color.red}❯${color.reset} Name of the page to remove: `);
65
+ if (!name) return;
66
+
67
+ if (!pageExists(name)) {
68
+ console.log(`\n${color.yellow}⚠ Page "${name}" does not exist.${color.reset}`);
69
+ return;
70
+ }
71
+
72
+ const confirmed = await confirm(`This permanently deletes all files for "${name}".`);
73
+ if (!confirmed) {
74
+ console.log(`\n${color.dim}Cancelled.${color.reset}`);
75
+ return;
76
+ }
77
+ removePage(name);
78
78
  }
79
79
 
80
- async function handleRenameRequest() {
81
- const oldName = await askPageName(`\n${c.yellow}❯${c.reset} Enter the name of the page to rename: `);
80
+ async function handleRename() {
81
+ const oldName = await askPageName(`\n${color.yellow}❯${color.reset} Page to rename: `);
82
82
  if (!oldName) return;
83
83
 
84
- const newName = await askPageName(`${c.yellow}❯${c.reset} Enter the new name: `);
84
+ const newName = await askPageName(`${color.yellow}❯${color.reset} New name: `);
85
85
  if (!newName) return;
86
86
 
87
87
  if (oldName === newName) {
88
- console.log(`\n${c.yellow}⚠ Old and new name are the same.${c.reset}`);
88
+ console.log(`\n${color.yellow}⚠ Old and new name are the same.${color.reset}`);
89
89
  return;
90
90
  }
91
-
92
91
  renamePage(oldName, newName);
93
92
  }
94
93
 
95
- async function handleOutputPathRequest() {
94
+ async function handleOutputPath() {
96
95
  const current = getCurrentOutputPath();
97
- const label = current ? `\n${c.dim}Current path: "${current}"${c.reset}\n` : '\n';
98
- const input = await ask(`${label}${c.magenta}❯${c.reset} Enter the new output path: `);
96
+ const label = current ? `\n${color.dim}Current path: "${current}"${color.reset}\n` : '\n';
97
+
98
+ const input = await ask(`${label}${color.magenta}❯${color.reset} New output path: `);
99
+ if (input === null) return;
99
100
 
100
101
  const error = validateOutputPath(input);
101
102
  if (error) {
102
- console.log(`\n${c.red}✖ ${error}${c.reset}`);
103
- } else {
104
- updateOutputPath(input);
103
+ console.log(`\n${color.red}✖ ${error}${color.reset}`);
104
+ return;
105
105
  }
106
+ updateOutputPath(input);
106
107
  }
107
108
 
108
109
  const MENU_ACTIONS = {
109
- '1': handleCreateRequest,
110
- '2': handleRemoveRequest,
111
- '3': handleRenameRequest,
112
- '4': handleOutputPathRequest,
110
+ '1': handleCreate,
111
+ '2': handleRemove,
112
+ '3': handleRename,
113
+ '4': handleOutputPath,
113
114
  };
114
115
 
115
- async function displayMainMenu() {
116
- console.log(`\n${c.cyan}${c.bold}╭────────────────────────╮`);
116
+ function renderMenu() {
117
+ console.log(`\n${color.cyan}${color.bold}╭────────────────────────╮`);
117
118
  console.log(`│ Berna-Stencil CLI │`);
118
- console.log(`╰────────────────────────╯${c.reset}\n`);
119
- console.log(` ${c.green}1.${c.reset} Create page`);
120
- console.log(` ${c.red}2.${c.reset} Remove page`);
121
- console.log(` ${c.yellow}3.${c.reset} Rename page`);
122
- console.log(` ${c.magenta}4.${c.reset} Configure output path`);
123
- console.log(`\n ${c.dim}CTRL/CMD + C to exit${c.reset}\n`);
124
-
125
- const choice = (await ask(`${c.cyan}❯${c.reset} Choose an option: `)).trim();
126
-
127
- if (choice === '0') {
128
- readerInterface.close();
129
- process.exit(0);
130
- }
119
+ console.log(`╰────────────────────────╯${color.reset}\n`);
120
+ console.log(` ${color.green}1.${color.reset} Create page`);
121
+ console.log(` ${color.red}2.${color.reset} Remove page`);
122
+ console.log(` ${color.yellow}3.${color.reset} Rename page`);
123
+ console.log(` ${color.magenta}4.${color.reset} Configure output path`);
124
+ console.log(` ${color.dim}CTRL + C to exit\n`);
125
+ }
126
+
127
+ async function main() {
128
+ while (true) {
129
+ renderMenu();
130
+
131
+ const choice = await ask(`${color.cyan}❯${color.reset} Choose an option: `);
132
+
133
+ const action = MENU_ACTIONS[choice];
134
+ if (!action) {
135
+ console.log(`\n${color.red}✖ Invalid option.${color.reset}`);
136
+ continue;
137
+ }
131
138
 
132
- const action = MENU_ACTIONS[choice];
133
- if (action) {
134
139
  try {
135
140
  await action();
136
141
  } catch (err) {
137
- console.log(`\n${c.red}✖ Unexpected error: ${err.message}${c.reset}`);
142
+ console.log(`\n${color.red}✖ Unexpected error: ${err.message}${color.reset}`);
138
143
  }
139
- } else {
140
- console.log(`\n${c.red}✖ Invalid option.${c.reset}`);
141
144
  }
142
145
 
143
- displayMainMenu();
146
+ rl.close();
147
+ process.exit(0);
144
148
  }
145
149
 
146
- displayMainMenu();
150
+ main();
@@ -0,0 +1,23 @@
1
+ const path = require('path');
2
+
3
+ const ROOT = path.resolve(__dirname, '..', '..');
4
+ const FRONTEND_DIR = path.join(ROOT, 'src', 'frontend');
5
+
6
+ const MAX_PAGE_NAME_LENGTH = 50;
7
+ const PROTECTED_PAGES = Object.freeze(['homepage', '404']);
8
+
9
+ const PATHS = Object.freeze({
10
+ root: ROOT,
11
+ routes: path.join(FRONTEND_DIR, '_routes'),
12
+ scssPages: path.join(FRONTEND_DIR, 'scss', 'pages'),
13
+ jsPages: path.join(FRONTEND_DIR, 'js', 'pages'),
14
+ tsPages: path.join(FRONTEND_DIR, 'ts', 'pages'),
15
+ siteData: path.join(FRONTEND_DIR, 'data', 'site.json'),
16
+ pageComponents: path.join(FRONTEND_DIR, 'layouts', 'pageComponents.njk'),
17
+ templates: path.join(ROOT, '_tools', 'res', 'templates'),
18
+ eleventyConfig: path.join(ROOT, '.eleventy.js'),
19
+ packageJson: path.join(ROOT, 'package.json'),
20
+ tsconfig: path.join(ROOT, 'tsconfig.json'),
21
+ });
22
+
23
+ module.exports = { PATHS, MAX_PAGE_NAME_LENGTH, PROTECTED_PAGES };
@@ -0,0 +1,77 @@
1
+ const fs = require('fs');
2
+ const { PATHS } = require('./constants');
3
+ const { toCamelCase } = require('./utils');
4
+
5
+ function readPageComponents() {
6
+ if (!fs.existsSync(PATHS.pageComponents)) {
7
+ console.log(`[skip] not found: ${PATHS.pageComponents}`);
8
+ return null;
9
+ }
10
+ return fs.readFileSync(PATHS.pageComponents, 'utf8');
11
+ }
12
+
13
+ function writePageComponents(content) {
14
+ fs.writeFileSync(PATHS.pageComponents, content);
15
+ }
16
+
17
+ function elifBlockRegex(camelName) {
18
+ return new RegExp(
19
+ `[ \\t]*\\{%\\s*elif\\s+title\\s*==\\s*"${camelName}"\\s*%\\}[\\s\\S]*?(?=[ \\t]*\\{%\\s*(?:elif|else|endif))`,
20
+ );
21
+ }
22
+
23
+ function addPageBlock(pageName) {
24
+ const content = readPageComponents();
25
+ if (!content) return;
26
+
27
+ const camelName = toCamelCase(pageName);
28
+ if (content.includes(`{% elif title == "${camelName}" %}`)) return;
29
+
30
+ if (!content.includes('{% else %}')) {
31
+ console.log('[skip] no {% else %} anchor in pageComponents.njk');
32
+ return;
33
+ }
34
+
35
+ const block =
36
+ `{% elif title == "${camelName}" %}\n` +
37
+ ` {#{% include "component.njk" %}#}\n\n`;
38
+
39
+ writePageComponents(content.replace('{% else %}', `${block}{% else %}`));
40
+ console.log(`[updated] page block added for "${camelName}"`);
41
+ }
42
+
43
+ function removePageBlock(pageName) {
44
+ const content = readPageComponents();
45
+ if (!content) return;
46
+
47
+ const camelName = toCamelCase(pageName);
48
+ if (!elifBlockRegex(camelName).test(content)) {
49
+ console.log(`[skip] page block for "${camelName}" not found`);
50
+ return;
51
+ }
52
+
53
+ const updated = content
54
+ .replace(elifBlockRegex(camelName), '')
55
+ .replace(/\n\s*\n\s*\n/g, '\n\n');
56
+
57
+ writePageComponents(updated);
58
+ console.log(`[cleaned] page block removed for "${camelName}"`);
59
+ }
60
+
61
+ function renamePageBlock(oldName, newName) {
62
+ const content = readPageComponents();
63
+ if (!content) return;
64
+
65
+ const oldLine = `{% elif title == "${toCamelCase(oldName)}" %}`;
66
+ const newLine = `{% elif title == "${toCamelCase(newName)}" %}`;
67
+
68
+ if (!content.includes(oldLine)) {
69
+ console.log(`[skip] page block for "${toCamelCase(oldName)}" not found`);
70
+ return;
71
+ }
72
+
73
+ writePageComponents(content.replace(oldLine, newLine));
74
+ console.log(`[renamed] page block "${toCamelCase(oldName)}" → "${toCamelCase(newName)}"`);
75
+ }
76
+
77
+ module.exports = { addPageBlock, removePageBlock, renamePageBlock };
@@ -1,72 +1,60 @@
1
- const fileSystem = require('fs');
1
+ const fs = require('fs');
2
+ const { PATHS } = require('./constants');
2
3
  const { toCamelCase, toNiceTitle } = require('./utils');
3
4
 
4
- const SITE_DATA_PATH = 'src/frontend/data/site.json';
5
-
6
- // Returns the parsed site.json content, or null if the file doesn't exist
7
5
  function readSiteData() {
8
- if (!fileSystem.existsSync(SITE_DATA_PATH)) return null;
9
- return JSON.parse(fileSystem.readFileSync(SITE_DATA_PATH, 'utf8'));
6
+ if (!fs.existsSync(PATHS.siteData)) {
7
+ console.log(`[skip] not found: ${PATHS.siteData}`);
8
+ return null;
9
+ }
10
+ try {
11
+ const data = JSON.parse(fs.readFileSync(PATHS.siteData, 'utf8'));
12
+ if (!data.pages || typeof data.pages !== 'object') data.pages = {};
13
+ return data;
14
+ } catch (err) {
15
+ console.log(`[error] cannot parse site.json: ${err.message}`);
16
+ return null;
17
+ }
10
18
  }
11
19
 
12
- // Serializes and writes the data object back to site.json
13
20
  function writeSiteData(data) {
14
- fileSystem.writeFileSync(SITE_DATA_PATH, JSON.stringify(data, null, 2));
21
+ fs.writeFileSync(PATHS.siteData, `${JSON.stringify(data, null, 2)}\n`);
15
22
  }
16
23
 
17
- // --- Public API ---
18
-
19
- // Adds a new page record to site.json
20
- // Skips silently if the file doesn't exist or the record is already present
21
24
  function addSiteData(pageName) {
22
25
  const data = readSiteData();
23
26
  if (!data) return;
24
27
 
25
28
  const camelName = toCamelCase(pageName);
26
-
27
29
  if (data.pages[camelName]) {
28
- console.log(`[SKIP] Record "${camelName}" already exists.`);
30
+ console.log(`[skip] record "${camelName}" already exists`);
29
31
  return;
30
32
  }
31
33
 
32
- // Build the default page record with SEO metadata and empty CDN arrays
33
34
  data.pages[camelName] = {
34
- seo: {
35
- title: toNiceTitle(pageName),
36
- description: 'description',
37
- },
38
- cdn: {
39
- css: [],
40
- js: []
41
- }
35
+ seo: { title: toNiceTitle(pageName), description: 'description' },
36
+ cdn: { css: [], js: [] },
42
37
  };
43
38
 
44
39
  writeSiteData(data);
45
- console.log(`[UPDATED] Record "${camelName}" added.`);
40
+ console.log(`[updated] record "${camelName}" added`);
46
41
  }
47
42
 
48
- // Removes a page record from site.json
49
- // Skips silently if the file doesn't exist or the record is not found
50
43
  function removeSiteData(pageName) {
51
44
  const data = readSiteData();
52
45
  if (!data) return;
53
46
 
54
47
  const camelName = toCamelCase(pageName);
55
-
56
48
  if (!data.pages[camelName]) {
57
- console.log(`[SKIP] Record "${camelName}" not found.`);
49
+ console.log(`[skip] record "${camelName}" not found`);
58
50
  return;
59
51
  }
60
52
 
61
53
  delete data.pages[camelName];
62
-
63
54
  writeSiteData(data);
64
- console.log(`[CLEANED] Record "${camelName}" removed.`);
55
+ console.log(`[cleaned] record "${camelName}" removed`);
65
56
  }
66
57
 
67
- // Renames a page record in site.json
68
- // Preserves all existing fields (cdn, etc.) and only updates the SEO title
69
- // Skips if the source record doesn't exist or the target name is already taken
70
58
  function renameSiteData(oldName, newName) {
71
59
  const data = readSiteData();
72
60
  if (!data) return;
@@ -75,29 +63,22 @@ function renameSiteData(oldName, newName) {
75
63
  const newCamel = toCamelCase(newName);
76
64
 
77
65
  if (!data.pages[oldCamel]) {
78
- console.log(`[SKIP] Record "${oldCamel}" not found.`);
66
+ console.log(`[skip] record "${oldCamel}" not found`);
79
67
  return;
80
68
  }
81
-
82
69
  if (data.pages[newCamel]) {
83
- console.log(`[SKIP] Record "${newCamel}" already exists.`);
70
+ console.log(`[skip] record "${newCamel}" already exists`);
84
71
  return;
85
72
  }
86
73
 
87
- // Spread the existing record to preserve cdn and any future fields,
88
- // then override only the seo.title with the new page name
89
74
  data.pages[newCamel] = {
90
75
  ...data.pages[oldCamel],
91
- seo: {
92
- ...data.pages[oldCamel].seo,
93
- title: toNiceTitle(newName),
94
- }
76
+ seo: { ...data.pages[oldCamel].seo, title: toNiceTitle(newName) },
95
77
  };
96
-
97
78
  delete data.pages[oldCamel];
98
79
 
99
80
  writeSiteData(data);
100
- console.log(`[UPDATED] Record "${oldCamel}" renamed to "${newCamel}".`);
81
+ console.log(`[updated] record "${oldCamel}" renamed to "${newCamel}"`);
101
82
  }
102
83
 
103
84
  module.exports = { addSiteData, removeSiteData, renameSiteData };