create-berna-stencil 1.0.51 → 1.0.53
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 -1
- package/_tools/assistant.js +94 -83
- package/_tools/modules/updateData.js +79 -15
- package/_tools/modules/updateIncludes.js +51 -18
- package/_tools/modules/updateOutputPath.js +34 -25
- package/_tools/modules/updatePage.js +45 -39
- package/_tools/modules/utils.js +12 -0
- package/docs/Assistant CLI.md +45 -22
- package/docs/Backend.md +210 -0
- package/docs/Head and SEO.md +2 -2
- package/docs/Javascript.md +40 -24
- package/docs/Styling with SCSS.md +53 -53
- package/package.json +6 -6
- package/src/frontend/js/modules/forms/textAreaAutoExpand.js +2 -2
- package/src/frontend/scss/modules/frameworks/_bootstrap.scss +66 -66
- package/src/frontend/scss/modules/frameworks/_bulma.scss +65 -65
- package/src/frontend/scss/modules/frameworks/_foundation.scss +98 -98
- package/src/frontend/scss/modules/frameworks/_uikit.scss +79 -79
package/.eleventy.js
CHANGED
package/_tools/assistant.js
CHANGED
|
@@ -2,6 +2,8 @@ const readline = require('readline');
|
|
|
2
2
|
const { addPage, removePage, renamePage } = require('./modules/updatePage');
|
|
3
3
|
const { updateOutputPath, getCurrentOutputPath } = require('./modules/updateOutputPath');
|
|
4
4
|
|
|
5
|
+
// --- Setup ---
|
|
6
|
+
|
|
5
7
|
const readerInterface = readline.createInterface({
|
|
6
8
|
input: process.stdin,
|
|
7
9
|
output: process.stdout,
|
|
@@ -10,86 +12,96 @@ const readerInterface = readline.createInterface({
|
|
|
10
12
|
|
|
11
13
|
const PROTECTED_PAGES = ['homepage', '404'];
|
|
12
14
|
|
|
15
|
+
// --- Utility ---
|
|
16
|
+
|
|
17
|
+
// Converts any string to kebab-case
|
|
13
18
|
function toKebabCase(str) {
|
|
14
19
|
return str.trim().toLowerCase()
|
|
15
20
|
.replace(/[\s_]+/g, '-')
|
|
16
21
|
.replace(/-+/g, '-');
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
// Returns an error message if the name is invalid, null otherwise
|
|
25
|
+
function validatePageName(name) {
|
|
26
|
+
if (!name) return 'Invalid name.';
|
|
27
|
+
if (/^\d/.test(name)) return 'Page name cannot start with a number.';
|
|
28
|
+
if (PROTECTED_PAGES.includes(name)) return `"${name}" is a protected page name.`;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Wraps readerInterface.question in a Promise for use with async/await
|
|
33
|
+
function ask(prompt) {
|
|
34
|
+
return new Promise(resolve =>
|
|
35
|
+
readerInterface.question(prompt, answer => resolve(answer))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Asks for a page name, converts it to kebab-case, validates it,
|
|
40
|
+
// logs the error and returns null if invalid
|
|
41
|
+
async function askPageName(prompt) {
|
|
42
|
+
const raw = await ask(prompt);
|
|
43
|
+
const name = toKebabCase(raw);
|
|
44
|
+
const error = validatePageName(name);
|
|
45
|
+
if (error) {
|
|
46
|
+
console.log(`(!) ${error}`);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return name;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// --- Handlers ---
|
|
53
|
+
|
|
54
|
+
async function handleCreateRequest() {
|
|
55
|
+
const name = await askPageName('\n> Enter the name of the new page: ');
|
|
56
|
+
if (name) addPage(name, null);
|
|
33
57
|
}
|
|
34
58
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!oldName) {
|
|
39
|
-
console.log('(!) Invalid name.');
|
|
40
|
-
return displayMainMenu();
|
|
41
|
-
}
|
|
42
|
-
if (PROTECTED_PAGES.includes(oldName)) {
|
|
43
|
-
console.log(`(!) "${oldName}" is a protected page and cannot be renamed.`);
|
|
44
|
-
return displayMainMenu();
|
|
45
|
-
}
|
|
46
|
-
readerInterface.question('> enter the new name: ', (inputNew) => {
|
|
47
|
-
const newName = toKebabCase(inputNew);
|
|
48
|
-
if (!newName) {
|
|
49
|
-
console.log('(!) invalid name.');
|
|
50
|
-
} else if (/^\d/.test(newName)) {
|
|
51
|
-
console.log('(!) Invalid name. Page name cannot start with a number.');
|
|
52
|
-
} else if (PROTECTED_PAGES.includes(newName)) {
|
|
53
|
-
console.log(`(!) "${newName}" is a protected page name.`);
|
|
54
|
-
} else if (oldName === newName) {
|
|
55
|
-
console.log('(!) Old and new name are the same.');
|
|
56
|
-
} else {
|
|
57
|
-
renamePage(oldName, newName);
|
|
58
|
-
}
|
|
59
|
-
displayMainMenu();
|
|
60
|
-
});
|
|
61
|
-
});
|
|
59
|
+
async function handleRemoveRequest() {
|
|
60
|
+
const name = await askPageName('\n> Enter the name of the page to remove: ');
|
|
61
|
+
if (name) removePage(name);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
64
|
+
async function handleRenameRequest() {
|
|
65
|
+
const oldName = await askPageName('\n> Enter the name of the page to rename: ');
|
|
66
|
+
if (!oldName) return;
|
|
67
|
+
|
|
68
|
+
const newName = await askPageName('> Enter the new name: ');
|
|
69
|
+
if (!newName) return;
|
|
70
|
+
|
|
71
|
+
// Extra check: old and new name must differ
|
|
72
|
+
if (oldName === newName) {
|
|
73
|
+
console.log('(!) Old and new name are the same.');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
renamePage(oldName, newName);
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
function handleOutputPathRequest() {
|
|
80
|
+
async function handleOutputPathRequest() {
|
|
79
81
|
const current = getCurrentOutputPath();
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
displayMainMenu();
|
|
89
|
-
});
|
|
82
|
+
const label = current ? ` Current path: "${current}"\n` : '';
|
|
83
|
+
const input = await ask(`${label} Enter the new output path: `);
|
|
84
|
+
|
|
85
|
+
if (!input.trim()) {
|
|
86
|
+
console.log('(!) Invalid path.');
|
|
87
|
+
} else {
|
|
88
|
+
updateOutputPath(input);
|
|
89
|
+
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
// --- Menu ---
|
|
93
|
+
|
|
94
|
+
// Maps each menu choice to its handler function
|
|
95
|
+
const MENU_ACTIONS = {
|
|
96
|
+
'1': handleCreateRequest,
|
|
97
|
+
'2': handleRemoveRequest,
|
|
98
|
+
'3': handleRenameRequest,
|
|
99
|
+
'4': handleOutputPathRequest,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Displays the menu, waits for input, executes the chosen action,
|
|
103
|
+
// then calls itself again to keep the CLI alive (async recursion, no stack buildup)
|
|
104
|
+
async function displayMainMenu() {
|
|
93
105
|
console.log('\n========================');
|
|
94
106
|
console.log(' Berna-Stencil CLI ');
|
|
95
107
|
console.log('========================\n');
|
|
@@ -99,24 +111,23 @@ function displayMainMenu() {
|
|
|
99
111
|
console.log('4. Configure output path');
|
|
100
112
|
console.log('\nCTRL/CMD + C to exit');
|
|
101
113
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
114
|
+
const choice = (await ask('\nChoose an option: ')).trim();
|
|
115
|
+
|
|
116
|
+
if (choice === '0') {
|
|
117
|
+
readerInterface.close();
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const action = MENU_ACTIONS[choice];
|
|
122
|
+
if (action) {
|
|
123
|
+
await action();
|
|
124
|
+
} else {
|
|
125
|
+
console.log('(!) Invalid option.');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Recurse to redisplay the menu after each action.
|
|
129
|
+
// Safe because each iteration fully resolves before the next one starts.
|
|
130
|
+
displayMainMenu();
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
displayMainMenu();
|
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
const fileSystem = require('fs');
|
|
2
|
+
|
|
2
3
|
const SITE_DATA_PATH = 'src/frontend/data/site.json';
|
|
3
4
|
|
|
5
|
+
// --- Utility ---
|
|
6
|
+
|
|
7
|
+
// Converts a kebab-case string to camelCase
|
|
8
|
+
// slice(1) removes the delimiter character reliably, avoiding the regex flag bug
|
|
9
|
+
// of a double replace('-', '') without the /g flag
|
|
4
10
|
function toCamelCase(str) {
|
|
5
11
|
return str.toLowerCase().replace(/[-_][a-z0-9]/g, (group) =>
|
|
6
|
-
group.
|
|
12
|
+
group.slice(1).toUpperCase()
|
|
7
13
|
);
|
|
8
14
|
}
|
|
9
15
|
|
|
16
|
+
// Converts a kebab-case page name to a human-readable title
|
|
17
|
+
// e.g. "about-us" → "About Us"
|
|
18
|
+
function toNiceTitle(pageName) {
|
|
19
|
+
return pageName
|
|
20
|
+
.split('-')
|
|
21
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
22
|
+
.join(' ');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Returns the parsed site.json content, or null if the file doesn't exist
|
|
26
|
+
function readSiteData() {
|
|
27
|
+
if (!fileSystem.existsSync(SITE_DATA_PATH)) return null;
|
|
28
|
+
return JSON.parse(fileSystem.readFileSync(SITE_DATA_PATH, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Serializes and writes the data object back to site.json
|
|
32
|
+
function writeSiteData(data) {
|
|
33
|
+
fileSystem.writeFileSync(SITE_DATA_PATH, JSON.stringify(data, null, 2));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --- Public API ---
|
|
37
|
+
|
|
38
|
+
// Adds a new page record to site.json
|
|
39
|
+
// Skips silently if the file doesn't exist or the record is already present
|
|
10
40
|
function addSiteData(pageName) {
|
|
11
|
-
|
|
41
|
+
const data = readSiteData();
|
|
42
|
+
if (!data) return;
|
|
12
43
|
|
|
13
|
-
const data = JSON.parse(fileSystem.readFileSync(SITE_DATA_PATH, 'utf8'));
|
|
14
44
|
const camelName = toCamelCase(pageName);
|
|
15
45
|
|
|
16
46
|
if (data.pages[camelName]) {
|
|
@@ -18,15 +48,11 @@ function addSiteData(pageName) {
|
|
|
18
48
|
return;
|
|
19
49
|
}
|
|
20
50
|
|
|
21
|
-
|
|
22
|
-
.split('-')
|
|
23
|
-
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
24
|
-
.join(' ');
|
|
25
|
-
|
|
51
|
+
// Build the default page record with SEO metadata and empty CDN arrays
|
|
26
52
|
data.pages[camelName] = {
|
|
27
53
|
seo: {
|
|
28
|
-
title:
|
|
29
|
-
description:
|
|
54
|
+
title: toNiceTitle(pageName),
|
|
55
|
+
description: 'description',
|
|
30
56
|
},
|
|
31
57
|
cdn: {
|
|
32
58
|
css: [],
|
|
@@ -34,14 +60,16 @@ function addSiteData(pageName) {
|
|
|
34
60
|
}
|
|
35
61
|
};
|
|
36
62
|
|
|
37
|
-
|
|
63
|
+
writeSiteData(data);
|
|
38
64
|
console.log(`[UPDATED] Record "${camelName}" added.`);
|
|
39
65
|
}
|
|
40
66
|
|
|
67
|
+
// Removes a page record from site.json
|
|
68
|
+
// Skips silently if the file doesn't exist or the record is not found
|
|
41
69
|
function removeSiteData(pageName) {
|
|
42
|
-
|
|
70
|
+
const data = readSiteData();
|
|
71
|
+
if (!data) return;
|
|
43
72
|
|
|
44
|
-
const data = JSON.parse(fileSystem.readFileSync(SITE_DATA_PATH, 'utf8'));
|
|
45
73
|
const camelName = toCamelCase(pageName);
|
|
46
74
|
|
|
47
75
|
if (!data.pages[camelName]) {
|
|
@@ -51,8 +79,44 @@ function removeSiteData(pageName) {
|
|
|
51
79
|
|
|
52
80
|
delete data.pages[camelName];
|
|
53
81
|
|
|
54
|
-
|
|
82
|
+
writeSiteData(data);
|
|
55
83
|
console.log(`[CLEANED] Record "${camelName}" removed.`);
|
|
56
84
|
}
|
|
57
85
|
|
|
58
|
-
|
|
86
|
+
// Renames a page record in site.json
|
|
87
|
+
// Preserves all existing fields (cdn, etc.) and only updates the SEO title
|
|
88
|
+
// Skips if the source record doesn't exist or the target name is already taken
|
|
89
|
+
function renameSiteData(oldName, newName) {
|
|
90
|
+
const data = readSiteData();
|
|
91
|
+
if (!data) return;
|
|
92
|
+
|
|
93
|
+
const oldCamel = toCamelCase(oldName);
|
|
94
|
+
const newCamel = toCamelCase(newName);
|
|
95
|
+
|
|
96
|
+
if (!data.pages[oldCamel]) {
|
|
97
|
+
console.log(`[SKIP] Record "${oldCamel}" not found.`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (data.pages[newCamel]) {
|
|
102
|
+
console.log(`[SKIP] Record "${newCamel}" already exists.`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Spread the existing record to preserve cdn and any future fields,
|
|
107
|
+
// then override only the seo.title with the new page name
|
|
108
|
+
data.pages[newCamel] = {
|
|
109
|
+
...data.pages[oldCamel],
|
|
110
|
+
seo: {
|
|
111
|
+
...data.pages[oldCamel].seo,
|
|
112
|
+
title: toNiceTitle(newName),
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
delete data.pages[oldCamel];
|
|
117
|
+
|
|
118
|
+
writeSiteData(data);
|
|
119
|
+
console.log(`[UPDATED] Record "${oldCamel}" renamed to "${newCamel}".`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { addSiteData, removeSiteData, renameSiteData };
|
|
@@ -1,45 +1,78 @@
|
|
|
1
1
|
const fileSystem = require('fs');
|
|
2
|
+
const { toCamelCase } = require('./utils');
|
|
3
|
+
|
|
2
4
|
const INCLUDES_PATH = 'src/frontend/components/layouts/includes.njk';
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
// --- Helpers ---
|
|
7
|
+
|
|
8
|
+
// Returns the file content as a string, or null if the file doesn't exist
|
|
9
|
+
function readIncludes() {
|
|
10
|
+
if (!fileSystem.existsSync(INCLUDES_PATH)) return null;
|
|
11
|
+
return fileSystem.readFileSync(INCLUDES_PATH, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Writes updated content back to the includes file
|
|
15
|
+
function writeIncludes(content) {
|
|
16
|
+
fileSystem.writeFileSync(INCLUDES_PATH, content);
|
|
17
|
+
}
|
|
18
|
+
|
|
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
|
+
function buildElifRegex(camelName) {
|
|
24
|
+
return new RegExp(
|
|
25
|
+
`[ \\t]*\\{%\\s*elif\\s+title\\s*==\\s*"${camelName}"\\s*%\\}[\\s\\S]*?(?=[ \\t]*\\{%\\s*(?:elif|else|endif))`,
|
|
7
26
|
);
|
|
8
27
|
}
|
|
9
28
|
|
|
29
|
+
// --- Public API ---
|
|
30
|
+
|
|
31
|
+
// Inserts a new elif block before {% else %} for the given page
|
|
10
32
|
function addLayout(pageName) {
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
33
|
+
const content = readIncludes();
|
|
34
|
+
if (!content) return;
|
|
13
35
|
|
|
14
|
-
|
|
36
|
+
const camelName = toCamelCase(pageName);
|
|
15
37
|
|
|
38
|
+
// Skip if the block already exists
|
|
16
39
|
if (content.includes(`{% elif title == "${camelName}" %}`)) return;
|
|
17
40
|
|
|
18
|
-
const newElif =
|
|
19
|
-
|
|
41
|
+
const newElif =
|
|
42
|
+
`{% elif title == "${camelName}" %}\n` +
|
|
43
|
+
` {# Insert your includes under this page #}\n` +
|
|
44
|
+
` {#{% include "component.njk" %}#}\n\n`;
|
|
20
45
|
|
|
21
|
-
|
|
46
|
+
writeIncludes(content.replace('{% else %}', `${newElif}{% else %}`));
|
|
22
47
|
console.log(`[UPDATED] Layout block added for "${camelName}".`);
|
|
23
48
|
}
|
|
24
49
|
|
|
50
|
+
// Removes the elif block for the given page, then collapses extra blank lines
|
|
25
51
|
function removeLayout(pageName) {
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
28
|
-
|
|
29
|
-
let content = fileSystem.readFileSync(INCLUDES_PATH, 'utf8');
|
|
52
|
+
const content = readIncludes();
|
|
53
|
+
if (!content) return;
|
|
30
54
|
|
|
31
|
-
const
|
|
55
|
+
const camelName = toCamelCase(pageName);
|
|
56
|
+
const regex = buildElifRegex(camelName);
|
|
32
57
|
|
|
33
58
|
if (!regex.test(content)) {
|
|
34
59
|
console.log(`[DEBUG] Layout block for "${camelName}" not found. Skipped.`);
|
|
35
60
|
return;
|
|
36
61
|
}
|
|
37
62
|
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
// Build a fresh regex instance for replace to avoid stale lastIndex
|
|
64
|
+
const updated = content
|
|
65
|
+
.replace(buildElifRegex(camelName), '')
|
|
66
|
+
.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
40
67
|
|
|
41
|
-
|
|
68
|
+
writeIncludes(updated);
|
|
42
69
|
console.log(`[CLEANED] Layout block removed for "${camelName}".`);
|
|
43
70
|
}
|
|
44
71
|
|
|
45
|
-
|
|
72
|
+
// Renames a layout block by removing the old one and inserting a new one
|
|
73
|
+
function renameLayout(oldName, newName) {
|
|
74
|
+
removeLayout(oldName);
|
|
75
|
+
addLayout(newName);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { addLayout, removeLayout, renameLayout };
|
|
@@ -4,11 +4,25 @@ const path = require('path');
|
|
|
4
4
|
const ELEVENTY_CONFIG = path.resolve(__dirname, '../../.eleventy.js');
|
|
5
5
|
const PACKAGE_JSON = path.resolve(__dirname, '../../package.json');
|
|
6
6
|
|
|
7
|
+
// Regex to locate the OUTPUT_DIR declaration in .eleventy.js.
|
|
8
|
+
// Extracted as a constant to avoid writing it by hand in multiple places.
|
|
9
|
+
const OUTPUT_DIR_REGEX = /const OUTPUT_DIR\s*=\s*['"`]([^'"`]*)['"`]/;
|
|
10
|
+
|
|
11
|
+
// --- Helpers ---
|
|
12
|
+
|
|
13
|
+
// Reads OUTPUT_DIR value from a given file content string, or returns null
|
|
14
|
+
function parseOutputDir(content) {
|
|
15
|
+
const match = content.match(OUTPUT_DIR_REGEX);
|
|
16
|
+
return match ? match[1] : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// --- Updaters ---
|
|
20
|
+
|
|
7
21
|
function updateEleventyConfig(newPath) {
|
|
8
|
-
|
|
22
|
+
const content = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
|
|
9
23
|
|
|
10
24
|
const updated = content.replace(
|
|
11
|
-
|
|
25
|
+
OUTPUT_DIR_REGEX,
|
|
12
26
|
`const OUTPUT_DIR = "${newPath}"`
|
|
13
27
|
);
|
|
14
28
|
|
|
@@ -23,34 +37,37 @@ function updateEleventyConfig(newPath) {
|
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
function updatePackageJson(newPath) {
|
|
26
|
-
const
|
|
27
|
-
const pkg = JSON.parse(raw);
|
|
40
|
+
const pkg = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf-8'));
|
|
28
41
|
|
|
42
|
+
// Reconstruct all output-dependent scripts from scratch to avoid
|
|
43
|
+
// partial string replacement bugs on the outdir flag
|
|
29
44
|
pkg.scripts['build:css'] = `sass src/frontend/scss:${newPath}/css --no-source-map --style=compressed --quiet`;
|
|
30
|
-
pkg.scripts['build:js']
|
|
45
|
+
pkg.scripts['build:js'] = `esbuild "src/frontend/js/pages/*.js" --bundle --outdir=${newPath}/js/pages --minify`;
|
|
31
46
|
pkg.scripts['serve:css'] = `sass --watch src/frontend/scss:${newPath}/css --no-source-map --quiet`;
|
|
32
|
-
pkg.scripts['serve:js']
|
|
47
|
+
pkg.scripts['serve:js'] = `esbuild "src/frontend/js/pages/*.js" --bundle --outdir=${newPath}/js/pages --watch`;
|
|
33
48
|
|
|
34
49
|
fs.writeFileSync(PACKAGE_JSON, JSON.stringify(pkg, null, 2), 'utf-8');
|
|
35
50
|
console.log(`(✓) package.json updated → ${newPath}`);
|
|
36
51
|
return true;
|
|
37
52
|
}
|
|
38
53
|
|
|
54
|
+
// --- Public API ---
|
|
55
|
+
|
|
39
56
|
function updateOutputPath(newPath) {
|
|
40
57
|
const trimmed = newPath.trim().replace(/\\/g, '/');
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
normalizedPath = trimmed.replace(/\/$/, '') + '/' + projectName + '-out';
|
|
48
|
-
}
|
|
59
|
+
// Normalize the path: bare "." becomes "out", everything else gets a
|
|
60
|
+
// project-scoped suffix to avoid collisions
|
|
61
|
+
const normalizedPath = trimmed === '.'
|
|
62
|
+
? 'out'
|
|
63
|
+
: `${trimmed.replace(/\/$/, '')}/${path.basename(process.cwd())}-out`;
|
|
49
64
|
|
|
65
|
+
// Read the config once and reuse it to get the old path —
|
|
66
|
+
// avoids a second disk read inside updateEleventyConfig
|
|
50
67
|
const eleventyContent = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
|
|
51
|
-
const
|
|
52
|
-
const oldPath = match ? match[1] : null;
|
|
68
|
+
const oldPath = parseOutputDir(eleventyContent);
|
|
53
69
|
|
|
70
|
+
// Delete the old output folder if it exists
|
|
54
71
|
if (oldPath) {
|
|
55
72
|
const oldAbsPath = path.resolve(__dirname, '../../', oldPath);
|
|
56
73
|
if (fs.existsSync(oldAbsPath)) {
|
|
@@ -67,19 +84,11 @@ function updateOutputPath(newPath) {
|
|
|
67
84
|
updateEleventyConfig(normalizedPath);
|
|
68
85
|
}
|
|
69
86
|
|
|
87
|
+
// Returns the current OUTPUT_DIR value from .eleventy.js, or null on failure
|
|
70
88
|
function getCurrentOutputPath() {
|
|
71
89
|
try {
|
|
72
90
|
const content = fs.readFileSync(ELEVENTY_CONFIG, 'utf-8');
|
|
73
|
-
|
|
74
|
-
if (!match) return null;
|
|
75
|
-
|
|
76
|
-
const outputDir = match[1];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const parent = path.dirname(outputDir);
|
|
80
|
-
const projectName = path.basename(outputDir);
|
|
81
|
-
|
|
82
|
-
return `${parent}/${projectName}`;
|
|
91
|
+
return parseOutputDir(content);
|
|
83
92
|
} catch {
|
|
84
93
|
return null;
|
|
85
94
|
}
|