create-berna-stencil 2.3.0 → 2.4.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.
- package/.eleventy.js +1 -0
- package/README.md +1 -1
- package/_tools/modules/updateIncludes.js +16 -13
- package/_tools/res/templates/template.js +0 -8
- package/_tools/res/templates/template.ts +0 -8
- package/bin/create.js +1 -1
- package/docs/Backend.md +8 -2
- package/docs/Deploy.md +55 -0
- package/docs/Javascript.md +3 -13
- package/package.json +1 -1
- package/src/backend/config.php +2 -2
- package/src/frontend/components/welcome.njk +7 -0
- package/src/frontend/js/modules/exampleModule.js +1 -5
- package/src/frontend/js/pages/404.js +3 -9
- package/src/frontend/js/pages/homepage.js +3 -9
- package/src/frontend/scss/modules/_global.scss +1 -1
- package/src/frontend/scss/modules/_header.scss +0 -3
- package/src/frontend/ts/modules/exampleModule.ts +1 -1
- package/src/frontend/ts/pages/404.ts +3 -9
- package/src/frontend/ts/pages/homepage.ts +3 -9
package/.eleventy.js
CHANGED
|
@@ -43,6 +43,7 @@ module.exports = function (eleventyConfig) {
|
|
|
43
43
|
eleventyConfig.addPassthroughCopy("src/frontend/.htaccess");
|
|
44
44
|
eleventyConfig.addPassthroughCopy("src/frontend/web.config");
|
|
45
45
|
eleventyConfig.addPassthroughCopy("src/frontend/assets");
|
|
46
|
+
eleventyConfig.addPassthroughCopy("src/frontend/data");
|
|
46
47
|
eleventyConfig.addPassthroughCopy("src/frontend/robots.txt");
|
|
47
48
|
|
|
48
49
|
eleventyConfig.addPassthroughCopy({
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Building a website from scratch involves a lot of moving parts: templating engin
|
|
|
12
12
|
- 📁 **Scalable structure** — a clean, opinionated project layout that grows with your needs
|
|
13
13
|
- 🌍 **Open source** — free to use, free to modify, free to share
|
|
14
14
|
|
|
15
|
-

|
|
16
16
|

|
|
17
17
|

|
|
18
18
|
|
|
@@ -5,21 +5,15 @@ const INCLUDES_PATH = 'src/frontend/components/layouts/includes.njk';
|
|
|
5
5
|
|
|
6
6
|
// --- Helpers ---
|
|
7
7
|
|
|
8
|
-
// Returns the file content as a string, or null if the file doesn't exist
|
|
9
8
|
function readIncludes() {
|
|
10
9
|
if (!fileSystem.existsSync(INCLUDES_PATH)) return null;
|
|
11
10
|
return fileSystem.readFileSync(INCLUDES_PATH, 'utf8');
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
// Writes updated content back to the includes file
|
|
15
13
|
function writeIncludes(content) {
|
|
16
14
|
fileSystem.writeFileSync(INCLUDES_PATH, content);
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
// Builds the regex that matches the elif block for a given camelCase page name.
|
|
20
|
-
// Defined once here to avoid duplication and the stateful /g flag bug:
|
|
21
|
-
// using /g with .test() advances lastIndex, making a subsequent .replace() start
|
|
22
|
-
// from the wrong position. A fresh non-/g regex avoids this entirely.
|
|
23
17
|
function buildElifRegex(camelName) {
|
|
24
18
|
return new RegExp(
|
|
25
19
|
`[ \\t]*\\{%\\s*elif\\s+title\\s*==\\s*"${camelName}"\\s*%\\}[\\s\\S]*?(?=[ \\t]*\\{%\\s*(?:elif|else|endif))`,
|
|
@@ -28,14 +22,12 @@ function buildElifRegex(camelName) {
|
|
|
28
22
|
|
|
29
23
|
// --- Public API ---
|
|
30
24
|
|
|
31
|
-
// Inserts a new elif block before {% else %} for the given page
|
|
32
25
|
function addLayout(pageName) {
|
|
33
26
|
const content = readIncludes();
|
|
34
27
|
if (!content) return;
|
|
35
28
|
|
|
36
29
|
const camelName = toCamelCase(pageName);
|
|
37
30
|
|
|
38
|
-
// Skip if the block already exists
|
|
39
31
|
if (content.includes(`{% elif title == "${camelName}" %}`)) return;
|
|
40
32
|
|
|
41
33
|
const newElif =
|
|
@@ -47,7 +39,6 @@ function addLayout(pageName) {
|
|
|
47
39
|
console.log(`[UPDATED] Layout block added for "${camelName}".`);
|
|
48
40
|
}
|
|
49
41
|
|
|
50
|
-
// Removes the elif block for the given page, then collapses extra blank lines
|
|
51
42
|
function removeLayout(pageName) {
|
|
52
43
|
const content = readIncludes();
|
|
53
44
|
if (!content) return;
|
|
@@ -60,7 +51,6 @@ function removeLayout(pageName) {
|
|
|
60
51
|
return;
|
|
61
52
|
}
|
|
62
53
|
|
|
63
|
-
// Build a fresh regex instance for replace to avoid stale lastIndex
|
|
64
54
|
const updated = content
|
|
65
55
|
.replace(buildElifRegex(camelName), '')
|
|
66
56
|
.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
@@ -69,10 +59,23 @@ function removeLayout(pageName) {
|
|
|
69
59
|
console.log(`[CLEANED] Layout block removed for "${camelName}".`);
|
|
70
60
|
}
|
|
71
61
|
|
|
72
|
-
// Renames a layout block by removing the old one and inserting a new one
|
|
73
62
|
function renameLayout(oldName, newName) {
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
const content = readIncludes();
|
|
64
|
+
if (!content) return;
|
|
65
|
+
|
|
66
|
+
const oldCamel = toCamelCase(oldName);
|
|
67
|
+
const newCamel = toCamelCase(newName);
|
|
68
|
+
|
|
69
|
+
const oldLine = `{% elif title == "${oldCamel}" %}`;
|
|
70
|
+
const newLine = `{% elif title == "${newCamel}" %}`;
|
|
71
|
+
|
|
72
|
+
if (!content.includes(oldLine)) {
|
|
73
|
+
console.log(`[DEBUG] Layout block for "${oldCamel}" not found. Skipped.`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
writeIncludes(content.replace(oldLine, newLine));
|
|
78
|
+
console.log(`[RENAMED] Layout block renamed from "${oldCamel}" to "${newCamel}".`);
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
module.exports = { addLayout, removeLayout, renameLayout };
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
//===========================
|
|
2
|
-
// JAVASCRIPT MODULES IMPORTS
|
|
3
|
-
//===========================
|
|
4
|
-
|
|
5
1
|
// import { initExampleModule } from '../modules/exampleModule.js';
|
|
6
2
|
|
|
7
|
-
//==========================
|
|
8
|
-
// PAGE CUSTOM JAVASCRIPT
|
|
9
|
-
//==========================
|
|
10
|
-
|
|
11
3
|
document.addEventListener("DOMContentLoaded", () => {
|
|
12
4
|
// initExampleModule();
|
|
13
5
|
});
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
//===========================
|
|
2
|
-
// TYPESCRIPT MODULES IMPORTS
|
|
3
|
-
//===========================
|
|
4
|
-
|
|
5
1
|
// import { initExampleModule } from '../modules/exampleModule';
|
|
6
2
|
|
|
7
|
-
//==========================
|
|
8
|
-
// PAGE CUSTOM TYPESCRIPT
|
|
9
|
-
//==========================
|
|
10
|
-
|
|
11
3
|
document.addEventListener("DOMContentLoaded", (): void => {
|
|
12
4
|
// initExampleModule();
|
|
13
5
|
});
|
package/bin/create.js
CHANGED
package/docs/Backend.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Backend
|
|
2
2
|
|
|
3
|
+
> **PHP requirement** — the backend runs only on servers with PHP support:
|
|
4
|
+
> - **Apache** (shared hosting, VPS) ✅
|
|
5
|
+
> - **Nginx** + PHP-FPM (VPS) ✅
|
|
6
|
+
> - **IIS** + FastCGI (Windows Server) ✅
|
|
7
|
+
> - **Static hosting** (Netlify, Vercel, GitHub Pages, Cloudflare Pages) ❌
|
|
8
|
+
|
|
3
9
|
The backend is a PHP REST API located in `src/backend/`, copied to the output directory automatically at build time.
|
|
4
10
|
|
|
5
11
|
## Structure
|
|
@@ -30,9 +36,9 @@ return [
|
|
|
30
36
|
'API_KEY' => 'default-key',
|
|
31
37
|
|
|
32
38
|
// If you want restrict access to protected endpoints to specific clients, you can define custom keys for each endpoint
|
|
33
|
-
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
34
39
|
'ENDPOINT_KEYS' => [
|
|
35
|
-
|
|
40
|
+
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
41
|
+
// 'subfolder/endpoint' => 'example-key',
|
|
36
42
|
],
|
|
37
43
|
|
|
38
44
|
'DB_HOST' => '127.0.0.1',
|
package/docs/Deploy.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Server Configuration
|
|
2
|
+
|
|
3
|
+
Berna-Stencil includes ready-made server configuration for Apache and IIS. For Nginx, a reference config is provided in the project root.
|
|
4
|
+
|
|
5
|
+
## Apache
|
|
6
|
+
|
|
7
|
+
`.htaccess` files at `src/frontend/` and `src/backend/` are automatically copied to the build output by Eleventy. No additional setup required.
|
|
8
|
+
|
|
9
|
+
Covers:
|
|
10
|
+
- Directory listing disabled
|
|
11
|
+
- 403 / 404 → `/404.html`
|
|
12
|
+
- Sensitive files blocked (`web.config`, dotfiles, etc.)
|
|
13
|
+
- `/api/*` → `backend/_core/index.php`
|
|
14
|
+
- HTTPS redirect
|
|
15
|
+
|
|
16
|
+
## IIS
|
|
17
|
+
|
|
18
|
+
`web.config` files at `src/frontend/` and `src/backend/` are automatically copied to the build output by Eleventy. No additional setup required.
|
|
19
|
+
|
|
20
|
+
Covers the same rules as the Apache configuration above.
|
|
21
|
+
|
|
22
|
+
## Nginx
|
|
23
|
+
|
|
24
|
+
Nginx does not support per-directory configuration files. The `nginx.conf` in the project root is a reference config that must be manually placed on the server.
|
|
25
|
+
|
|
26
|
+
### Setup
|
|
27
|
+
|
|
28
|
+
1. Copy `nginx.conf` to the server:
|
|
29
|
+
```bash
|
|
30
|
+
scp nginx.conf user@server:/etc/nginx/sites-available/your-site
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Edit `server_name` and `root` to match your environment:
|
|
34
|
+
```nginx
|
|
35
|
+
server_name example.com;
|
|
36
|
+
root /var/www/html/out;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. Add your SSL certificate paths.
|
|
40
|
+
|
|
41
|
+
4. Enable and reload:
|
|
42
|
+
```bash
|
|
43
|
+
ln -s /etc/nginx/sites-available/your-site /etc/nginx/sites-enabled/
|
|
44
|
+
nginx -t && systemctl reload nginx
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### PHP-FPM socket
|
|
48
|
+
|
|
49
|
+
The default socket path in `nginx.conf` targets RHEL / Fedora systems. Adjust for your distro:
|
|
50
|
+
|
|
51
|
+
| Distro | Path |
|
|
52
|
+
|---|---|
|
|
53
|
+
| RHEL / Fedora | `unix:/run/php-fpm/php-fpm.sock` |
|
|
54
|
+
| Debian / Ubuntu | `unix:/run/php/php-fpm.sock` |
|
|
55
|
+
| TCP fallback | `127.0.0.1:9000` |
|
package/docs/Javascript.md
CHANGED
|
@@ -11,19 +11,13 @@ Import only what the page needs.
|
|
|
11
11
|
### examplePage.js <small>(`src/frontend/js/pages/`)</small>
|
|
12
12
|
|
|
13
13
|
```js
|
|
14
|
-
//===========================
|
|
15
|
-
// JAVASCRIPT MODULES IMPORTS
|
|
16
|
-
//===========================
|
|
17
|
-
|
|
18
14
|
// import { initExampleModule } from '../modules/exampleModule.js';
|
|
19
15
|
|
|
20
|
-
//==========================
|
|
21
|
-
// PAGE CUSTOM JAVASCRIPT
|
|
22
|
-
//==========================
|
|
23
|
-
|
|
24
16
|
document.addEventListener("DOMContentLoaded", () => {
|
|
25
17
|
// initExampleModule();
|
|
26
18
|
});
|
|
19
|
+
|
|
20
|
+
// Page logic here
|
|
27
21
|
```
|
|
28
22
|
|
|
29
23
|
## Modules
|
|
@@ -39,12 +33,8 @@ Use ESM syntax — esbuild handles the bundling:
|
|
|
39
33
|
### exampleModule.js <small>(`src/frontend/js/modules/`)</small>
|
|
40
34
|
|
|
41
35
|
```js
|
|
42
|
-
//==========================
|
|
43
|
-
// EXAMPLE MODULE
|
|
44
|
-
//==========================
|
|
45
|
-
|
|
46
36
|
export function exampleModule() {
|
|
47
|
-
//
|
|
37
|
+
// Module logic here
|
|
48
38
|
}
|
|
49
39
|
```
|
|
50
40
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-berna-stencil",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Eleventy boilerplate with per-page SCSS/JS pipeline, esbuild bundling, multi-framework CSS support and a built-in page management CLI",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "Michele Garofalo",
|
package/src/backend/config.php
CHANGED
|
@@ -6,9 +6,9 @@ return [
|
|
|
6
6
|
'API_KEY' => 'DEFAULT_KEY',
|
|
7
7
|
|
|
8
8
|
// If you want restrict access to protected endpoints to specific clients, you can define custom keys for each endpoint
|
|
9
|
-
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
10
9
|
'ENDPOINT_KEYS' => [
|
|
11
|
-
'subfolder/
|
|
10
|
+
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
11
|
+
// 'subfolder/endpoint' => 'example-key',
|
|
12
12
|
],
|
|
13
13
|
|
|
14
14
|
// Database configuration
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
<input type="radio" name="guide-filter" value="backend" />
|
|
36
36
|
<span class="filter-pill">Backend</span>
|
|
37
37
|
</label>
|
|
38
|
+
<label>
|
|
39
|
+
<input type="radio" name="guide-filter" value="deploy" />
|
|
40
|
+
<span class="filter-pill">Deploy</span>
|
|
41
|
+
</label>
|
|
38
42
|
</div>
|
|
39
43
|
|
|
40
44
|
<div class="tabs-container">
|
|
@@ -90,6 +94,9 @@
|
|
|
90
94
|
<div id="content-backend" class="tab-content markdown-body">
|
|
91
95
|
{% mdFile "docs/backend.md" %}
|
|
92
96
|
</div>
|
|
97
|
+
<div id="content-deploy" class="tab-content markdown-body">
|
|
98
|
+
{% mdFile "docs/deploy.md" %}
|
|
99
|
+
</div>
|
|
93
100
|
|
|
94
101
|
</div>
|
|
95
102
|
</div>
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
//===========================
|
|
2
|
-
// JAVASCRIPT MODULES IMPORTS
|
|
3
|
-
//===========================
|
|
4
|
-
|
|
5
1
|
// import { initExampleModule } from '../modules/exampleModule.js';
|
|
6
2
|
|
|
7
|
-
//==========================
|
|
8
|
-
// PAGE CUSTOM JAVASCRIPT
|
|
9
|
-
//==========================
|
|
10
|
-
|
|
11
3
|
document.addEventListener("DOMContentLoaded", () => {
|
|
12
4
|
// initExampleModule();
|
|
13
|
-
});
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
// Page logic here
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
//===========================
|
|
2
|
-
// JAVASCRIPT MODULES IMPORTS
|
|
3
|
-
//===========================
|
|
4
|
-
|
|
5
1
|
// import { initExampleModule } from '../modules/exampleModule.js';
|
|
6
2
|
|
|
7
|
-
//==========================
|
|
8
|
-
// PAGE CUSTOM JAVASCRIPT
|
|
9
|
-
//==========================
|
|
10
|
-
|
|
11
3
|
document.addEventListener("DOMContentLoaded", () => {
|
|
12
4
|
// initExampleModule();
|
|
13
|
-
});
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
// Page logic here
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
//===========================
|
|
2
|
-
// TYPESCRIPT MODULES IMPORTS
|
|
3
|
-
//===========================
|
|
4
|
-
|
|
5
1
|
// import { initExampleModule } from '../modules/exampleModule';
|
|
6
2
|
|
|
7
|
-
//==========================
|
|
8
|
-
// PAGE CUSTOM TYPESCRIPT
|
|
9
|
-
//==========================
|
|
10
|
-
|
|
11
3
|
document.addEventListener("DOMContentLoaded", (): void => {
|
|
12
4
|
// initExampleModule();
|
|
13
|
-
});
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
// Page logic here
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
//===========================
|
|
2
|
-
// TYPESCRIPT MODULES IMPORTS
|
|
3
|
-
//===========================
|
|
4
|
-
|
|
5
1
|
// import { initExampleModule } from '../modules/exampleModule';
|
|
6
2
|
|
|
7
|
-
//==========================
|
|
8
|
-
// PAGE CUSTOM TYPESCRIPT
|
|
9
|
-
//==========================
|
|
10
|
-
|
|
11
3
|
document.addEventListener("DOMContentLoaded", (): void => {
|
|
12
4
|
// initExampleModule();
|
|
13
|
-
});
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
// Page logic here
|