nibula 1.2.3 → 1.2.5
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 -81
- package/.eleventyignore +3 -3
- package/.github/workflows/publish.yml +37 -0
- package/CHANGELOG.md +166 -135
- package/LICENSE +169 -169
- package/NOTICE +4 -4
- package/README.md +120 -123
- package/bin/create.js +507 -507
- package/bin/nibula.js +316 -307
- package/docs/Assistant CLI.md +65 -65
- package/docs/Backend.md +295 -295
- package/docs/Components.md +84 -84
- package/docs/Creating pages.md +64 -64
- package/docs/Deploy.md +189 -189
- package/docs/Head and SEO.md +138 -138
- package/docs/Javascript.md +50 -50
- package/docs/Styling with SCSS.md +141 -141
- package/nginx.conf +75 -75
- package/package.json +1 -1
- package/src/backend/.htaccess +6 -6
- package/src/backend/_core/composer.json +5 -5
- package/src/backend/_core/composer.lock +492 -492
- package/src/backend/_core/index.js +267 -267
- package/src/backend/_core/index.php +147 -147
- package/src/backend/_core/init.js +52 -52
- package/src/backend/_core/init.php +33 -33
- package/src/backend/_core/modules/RateLimiter.js +58 -58
- package/src/backend/_core/modules/RateLimiter.php +30 -30
- package/src/backend/_core/modules/Response.js +59 -59
- package/src/backend/_core/modules/Response.php +48 -48
- package/src/backend/api/protected/example-protected.js +23 -23
- package/src/backend/api/protected/example-protected.php +16 -16
- package/src/backend/api/public/example-public.js +24 -24
- package/src/backend/api/public/example-public.php +16 -16
- package/src/backend/backend-node.service.example +30 -30
- package/src/backend/database/Database.js +46 -46
- package/src/backend/database/Database.php +23 -23
- package/src/backend/example.config.js +37 -37
- package/src/backend/example.config.php +27 -27
- package/src/backend/package.json +18 -18
- package/src/backend/web.config +16 -16
- package/src/frontend/.htaccess +51 -51
- package/src/frontend/404.njk +17 -17
- package/src/frontend/assets/brand/favicon.svg +54 -54
- package/src/frontend/assets/brand/logo.svg +54 -54
- package/src/frontend/components/global/footer.njk +25 -25
- package/src/frontend/components/global/header.njk +6 -6
- package/src/frontend/components/welcome.njk +114 -114
- package/src/frontend/data/site.json +48 -48
- package/src/frontend/index.njk +8 -8
- package/src/frontend/js/modules/exampleModule.js +2 -2
- package/src/frontend/js/pages/404.js +6 -6
- package/src/frontend/js/pages/homepage.js +6 -6
- package/src/frontend/layouts/base.njk +132 -132
- package/src/frontend/layouts/page-components.njk +13 -13
- package/src/frontend/llms.njk +17 -17
- package/src/frontend/robots.njk +7 -7
- package/src/frontend/scss/modules/_animations.scss +24 -24
- package/src/frontend/scss/modules/_footer.scss +27 -27
- package/src/frontend/scss/modules/_global.scss +47 -47
- package/src/frontend/scss/modules/_header.scss +27 -27
- package/src/frontend/scss/modules/_mobile.scss +29 -29
- package/src/frontend/scss/modules/_root.scss +34 -34
- package/src/frontend/scss/modules/_typography.scss +14 -14
- package/src/frontend/scss/modules/frameworks/_bootstrap.scss +109 -109
- package/src/frontend/scss/modules/frameworks/_bulma.scss +108 -108
- package/src/frontend/scss/modules/frameworks/_foundation.scss +138 -139
- package/src/frontend/scss/modules/frameworks/_uikit.scss +109 -109
- package/src/frontend/scss/pages/404.scss +27 -27
- package/src/frontend/scss/pages/homepage.scss +22 -22
- package/src/frontend/sitemap.njk +17 -17
- package/src/frontend/ts/modules/exampleModule.ts +2 -2
- package/src/frontend/ts/pages/404.ts +6 -6
- package/src/frontend/ts/pages/homepage.ts +6 -6
- package/src/frontend/web.config +55 -55
- package/tools/assistant.js +122 -151
- package/tools/buildJs.js +39 -37
- package/tools/cleanOutput.js +23 -25
- package/tools/cli/prompt.js +40 -0
- package/tools/cli/ui.js +74 -0
- package/tools/config/messages.json +78 -0
- package/tools/config/settings.json +135 -0
- package/tools/lib/colors.js +17 -0
- package/tools/lib/files.js +65 -0
- package/tools/lib/logger.js +22 -0
- package/tools/lib/outputPath.js +141 -0
- package/tools/lib/pageActions.js +138 -0
- package/tools/lib/pageArtifacts.js +46 -0
- package/tools/lib/pageComponents.js +88 -0
- package/tools/lib/paths.js +61 -0
- package/tools/lib/project.js +54 -0
- package/tools/lib/siteData.js +93 -0
- package/tools/lib/text.js +47 -0
- package/tools/lib/validation.js +39 -0
- package/tools/res/templates/template.js +7 -5
- package/tools/res/templates/template.njk +8 -8
- package/tools/res/templates/template.scss +22 -22
- package/tools/res/templates/template.ts +7 -5
- package/tsconfig.json +24 -24
- package/tools/modules/constants.js +0 -66
- package/tools/modules/pageComponents.js +0 -77
- package/tools/modules/updateData.js +0 -90
- package/tools/modules/updateOutputPath.js +0 -133
- package/tools/modules/updatePage.js +0 -162
- package/tools/modules/utils.js +0 -27
- package/tools/modules/validation.js +0 -30
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { PATHS } = require('./constants');
|
|
3
|
-
const { toCamelCase } = require('./utils');
|
|
4
|
-
|
|
5
|
-
function readPageComponents() {
|
|
6
|
-
if (!fs.existsSync(PATHS.pageComponents)) {
|
|
7
|
-
console.log(`[skip] not found: ${PATHS.pageComponents}`);
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
return fs.readFileSync(PATHS.pageComponents, 'utf8');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function writePageComponents(content) {
|
|
14
|
-
fs.writeFileSync(PATHS.pageComponents, content);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function elifBlockRegex(camelName) {
|
|
18
|
-
return new RegExp(
|
|
19
|
-
`[ \\t]*\\{%\\s*elif\\s+title\\s*==\\s*"${camelName}"\\s*%\\}[\\s\\S]*?(?=[ \\t]*\\{%\\s*(?:elif|else|endif))`,
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function addPageBlock(pageName) {
|
|
24
|
-
const content = readPageComponents();
|
|
25
|
-
if (!content) return;
|
|
26
|
-
|
|
27
|
-
const camelName = toCamelCase(pageName);
|
|
28
|
-
if (content.includes(`{% elif title == "${camelName}" %}`)) return;
|
|
29
|
-
|
|
30
|
-
if (!content.includes('{% else %}')) {
|
|
31
|
-
console.log('[skip] no {% else %} anchor in page-components.njk');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const block =
|
|
36
|
-
`{% elif title == "${camelName}" %}\n` +
|
|
37
|
-
`{% include "component.njk" ignore missing %}\n\n`;
|
|
38
|
-
|
|
39
|
-
writePageComponents(content.replace('{% else %}', `${block}{% else %}`));
|
|
40
|
-
console.log(`[updated] page block added for "${camelName}"`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function removePageBlock(pageName) {
|
|
44
|
-
const content = readPageComponents();
|
|
45
|
-
if (!content) return;
|
|
46
|
-
|
|
47
|
-
const camelName = toCamelCase(pageName);
|
|
48
|
-
if (!elifBlockRegex(camelName).test(content)) {
|
|
49
|
-
console.log(`[skip] page block for "${camelName}" not found`);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const updated = content
|
|
54
|
-
.replace(elifBlockRegex(camelName), '')
|
|
55
|
-
.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
56
|
-
|
|
57
|
-
writePageComponents(updated);
|
|
58
|
-
console.log(`[cleaned] page block removed for "${camelName}"`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function renamePageBlock(oldName, newName) {
|
|
62
|
-
const content = readPageComponents();
|
|
63
|
-
if (!content) return;
|
|
64
|
-
|
|
65
|
-
const oldLine = `{% elif title == "${toCamelCase(oldName)}" %}`;
|
|
66
|
-
const newLine = `{% elif title == "${toCamelCase(newName)}" %}`;
|
|
67
|
-
|
|
68
|
-
if (!content.includes(oldLine)) {
|
|
69
|
-
console.log(`[skip] page block for "${toCamelCase(oldName)}" not found`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
writePageComponents(content.replace(oldLine, newLine));
|
|
74
|
-
console.log(`[renamed] page block "${toCamelCase(oldName)}" → "${toCamelCase(newName)}"`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
module.exports = { addPageBlock, removePageBlock, renamePageBlock };
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { PATHS } = require('./constants');
|
|
3
|
-
const { toCamelCase, toNiceTitle } = require('./utils');
|
|
4
|
-
|
|
5
|
-
function readSiteData() {
|
|
6
|
-
if (!fs.existsSync(PATHS.siteData)) {
|
|
7
|
-
console.log(`[skip] not found: ${PATHS.siteData}`);
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
try {
|
|
11
|
-
const data = JSON.parse(fs.readFileSync(PATHS.siteData, 'utf8'));
|
|
12
|
-
if (!data.pages || typeof data.pages !== 'object') data.pages = {};
|
|
13
|
-
return data;
|
|
14
|
-
} catch (err) {
|
|
15
|
-
console.log(`[error] cannot parse site.json: ${err.message}`);
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function writeSiteData(data) {
|
|
21
|
-
fs.writeFileSync(PATHS.siteData, `${JSON.stringify(data, null, 2)}\n`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function addSiteData(pageName) {
|
|
25
|
-
const data = readSiteData();
|
|
26
|
-
if (!data) return;
|
|
27
|
-
|
|
28
|
-
const camelName = toCamelCase(pageName);
|
|
29
|
-
if (data.pages[camelName]) {
|
|
30
|
-
console.log(`[skip] record "${camelName}" already exists`);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
data.pages[camelName] = {
|
|
35
|
-
seo: {
|
|
36
|
-
title: toNiceTitle(pageName),
|
|
37
|
-
description: 'description',
|
|
38
|
-
keywords: '',
|
|
39
|
-
noindex: false,
|
|
40
|
-
canonical: '',
|
|
41
|
-
},
|
|
42
|
-
cdn: { css: [], js: [] },
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
writeSiteData(data);
|
|
46
|
-
console.log(`[updated] record "${camelName}" added`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function removeSiteData(pageName) {
|
|
50
|
-
const data = readSiteData();
|
|
51
|
-
if (!data) return;
|
|
52
|
-
|
|
53
|
-
const camelName = toCamelCase(pageName);
|
|
54
|
-
if (!data.pages[camelName]) {
|
|
55
|
-
console.log(`[skip] record "${camelName}" not found`);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
delete data.pages[camelName];
|
|
60
|
-
writeSiteData(data);
|
|
61
|
-
console.log(`[cleaned] record "${camelName}" removed`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function renameSiteData(oldName, newName) {
|
|
65
|
-
const data = readSiteData();
|
|
66
|
-
if (!data) return;
|
|
67
|
-
|
|
68
|
-
const oldCamel = toCamelCase(oldName);
|
|
69
|
-
const newCamel = toCamelCase(newName);
|
|
70
|
-
|
|
71
|
-
if (!data.pages[oldCamel]) {
|
|
72
|
-
console.log(`[skip] record "${oldCamel}" not found`);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (data.pages[newCamel]) {
|
|
76
|
-
console.log(`[skip] record "${newCamel}" already exists`);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
data.pages[newCamel] = {
|
|
81
|
-
...data.pages[oldCamel],
|
|
82
|
-
seo: { ...data.pages[oldCamel].seo, title: toNiceTitle(newName) },
|
|
83
|
-
};
|
|
84
|
-
delete data.pages[oldCamel];
|
|
85
|
-
|
|
86
|
-
writeSiteData(data);
|
|
87
|
-
console.log(`[updated] record "${oldCamel}" renamed to "${newCamel}"`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
module.exports = { addSiteData, removeSiteData, renameSiteData };
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { PATHS } = require('./constants');
|
|
4
|
-
const { isTypeScriptProject } = require('./utils');
|
|
5
|
-
|
|
6
|
-
const OUTPUT_DIR_REGEX = /const OUTPUT_DIR\s*=\s*['"`]([^'"`]*)['"`]/;
|
|
7
|
-
const TSCONFIG_OUTDIR_REGEX = /"outDir"\s*:\s*"[^"]*"/;
|
|
8
|
-
|
|
9
|
-
const OUT_SCRIPTS = [
|
|
10
|
-
['build:css', '/css', 'sass src/frontend/scss:{OUT}/css --no-source-map --style=compressed --quiet --load-path=node_modules'],
|
|
11
|
-
['serve:css', '/css', 'sass --watch src/frontend/scss:{OUT}/css --no-source-map --quiet --load-path=node_modules'],
|
|
12
|
-
['build:js', '/js/pages', 'esbuild "{GLOB}" --bundle --outdir={OUT}/js/pages --minify'],
|
|
13
|
-
['serve:js', '/js/pages', 'esbuild "{GLOB}" --bundle --outdir={OUT}/js/pages --watch'],
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
function parseOutputDir(content) {
|
|
17
|
-
const match = content.match(OUTPUT_DIR_REGEX);
|
|
18
|
-
return match ? match[1] : null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function isAbsolutePath(p) {
|
|
22
|
-
return path.isAbsolute(p) || /^[a-zA-Z]:/.test(p);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function updateEleventyConfig(newPath) {
|
|
26
|
-
const content = fs.readFileSync(PATHS.eleventyConfig, 'utf8');
|
|
27
|
-
const updated = content.replace(OUTPUT_DIR_REGEX, `const OUTPUT_DIR = "${newPath}"`);
|
|
28
|
-
|
|
29
|
-
if (content === updated) {
|
|
30
|
-
console.log('[skip] OUTPUT_DIR not found in .eleventy.js');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
fs.writeFileSync(PATHS.eleventyConfig, updated);
|
|
34
|
-
console.log(`[updated] .eleventy.js → ${newPath}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function updatePackageJson(newPath, oldPath) {
|
|
38
|
-
const pkg = JSON.parse(fs.readFileSync(PATHS.packageJson, 'utf8'));
|
|
39
|
-
const scriptGlob = isTypeScriptProject()
|
|
40
|
-
? 'src/frontend/ts/pages/*.ts'
|
|
41
|
-
: 'src/frontend/js/pages/*.js';
|
|
42
|
-
|
|
43
|
-
pkg.outputDir = newPath;
|
|
44
|
-
pkg.scripts = pkg.scripts || {};
|
|
45
|
-
|
|
46
|
-
for (const [name, suffix, template] of OUT_SCRIPTS) {
|
|
47
|
-
const current = pkg.scripts[name];
|
|
48
|
-
const token = oldPath ? `${oldPath}${suffix}` : null;
|
|
49
|
-
|
|
50
|
-
if (current && token && current.includes(token)) {
|
|
51
|
-
pkg.scripts[name] = current.split(token).join(`${newPath}${suffix}`);
|
|
52
|
-
} else {
|
|
53
|
-
pkg.scripts[name] = template
|
|
54
|
-
.replace(/\{OUT\}/g, newPath)
|
|
55
|
-
.replace(/\{GLOB\}/g, scriptGlob);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
fs.writeFileSync(PATHS.packageJson, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
60
|
-
console.log(`[updated] package.json → ${newPath}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function updateTsConfig(newPath) {
|
|
64
|
-
if (!isTypeScriptProject()) return;
|
|
65
|
-
|
|
66
|
-
const outDir = isAbsolutePath(newPath) ? `${newPath}/ts` : `./${newPath}/ts`;
|
|
67
|
-
const content = fs.readFileSync(PATHS.tsconfig, 'utf8');
|
|
68
|
-
const updated = content.replace(TSCONFIG_OUTDIR_REGEX, `"outDir": "${outDir}"`);
|
|
69
|
-
|
|
70
|
-
if (content === updated) {
|
|
71
|
-
console.log('[skip] outDir not found in tsconfig.json');
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
fs.writeFileSync(PATHS.tsconfig, updated);
|
|
75
|
-
console.log(`[updated] tsconfig.json → ${outDir}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function deleteOldOutput(oldPath) {
|
|
79
|
-
if (!oldPath) return;
|
|
80
|
-
|
|
81
|
-
const oldAbsPath = path.resolve(PATHS.root, oldPath);
|
|
82
|
-
const insideProject = oldAbsPath.startsWith(PATHS.root + path.sep);
|
|
83
|
-
|
|
84
|
-
if (!insideProject || oldAbsPath === PATHS.root) {
|
|
85
|
-
console.log(`[skip] refusing to delete "${oldAbsPath}" (outside project)`);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (fs.existsSync(oldAbsPath)) {
|
|
89
|
-
fs.rmSync(oldAbsPath, { recursive: true, force: true });
|
|
90
|
-
console.log(`[deleted] ${oldAbsPath}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function updateOutputPath(rawPath) {
|
|
95
|
-
const trimmed = (rawPath ?? '').trim().replace(/\\/g, '/');
|
|
96
|
-
if (!trimmed) {
|
|
97
|
-
console.log('[skip] empty output path');
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const normalizedPath = trimmed === '.'
|
|
102
|
-
? 'out'
|
|
103
|
-
: `${trimmed.replace(/\/$/, '')}/${path.basename(PATHS.root)}-out`;
|
|
104
|
-
|
|
105
|
-
let oldPath = null;
|
|
106
|
-
try {
|
|
107
|
-
oldPath = parseOutputDir(fs.readFileSync(PATHS.eleventyConfig, 'utf8'));
|
|
108
|
-
} catch (err) {
|
|
109
|
-
console.log(`[error] cannot read .eleventy.js: ${err.message}`);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
deleteOldOutput(oldPath);
|
|
114
|
-
|
|
115
|
-
console.log(`\nupdating output path → "${normalizedPath}"`);
|
|
116
|
-
try {
|
|
117
|
-
updatePackageJson(normalizedPath, oldPath);
|
|
118
|
-
updateEleventyConfig(normalizedPath);
|
|
119
|
-
updateTsConfig(normalizedPath);
|
|
120
|
-
} catch (err) {
|
|
121
|
-
console.log(`[error] could not update output path: ${err.message}`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function getCurrentOutputPath() {
|
|
126
|
-
try {
|
|
127
|
-
return parseOutputDir(fs.readFileSync(PATHS.eleventyConfig, 'utf8'));
|
|
128
|
-
} catch {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
module.exports = { updateOutputPath, getCurrentOutputPath };
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const { PATHS } = require('./constants');
|
|
5
|
-
const { toCamelCase, isTypeScriptProject } = require('./utils');
|
|
6
|
-
const { addSiteData, removeSiteData, renameSiteData } = require('./updateData');
|
|
7
|
-
const { addPageBlock, removePageBlock, renamePageBlock } = require('./pageComponents');
|
|
8
|
-
const { getCurrentOutputPath } = require('./updateOutputPath');
|
|
9
|
-
|
|
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`),
|
|
23
|
-
},
|
|
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
|
-
],
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function pageExists(pageName) {
|
|
40
|
-
return fs.existsSync(path.join(PATHS.routes, `${pageName}.njk`));
|
|
41
|
-
}
|
|
42
|
-
|
|
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
|
-
}
|
|
49
|
-
|
|
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
|
-
];
|
|
64
|
-
|
|
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);
|
|
85
|
-
addSiteData(pageName);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function renamePage(oldName, newName) {
|
|
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 },
|
|
105
|
-
];
|
|
106
|
-
|
|
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);
|
|
125
|
-
renameSiteData(oldName, newName);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function removePage(pageName) {
|
|
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}`);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
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}`);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
removePageBlock(pageName);
|
|
159
|
-
removeSiteData(pageName);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
module.exports = { addPage, removePage, renamePage, pageExists };
|
package/tools/modules/utils.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { PATHS } = require('./constants');
|
|
3
|
-
|
|
4
|
-
function toCamelCase(value) {
|
|
5
|
-
return value.toLowerCase().replace(/[-_][a-z0-9]/g, (group) => group.slice(1).toUpperCase());
|
|
6
|
-
}
|
|
7
|
-
|
|
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, '');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function toNiceTitle(pageName) {
|
|
17
|
-
return pageName.split('-')
|
|
18
|
-
.filter(Boolean)
|
|
19
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
20
|
-
.join(' ');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function isTypeScriptProject() {
|
|
24
|
-
return fs.existsSync(PATHS.tsconfig);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
module.exports = { toCamelCase, toKebabCase, toNiceTitle, isTypeScriptProject };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { PATHS, PROTECTED_PAGES } = require('./constants');
|
|
3
|
-
|
|
4
|
-
function validatePageName(name) {
|
|
5
|
-
if (!name) return 'Invalid name.';
|
|
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 (/[<>|?*"']/.test(value)) return 'Path contains invalid characters.';
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function checkRequiredFiles() {
|
|
20
|
-
const required = [
|
|
21
|
-
{ label: '.eleventy.js', path: PATHS.eleventyConfig },
|
|
22
|
-
{ label: 'package.json', path: PATHS.packageJson },
|
|
23
|
-
{ label: 'src/frontend/data/site.json', path: PATHS.siteData },
|
|
24
|
-
{ label: 'src/frontend/layouts/page-components.njk', path: PATHS.pageComponents },
|
|
25
|
-
{ label: 'CLI page templates', path: PATHS.templates },
|
|
26
|
-
];
|
|
27
|
-
return required.filter((item) => !fs.existsSync(item.path));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
module.exports = { validatePageName, validateOutputPath, checkRequiredFiles };
|