create-berna-stencil 2.5.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 (188) hide show
  1. package/.eleventy.js +1 -1
  2. package/.gitignore +5 -4
  3. package/README.md +5 -6
  4. package/_tools/assistant.js +92 -88
  5. package/_tools/modules/constants.js +23 -0
  6. package/_tools/modules/pageComponents.js +77 -0
  7. package/_tools/modules/updateData.js +25 -44
  8. package/_tools/modules/updateOutputPath.js +59 -63
  9. package/_tools/modules/updatePage.js +131 -104
  10. package/_tools/modules/utils.js +16 -19
  11. package/_tools/modules/validation.js +20 -0
  12. package/_tools/res/templates/template.njk +2 -2
  13. package/bin/create.js +76 -14
  14. package/docs/Assistant CLI.md +3 -3
  15. package/docs/Components.md +4 -4
  16. package/docs/Creating pages.md +3 -3
  17. package/docs/Styling with SCSS.md +1 -1
  18. package/package.json +50 -65
  19. package/src/backend/_core/vendor/composer/autoload_static.php +10 -10
  20. package/src/backend/_core/vendor/composer/installed.json +6 -6
  21. package/src/backend/_core/vendor/composer/installed.php +2 -2
  22. package/src/backend/_core/vendor/graham-campbell/result-type/.gitattributes +9 -0
  23. package/src/backend/_core/vendor/graham-campbell/result-type/.github/CODE_OF_CONDUCT.md +132 -0
  24. package/src/backend/_core/vendor/graham-campbell/result-type/.github/CONTRIBUTING.md +31 -0
  25. package/src/backend/_core/vendor/graham-campbell/result-type/.github/FUNDING.yml +2 -0
  26. package/src/backend/_core/vendor/graham-campbell/result-type/.github/SECURITY.md +14 -0
  27. package/src/backend/_core/vendor/graham-campbell/result-type/.github/workflows/stale.yml +11 -0
  28. package/src/backend/_core/vendor/graham-campbell/result-type/.github/workflows/tests.yml +40 -0
  29. package/src/backend/_core/vendor/graham-campbell/result-type/CHANGELOG.md +53 -0
  30. package/src/backend/_core/vendor/graham-campbell/result-type/LICENSE +21 -21
  31. package/src/backend/_core/vendor/graham-campbell/result-type/README.md +42 -0
  32. package/src/backend/_core/vendor/graham-campbell/result-type/composer.json +33 -33
  33. package/src/backend/_core/vendor/graham-campbell/result-type/phpunit.xml.dist +13 -0
  34. package/src/backend/_core/vendor/graham-campbell/result-type/src/Error.php +121 -121
  35. package/src/backend/_core/vendor/graham-campbell/result-type/src/Result.php +69 -69
  36. package/src/backend/_core/vendor/graham-campbell/result-type/src/Success.php +120 -120
  37. package/src/backend/_core/vendor/graham-campbell/result-type/tests/ResultTest.php +95 -0
  38. package/src/backend/_core/vendor/phpoption/phpoption/.gitattributes +13 -0
  39. package/src/backend/_core/vendor/phpoption/phpoption/.github/CODE_OF_CONDUCT.md +132 -0
  40. package/src/backend/_core/vendor/phpoption/phpoption/.github/CONTRIBUTING.md +30 -0
  41. package/src/backend/_core/vendor/phpoption/phpoption/.github/FUNDING.yml +2 -0
  42. package/src/backend/_core/vendor/phpoption/phpoption/.github/SECURITY.md +14 -0
  43. package/src/backend/_core/vendor/phpoption/phpoption/.github/workflows/static.yml +40 -0
  44. package/src/backend/_core/vendor/phpoption/phpoption/.github/workflows/tests.yml +40 -0
  45. package/src/backend/_core/vendor/phpoption/phpoption/LICENSE +200 -200
  46. package/src/backend/_core/vendor/phpoption/phpoption/Makefile +17 -0
  47. package/src/backend/_core/vendor/phpoption/phpoption/README.md +201 -0
  48. package/src/backend/_core/vendor/phpoption/phpoption/composer.json +50 -50
  49. package/src/backend/_core/vendor/phpoption/phpoption/phpstan-baseline.neon +44 -0
  50. package/src/backend/_core/vendor/phpoption/phpoption/phpstan.neon.dist +7 -0
  51. package/src/backend/_core/vendor/phpoption/phpoption/phpunit.xml.dist +13 -0
  52. package/src/backend/_core/vendor/phpoption/phpoption/src/PhpOption/LazyOption.php +175 -175
  53. package/src/backend/_core/vendor/phpoption/phpoption/src/PhpOption/None.php +136 -136
  54. package/src/backend/_core/vendor/phpoption/phpoption/src/PhpOption/Option.php +434 -434
  55. package/src/backend/_core/vendor/phpoption/phpoption/src/PhpOption/Some.php +169 -169
  56. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/EnsureTest.php +72 -0
  57. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/LazyOptionTest.php +357 -0
  58. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/NoneTest.php +153 -0
  59. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/OptionTest.php +166 -0
  60. package/src/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/SomeTest.php +194 -0
  61. package/src/backend/_core/vendor/phpoption/phpoption/tests/bootstrap.php +8 -0
  62. package/src/backend/_core/vendor/phpoption/phpoption/vendor-bin/phpstan/composer.json +8 -0
  63. package/src/backend/_core/vendor/symfony/polyfill-ctype/Ctype.php +232 -232
  64. package/src/backend/_core/vendor/symfony/polyfill-ctype/LICENSE +19 -19
  65. package/src/backend/_core/vendor/symfony/polyfill-ctype/README.md +12 -12
  66. package/src/backend/_core/vendor/symfony/polyfill-ctype/bootstrap.php +50 -50
  67. package/src/backend/_core/vendor/symfony/polyfill-ctype/bootstrap80.php +46 -46
  68. package/src/backend/_core/vendor/symfony/polyfill-ctype/composer.json +38 -38
  69. package/src/backend/_core/vendor/symfony/polyfill-mbstring/LICENSE +19 -19
  70. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Mbstring.php +1077 -1077
  71. package/src/backend/_core/vendor/symfony/polyfill-mbstring/README.md +13 -13
  72. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php +119 -119
  73. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php +1397 -1397
  74. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php +5 -5
  75. package/src/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php +1489 -1489
  76. package/src/backend/_core/vendor/symfony/polyfill-mbstring/bootstrap.php +171 -171
  77. package/src/backend/_core/vendor/symfony/polyfill-mbstring/bootstrap80.php +167 -167
  78. package/src/backend/_core/vendor/symfony/polyfill-mbstring/composer.json +39 -39
  79. package/src/backend/_core/vendor/symfony/polyfill-php80/LICENSE +19 -19
  80. package/src/backend/_core/vendor/symfony/polyfill-php80/Php80.php +115 -115
  81. package/src/backend/_core/vendor/symfony/polyfill-php80/PhpToken.php +106 -106
  82. package/src/backend/_core/vendor/symfony/polyfill-php80/README.md +25 -25
  83. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php +31 -31
  84. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php +16 -16
  85. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php +20 -20
  86. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php +16 -16
  87. package/src/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php +16 -16
  88. package/src/backend/_core/vendor/symfony/polyfill-php80/bootstrap.php +42 -42
  89. package/src/backend/_core/vendor/symfony/polyfill-php80/composer.json +37 -37
  90. package/src/backend/_core/vendor/vlucas/phpdotenv/.editorconfig +15 -0
  91. package/src/backend/_core/vendor/vlucas/phpdotenv/.gitattributes +15 -0
  92. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/CODE_OF_CONDUCT.md +132 -0
  93. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/CONTRIBUTING.md +30 -0
  94. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/FUNDING.yml +2 -0
  95. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/SECURITY.md +14 -0
  96. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/workflows/static.yml +40 -0
  97. package/src/backend/_core/vendor/vlucas/phpdotenv/.github/workflows/tests.yml +70 -0
  98. package/src/backend/_core/vendor/vlucas/phpdotenv/LICENSE +30 -30
  99. package/src/backend/_core/vendor/vlucas/phpdotenv/Makefile +17 -0
  100. package/src/backend/_core/vendor/vlucas/phpdotenv/README.md +370 -0
  101. package/src/backend/_core/vendor/vlucas/phpdotenv/UPGRADING.md +196 -0
  102. package/src/backend/_core/vendor/vlucas/phpdotenv/composer.json +60 -60
  103. package/src/backend/_core/vendor/vlucas/phpdotenv/phpstan-baseline.neon +157 -0
  104. package/src/backend/_core/vendor/vlucas/phpdotenv/phpstan.neon.dist +7 -0
  105. package/src/backend/_core/vendor/vlucas/phpdotenv/phpunit.xml.dist +13 -0
  106. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Dotenv.php +267 -267
  107. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php +12 -12
  108. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidEncodingException.php +12 -12
  109. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidFileException.php +12 -12
  110. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidPathException.php +12 -12
  111. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Exception/ValidationException.php +12 -12
  112. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Loader/Loader.php +48 -48
  113. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Loader/LoaderInterface.php +20 -20
  114. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Loader/Resolver.php +65 -65
  115. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Entry.php +59 -59
  116. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php +299 -299
  117. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Lexer.php +58 -58
  118. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Lines.php +127 -127
  119. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Parser.php +53 -53
  120. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/ParserInterface.php +19 -19
  121. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Value.php +88 -88
  122. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/AdapterInterface.php +15 -15
  123. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ApacheAdapter.php +89 -89
  124. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ArrayAdapter.php +80 -80
  125. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php +88 -88
  126. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/GuardedWriter.php +85 -85
  127. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ImmutableWriter.php +110 -110
  128. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiReader.php +48 -48
  129. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiWriter.php +64 -64
  130. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/PutenvAdapter.php +91 -91
  131. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReaderInterface.php +17 -17
  132. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReplacingWriter.php +104 -104
  133. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php +88 -88
  134. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/WriterInterface.php +27 -27
  135. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/AdapterRepository.php +107 -107
  136. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/RepositoryBuilder.php +272 -272
  137. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Repository/RepositoryInterface.php +51 -51
  138. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/File/Paths.php +44 -44
  139. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/File/Reader.php +81 -81
  140. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/FileStore.php +72 -72
  141. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/StoreBuilder.php +141 -141
  142. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/StoreInterface.php +17 -17
  143. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Store/StringStore.php +37 -37
  144. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Util/Regex.php +112 -112
  145. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Util/Str.php +108 -108
  146. package/src/backend/_core/vendor/vlucas/phpdotenv/src/Validator.php +207 -207
  147. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/DotenvTest.php +387 -0
  148. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Loader/LoaderTest.php +86 -0
  149. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/EntryParserTest.php +234 -0
  150. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LexerTest.php +40 -0
  151. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LinesTest.php +53 -0
  152. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/ParserTest.php +98 -0
  153. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ArrayAdapterTest.php +57 -0
  154. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/EnvConstAdapterTest.php +75 -0
  155. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/PutenvAdapterTest.php +52 -0
  156. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ServerConstAdapterTest.php +75 -0
  157. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/RepositoryTest.php +305 -0
  158. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Store/StoreTest.php +141 -0
  159. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/ValidatorTest.php +479 -0
  160. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/.env +5 -0
  161. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/assertions.env +18 -0
  162. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/booleans.env +33 -0
  163. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/commented.env +15 -0
  164. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/empty.env +1 -0
  165. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/example.env +1 -0
  166. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/exported.env +7 -0
  167. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/immutable.env +1 -0
  168. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/integers.env +17 -0
  169. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/large.env +2 -0
  170. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multibyte.env +3 -0
  171. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multiline.env +14 -0
  172. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multiple.env +4 -0
  173. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/mutable.env +1 -0
  174. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/nested.env +15 -0
  175. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/quoted.env +11 -0
  176. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/specialchars.env +8 -0
  177. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/unicodevarnames.env +2 -0
  178. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/utf8-with-bom-encoding.env +3 -0
  179. package/src/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/windows.env +1 -0
  180. package/src/backend/_core/vendor/vlucas/phpdotenv/vendor-bin/phpstan/composer.json +15 -0
  181. package/src/backend/{config.example.php → config.php} +4 -13
  182. package/src/frontend/data/site.json +44 -44
  183. package/src/frontend/index.njk +2 -2
  184. package/src/frontend/{components/layouts → layouts}/base.njk +1 -1
  185. package/_tools/buildJs.js +0 -28
  186. package/_tools/cleanOutput.js +0 -20
  187. package/_tools/modules/updateIncludes.js +0 -81
  188. /package/src/frontend/{components/layouts/includes.njk → layouts/pageComponents.njk} +0 -0
