create-berna-stencil 2.6.0 → 2.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.
Files changed (199) hide show
  1. package/README.md +1 -1
  2. package/_tools/assistant.js +92 -88
  3. package/_tools/modules/constants.js +23 -0
  4. package/_tools/modules/pageComponents.js +77 -0
  5. package/_tools/modules/updateData.js +25 -44
  6. package/_tools/modules/updateOutputPath.js +59 -63
  7. package/_tools/modules/updatePage.js +131 -104
  8. package/_tools/modules/utils.js +16 -19
  9. package/_tools/modules/validation.js +20 -0
  10. package/bin/create.js +74 -12
  11. package/out/.htaccess +16 -0
  12. package/out/404.html +163 -0
  13. package/out/assets/brand/favicon.svg +37 -0
  14. package/out/assets/brand/logo.svg +37 -0
  15. package/out/backend/.htaccess +7 -0
  16. package/out/backend/_core/composer.json +5 -0
  17. package/out/backend/_core/composer.lock +492 -0
  18. package/out/backend/_core/index.php +148 -0
  19. package/out/backend/_core/init.php +34 -0
  20. package/out/backend/_core/modules/RateLimiter.php +31 -0
  21. package/out/backend/_core/modules/Response.php +49 -0
  22. package/{src → out}/backend/_core/vendor/composer/autoload_static.php +10 -10
  23. package/{src → out}/backend/_core/vendor/composer/installed.json +6 -6
  24. package/{src → out}/backend/_core/vendor/composer/installed.php +2 -2
  25. package/out/backend/_core/vendor/graham-campbell/result-type/CHANGELOG.md +53 -0
  26. package/{src → out}/backend/_core/vendor/graham-campbell/result-type/LICENSE +21 -21
  27. package/out/backend/_core/vendor/graham-campbell/result-type/README.md +42 -0
  28. package/{src → out}/backend/_core/vendor/graham-campbell/result-type/composer.json +33 -33
  29. package/out/backend/_core/vendor/graham-campbell/result-type/phpunit.xml.dist +13 -0
  30. package/{src → out}/backend/_core/vendor/graham-campbell/result-type/src/Error.php +121 -121
  31. package/{src → out}/backend/_core/vendor/graham-campbell/result-type/src/Result.php +69 -69
  32. package/{src → out}/backend/_core/vendor/graham-campbell/result-type/src/Success.php +120 -120
  33. package/out/backend/_core/vendor/graham-campbell/result-type/tests/ResultTest.php +95 -0
  34. package/{src → out}/backend/_core/vendor/phpoption/phpoption/LICENSE +200 -200
  35. package/out/backend/_core/vendor/phpoption/phpoption/Makefile +17 -0
  36. package/out/backend/_core/vendor/phpoption/phpoption/README.md +201 -0
  37. package/{src → out}/backend/_core/vendor/phpoption/phpoption/composer.json +50 -50
  38. package/out/backend/_core/vendor/phpoption/phpoption/phpstan-baseline.neon +44 -0
  39. package/out/backend/_core/vendor/phpoption/phpoption/phpstan.neon.dist +7 -0
  40. package/out/backend/_core/vendor/phpoption/phpoption/phpunit.xml.dist +13 -0
  41. package/{src → out}/backend/_core/vendor/phpoption/phpoption/src/PhpOption/LazyOption.php +175 -175
  42. package/{src → out}/backend/_core/vendor/phpoption/phpoption/src/PhpOption/None.php +136 -136
  43. package/{src → out}/backend/_core/vendor/phpoption/phpoption/src/PhpOption/Option.php +434 -434
  44. package/{src → out}/backend/_core/vendor/phpoption/phpoption/src/PhpOption/Some.php +169 -169
  45. package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/EnsureTest.php +72 -0
  46. package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/LazyOptionTest.php +357 -0
  47. package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/NoneTest.php +153 -0
  48. package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/OptionTest.php +166 -0
  49. package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/SomeTest.php +194 -0
  50. package/out/backend/_core/vendor/phpoption/phpoption/tests/bootstrap.php +8 -0
  51. package/out/backend/_core/vendor/phpoption/phpoption/vendor-bin/phpstan/composer.json +8 -0
  52. package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/Ctype.php +232 -232
  53. package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/LICENSE +19 -19
  54. package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/README.md +12 -12
  55. package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/bootstrap.php +50 -50
  56. package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/bootstrap80.php +46 -46
  57. package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/composer.json +38 -38
  58. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/LICENSE +19 -19
  59. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Mbstring.php +1077 -1077
  60. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/README.md +13 -13
  61. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php +119 -119
  62. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php +1397 -1397
  63. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php +5 -5
  64. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php +1489 -1489
  65. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/bootstrap.php +171 -171
  66. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/bootstrap80.php +167 -167
  67. package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/composer.json +39 -39
  68. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/LICENSE +19 -19
  69. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Php80.php +115 -115
  70. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/PhpToken.php +106 -106
  71. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/README.md +25 -25
  72. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php +31 -31
  73. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php +16 -16
  74. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php +20 -20
  75. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php +16 -16
  76. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php +16 -16
  77. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/bootstrap.php +42 -42
  78. package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/composer.json +37 -37
  79. package/out/backend/_core/vendor/vlucas/phpdotenv/.editorconfig +15 -0
  80. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/LICENSE +30 -30
  81. package/out/backend/_core/vendor/vlucas/phpdotenv/Makefile +17 -0
  82. package/out/backend/_core/vendor/vlucas/phpdotenv/README.md +370 -0
  83. package/out/backend/_core/vendor/vlucas/phpdotenv/UPGRADING.md +196 -0
  84. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/composer.json +60 -60
  85. package/out/backend/_core/vendor/vlucas/phpdotenv/phpstan-baseline.neon +157 -0
  86. package/out/backend/_core/vendor/vlucas/phpdotenv/phpstan.neon.dist +7 -0
  87. package/out/backend/_core/vendor/vlucas/phpdotenv/phpunit.xml.dist +13 -0
  88. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Dotenv.php +267 -267
  89. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php +12 -12
  90. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidEncodingException.php +12 -12
  91. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidFileException.php +12 -12
  92. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidPathException.php +12 -12
  93. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/ValidationException.php +12 -12
  94. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Loader/Loader.php +48 -48
  95. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Loader/LoaderInterface.php +20 -20
  96. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Loader/Resolver.php +65 -65
  97. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Entry.php +59 -59
  98. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php +299 -299
  99. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Lexer.php +58 -58
  100. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Lines.php +127 -127
  101. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Parser.php +53 -53
  102. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/ParserInterface.php +19 -19
  103. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Value.php +88 -88
  104. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/AdapterInterface.php +15 -15
  105. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ApacheAdapter.php +89 -89
  106. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ArrayAdapter.php +80 -80
  107. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php +88 -88
  108. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/GuardedWriter.php +85 -85
  109. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ImmutableWriter.php +110 -110
  110. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiReader.php +48 -48
  111. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiWriter.php +64 -64
  112. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/PutenvAdapter.php +91 -91
  113. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReaderInterface.php +17 -17
  114. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReplacingWriter.php +104 -104
  115. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php +88 -88
  116. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/WriterInterface.php +27 -27
  117. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/AdapterRepository.php +107 -107
  118. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/RepositoryBuilder.php +272 -272
  119. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/RepositoryInterface.php +51 -51
  120. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/File/Paths.php +44 -44
  121. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/File/Reader.php +81 -81
  122. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/FileStore.php +72 -72
  123. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/StoreBuilder.php +141 -141
  124. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/StoreInterface.php +17 -17
  125. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/StringStore.php +37 -37
  126. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Util/Regex.php +112 -112
  127. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Util/Str.php +108 -108
  128. package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Validator.php +207 -207
  129. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/DotenvTest.php +387 -0
  130. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Loader/LoaderTest.php +86 -0
  131. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/EntryParserTest.php +234 -0
  132. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LexerTest.php +40 -0
  133. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LinesTest.php +53 -0
  134. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/ParserTest.php +98 -0
  135. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ArrayAdapterTest.php +57 -0
  136. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/EnvConstAdapterTest.php +75 -0
  137. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/PutenvAdapterTest.php +52 -0
  138. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ServerConstAdapterTest.php +75 -0
  139. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/RepositoryTest.php +305 -0
  140. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Store/StoreTest.php +141 -0
  141. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/ValidatorTest.php +479 -0
  142. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/.env +5 -0
  143. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/assertions.env +18 -0
  144. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/booleans.env +33 -0
  145. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/commented.env +15 -0
  146. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/empty.env +1 -0
  147. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/example.env +1 -0
  148. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/exported.env +7 -0
  149. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/immutable.env +1 -0
  150. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/integers.env +17 -0
  151. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/large.env +2 -0
  152. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multibyte.env +3 -0
  153. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multiline.env +14 -0
  154. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multiple.env +4 -0
  155. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/mutable.env +1 -0
  156. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/nested.env +15 -0
  157. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/quoted.env +11 -0
  158. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/specialchars.env +8 -0
  159. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/unicodevarnames.env +2 -0
  160. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/utf8-with-bom-encoding.env +3 -0
  161. package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/windows.env +1 -0
  162. package/out/backend/_core/vendor/vlucas/phpdotenv/vendor-bin/phpstan/composer.json +15 -0
  163. package/out/backend/api/protected/example-protected.php +17 -0
  164. package/out/backend/api/public/example-public.php +17 -0
  165. package/out/backend/database/Database.php +24 -0
  166. package/out/backend/database/migrations/create_example_db.sql +1 -0
  167. package/out/backend/web.config +17 -0
  168. package/out/css/fonts/bootstrap-icons.woff +0 -0
  169. package/out/css/fonts/bootstrap-icons.woff2 +0 -0
  170. package/out/css/github-markdown-dark.css +1124 -0
  171. package/out/css/github-markdown-light.css +1124 -0
  172. package/out/css/pages/404.css +21517 -0
  173. package/out/css/pages/homepage.css +21508 -0
  174. package/out/data/site.json +44 -0
  175. package/out/index.html +1147 -0
  176. package/out/js/bootstrap.bundle.min.js +7 -0
  177. package/out/js/pages/js/pages/404.js +6 -0
  178. package/out/js/pages/js/pages/homepage.js +6 -0
  179. package/out/js/pages/ts/pages/404.js +6 -0
  180. package/out/js/pages/ts/pages/homepage.js +6 -0
  181. package/out/llms.txt +14 -0
  182. package/out/robots.txt +4 -0
  183. package/out/sitemap.xml +15 -0
  184. package/out/web.config +27 -0
  185. package/package.json +14 -47
  186. package/src/frontend/data/site.json +1 -1
  187. package/.gitignore +0 -4
  188. package/_tools/modules/updateIncludes.js +0 -81
  189. package/src/backend/config.example.php +0 -28
  190. /package/{src → out}/backend/_core/vendor/autoload.php +0 -0
  191. /package/{src → out}/backend/_core/vendor/composer/ClassLoader.php +0 -0
  192. /package/{src → out}/backend/_core/vendor/composer/InstalledVersions.php +0 -0
  193. /package/{src → out}/backend/_core/vendor/composer/LICENSE +0 -0
  194. /package/{src → out}/backend/_core/vendor/composer/autoload_classmap.php +0 -0
  195. /package/{src → out}/backend/_core/vendor/composer/autoload_files.php +0 -0
  196. /package/{src → out}/backend/_core/vendor/composer/autoload_namespaces.php +0 -0
  197. /package/{src → out}/backend/_core/vendor/composer/autoload_psr4.php +0 -0
  198. /package/{src → out}/backend/_core/vendor/composer/autoload_real.php +0 -0
  199. /package/{src → out}/backend/_core/vendor/composer/platform_check.php +0 -0
