create-berna-stencil 1.0.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 +81 -0
- package/.eleventyignore +4 -0
- package/.gitignore +2 -0
- package/README.md +31 -0
- package/bin/create.js +96 -0
- package/package.json +71 -0
- package/src/404.njk +16 -0
- package/src/_routes/another-page.njk +9 -0
- package/src/api/configExample.php +28 -0
- package/src/api/sendEmail.php +131 -0
- package/src/assets/brand/favicon.svg +37 -0
- package/src/assets/brand/logo.svg +37 -0
- package/src/components/exampleComponent.njk +12 -0
- package/src/components/global/footer.njk +32 -0
- package/src/components/global/header.njk +15 -0
- package/src/components/layouts/base.njk +100 -0
- package/src/components/layouts/includes.njk +16 -0
- package/src/data/lang.json +32 -0
- package/src/data/site.json +54 -0
- package/src/index.njk +9 -0
- package/src/js/modules/forms/form.js +45 -0
- package/src/js/modules/forms/normalizePhoneNumber.js +42 -0
- package/src/js/modules/forms/textAreaAutoExpand.js +38 -0
- package/src/js/modules/langSwitcher.js +62 -0
- package/src/js/modules/notification.js +39 -0
- package/src/js/pages/404.js +23 -0
- package/src/js/pages/anotherPage.js +23 -0
- package/src/js/pages/homepage.js +25 -0
- package/src/robots.txt +4 -0
- package/src/scss/modules/_animations.scss +25 -0
- package/src/scss/modules/_footer.scss +12 -0
- package/src/scss/modules/_global.scss +40 -0
- package/src/scss/modules/_header.scss +32 -0
- package/src/scss/modules/_mobile.scss +30 -0
- package/src/scss/modules/_notification.scss +56 -0
- package/src/scss/modules/_root.scss +37 -0
- package/src/scss/modules/_typography.scss +15 -0
- package/src/scss/modules/frameworks/_bootstrap.scss +111 -0
- package/src/scss/modules/frameworks/_bulma.scss +110 -0
- package/src/scss/modules/frameworks/_foundation.scss +140 -0
- package/src/scss/modules/frameworks/_uikit.scss +111 -0
- package/src/scss/pages/404.scss +20 -0
- package/src/scss/pages/anotherPage.scss +21 -0
- package/src/scss/pages/homepage.scss +21 -0
- package/src/sitemap.njk +17 -0
- package/tools/assistant.js +127 -0
- package/tools/cleanOutput.js +24 -0
- package/tools/modules/updateData.js +58 -0
- package/tools/modules/updateIncludes.js +45 -0
- package/tools/modules/updateOutputPath.js +91 -0
- package/tools/modules/updatePage.js +150 -0
- package/tools/res/templates.json +56 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// CSS MODULES IMPORTS
|
|
3
|
+
//==========================
|
|
4
|
+
// Use @use with a namespace to avoid framework variable conflicts:
|
|
5
|
+
@use "../modules/root" as root;
|
|
6
|
+
|
|
7
|
+
// Page layout modules — comment out any section you don't need
|
|
8
|
+
@import "../modules/global";
|
|
9
|
+
// Import any other module you need by import down here
|
|
10
|
+
@import "../modules/notification";
|
|
11
|
+
|
|
12
|
+
//==========================
|
|
13
|
+
// PAGE CUSTOM CSS RULES FOR PAGE: 404
|
|
14
|
+
//==========================
|
|
15
|
+
|
|
16
|
+
// Add any custom rule specific to this page below
|
|
17
|
+
// These rules override the framework and module styles
|
|
18
|
+
// body {
|
|
19
|
+
// background-color: root.$primary;
|
|
20
|
+
// }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// CSS MODULES IMPORTS
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Use @use with a namespace to avoid framework variable conflicts:
|
|
6
|
+
@use "../modules/root" as root;
|
|
7
|
+
|
|
8
|
+
// Page layout modules — comment out any section you don't need
|
|
9
|
+
@import "../modules/global";
|
|
10
|
+
// Import any other module you need by import down here
|
|
11
|
+
@import "../modules/notification";
|
|
12
|
+
|
|
13
|
+
//==========================
|
|
14
|
+
// PAGE CUSTOM CSS RULES FOR PAGE: another-page
|
|
15
|
+
//==========================
|
|
16
|
+
|
|
17
|
+
// Add any custom rule specific to this page below
|
|
18
|
+
// These rules override the framework and module styles
|
|
19
|
+
// body {
|
|
20
|
+
// background-color: root.$primary;
|
|
21
|
+
// }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// CSS MODULES IMPORTS
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Use @use with a namespace to avoid framework variable conflicts:
|
|
6
|
+
@use "../modules/root" as root;
|
|
7
|
+
|
|
8
|
+
// Page layout modules — comment out any section you don't need
|
|
9
|
+
@import "../modules/global";
|
|
10
|
+
// Import any other module you need by import down here
|
|
11
|
+
@import "../modules/notification";
|
|
12
|
+
|
|
13
|
+
//==========================
|
|
14
|
+
// PAGE CUSTOM CSS RULES FOR PAGE: homepage
|
|
15
|
+
//==========================
|
|
16
|
+
|
|
17
|
+
// Add any custom rule specific to this page below
|
|
18
|
+
// These rules override the framework and module styles
|
|
19
|
+
// body {
|
|
20
|
+
// background-color: root.$primary;
|
|
21
|
+
// }
|
package/src/sitemap.njk
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
permalink: /sitemap.xml
|
|
3
|
+
eleventyExcludeFromCollections: true
|
|
4
|
+
---
|
|
5
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
6
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
7
|
+
{% for page in collections.all %}
|
|
8
|
+
{% if not page.data.draft and '/404' not in page.url %}
|
|
9
|
+
<url>
|
|
10
|
+
<loc>{{ site.url }}{{ page.url | lower }}</loc>
|
|
11
|
+
<lastmod>{{ page.date.toISOString() }}</lastmod>
|
|
12
|
+
<changefreq>weekly</changefreq>
|
|
13
|
+
<priority>{{ '1.0' if page.url == '/' else '0.8' }}</priority>
|
|
14
|
+
</url>
|
|
15
|
+
{% endif %}
|
|
16
|
+
{% endfor %}
|
|
17
|
+
</urlset>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const { addPage, removePage, renamePage } = require('./modules/updatePage');
|
|
3
|
+
const { updateOutputPath, getCurrentOutputPath } = require('./modules/updateOutputPath');
|
|
4
|
+
|
|
5
|
+
const readerInterface = readline.createInterface({
|
|
6
|
+
input: process.stdin,
|
|
7
|
+
output: process.stdout,
|
|
8
|
+
terminal: true
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const PROTECTED_PAGES = ['homepage', '404'];
|
|
12
|
+
|
|
13
|
+
function toKebabCase(str) {
|
|
14
|
+
return str.trim().toLowerCase()
|
|
15
|
+
.replace(/[\s_]+/g, '-')
|
|
16
|
+
.replace(/-+/g, '-');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function handleCreateRequest() {
|
|
20
|
+
readerInterface.question('\n> enter the name of the new page: ', (inputName) => {
|
|
21
|
+
const kebabName = toKebabCase(inputName);
|
|
22
|
+
if (!kebabName) {
|
|
23
|
+
console.log('(!) invalid name.');
|
|
24
|
+
} else if (/^\d/.test(kebabName)) {
|
|
25
|
+
console.log('(!) invalid name. Page name cannot start with a number.');
|
|
26
|
+
} else if (PROTECTED_PAGES.includes(kebabName)) {
|
|
27
|
+
console.log(`(!) "${kebabName}" is a protected page and cannot be created.`);
|
|
28
|
+
} else {
|
|
29
|
+
readerInterface.question('> parent path (leave empty for root): ', (inputParent) => {
|
|
30
|
+
const parentPath = inputParent.trim().toLowerCase().replace(/\\/g, '/').replace(/^\/|\/$/g, '');
|
|
31
|
+
addPage(kebabName, parentPath || null);
|
|
32
|
+
displayMainMenu();
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
displayMainMenu();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleRenameRequest() {
|
|
41
|
+
readerInterface.question('\n> enter the name of the page to rename: ', (inputOld) => {
|
|
42
|
+
const oldName = toKebabCase(inputOld);
|
|
43
|
+
if (!oldName) {
|
|
44
|
+
console.log('(!) invalid name.');
|
|
45
|
+
return displayMainMenu();
|
|
46
|
+
}
|
|
47
|
+
if (PROTECTED_PAGES.includes(oldName)) {
|
|
48
|
+
console.log(`(!) "${oldName}" is a protected page and cannot be renamed.`);
|
|
49
|
+
return displayMainMenu();
|
|
50
|
+
}
|
|
51
|
+
readerInterface.question('> enter the new name: ', (inputNew) => {
|
|
52
|
+
const newName = toKebabCase(inputNew);
|
|
53
|
+
if (!newName) {
|
|
54
|
+
console.log('(!) invalid name.');
|
|
55
|
+
} else if (/^\d/.test(newName)) {
|
|
56
|
+
console.log('(!) invalid name. Page name cannot start with a number.');
|
|
57
|
+
} else if (PROTECTED_PAGES.includes(newName)) {
|
|
58
|
+
console.log(`(!) "${newName}" is a protected page name.`);
|
|
59
|
+
} else if (oldName === newName) {
|
|
60
|
+
console.log('(!) old and new name are the same.');
|
|
61
|
+
} else {
|
|
62
|
+
renamePage(oldName, newName);
|
|
63
|
+
}
|
|
64
|
+
displayMainMenu();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleRemoveRequest() {
|
|
70
|
+
readerInterface.question('\n> enter the name of the page to remove: ', (inputName) => {
|
|
71
|
+
const kebabName = toKebabCase(inputName);
|
|
72
|
+
if (!kebabName) {
|
|
73
|
+
console.log('(!) invalid name.');
|
|
74
|
+
} else if (PROTECTED_PAGES.includes(kebabName)) {
|
|
75
|
+
console.log(`(!) "${kebabName}" is a protected page and cannot be removed.`);
|
|
76
|
+
} else {
|
|
77
|
+
removePage(kebabName);
|
|
78
|
+
}
|
|
79
|
+
displayMainMenu();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleOutputPathRequest() {
|
|
84
|
+
const current = getCurrentOutputPath();
|
|
85
|
+
const currentLabel = current ? ` (current: "${current}")` : '';
|
|
86
|
+
|
|
87
|
+
readerInterface.question(`\n> enter the new output path${currentLabel}\n (e.g. C:/laragon/www or . for root): `, (inputPath) => {
|
|
88
|
+
if (!inputPath.trim()) {
|
|
89
|
+
console.log('(!) invalid path.');
|
|
90
|
+
} else {
|
|
91
|
+
updateOutputPath(inputPath);
|
|
92
|
+
}
|
|
93
|
+
displayMainMenu();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function displayMainMenu() {
|
|
98
|
+
console.log('\n========================');
|
|
99
|
+
console.log(' Berna-Stencil CLI ');
|
|
100
|
+
console.log('========================\n');
|
|
101
|
+
console.log('1. Create page');
|
|
102
|
+
console.log('2. Rename page');
|
|
103
|
+
console.log('3. Remove page');
|
|
104
|
+
console.log('4. Configure output path');
|
|
105
|
+
console.log('\nCTRL/CMD + C to exit');
|
|
106
|
+
|
|
107
|
+
readerInterface.question('\nChoose an option: ', (choice) => {
|
|
108
|
+
const cleanChoice = choice.trim();
|
|
109
|
+
if (cleanChoice === '1') {
|
|
110
|
+
handleCreateRequest();
|
|
111
|
+
} else if (cleanChoice === '2') {
|
|
112
|
+
handleRenameRequest();
|
|
113
|
+
} else if (cleanChoice === '3') {
|
|
114
|
+
handleRemoveRequest();
|
|
115
|
+
} else if (cleanChoice === '4') {
|
|
116
|
+
handleOutputPathRequest();
|
|
117
|
+
} else if (cleanChoice === '0') {
|
|
118
|
+
readerInterface.close();
|
|
119
|
+
process.exit(0);
|
|
120
|
+
} else {
|
|
121
|
+
console.log('(!) Invalid option.');
|
|
122
|
+
displayMainMenu();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
displayMainMenu();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const ELEVENTY_CONFIG = path.resolve(__dirname, '../.eleventy.js');
|
|
5
|
+
|
|
6
|
+
const content = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
|
|
7
|
+
const match = content.match(/const OUTPUT_DIR\s*=\s*['"`]([^'"`]*)['"`]/);
|
|
8
|
+
|
|
9
|
+
if (!match) {
|
|
10
|
+
console.log('(!) OUTPUT_DIR not found in .eleventy.js');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const outputDir = match[1];
|
|
15
|
+
const absPath = path.isAbsolute(outputDir)
|
|
16
|
+
? outputDir
|
|
17
|
+
: path.resolve(__dirname, '..', outputDir);
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(absPath)) {
|
|
20
|
+
fs.rmSync(absPath, { recursive: true, force: true });
|
|
21
|
+
console.log(`(✓) cleaned → ${absPath}`);
|
|
22
|
+
} else {
|
|
23
|
+
console.log(`(i) nothing to clean → ${absPath}`);
|
|
24
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const fileSystem = require('fs');
|
|
2
|
+
const SITE_DATA_PATH = 'src/data/site.json';
|
|
3
|
+
|
|
4
|
+
function toCamelCase(str) {
|
|
5
|
+
return str.toLowerCase().replace(/[-_][a-z0-9]/g, (group) =>
|
|
6
|
+
group.toUpperCase().replace('-', '').replace('_', '')
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function addSiteData(pageName) {
|
|
11
|
+
if (!fileSystem.existsSync(SITE_DATA_PATH)) return;
|
|
12
|
+
|
|
13
|
+
const data = JSON.parse(fileSystem.readFileSync(SITE_DATA_PATH, 'utf8'));
|
|
14
|
+
const camelName = toCamelCase(pageName);
|
|
15
|
+
|
|
16
|
+
if (data.pages[camelName]) {
|
|
17
|
+
console.log(`[SKIP] Record "${camelName}" already exists.`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const niceTitle = pageName
|
|
22
|
+
.split('-')
|
|
23
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
24
|
+
.join(' ');
|
|
25
|
+
|
|
26
|
+
data.pages[camelName] = {
|
|
27
|
+
seo: {
|
|
28
|
+
title: niceTitle,
|
|
29
|
+
description: "description",
|
|
30
|
+
},
|
|
31
|
+
cdn: {
|
|
32
|
+
css: [],
|
|
33
|
+
js: []
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
fileSystem.writeFileSync(SITE_DATA_PATH, JSON.stringify(data, null, 2));
|
|
38
|
+
console.log(`[UPDATED] Record "${camelName}" added.`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function removeSiteData(pageName) {
|
|
42
|
+
if (!fileSystem.existsSync(SITE_DATA_PATH)) return;
|
|
43
|
+
|
|
44
|
+
const data = JSON.parse(fileSystem.readFileSync(SITE_DATA_PATH, 'utf8'));
|
|
45
|
+
const camelName = toCamelCase(pageName);
|
|
46
|
+
|
|
47
|
+
if (!data.pages[camelName]) {
|
|
48
|
+
console.log(`[SKIP] Record "${camelName}" not found.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
delete data.pages[camelName];
|
|
53
|
+
|
|
54
|
+
fileSystem.writeFileSync(SITE_DATA_PATH, JSON.stringify(data, null, 2));
|
|
55
|
+
console.log(`[CLEANED] Record "${camelName}" removed.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { addSiteData, removeSiteData };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const fileSystem = require('fs');
|
|
2
|
+
const INCLUDES_PATH = 'src/components/layouts/includes.njk';
|
|
3
|
+
|
|
4
|
+
function toCamelCase(str) {
|
|
5
|
+
return str.toLowerCase().replace(/[-_][a-z0-9]/g, (group) =>
|
|
6
|
+
group.toUpperCase().replace('-', '').replace('_', '')
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function addLayout(pageName) {
|
|
11
|
+
const camelName = toCamelCase(pageName);
|
|
12
|
+
if (!fileSystem.existsSync(INCLUDES_PATH)) return;
|
|
13
|
+
|
|
14
|
+
let content = fileSystem.readFileSync(INCLUDES_PATH, 'utf8');
|
|
15
|
+
|
|
16
|
+
if (content.includes(`{% elif title == "${camelName}" %}`)) return;
|
|
17
|
+
|
|
18
|
+
const newElif = `{% elif title == "${camelName}" %}\n {#{% include "exampleComponent.njk" %}#}\n\n`;
|
|
19
|
+
const updatedContent = content.replace('{% else %}', `${newElif}{% else %}`);
|
|
20
|
+
|
|
21
|
+
fileSystem.writeFileSync(INCLUDES_PATH, updatedContent);
|
|
22
|
+
console.log(`[UPDATED] Layout block added for "${camelName}".`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function removeLayout(pageName) {
|
|
26
|
+
const camelName = toCamelCase(pageName);
|
|
27
|
+
if (!fileSystem.existsSync(INCLUDES_PATH)) return;
|
|
28
|
+
|
|
29
|
+
let content = fileSystem.readFileSync(INCLUDES_PATH, 'utf8');
|
|
30
|
+
|
|
31
|
+
const regex = new RegExp(`[ \\t]*\\{%\\s*elif\\s+title\\s*==\\s*"${camelName}"\\s*%\\}[\\s\\S]*?(?=[ \\t]*\\{%\\s*(?:elif|else|endif))`, 'g');
|
|
32
|
+
|
|
33
|
+
if (!regex.test(content)) {
|
|
34
|
+
console.log(`[DEBUG] Layout block for "${camelName}" not found. Skipped.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let updatedContent = content.replace(regex, '');
|
|
39
|
+
updatedContent = updatedContent.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
40
|
+
|
|
41
|
+
fileSystem.writeFileSync(INCLUDES_PATH, updatedContent);
|
|
42
|
+
console.log(`[CLEANED] Layout block removed for "${camelName}".`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { addLayout, removeLayout };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const ELEVENTY_CONFIG = path.resolve(__dirname, '../../.eleventy.js');
|
|
5
|
+
const PACKAGE_JSON = path.resolve(__dirname, '../../package.json');
|
|
6
|
+
|
|
7
|
+
function updateEleventyConfig(newPath) {
|
|
8
|
+
let content = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
|
|
9
|
+
|
|
10
|
+
const updated = content.replace(
|
|
11
|
+
/const OUTPUT_DIR\s*=\s*['"`][^'"`]*['"`]/,
|
|
12
|
+
`const OUTPUT_DIR = "${newPath}"`
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
if (content === updated) {
|
|
16
|
+
console.log('(!) OUTPUT_DIR not found in .eleventy.js');
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fs.writeFileSync(ELEVENTY_CONFIG, updated, 'utf-8');
|
|
21
|
+
console.log(`(✓) .eleventy.js updated → ${newPath}`);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function updatePackageJson(newPath) {
|
|
26
|
+
const raw = fs.readFileSync(PACKAGE_JSON, 'utf-8');
|
|
27
|
+
const pkg = JSON.parse(raw);
|
|
28
|
+
|
|
29
|
+
pkg.scripts['build:css'] = `sass src/scss:${newPath}/css --no-source-map --style=compressed --quiet`;
|
|
30
|
+
pkg.scripts['build:js'] = `esbuild "src/js/pages/*.js" --bundle --outdir=${newPath}/js/pages --minify`;
|
|
31
|
+
pkg.scripts['serve:css'] = `sass --watch src/scss:${newPath}/css --no-source-map --quiet`;
|
|
32
|
+
pkg.scripts['serve:js'] = `esbuild "src/js/pages/*.js" --bundle --outdir=${newPath}/js/pages --watch`;
|
|
33
|
+
|
|
34
|
+
fs.writeFileSync(PACKAGE_JSON, JSON.stringify(pkg, null, 2), 'utf-8');
|
|
35
|
+
console.log(`(✓) package.json updated → ${newPath}`);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function updateOutputPath(newPath) {
|
|
40
|
+
const trimmed = newPath.trim().replace(/\\/g, '/');
|
|
41
|
+
|
|
42
|
+
let normalizedPath;
|
|
43
|
+
if (trimmed === '.') {
|
|
44
|
+
normalizedPath = 'out';
|
|
45
|
+
} else {
|
|
46
|
+
const projectName = path.basename(process.cwd());
|
|
47
|
+
normalizedPath = trimmed.replace(/\/$/, '') + '/' + projectName + '-out';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const eleventyContent = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
|
|
51
|
+
const match = eleventyContent.match(/const OUTPUT_DIR\s*=\s*['"`]([^'"`]*)['"`]/);
|
|
52
|
+
const oldPath = match ? match[1] : null;
|
|
53
|
+
|
|
54
|
+
if (oldPath) {
|
|
55
|
+
const oldAbsPath = path.resolve(__dirname, '../../', oldPath);
|
|
56
|
+
if (fs.existsSync(oldAbsPath)) {
|
|
57
|
+
fs.rmSync(oldAbsPath, { recursive: true, force: true });
|
|
58
|
+
console.log(`(✓) folder deleted → ${oldAbsPath}`);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`(i) folder not found, nothing to delete → ${oldAbsPath}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`\nupdating output path → "${normalizedPath}"...`);
|
|
65
|
+
|
|
66
|
+
updatePackageJson(normalizedPath);
|
|
67
|
+
updateEleventyConfig(normalizedPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getCurrentOutputPath() {
|
|
71
|
+
try {
|
|
72
|
+
const content = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
|
|
73
|
+
const match = content.match(/const OUTPUT_DIR\s*=\s*['"`]([^'"`]*)['"`]/);
|
|
74
|
+
if (!match) return null;
|
|
75
|
+
|
|
76
|
+
const outputDir = match[1];
|
|
77
|
+
|
|
78
|
+
if (!path.isAbsolute(outputDir)) {
|
|
79
|
+
return 'project root/out';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const parent = path.dirname(outputDir);
|
|
83
|
+
const projectName = path.basename(outputDir);
|
|
84
|
+
|
|
85
|
+
return `${parent}/${projectName}`;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { updateOutputPath, getCurrentOutputPath };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const fileSystem = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const { addSiteData, removeSiteData } = require("./updateData");
|
|
5
|
+
const { addLayout, removeLayout } = require("./updateIncludes");
|
|
6
|
+
|
|
7
|
+
const TEMPLATE_FILE_PATH = path.join(__dirname, '..', 'res', 'templates.json');
|
|
8
|
+
|
|
9
|
+
function toCamelCase(str) {
|
|
10
|
+
return str.toLowerCase().replace(/[-_][a-z0-9]/g, (group) =>
|
|
11
|
+
group.toUpperCase().replace('-', '').replace('_', '')
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getFileInitialContent(pageName, extension) {
|
|
16
|
+
try {
|
|
17
|
+
const rawData = fileSystem.readFileSync(TEMPLATE_FILE_PATH, "utf8");
|
|
18
|
+
const templates = JSON.parse(rawData);
|
|
19
|
+
let selectedTemplate = templates[extension];
|
|
20
|
+
if (!selectedTemplate) return "";
|
|
21
|
+
|
|
22
|
+
const content = Array.isArray(selectedTemplate) ? selectedTemplate.join("\n") : selectedTemplate;
|
|
23
|
+
|
|
24
|
+
const kebabName = pageName;
|
|
25
|
+
const camelName = toCamelCase(pageName);
|
|
26
|
+
|
|
27
|
+
let processedContent = content
|
|
28
|
+
.replace(/{{pageName}}/g, kebabName)
|
|
29
|
+
.replace(/{{camelName}}/g, camelName);
|
|
30
|
+
|
|
31
|
+
if (extension === '.njk') {
|
|
32
|
+
processedContent = processedContent.replace(/^title:\s*.*$/m, `title: "${camelName}"`);
|
|
33
|
+
|
|
34
|
+
if (processedContent.includes('permalink:')) {
|
|
35
|
+
processedContent = processedContent.replace(/^permalink:\s*.*$/m, `permalink: "/${kebabName}/"`);
|
|
36
|
+
} else {
|
|
37
|
+
processedContent = processedContent.replace(`title: "${camelName}"`, `title: "${camelName}"\npermalink: "/${kebabName}/"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return processedContent;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`[error] ${error.message}`);
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function addPage(pageName) {
|
|
49
|
+
const camelName = toCamelCase(pageName);
|
|
50
|
+
|
|
51
|
+
const targets = [
|
|
52
|
+
{ folder: "src/scss/pages", extension: ".scss", fileName: camelName },
|
|
53
|
+
{ folder: "src/js/pages", extension: ".js", fileName: camelName },
|
|
54
|
+
{ folder: "src/_routes", extension: ".njk", fileName: pageName },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
targets.forEach((target) => {
|
|
58
|
+
const filePath = path.join(target.folder, `${target.fileName}${target.extension}`);
|
|
59
|
+
fileSystem.mkdirSync(target.folder, { recursive: true });
|
|
60
|
+
|
|
61
|
+
if (!fileSystem.existsSync(filePath)) {
|
|
62
|
+
const fileContent = getFileInitialContent(pageName, target.extension);
|
|
63
|
+
fileSystem.writeFileSync(filePath, fileContent);
|
|
64
|
+
console.log(`[created file] ${filePath}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
addLayout(pageName);
|
|
69
|
+
addSiteData(pageName);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renamePage(oldName, newName) {
|
|
73
|
+
const oldCamel = toCamelCase(oldName);
|
|
74
|
+
const newCamel = toCamelCase(newName);
|
|
75
|
+
|
|
76
|
+
const filesToRename = [
|
|
77
|
+
{
|
|
78
|
+
src: `src/scss/pages/${oldCamel}.scss`,
|
|
79
|
+
dest: `src/scss/pages/${newCamel}.scss`,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
src: `src/js/pages/${oldCamel}.js`,
|
|
83
|
+
dest: `src/js/pages/${newCamel}.js`,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
src: `src/_routes/${oldName}.njk`,
|
|
87
|
+
dest: `src/_routes/${newName}.njk`,
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
filesToRename.forEach(({ src, dest }) => {
|
|
92
|
+
if (!fileSystem.existsSync(src)) {
|
|
93
|
+
console.log(`[skip] not found: ${src}`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
let content = fileSystem.readFileSync(src, 'utf8');
|
|
97
|
+
content = content
|
|
98
|
+
.replace(new RegExp(oldName, 'g'), newName)
|
|
99
|
+
.replace(new RegExp(oldCamel, 'g'), newCamel);
|
|
100
|
+
fileSystem.writeFileSync(dest, content);
|
|
101
|
+
fileSystem.unlinkSync(src);
|
|
102
|
+
console.log(`[renamed] ${src} → ${dest}`);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
removeLayout(oldName);
|
|
106
|
+
addLayout(newName);
|
|
107
|
+
removeSiteData(oldName);
|
|
108
|
+
addSiteData(newName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function removePage(pageName) {
|
|
112
|
+
const camelName = toCamelCase(pageName);
|
|
113
|
+
const OUTPUT_DIR = "out";
|
|
114
|
+
|
|
115
|
+
const filesToDelete = [
|
|
116
|
+
`src/scss/pages/${camelName}.scss`,
|
|
117
|
+
`src/js/pages/${camelName}.js`,
|
|
118
|
+
`src/_routes/${pageName}.njk`,
|
|
119
|
+
|
|
120
|
+
path.join(OUTPUT_DIR, "js/pages", `${camelName}.js`),
|
|
121
|
+
path.join(OUTPUT_DIR, "css/pages", `${camelName}.css`),
|
|
122
|
+
|
|
123
|
+
path.join(OUTPUT_DIR, `${pageName}.html`),
|
|
124
|
+
path.join(OUTPUT_DIR, "pages", `${pageName}.html`)
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const foldersToDelete = [
|
|
128
|
+
path.join(OUTPUT_DIR, pageName),
|
|
129
|
+
path.join(OUTPUT_DIR, "pages", pageName)
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
filesToDelete.forEach(f => {
|
|
133
|
+
if (fileSystem.existsSync(f)) {
|
|
134
|
+
fileSystem.unlinkSync(f);
|
|
135
|
+
console.log(`[deleted file] ${f}`);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
foldersToDelete.forEach(folder => {
|
|
140
|
+
if (fileSystem.existsSync(folder)) {
|
|
141
|
+
fileSystem.rmSync(folder, { recursive: true, force: true });
|
|
142
|
+
console.log(`[deleted folder] ${folder}`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
removeLayout(pageName);
|
|
147
|
+
removeSiteData(pageName);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { addPage, removePage, renamePage };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
".scss": [
|
|
3
|
+
"//==========================",
|
|
4
|
+
"// CSS MODULES IMPORTS",
|
|
5
|
+
"//==========================",
|
|
6
|
+
"",
|
|
7
|
+
"@use \"../modules/root\" as root;",
|
|
8
|
+
"",
|
|
9
|
+
"@import \"../modules/global\";",
|
|
10
|
+
"// Import any other module you need by import down here",
|
|
11
|
+
"@import \"../modules/notification\";",
|
|
12
|
+
"",
|
|
13
|
+
"//==========================",
|
|
14
|
+
"// PAGE CUSTOM CSS RULES FOR PAGE: {{pageName}}",
|
|
15
|
+
"//==========================",
|
|
16
|
+
"",
|
|
17
|
+
"// body {",
|
|
18
|
+
"// background-color: root.$primary;",
|
|
19
|
+
"// }"
|
|
20
|
+
],
|
|
21
|
+
".njk": [
|
|
22
|
+
"---",
|
|
23
|
+
"title: {{pageName}}",
|
|
24
|
+
"permalink: /{{ title | slug }}/",
|
|
25
|
+
"layout: includes.njk",
|
|
26
|
+
"---",
|
|
27
|
+
"",
|
|
28
|
+
"<!-- Add content via components in src/components/, not directly here.",
|
|
29
|
+
" Register them in src/layouts/includes.njk. -->"
|
|
30
|
+
],
|
|
31
|
+
".js": [
|
|
32
|
+
"//==========================",
|
|
33
|
+
"// JAVASCRIPT MODULES IMPORTS",
|
|
34
|
+
"//==========================",
|
|
35
|
+
"",
|
|
36
|
+
"// Call inside DOMContentLoaded",
|
|
37
|
+
"import { initLangSwitcher } from '../modules/langSwitcher.js';",
|
|
38
|
+
"",
|
|
39
|
+
"// Call anywhere",
|
|
40
|
+
"import { showNotification } from '../modules/notification.js';",
|
|
41
|
+
"",
|
|
42
|
+
"// Uncomment to enable optional modules (call inside DOMContentLoaded)",
|
|
43
|
+
"// import { initTextAreaAutoExpand } from '../modules/textAreaAutoExpand.js';",
|
|
44
|
+
"// import { initNormalizePhoneNumber } from '../modules/normalizePhoneNumber.js';",
|
|
45
|
+
"",
|
|
46
|
+
"//==========================",
|
|
47
|
+
"// \"{{pageName}}\" PAGE CUSTOM JAVASCRIPT",
|
|
48
|
+
"//==========================",
|
|
49
|
+
"",
|
|
50
|
+
"document.addEventListener(\"DOMContentLoaded\", () => {",
|
|
51
|
+
" initLangSwitcher();",
|
|
52
|
+
"});",
|
|
53
|
+
"",
|
|
54
|
+
"showNotification(\"{{pageName}} notification\", \"success\", 3000);"
|
|
55
|
+
]
|
|
56
|
+
}
|