@@ -1,113 +1,109 @@
1
- const fs = require('fs');
1
+ const fs = require('fs');
2
2
  const path = require('path');
3
+ const { PATHS } = require('./constants');
3
4
  const { isTypeScriptProject } = require('./utils');
4
5
 
5
- const ELEVENTY_CONFIG = path.resolve(__dirname, '../../.eleventy.js');
6
- const PACKAGE_JSON = path.resolve(__dirname, '../../package.json');
7
- const TSCONFIG = path.resolve(__dirname, '../../tsconfig.json');
8
-
9
6
  const OUTPUT_DIR_REGEX = /const OUTPUT_DIR\s*=\s*['"`]([^'"`]*)['"`]/;
10
-
11
- // --- Helpers ---
7
+ const TSCONFIG_OUTDIR_REGEX = /"outDir"\s*:\s*"[^"]*"/;
12
8
 
13
9
  function parseOutputDir(content) {
14
10
  const match = content.match(OUTPUT_DIR_REGEX);
15
11
  return match ? match[1] : null;
16
12
  }
17
13
 
18
- // --- Updaters ---
19
-
20
14
  function updateEleventyConfig(newPath) {
21
- const content = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
22
-
23
- const updated = content.replace(
24
- OUTPUT_DIR_REGEX,
25
- `const OUTPUT_DIR = "${newPath}"`
26
- );
15
+ const content = fs.readFileSync(PATHS.eleventyConfig, 'utf8');
16
+ const updated = content.replace(OUTPUT_DIR_REGEX, `const OUTPUT_DIR = "${newPath}"`);
27
17
 
28
18
  if (content === updated) {
29
- console.log('(!) OUTPUT_DIR not found in .eleventy.js');
30
- return false;
19
+ console.log('[skip] OUTPUT_DIR not found in .eleventy.js');
20
+ return;
31
21
  }
32
-
33
- fs.writeFileSync(ELEVENTY_CONFIG, updated, 'utf-8');
34
- console.log(`(✓) .eleventy.js updated → ${newPath}`);
35
- return true;
22
+ fs.writeFileSync(PATHS.eleventyConfig, updated);
23
+ console.log(`[updated] .eleventy.js → ${newPath}`);
36
24
  }
37
25
 
38
26
  function updatePackageJson(newPath) {
39
- const pkg = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf-8'));
27
+ const pkg = JSON.parse(fs.readFileSync(PATHS.packageJson, 'utf8'));
28
+ const scriptGlob = isTypeScriptProject()
29
+ ? 'src/frontend/ts/pages/*.ts'
30
+ : 'src/frontend/js/pages/*.js';
40
31
 
41
32
  pkg.outputDir = newPath;
42
-
43
- const usesTs = isTypeScriptProject();
44
-
33
+ pkg.scripts = pkg.scripts || {};
45
34
  pkg.scripts['build:css'] = `sass src/frontend/scss:${newPath}/css --no-source-map --style=compressed --quiet`;
46
35
  pkg.scripts['serve:css'] = `sass --watch src/frontend/scss:${newPath}/css --no-source-map --quiet`;
36
+ pkg.scripts['build:js'] = `esbuild "${scriptGlob}" --bundle --outdir=${newPath}/js/pages --minify`;
37
+ pkg.scripts['serve:js'] = `esbuild "${scriptGlob}" --bundle --outdir=${newPath}/js/pages --watch`;
47
38
 
48
- if (usesTs) {
49
- pkg.scripts['build:js'] = `esbuild "src/frontend/ts/pages/*.ts" --bundle --outdir=${newPath}/js/pages --minify`;
50
- pkg.scripts['serve:js'] = `esbuild "src/frontend/ts/pages/*.ts" --bundle --outdir=${newPath}/js/pages --watch`;
51
- } else {
52
- pkg.scripts['build:js'] = `esbuild "src/frontend/js/pages/*.js" --bundle --outdir=${newPath}/js/pages --minify`;
53
- pkg.scripts['serve:js'] = `esbuild "src/frontend/js/pages/*.js" --bundle --outdir=${newPath}/js/pages --watch`;
54
- }
55
-
56
- fs.writeFileSync(PACKAGE_JSON, JSON.stringify(pkg, null, 2), 'utf-8');
57
- console.log(`(✓) package.json updated → ${newPath}`);
58
- return true;
39
+ fs.writeFileSync(PATHS.packageJson, `${JSON.stringify(pkg, null, 2)}\n`);
40
+ console.log(`[updated] package.json ${newPath}`);
59
41
  }
60
42
 
61
- const TSCONFIG_OUTDIR_REGEX = /"outDir"\s*:\s*"[^"]*"/;
62
-
63
43
  function updateTsConfig(newPath) {
64
44
  if (!isTypeScriptProject()) return;
65
45
 
66
- const content = fs.readFileSync(TSCONFIG, 'utf-8');
46
+ const content = fs.readFileSync(PATHS.tsconfig, 'utf8');
67
47
  const updated = content.replace(TSCONFIG_OUTDIR_REGEX, `"outDir": "./${newPath}/ts"`);
68
48
 
69
49
  if (content === updated) {
70
- console.log('(!) outDir not found in tsconfig.json');
50
+ console.log('[skip] outDir not found in tsconfig.json');
71
51
  return;
72
52
  }
73
-
74
- fs.writeFileSync(TSCONFIG, updated, 'utf-8');
75
- console.log(`(✓) tsconfig.json updated → ${newPath}/ts`);
53
+ fs.writeFileSync(PATHS.tsconfig, updated);
54
+ console.log(`[updated] tsconfig.json → ${newPath}/ts`);
76
55
  }
77
56
 
78
- // --- Public API ---
57
+ function deleteOldOutput(oldPath) {
58
+ if (!oldPath) return;
79
59
 
80
- function updateOutputPath(newPath) {
81
- const trimmed = newPath.trim().replace(/\\/g, '/');
60
+ const oldAbsPath = path.resolve(PATHS.root, oldPath);
61
+ const insideProject = oldAbsPath.startsWith(PATHS.root + path.sep);
62
+
63
+ if (!insideProject || oldAbsPath === PATHS.root) {
64
+ console.log(`[skip] refusing to delete "${oldAbsPath}" (outside project)`);
65
+ return;
66
+ }
67
+ if (fs.existsSync(oldAbsPath)) {
68
+ fs.rmSync(oldAbsPath, { recursive: true, force: true });
69
+ console.log(`[deleted] ${oldAbsPath}`);
70
+ }
71
+ }
72
+
73
+ function updateOutputPath(rawPath) {
74
+ const trimmed = (rawPath ?? '').trim().replace(/\\/g, '/');
75
+ if (!trimmed) {
76
+ console.log('[skip] empty output path');
77
+ return;
78
+ }
82
79
 
83
80
  const normalizedPath = trimmed === '.'
84
81
  ? 'out'
85
82
  : `${trimmed.replace(/\/$/, '')}/${path.basename(process.cwd())}-out`;
86
83
 
87
- const eleventyContent = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
88
- const oldPath = parseOutputDir(eleventyContent);
89
-
90
- if (oldPath) {
91
- const oldAbsPath = path.resolve(__dirname, '../../', oldPath);
92
- if (fs.existsSync(oldAbsPath)) {
93
- fs.rmSync(oldAbsPath, { recursive: true, force: true });
94
- console.log(`(✓) folder deleted → ${oldAbsPath}`);
95
- } else {
96
- console.log(`(i) folder not found, nothing to delete → ${oldAbsPath}`);
97
- }
84
+ let oldPath = null;
85
+ try {
86
+ oldPath = parseOutputDir(fs.readFileSync(PATHS.eleventyConfig, 'utf8'));
87
+ } catch (err) {
88
+ console.log(`[error] cannot read .eleventy.js: ${err.message}`);
89
+ return;
98
90
  }
99
91
 
100
- console.log(`\nupdating output path → "${normalizedPath}"...`);
92
+ deleteOldOutput(oldPath);
101
93
 
102
- updatePackageJson(normalizedPath);
103
- updateEleventyConfig(normalizedPath);
104
- updateTsConfig(normalizedPath);
94
+ console.log(`\nupdating output path → "${normalizedPath}"`);
95
+ try {
96
+ updatePackageJson(normalizedPath);
97
+ updateEleventyConfig(normalizedPath);
98
+ updateTsConfig(normalizedPath);
99
+ } catch (err) {
100
+ console.log(`[error] could not update output path: ${err.message}`);
101
+ }
105
102
  }
106
103
 
107
104
  function getCurrentOutputPath() {
108
105
  try {
109
- const content = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
110
- return parseOutputDir(content);
106
+ return parseOutputDir(fs.readFileSync(PATHS.eleventyConfig, 'utf8'));
111
107
  } catch {
112
108
  return null;
113
109
  }
@@ -1,135 +1,162 @@
1
- const fileSystem = require('fs');
1
+ const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
+ const { PATHS } = require('./constants');
5
+ const { toCamelCase, isTypeScriptProject } = require('./utils');
4
6
  const { addSiteData, removeSiteData, renameSiteData } = require('./updateData');
5
- const { addLayout, removeLayout, renameLayout } = require('./updateIncludes');
7
+ const { addPageBlock, removePageBlock, renamePageBlock } = require('./pageComponents');
6
8
  const { getCurrentOutputPath } = require('./updateOutputPath');
7
- const { toCamelCase, isTypeScriptProject } = require('./utils');
8
-
9
- const TEMPLATES_DIR = path.join(__dirname, '..', 'res', 'templates');
10
-
11
- function getPageTargets(pageName) {
12
- const camelName = toCamelCase(pageName);
13
- const usesTs = isTypeScriptProject();
14
9
 
15
- return [
16
- {
17
- folder: 'src/frontend/scss/pages',
18
- templateFile: 'template.scss',
19
- fileName: `${camelName}.scss`,
20
- },
21
- {
22
- folder: usesTs ? 'src/frontend/ts/pages' : 'src/frontend/js/pages',
23
- templateFile: usesTs ? 'template.ts' : 'template.js',
24
- fileName: usesTs ? `${camelName}.ts` : `${camelName}.js`,
10
+ function getPageArtifacts(pageName) {
11
+ const camelName = toCamelCase(pageName);
12
+ const usesTs = isTypeScriptProject();
13
+ const scriptDir = usesTs ? PATHS.tsPages : PATHS.jsPages;
14
+ const scriptExt = usesTs ? 'ts' : 'js';
15
+ const outputRoot = path.join(PATHS.root, getCurrentOutputPath() || 'out');
16
+
17
+ return {
18
+ camelName,
19
+ source: {
20
+ scss: path.join(PATHS.scssPages, `${camelName}.scss`),
21
+ script: path.join(scriptDir, `${camelName}.${scriptExt}`),
22
+ route: path.join(PATHS.routes, `${pageName}.njk`),
25
23
  },
26
- {
27
- folder: 'src/frontend/_routes',
28
- templateFile: 'template.njk',
29
- fileName: `${pageName}.njk`,
24
+ output: {
25
+ files: [
26
+ path.join(outputRoot, 'js', 'pages', `${camelName}.js`),
27
+ path.join(outputRoot, 'css', 'pages', `${camelName}.css`),
28
+ path.join(outputRoot, `${pageName}.html`),
29
+ path.join(outputRoot, 'pages', `${pageName}.html`),
30
+ ],
31
+ folders: [
32
+ path.join(outputRoot, pageName),
33
+ path.join(outputRoot, 'pages', pageName),
34
+ ],
30
35
  },
31
- ];
36
+ };
32
37
  }
33
38
 
34
- function addPage(pageName) {
35
- const camelName = toCamelCase(pageName);
36
-
37
- getPageTargets(pageName).forEach(({ folder, templateFile, fileName }) => {
38
- const destPath = path.join(folder, fileName);
39
- fileSystem.mkdirSync(folder, { recursive: true });
40
-
41
- if (fileSystem.existsSync(destPath)) return;
42
-
43
- const srcPath = path.join(TEMPLATES_DIR, templateFile);
39
+ function pageExists(pageName) {
40
+ return fs.existsSync(path.join(PATHS.routes, `${pageName}.njk`));
41
+ }
44
42
 
45
- if (templateFile === 'template.njk') {
46
- const content = fileSystem.readFileSync(srcPath, 'utf8')
47
- .replace(/^title:.*$/m, `title: "${camelName}"`)
48
- .replace(/^permalink:.*$/m, `permalink: "/${pageName}/"`);
49
- fileSystem.writeFileSync(destPath, content);
50
- } else {
51
- fileSystem.copyFileSync(srcPath, destPath);
52
- }
43
+ function applyRouteFrontMatter(filePath, camelName, pageName) {
44
+ const content = fs.readFileSync(filePath, 'utf8')
45
+ .replace(/^title:.*$/m, `title: "${camelName}"`)
46
+ .replace(/^permalink:.*$/m, `permalink: "/${pageName}/"`);
47
+ fs.writeFileSync(filePath, content);
48
+ }
53
49
 
54
- console.log(`[created file] ${destPath}`);
55
- });
50
+ function addPage(pageName) {
51
+ if (pageExists(pageName)) {
52
+ console.log(`[skip] page "${pageName}" already exists`);
53
+ return;
54
+ }
55
+
56
+ const { camelName, source } = getPageArtifacts(pageName);
57
+ const usesTs = isTypeScriptProject();
58
+
59
+ const creations = [
60
+ { dest: source.scss, template: 'template.scss', isRoute: false },
61
+ { dest: source.script, template: usesTs ? 'template.ts' : 'template.js', isRoute: false },
62
+ { dest: source.route, template: 'template.njk', isRoute: true },
63
+ ];
56
64
 
57
- addLayout(pageName);
65
+ try {
66
+ creations.forEach(({ dest, template, isRoute }) => {
67
+ const templatePath = path.join(PATHS.templates, template);
68
+ if (!fs.existsSync(templatePath)) {
69
+ console.log(`[skip] template not found: ${templatePath}`);
70
+ return;
71
+ }
72
+
73
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
74
+ fs.copyFileSync(templatePath, dest);
75
+ if (isRoute) applyRouteFrontMatter(dest, camelName, pageName);
76
+
77
+ console.log(`[created] ${dest}`);
78
+ });
79
+ } catch (err) {
80
+ console.log(`[error] could not create page files: ${err.message}`);
81
+ return;
82
+ }
83
+
84
+ addPageBlock(pageName);
58
85
  addSiteData(pageName);
59
86
  }
60
87
 
61
88
  function renamePage(oldName, newName) {
62
- const oldCamel = toCamelCase(oldName);
63
- const newCamel = toCamelCase(newName);
64
- const usesTs = isTypeScriptProject();
65
- const ext = usesTs ? 'ts' : 'js';
66
- const jsFolder = usesTs ? 'src/frontend/ts/pages' : 'src/frontend/js/pages';
67
-
68
- const filesToRename = [
69
- { src: `src/frontend/scss/pages/${oldCamel}.scss`, dest: `src/frontend/scss/pages/${newCamel}.scss` },
70
- { src: `${jsFolder}/${oldCamel}.${ext}`, dest: `${jsFolder}/${newCamel}.${ext}` },
71
- { src: `src/frontend/_routes/${oldName}.njk`, dest: `src/frontend/_routes/${newName}.njk` },
89
+ if (!pageExists(oldName)) {
90
+ console.log(`[skip] page "${oldName}" does not exist`);
91
+ return;
92
+ }
93
+ if (pageExists(newName)) {
94
+ console.log(`[skip] target page "${newName}" already exists`);
95
+ return;
96
+ }
97
+
98
+ const oldArtifacts = getPageArtifacts(oldName);
99
+ const newArtifacts = getPageArtifacts(newName);
100
+
101
+ const moves = [
102
+ { src: oldArtifacts.source.scss, dest: newArtifacts.source.scss },
103
+ { src: oldArtifacts.source.script, dest: newArtifacts.source.script },
104
+ { src: oldArtifacts.source.route, dest: newArtifacts.source.route },
72
105
  ];
73
106
 
74
- filesToRename.forEach(({ src, dest }) => {
75
- if (!fileSystem.existsSync(src)) {
76
- console.log(`[skip] not found: ${src}`);
77
- return;
78
- }
79
-
80
- fileSystem.renameSync(src, dest);
81
- console.log(`[renamed] ${src} → ${dest}`);
82
-
83
- if (dest.endsWith('.njk')) {
84
- const content = fileSystem.readFileSync(dest, 'utf8')
85
- .replace(/^title:.*$/m, `title: "${newCamel}"`)
86
- .replace(/^permalink:.*$/m, `permalink: "/${newName}/"`);
87
- fileSystem.writeFileSync(dest, content);
88
- }
89
- });
90
-
91
- renameLayout(oldName, newName);
107
+ try {
108
+ moves.forEach(({ src, dest }) => {
109
+ if (!fs.existsSync(src)) {
110
+ console.log(`[skip] not found: ${src}`);
111
+ return;
112
+ }
113
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
114
+ fs.renameSync(src, dest);
115
+ console.log(`[renamed] ${src} → ${dest}`);
116
+
117
+ if (dest.endsWith('.njk')) applyRouteFrontMatter(dest, newArtifacts.camelName, newName);
118
+ });
119
+ } catch (err) {
120
+ console.log(`[error] could not rename page files: ${err.message}`);
121
+ return;
122
+ }
123
+
124
+ renamePageBlock(oldName, newName);
92
125
  renameSiteData(oldName, newName);
93
126
  }
94
127
 
95
128
  function removePage(pageName) {
96
- const camelName = toCamelCase(pageName);
97
- const usesTs = isTypeScriptProject();
98
- const ext = usesTs ? 'ts' : 'js';
99
- const jsFolder = usesTs ? 'src/frontend/ts/pages' : 'src/frontend/js/pages';
100
- const OUTPUT_DIR = getCurrentOutputPath() || 'out';
101
-
102
- const filesToDelete = [
103
- `src/frontend/scss/pages/${camelName}.scss`,
104
- `${jsFolder}/${camelName}.${ext}`,
105
- `src/frontend/_routes/${pageName}.njk`,
106
- path.join(OUTPUT_DIR, 'js/pages', `${camelName}.js`),
107
- path.join(OUTPUT_DIR, 'css/pages', `${camelName}.css`),
108
- path.join(OUTPUT_DIR, `${pageName}.html`),
109
- path.join(OUTPUT_DIR, 'pages', `${pageName}.html`),
110
- ];
111
-
112
- const foldersToDelete = [
113
- path.join(OUTPUT_DIR, pageName),
114
- path.join(OUTPUT_DIR, 'pages', pageName),
115
- ];
116
-
117
- filesToDelete.forEach(f => {
118
- if (fileSystem.existsSync(f)) {
119
- fileSystem.unlinkSync(f);
120
- console.log(`[deleted file] ${f}`);
129
+ if (!pageExists(pageName)) {
130
+ console.log(`[skip] page "${pageName}" does not exist`);
131
+ }
132
+
133
+ const { source, output } = getPageArtifacts(pageName);
134
+ const filesToDelete = [source.scss, source.script, source.route, ...output.files];
135
+
136
+ filesToDelete.forEach((file) => {
137
+ try {
138
+ if (fs.existsSync(file)) {
139
+ fs.unlinkSync(file);
140
+ console.log(`[deleted] ${file}`);
141
+ }
142
+ } catch (err) {
143
+ console.log(`[error] ${file}: ${err.message}`);
121
144
  }
122
145
  });
123
146
 
124
- foldersToDelete.forEach(folder => {
125
- if (fileSystem.existsSync(folder)) {
126
- fileSystem.rmSync(folder, { recursive: true, force: true });
127
- console.log(`[deleted folder] ${folder}`);
147
+ output.folders.forEach((folder) => {
148
+ try {
149
+ if (fs.existsSync(folder)) {
150
+ fs.rmSync(folder, { recursive: true, force: true });
151
+ console.log(`[deleted] ${folder}`);
152
+ }
153
+ } catch (err) {
154
+ console.log(`[error] ${folder}: ${err.message}`);
128
155
  }
129
156
  });
130
157
 
131
- removeLayout(pageName);
158
+ removePageBlock(pageName);
132
159
  removeSiteData(pageName);
133
160
  }
134
161
 
135
- module.exports = { addPage, removePage, renamePage };
162
+ module.exports = { addPage, removePage, renamePage, pageExists };
@@ -1,30 +1,27 @@
1
- const fs = require('fs');
2
- const path = require('path');
1
+ const fs = require('fs');
2
+ const { PATHS } = require('./constants');
3
3
 
4
- const TSCONFIG = path.resolve(__dirname, '../../tsconfig.json');
5
-
6
- // Shared utility functions used across modules
4
+ function toCamelCase(value) {
5
+ return value.toLowerCase().replace(/[-_][a-z0-9]/g, (group) => group.slice(1).toUpperCase());
6
+ }
7
7
 
8
- // Converts a kebab-case or snake_case string to camelCase.
9
- // Uses slice(1) to remove the delimiter, avoiding the double-replace bug
10
- // that occurs when using .replace('-', '') without the /g flag.
11
- function toCamelCase(str) {
12
- return str.toLowerCase().replace(/[-_][a-z0-9]/g, (group) =>
13
- group.slice(1).toUpperCase()
14
- );
8
+ function toKebabCase(value) {
9
+ return value.trim().toLowerCase()
10
+ .replace(/[^a-z0-9\s_-]/g, '')
11
+ .replace(/[\s_]+/g, '-')
12
+ .replace(/-+/g, '-')
13
+ .replace(/^-+|-+$/g, '');
15
14
  }
16
15
 
17
- // Converts a kebab-case page name to a human-readable title
18
- // e.g. "about-us" → "About Us"
19
16
  function toNiceTitle(pageName) {
20
- return pageName
21
- .split('-')
22
- .map(w => w.charAt(0).toUpperCase() + w.slice(1))
17
+ return pageName.split('-')
18
+ .filter(Boolean)
19
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
23
20
  .join(' ');
24
21
  }
25
22
 
26
23
  function isTypeScriptProject() {
27
- return fs.existsSync(TSCONFIG);
24
+ return fs.existsSync(PATHS.tsconfig);
28
25
  }
29
26
 
30
- module.exports = { toCamelCase, toNiceTitle, isTypeScriptProject };
27
+ module.exports = { toCamelCase, toKebabCase, toNiceTitle, isTypeScriptProject };
@@ -0,0 +1,20 @@
1
+ const { MAX_PAGE_NAME_LENGTH, PROTECTED_PAGES } = require('./constants');
2
+
3
+ function validatePageName(name) {
4
+ if (!name) return 'Invalid name.';
5
+ if (name.length > MAX_PAGE_NAME_LENGTH) return `Name must be ${MAX_PAGE_NAME_LENGTH} characters or fewer.`;
6
+ if (!/^[a-z0-9-]+$/.test(name)) return 'Page name can only contain lowercase letters, numbers, and hyphens.';
7
+ if (/^\d/.test(name)) return 'Page name cannot start with a number.';
8
+ if (PROTECTED_PAGES.includes(name)) return `"${name}" is a protected page name.`;
9
+ return null;
10
+ }
11
+
12
+ function validateOutputPath(input) {
13
+ const value = (input ?? '').trim();
14
+ if (!value) return 'Invalid path.';
15
+ if (value.includes('..')) return 'Path cannot contain "..".';
16
+ if (/[<>|?*"']/.test(value)) return 'Path contains invalid characters.';
17
+ return null;
18
+ }
19
+
20
+ module.exports = { validatePageName, validateOutputPath };
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  title: ""
3
3
  permalink: ""
4
- layout: includes.njk
4
+ layout: pageComponents.njk
5
5
  ---
6
6
 
7
7
  <!-- !IMPORTANT -->
8
8
  <!-- DO NOT ADD ANYTHING HERE -->
9
- <!-- You should create a new component.njk into src/frontend/components and include that in components/layouts/includes.njk -->
9
+ <!-- You should create a new component.njk into src/frontend and include that in layouts/pageComponents.njk -->