@@ -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 };
package/bin/create.js CHANGED
@@ -4,6 +4,19 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const readline = require('readline');
6
6
  const { writeSync } = require('fs');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const color = {
10
+ reset: '\x1b[0m',
11
+ bold: '\x1b[1m',
12
+ dim: '\x1b[2m',
13
+ red: '\x1b[31m',
14
+ green: '\x1b[32m',
15
+ yellow: '\x1b[33m',
16
+ blue: '\x1b[34m',
17
+ magenta: '\x1b[35m',
18
+ cyan: '\x1b[36m',
19
+ };
7
20
 
8
21
  // ── PATHS ────────────────────────────────────────────────────────────────────
9
22
 
@@ -134,8 +147,7 @@ const PROJECT_PACKAGE = {
134
147
  "serve:11ty": "eleventy --serve --quiet",
135
148
  "clean": "node _tools/cleanOutput.js",
136
149
  "serve": "npm run clean && concurrently \"npm run serve:11ty\" \"npm run serve:css\" \"npm run serve:js\"",
137
- "assistant": "node _tools/assistant.js",
138
- "postinstall": "cd src/backend/_core && composer install --quiet"
150
+ "assistant": "node _tools/assistant.js"
139
151
  },
140
152
  dependencies: {
141
153
  '@11ty/eleventy': '^3.1.2',
@@ -161,6 +173,10 @@ function log(msg) {
161
173
  writeSync(1, msg + '\n');
162
174
  }
163
175
 
176
+ function logAdd(name) {
177
+ log(`${color.green}+${color.reset} ${name}`);
178
+ }
179
+
164
180
  function escapeRegex(str) {
165
181
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
166
182
  }
@@ -209,6 +225,48 @@ function njkUncomment(content, line) {
209
225
  return content.split(`{# ${line} #}`).join(line);
210
226
  }
211
227
 
228
+ function installDependencies() {
229
+ const backendCore = path.join(targetDir, 'src', 'backend', '_core');
230
+
231
+ log(`${color.blue}\n>> Installing Node modules...${color.reset}`);
232
+ const npm = spawnSync('npm', ['install'], {
233
+ cwd: targetDir,
234
+ stdio: 'inherit',
235
+ shell: process.platform === 'win32',
236
+ });
237
+
238
+ if (npm.status !== 0) {
239
+ log('\n(!) npm install failed. Finish manually:');
240
+ if (process.argv[2]) log(` cd ${process.argv[2]}`);
241
+ log(' npm install\n');
242
+ return false;
243
+ }
244
+
245
+ if (!fs.existsSync(path.join(backendCore, 'composer.json'))) {
246
+ return true;
247
+ }
248
+
249
+ const probe = spawnSync('composer', ['--version'], {
250
+ stdio: 'ignore',
251
+ shell: process.platform === 'win32',
252
+ });
253
+
254
+ if (probe.status !== 0) {
255
+ log('\n(!) Composer not found — skipping backend dependencies.');
256
+ log(' Install Composer, then run: cd src/backend/_core && composer install\n');
257
+ return true;
258
+ }
259
+
260
+ log(`\n${color.blue}>> Installing Composer modules...${color.reset}\n`);
261
+ spawnSync('composer', ['install'], {
262
+ cwd: backendCore,
263
+ stdio: 'inherit',
264
+ shell: process.platform === 'win32',
265
+ });
266
+
267
+ return true;
268
+ }
269
+
212
270
  // ── APPLY ─────────────────────────────────────────────────────────────────────
213
271
 
214
272
  function applyFramework(framework) {
@@ -313,25 +371,27 @@ function askChoice(question, choices) {
313
371
  async function init() {
314
372
  if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
315
373
 
316
- log(`\n>> Creating berna-stencil project in ${targetDir}\n`);
374
+ log(`\n>> ${color.magenta}Creating berna-stencil project in ${targetDir}\n${color.reset}`);
317
375
 
318
376
  const language = await askChoice('Select a language', LANGUAGE_CHOICES);
319
377
  const framework = await askChoice('Select a CSS framework', FRAMEWORK_CHOICES);
320
378
 
379
+ log('');
380
+
321
381
  for (const target of MANDATORY_COPY) {
322
382
  const src = path.join(templateDir, target);
323
383
  const dest = path.join(targetDir, target);
324
384
  if (!fs.existsSync(src)) continue;
325
385
  const exclude = target === 'src/frontend' ? FRONTEND_EXCLUDE[language] : [];
326
386
  copyRecursive(src, dest, exclude);
327
- log(`+ ${target}`);
387
+ logAdd(target);
328
388
  }
329
389
 
330
390
  const configDest = path.join(targetDir, 'src/backend/config.php');
331
391
  const configExample = path.join(targetDir, 'src/backend/config.example.php');
332
392
  if (!fs.existsSync(configDest) && fs.existsSync(configExample)) {
333
393
  fs.copyFileSync(configExample, configDest);
334
- log('+ src/backend/config.php');
394
+ logAdd('src/backend/config.php');
335
395
  }
336
396
  deleteFileRecursive(targetDir, 'config.example.php');
337
397
 
@@ -341,15 +401,15 @@ async function init() {
341
401
  const tsSrc = path.join(templateDir, 'tsconfig.json');
342
402
  const tsDest = path.join(targetDir, 'tsconfig.json');
343
403
  fs.copyFileSync(tsSrc, tsDest);
344
- log('+ tsconfig.json');
404
+ logAdd('tsconfig.json');
345
405
  pkg.devDependencies = { ...pkg.devDependencies, typescript: 'latest' };
346
406
  }
347
407
 
348
408
  fs.writeFileSync(path.join(targetDir, 'package.json'), JSON.stringify(pkg, null, 2));
349
- log('+ package.json');
409
+ logAdd('package.json');
350
410
 
351
411
  fs.writeFileSync(path.join(targetDir, '.gitignore'), GITIGNORE_CONTENT);
352
- log('+ .gitignore');
412
+ logAdd('.gitignore');
353
413
 
354
414
  for (const dir of CREATE_DIRS) {
355
415
  fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
@@ -358,10 +418,12 @@ async function init() {
358
418
  applyFramework(framework);
359
419
  applyLanguage(language);
360
420
 
361
- log(`\n>> Done! Now run:\n`);
362
- if (process.argv[2]) log(`cd ${process.argv[2]}`);
363
- log('npm install');
364
- log('npm run serve\n');
421
+ installDependencies();
422
+
423
+ log(`\n${color.green}>> Done!${color.reset}`);
424
+ log(`${color.yellow}\nNow run:\n${color.reset}`);
425
+ if (process.argv[2]) log(` ${color.yellow}cd ${process.argv[2]}${color.reset}`);
426
+ log(` ${color.yellow}npm run serve${color.reset}\n`);
365
427
  }
366
428
 
367
429
  init();
package/out/.htaccess ADDED
@@ -0,0 +1,16 @@
1
+ Options -Indexes
2
+ ErrorDocument 404 /404.html
3
+ ErrorDocument 403 /404.html
4
+
5
+ <IfModule mod_rewrite.c>
6
+ RewriteEngine On
7
+ RewriteBase /
8
+
9
+ RewriteRule ^(web\.config|llms\.txt|robots\.txt|sitemap\.xml|\..*)$ /404.html [L,NC]
10
+
11
+ RewriteRule ^api/(.*)$ backend/_core/index.php [QSA,L]
12
+
13
+ RewriteCond %{REQUEST_FILENAME} -f [OR]
14
+ RewriteCond %{REQUEST_FILENAME} -d
15
+ RewriteRule ^ - [L]
16
+ </IfModule>
package/out/404.html ADDED
@@ -0,0 +1,163 @@
1
+ <!-- This is the base of every page that you will create -->
2
+
3
+ <!DOCTYPE html>
4
+ <html lang="en" data-bs-theme="dark">
5
+
6
+ <head>
7
+ <meta charset="UTF-8">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
+
10
+
11
+ <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+ <link rel="canonical" href="https://yoursite.com/404.html"/>
21
+ <title>404 - Not found</title>
22
+ <meta name="description" content="Site description">
23
+ <meta name="keywords" content="keyword1, keyword2, keyword3">
24
+ <meta name="author" content="Name and surname">
25
+ <meta name="theme-color" content="">
26
+
27
+
28
+ <meta property="og:title" content="404 - Not found">
29
+ <meta property="og:description" content="">
30
+ <meta property="og:type" content="website">
31
+ <meta property="og:url" content="https://yoursite.com/404.html">
32
+ <meta property="og:image" content="https://yoursite.com/assets/brand/logo.svg">
33
+ <meta property="og:site-name" content="NaN">
34
+
35
+
36
+ <meta name="twitter:card" content="summary_large_image">
37
+ <meta name="twitter:title" content="404 - Not found">
38
+ <meta name="twitter:description" content="">
39
+ <meta name="twitter:image" content="https://yoursite.com/assets/brand/logo.svg">
40
+
41
+
42
+
43
+ <script type="application/ld+json">
44
+ {
45
+ "@context": "https://schema.org",
46
+ "@type": "WebPage",
47
+ "name": "404 - Not found",
48
+ "description": "Site description",
49
+ "url": "https://yoursite.com/404.html",
50
+ "author": {
51
+ "@type": "Person",
52
+ "name": "Name and surname"
53
+ },
54
+ "publisher": {
55
+ "@type": "Organization",
56
+ "name": "Site name",
57
+ "logo": {
58
+ "@type": "ImageObject",
59
+ "url": "https://yoursite.com/assets/brand/logo.svg"
60
+ }
61
+ }
62
+ }
63
+ </script>
64
+
65
+
66
+
67
+ <link rel="icon" type="image/svg+xml" href="/assets/brand/favicon.svg">
68
+
69
+
70
+
71
+
72
+
73
+
74
+ <link rel="stylesheet" href="/css/github-markdown-dark.css">
75
+
76
+
77
+
78
+ <link rel="stylesheet" href="/css/pages/404.css"/>
79
+
80
+
81
+
82
+
83
+
84
+
85
+ </head>
86
+
87
+ <body>
88
+
89
+ <header>
90
+ <nav>
91
+ <a class="logo" href="/">
92
+ <img src="/assets/brand/logo.svg" alt="Site title" height="40">
93
+ </a>
94
+ <div class="nav-links">
95
+ <a class="btn" href="/">Homepage</a>
96
+ <a class="btn" href="/example-page">Example page</a>
97
+ </div>
98
+ </nav>
99
+ </header>
100
+
101
+ <main>
102
+
103
+ <!-- !IMPORTANT -->
104
+ <!-- This is the only page that you need to modify statically -->
105
+
106
+ <div class="fade-in not-found">
107
+ <h1>Oops! Page not found</h1>
108
+ <a href="/">Return to homepage</a>
109
+ </div>
110
+
111
+
112
+
113
+
114
+ </main>
115
+
116
+ <footer>
117
+ <div>
118
+ <h6>Site title</h6>
119
+ <p>Site description</p>
120
+ </div>
121
+
122
+ <div>
123
+ <h6>Sitemap</h6>
124
+ <ul>
125
+ <li><a href="/">Homepage</a></li>
126
+ </ul>
127
+ </div>
128
+ <div>
129
+ &copy; 2026 Copyright text
130
+ </div>
131
+ <div>
132
+ <h6>Legal</h6>
133
+ <small>
134
+ <ul>
135
+ <li><a href="">Privacy Policy</a></li>
136
+ <li><a href="">Cookie Policy</a></li>
137
+ <li><a href="">Terms and conditions</a></li>
138
+ </ul>
139
+ </small>
140
+ </div>
141
+ </div>
142
+ </footer>
143
+
144
+
145
+
146
+
147
+ <script src="/js/bootstrap.bundle.min.js" defer></script>
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+ <script src="/js/pages/404.js"></script>
160
+
161
+ </body>
162
+
163
+ </html>