nibula 1.2.2 → 1.2.4
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 +0 -5
- package/CHANGELOG.md +21 -9
- package/README.md +1 -1
- package/bin/create.js +1 -2
- package/bin/nibula.js +2 -5
- package/nginx.conf +75 -75
- package/package.json +74 -74
- package/src/backend/_core/index.js +267 -267
- package/src/backend/_core/init.js +52 -52
- package/src/backend/_core/modules/RateLimiter.js +58 -58
- package/src/backend/_core/modules/Response.js +59 -59
- package/src/backend/api/protected/example-protected.js +23 -23
- package/src/backend/api/public/example-public.js +24 -24
- package/src/backend/backend-node.service.example +30 -30
- package/src/backend/database/Database.js +46 -46
- package/src/backend/example.config.js +37 -37
- package/src/backend/package.json +18 -18
- package/src/frontend/.htaccess +51 -51
- package/src/frontend/assets/brand/favicon.svg +54 -54
- package/src/frontend/assets/brand/logo.svg +54 -54
- package/src/frontend/data/site.json +48 -48
- 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 +76 -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 +3 -1
- package/tools/res/templates/template.ts +3 -1
- 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 -112
- package/tools/modules/updatePage.js +0 -162
- package/tools/modules/utils.js +0 -27
- package/tools/modules/validation.js +0 -30
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": {
|
|
3
|
+
"marker": ".eleventy.js",
|
|
4
|
+
"defaultOutputDirectory": "out",
|
|
5
|
+
"currentDirectory": ".",
|
|
6
|
+
"outputSuffix": "-out"
|
|
7
|
+
},
|
|
8
|
+
"paths": {
|
|
9
|
+
"eleventyConfig": ".eleventy.js",
|
|
10
|
+
"packageJson": "package.json",
|
|
11
|
+
"typescriptConfig": "tsconfig.json",
|
|
12
|
+
"routes": "src/frontend/routes",
|
|
13
|
+
"stylePages": "src/frontend/scss/pages",
|
|
14
|
+
"siteData": "src/frontend/data/site.json",
|
|
15
|
+
"pageComponents": "src/frontend/layouts/page-components.njk",
|
|
16
|
+
"templates": "tools/res/templates"
|
|
17
|
+
},
|
|
18
|
+
"languages": {
|
|
19
|
+
"javascript": {
|
|
20
|
+
"pages": "src/frontend/js/pages",
|
|
21
|
+
"extension": "js",
|
|
22
|
+
"template": "template.js"
|
|
23
|
+
},
|
|
24
|
+
"typescript": {
|
|
25
|
+
"pages": "src/frontend/ts/pages",
|
|
26
|
+
"extension": "ts",
|
|
27
|
+
"template": "template.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"packageJson": {
|
|
31
|
+
"outputKey": "outputDir",
|
|
32
|
+
"scriptsKey": "scripts"
|
|
33
|
+
},
|
|
34
|
+
"eleventy": {
|
|
35
|
+
"outputVariable": "OUTPUT_DIR"
|
|
36
|
+
},
|
|
37
|
+
"typescript": {
|
|
38
|
+
"outputKey": "outDir",
|
|
39
|
+
"outputSubdirectory": "ts"
|
|
40
|
+
},
|
|
41
|
+
"build": {
|
|
42
|
+
"scriptOutput": "js/pages"
|
|
43
|
+
},
|
|
44
|
+
"outputScripts": [
|
|
45
|
+
{
|
|
46
|
+
"name": "build:css",
|
|
47
|
+
"marker": "/css",
|
|
48
|
+
"template": "sass src/frontend/scss:{output}/css --no-source-map --style=compressed --quiet --load-path=node_modules"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "serve:css",
|
|
52
|
+
"marker": "/css",
|
|
53
|
+
"template": "sass --watch src/frontend/scss:{output}/css --no-source-map --quiet --load-path=node_modules"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "build:js",
|
|
57
|
+
"marker": "/js/pages",
|
|
58
|
+
"template": "esbuild \"{entries}\" --bundle --outdir={output}/js/pages --minify"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "serve:js",
|
|
62
|
+
"marker": "/js/pages",
|
|
63
|
+
"template": "esbuild \"{entries}\" --bundle --outdir={output}/js/pages --watch"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"page": {
|
|
67
|
+
"protected": ["homepage", "404"],
|
|
68
|
+
"styleExtension": "scss",
|
|
69
|
+
"routeExtension": "njk",
|
|
70
|
+
"styleTemplate": "template.scss",
|
|
71
|
+
"routeTemplate": "template.njk",
|
|
72
|
+
"outputFiles": [
|
|
73
|
+
"js/pages/{camelName}.js",
|
|
74
|
+
"css/pages/{camelName}.css",
|
|
75
|
+
"{pageName}.html",
|
|
76
|
+
"pages/{pageName}.html"
|
|
77
|
+
],
|
|
78
|
+
"outputDirectories": [
|
|
79
|
+
"{pageName}",
|
|
80
|
+
"pages/{pageName}"
|
|
81
|
+
],
|
|
82
|
+
"dataKeys": {
|
|
83
|
+
"pages": "pages",
|
|
84
|
+
"seo": "seo",
|
|
85
|
+
"title": "title"
|
|
86
|
+
},
|
|
87
|
+
"defaultData": {
|
|
88
|
+
"seo": {
|
|
89
|
+
"title": "{title}",
|
|
90
|
+
"description": "description",
|
|
91
|
+
"keywords": "",
|
|
92
|
+
"noindex": false,
|
|
93
|
+
"canonical": ""
|
|
94
|
+
},
|
|
95
|
+
"cdn": {
|
|
96
|
+
"css": [],
|
|
97
|
+
"js": []
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"pageComponents": {
|
|
102
|
+
"anchor": "{% else %}",
|
|
103
|
+
"condition": "{% elif title == \"{camelName}\" %}",
|
|
104
|
+
"include": "{% include \"component.njk\" ignore missing %}"
|
|
105
|
+
},
|
|
106
|
+
"frontMatter": {
|
|
107
|
+
"title": "title: \"{camelName}\"",
|
|
108
|
+
"permalink": "permalink: \"/{pageName}/\""
|
|
109
|
+
},
|
|
110
|
+
"cli": {
|
|
111
|
+
"title": "Nibula CLI",
|
|
112
|
+
"boxWidth": 17,
|
|
113
|
+
"confirmHint": "[y/N]",
|
|
114
|
+
"symbols": {
|
|
115
|
+
"prompt": "\u276f",
|
|
116
|
+
"error": "\u2716",
|
|
117
|
+
"warning": "\u26a0",
|
|
118
|
+
"bullet": "-"
|
|
119
|
+
},
|
|
120
|
+
"colors": {
|
|
121
|
+
"box": "cyan",
|
|
122
|
+
"menuKey": "dim",
|
|
123
|
+
"prompt": "cyan",
|
|
124
|
+
"error": "red",
|
|
125
|
+
"warning": "yellow",
|
|
126
|
+
"notice": "dim"
|
|
127
|
+
},
|
|
128
|
+
"menu": [
|
|
129
|
+
{ "key": "1", "action": "createPage", "label": "Create page" },
|
|
130
|
+
{ "key": "2", "action": "removePage", "label": "Remove page" },
|
|
131
|
+
{ "key": "3", "action": "renamePage", "label": "Rename page" },
|
|
132
|
+
{ "key": "4", "action": "outputPath", "label": "Configure output path" }
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const color = Object.freeze({
|
|
2
|
+
reset: '\x1b[0m',
|
|
3
|
+
bold: '\x1b[1m',
|
|
4
|
+
dim: '\x1b[2m',
|
|
5
|
+
red: '\x1b[31m',
|
|
6
|
+
green: '\x1b[32m',
|
|
7
|
+
yellow: '\x1b[33m',
|
|
8
|
+
blue: '\x1b[34m',
|
|
9
|
+
magenta: '\x1b[35m',
|
|
10
|
+
cyan: '\x1b[36m',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function paint(name, text) {
|
|
14
|
+
return `${color[name] ?? ''}${text}${color.reset}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { color, paint };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const ENCODING = 'utf8';
|
|
5
|
+
const INDENTATION = 2;
|
|
6
|
+
const NEW_LINE = '\n';
|
|
7
|
+
|
|
8
|
+
function exists(target) {
|
|
9
|
+
return fs.existsSync(target);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ensureDirectory(target) {
|
|
13
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readText(target) {
|
|
17
|
+
return fs.readFileSync(target, ENCODING);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeText(target, content) {
|
|
21
|
+
ensureDirectory(target);
|
|
22
|
+
fs.writeFileSync(target, content);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readJson(target) {
|
|
26
|
+
return JSON.parse(readText(target));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeJson(target, data) {
|
|
30
|
+
writeText(target, `${JSON.stringify(data, null, INDENTATION)}${NEW_LINE}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function copyFile(source, destination) {
|
|
34
|
+
ensureDirectory(destination);
|
|
35
|
+
fs.copyFileSync(source, destination);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function moveFile(source, destination) {
|
|
39
|
+
ensureDirectory(destination);
|
|
40
|
+
fs.renameSync(source, destination);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function removeFile(target) {
|
|
44
|
+
if (!exists(target)) return false;
|
|
45
|
+
fs.unlinkSync(target);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function removeDirectory(target) {
|
|
50
|
+
if (!exists(target)) return false;
|
|
51
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
exists,
|
|
57
|
+
readText,
|
|
58
|
+
writeText,
|
|
59
|
+
readJson,
|
|
60
|
+
writeJson,
|
|
61
|
+
copyFile,
|
|
62
|
+
moveFile,
|
|
63
|
+
removeFile,
|
|
64
|
+
removeDirectory,
|
|
65
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const messages = require('../config/messages.json');
|
|
2
|
+
const { formatText } = require('./text');
|
|
3
|
+
|
|
4
|
+
const KEY_SEPARATOR = '.';
|
|
5
|
+
|
|
6
|
+
function resolveTemplate(key) {
|
|
7
|
+
return key.split(KEY_SEPARATOR).reduce(
|
|
8
|
+
(current, part) => (current !== null && typeof current === 'object' ? current[part] : undefined),
|
|
9
|
+
messages
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function message(key, values) {
|
|
14
|
+
const template = resolveTemplate(key);
|
|
15
|
+
return typeof template === 'string' ? formatText(template, values) : key;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function log(key, values) {
|
|
19
|
+
console.log(message(key, values));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { message, log };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const settings = require('../config/settings.json');
|
|
3
|
+
const { PATHS } = require('./paths');
|
|
4
|
+
const { readText, writeText, readJson, writeJson, removeDirectory } = require('./files');
|
|
5
|
+
const { log } = require('./logger');
|
|
6
|
+
const { formatText } = require('./text');
|
|
7
|
+
const { isTypeScriptProject, scriptEntries } = require('./project');
|
|
8
|
+
|
|
9
|
+
const OUTPUT_VARIABLE = settings.eleventy.outputVariable;
|
|
10
|
+
const TYPESCRIPT_OUTPUT_KEY = settings.typescript.outputKey;
|
|
11
|
+
const OUTPUT_VARIABLE_PATTERN = new RegExp(`const ${OUTPUT_VARIABLE}\\s*=\\s*['"\`]([^'"\`]*)['"\`]`);
|
|
12
|
+
const TYPESCRIPT_OUTPUT_PATTERN = new RegExp(`"${TYPESCRIPT_OUTPUT_KEY}"\\s*:\\s*"[^"]*"`);
|
|
13
|
+
const WINDOWS_DRIVE_PATTERN = /^[a-zA-Z]:/;
|
|
14
|
+
const BACKSLASH_PATTERN = /\\/g;
|
|
15
|
+
const TRAILING_SLASH_PATTERN = /\/$/;
|
|
16
|
+
const FORWARD_SLASH = '/';
|
|
17
|
+
const RELATIVE_PREFIX = './';
|
|
18
|
+
const EMPTY = '';
|
|
19
|
+
|
|
20
|
+
function parseOutputDirectory(content) {
|
|
21
|
+
const match = content.match(OUTPUT_VARIABLE_PATTERN);
|
|
22
|
+
return match ? match[1] : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isAbsolutePath(target) {
|
|
26
|
+
return path.isAbsolute(target) || WINDOWS_DRIVE_PATTERN.test(target);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function updateEleventyConfig(newPath) {
|
|
30
|
+
const content = readText(PATHS.eleventyConfig);
|
|
31
|
+
const updated = content.replace(OUTPUT_VARIABLE_PATTERN, `const ${OUTPUT_VARIABLE} = "${newPath}"`);
|
|
32
|
+
|
|
33
|
+
if (content === updated) {
|
|
34
|
+
log('output.variableMissing', { variable: OUTPUT_VARIABLE, file: settings.paths.eleventyConfig });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
writeText(PATHS.eleventyConfig, updated);
|
|
38
|
+
log('output.fileUpdated', { file: settings.paths.eleventyConfig, path: newPath });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function updatePackageJson(newPath, oldPath) {
|
|
42
|
+
const packageJson = readJson(PATHS.packageJson);
|
|
43
|
+
const entries = scriptEntries();
|
|
44
|
+
|
|
45
|
+
packageJson[settings.packageJson.outputKey] = newPath;
|
|
46
|
+
packageJson[settings.packageJson.scriptsKey] = packageJson[settings.packageJson.scriptsKey] || {};
|
|
47
|
+
|
|
48
|
+
const scripts = packageJson[settings.packageJson.scriptsKey];
|
|
49
|
+
|
|
50
|
+
for (const { name, marker, template } of settings.outputScripts) {
|
|
51
|
+
const current = scripts[name];
|
|
52
|
+
const token = oldPath ? `${oldPath}${marker}` : null;
|
|
53
|
+
|
|
54
|
+
if (current && token && current.includes(token)) {
|
|
55
|
+
scripts[name] = current.split(token).join(`${newPath}${marker}`);
|
|
56
|
+
} else {
|
|
57
|
+
scripts[name] = formatText(template, { output: newPath, entries });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
writeJson(PATHS.packageJson, packageJson);
|
|
62
|
+
log('output.fileUpdated', { file: settings.paths.packageJson, path: newPath });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function updateTypeScriptConfig(newPath) {
|
|
66
|
+
if (!isTypeScriptProject()) return;
|
|
67
|
+
|
|
68
|
+
const prefix = isAbsolutePath(newPath) ? EMPTY : RELATIVE_PREFIX;
|
|
69
|
+
const outputDirectory = `${prefix}${newPath}${FORWARD_SLASH}${settings.typescript.outputSubdirectory}`;
|
|
70
|
+
const content = readText(PATHS.typescriptConfig);
|
|
71
|
+
const updated = content.replace(TYPESCRIPT_OUTPUT_PATTERN, `"${TYPESCRIPT_OUTPUT_KEY}": "${outputDirectory}"`);
|
|
72
|
+
|
|
73
|
+
if (content === updated) {
|
|
74
|
+
log('output.keyMissing', { key: TYPESCRIPT_OUTPUT_KEY, file: settings.paths.typescriptConfig });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
writeText(PATHS.typescriptConfig, updated);
|
|
78
|
+
log('output.fileUpdated', { file: settings.paths.typescriptConfig, path: outputDirectory });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function deleteOldOutput(oldPath) {
|
|
82
|
+
if (!oldPath) return;
|
|
83
|
+
|
|
84
|
+
const target = path.resolve(PATHS.root, oldPath);
|
|
85
|
+
const insideProject = target.startsWith(PATHS.root + path.sep);
|
|
86
|
+
|
|
87
|
+
if (!insideProject || target === PATHS.root) {
|
|
88
|
+
log('output.refuseDelete', { path: target });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (removeDirectory(target)) {
|
|
92
|
+
log('output.directoryDeleted', { path: target });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeOutputPath(rawPath) {
|
|
97
|
+
const trimmed = (rawPath ?? '').trim().replace(BACKSLASH_PATTERN, FORWARD_SLASH);
|
|
98
|
+
if (!trimmed) return null;
|
|
99
|
+
|
|
100
|
+
if (trimmed === settings.project.currentDirectory) return settings.project.defaultOutputDirectory;
|
|
101
|
+
|
|
102
|
+
const parent = trimmed.replace(TRAILING_SLASH_PATTERN, EMPTY);
|
|
103
|
+
return `${parent}${FORWARD_SLASH}${path.basename(PATHS.root)}${settings.project.outputSuffix}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function updateOutputPath(rawPath) {
|
|
107
|
+
const normalizedPath = normalizeOutputPath(rawPath);
|
|
108
|
+
if (!normalizedPath) {
|
|
109
|
+
log('output.emptyPath');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let oldPath = null;
|
|
114
|
+
try {
|
|
115
|
+
oldPath = parseOutputDirectory(readText(PATHS.eleventyConfig));
|
|
116
|
+
} catch (error) {
|
|
117
|
+
log('output.readFailed', { file: settings.paths.eleventyConfig, error: error.message });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
deleteOldOutput(oldPath);
|
|
122
|
+
log('output.updating', { path: normalizedPath });
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
updatePackageJson(normalizedPath, oldPath);
|
|
126
|
+
updateEleventyConfig(normalizedPath);
|
|
127
|
+
updateTypeScriptConfig(normalizedPath);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
log('output.updateFailed', { error: error.message });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getCurrentOutputPath() {
|
|
134
|
+
try {
|
|
135
|
+
return parseOutputDirectory(readText(PATHS.eleventyConfig));
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = { updateOutputPath, getCurrentOutputPath };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const settings = require('../config/settings.json');
|
|
3
|
+
const { PATHS } = require('./paths');
|
|
4
|
+
const { exists, readText, writeText, copyFile, moveFile, removeFile, removeDirectory } = require('./files');
|
|
5
|
+
const { log } = require('./logger');
|
|
6
|
+
const { formatText } = require('./text');
|
|
7
|
+
const { languageSettings } = require('./project');
|
|
8
|
+
const { getPageArtifacts, pageExists } = require('./pageArtifacts');
|
|
9
|
+
const { addSiteData, removeSiteData, renameSiteData } = require('./siteData');
|
|
10
|
+
const { addPageBlock, removePageBlock, renamePageBlock } = require('./pageComponents');
|
|
11
|
+
|
|
12
|
+
const FRONT_MATTER_PATTERNS = Object.freeze({
|
|
13
|
+
title: /^title:.*$/m,
|
|
14
|
+
permalink: /^permalink:.*$/m,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function templatePath(name) {
|
|
18
|
+
return path.join(PATHS.templates, name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function applyFrontMatter(filePath, values) {
|
|
22
|
+
let content = readText(filePath);
|
|
23
|
+
|
|
24
|
+
for (const [key, pattern] of Object.entries(FRONT_MATTER_PATTERNS)) {
|
|
25
|
+
content = content.replace(pattern, formatText(settings.frontMatter[key], values));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
writeText(filePath, content);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function addPage(pageName) {
|
|
32
|
+
if (pageExists(pageName)) {
|
|
33
|
+
log('page.alreadyExists', { name: pageName });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { camelName, source } = getPageArtifacts(pageName);
|
|
38
|
+
const values = { camelName, pageName };
|
|
39
|
+
|
|
40
|
+
const creations = [
|
|
41
|
+
{ destination: source.style, template: settings.page.styleTemplate, isRoute: false },
|
|
42
|
+
{ destination: source.script, template: languageSettings().template, isRoute: false },
|
|
43
|
+
{ destination: source.route, template: settings.page.routeTemplate, isRoute: true },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
for (const { destination, template, isRoute } of creations) {
|
|
48
|
+
const templateFile = templatePath(template);
|
|
49
|
+
|
|
50
|
+
if (!exists(templateFile)) {
|
|
51
|
+
log('page.templateMissing', { path: templateFile });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
copyFile(templateFile, destination);
|
|
56
|
+
if (isRoute) applyFrontMatter(destination, values);
|
|
57
|
+
|
|
58
|
+
log('page.fileCreated', { path: destination });
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
log('page.createFailed', { error: error.message });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addPageBlock(pageName);
|
|
66
|
+
addSiteData(pageName);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function renamePage(oldName, newName) {
|
|
70
|
+
if (!pageExists(oldName)) {
|
|
71
|
+
log('page.doesNotExist', { name: oldName });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (pageExists(newName)) {
|
|
75
|
+
log('page.targetExists', { name: newName });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const current = getPageArtifacts(oldName);
|
|
80
|
+
const next = getPageArtifacts(newName);
|
|
81
|
+
const values = { camelName: next.camelName, pageName: newName };
|
|
82
|
+
|
|
83
|
+
const moves = [
|
|
84
|
+
{ source: current.source.style, destination: next.source.style, isRoute: false },
|
|
85
|
+
{ source: current.source.script, destination: next.source.script, isRoute: false },
|
|
86
|
+
{ source: current.source.route, destination: next.source.route, isRoute: true },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
for (const { source, destination, isRoute } of moves) {
|
|
91
|
+
if (!exists(source)) {
|
|
92
|
+
log('page.fileMissing', { path: source });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
moveFile(source, destination);
|
|
97
|
+
log('page.fileRenamed', { source, destination });
|
|
98
|
+
|
|
99
|
+
if (isRoute) applyFrontMatter(destination, values);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
log('page.renameFailed', { error: error.message });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
renamePageBlock(oldName, newName);
|
|
107
|
+
renameSiteData(oldName, newName);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function removePage(pageName) {
|
|
111
|
+
if (!pageExists(pageName)) {
|
|
112
|
+
log('page.doesNotExist', { name: pageName });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const { source, output } = getPageArtifacts(pageName);
|
|
116
|
+
const files = [source.style, source.script, source.route, ...output.files];
|
|
117
|
+
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
try {
|
|
120
|
+
if (removeFile(file)) log('page.fileDeleted', { path: file });
|
|
121
|
+
} catch (error) {
|
|
122
|
+
log('page.deleteFailed', { path: file, error: error.message });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const directory of output.directories) {
|
|
127
|
+
try {
|
|
128
|
+
if (removeDirectory(directory)) log('page.fileDeleted', { path: directory });
|
|
129
|
+
} catch (error) {
|
|
130
|
+
log('page.deleteFailed', { path: directory, error: error.message });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
removePageBlock(pageName);
|
|
135
|
+
removeSiteData(pageName);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { addPage, removePage, renamePage };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const settings = require('../config/settings.json');
|
|
3
|
+
const { PATHS } = require('./paths');
|
|
4
|
+
const { exists } = require('./files');
|
|
5
|
+
const { languageSettings, scriptPagesDirectory } = require('./project');
|
|
6
|
+
const { getCurrentOutputPath } = require('./outputPath');
|
|
7
|
+
const { toCamelCase, formatText } = require('./text');
|
|
8
|
+
|
|
9
|
+
const PATH_SEPARATOR = '/';
|
|
10
|
+
|
|
11
|
+
function outputRoot() {
|
|
12
|
+
return path.join(PATHS.root, getCurrentOutputPath() || settings.project.defaultOutputDirectory);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveOutput(root, template, values) {
|
|
16
|
+
return path.join(root, ...formatText(template, values).split(PATH_SEPARATOR));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function routeFile(pageName) {
|
|
20
|
+
return path.join(PATHS.routes, `${pageName}.${settings.page.routeExtension}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function pageExists(pageName) {
|
|
24
|
+
return exists(routeFile(pageName));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getPageArtifacts(pageName) {
|
|
28
|
+
const camelName = toCamelCase(pageName);
|
|
29
|
+
const values = { camelName, pageName };
|
|
30
|
+
const root = outputRoot();
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
camelName,
|
|
34
|
+
source: {
|
|
35
|
+
style: path.join(PATHS.stylePages, `${camelName}.${settings.page.styleExtension}`),
|
|
36
|
+
script: path.join(scriptPagesDirectory(), `${camelName}.${languageSettings().extension}`),
|
|
37
|
+
route: routeFile(pageName),
|
|
38
|
+
},
|
|
39
|
+
output: {
|
|
40
|
+
files: settings.page.outputFiles.map((template) => resolveOutput(root, template, values)),
|
|
41
|
+
directories: settings.page.outputDirectories.map((template) => resolveOutput(root, template, values)),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { getPageArtifacts, pageExists };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const settings = require('../config/settings.json');
|
|
3
|
+
const { PATHS } = require('./paths');
|
|
4
|
+
const { exists, readText, writeText } = require('./files');
|
|
5
|
+
const { log } = require('./logger');
|
|
6
|
+
const { toCamelCase, formatText } = require('./text');
|
|
7
|
+
|
|
8
|
+
const EXTRA_BLANK_LINES_PATTERN = /\n\s*\n\s*\n/g;
|
|
9
|
+
const BLANK_LINES = '\n\n';
|
|
10
|
+
const ANCHOR = settings.pageComponents.anchor;
|
|
11
|
+
|
|
12
|
+
function blockPattern(camelName) {
|
|
13
|
+
return new RegExp(
|
|
14
|
+
`[ \\t]*\\{%\\s*elif\\s+title\\s*==\\s*"${camelName}"\\s*%\\}[\\s\\S]*?(?=[ \\t]*\\{%\\s*(?:elif|else|endif))`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function conditionFor(camelName) {
|
|
19
|
+
return formatText(settings.pageComponents.condition, { camelName });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readPageComponents() {
|
|
23
|
+
if (!exists(PATHS.pageComponents)) {
|
|
24
|
+
log('components.fileMissing', { path: PATHS.pageComponents });
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return readText(PATHS.pageComponents);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writePageComponents(content) {
|
|
31
|
+
writeText(PATHS.pageComponents, content);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function addPageBlock(pageName) {
|
|
35
|
+
const content = readPageComponents();
|
|
36
|
+
if (content === null) return;
|
|
37
|
+
|
|
38
|
+
const camelName = toCamelCase(pageName);
|
|
39
|
+
const condition = conditionFor(camelName);
|
|
40
|
+
if (content.includes(condition)) return;
|
|
41
|
+
|
|
42
|
+
if (!content.includes(ANCHOR)) {
|
|
43
|
+
log('components.anchorMissing', { anchor: ANCHOR, file: path.basename(PATHS.pageComponents) });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const block = `${condition}\n${settings.pageComponents.include}\n\n`;
|
|
48
|
+
|
|
49
|
+
writePageComponents(content.replace(ANCHOR, `${block}${ANCHOR}`));
|
|
50
|
+
log('components.blockAdded', { name: camelName });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function removePageBlock(pageName) {
|
|
54
|
+
const content = readPageComponents();
|
|
55
|
+
if (content === null) return;
|
|
56
|
+
|
|
57
|
+
const camelName = toCamelCase(pageName);
|
|
58
|
+
if (!blockPattern(camelName).test(content)) {
|
|
59
|
+
log('components.blockMissing', { name: camelName });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const updated = content
|
|
64
|
+
.replace(blockPattern(camelName), '')
|
|
65
|
+
.replace(EXTRA_BLANK_LINES_PATTERN, BLANK_LINES);
|
|
66
|
+
|
|
67
|
+
writePageComponents(updated);
|
|
68
|
+
log('components.blockRemoved', { name: camelName });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function renamePageBlock(oldName, newName) {
|
|
72
|
+
const content = readPageComponents();
|
|
73
|
+
if (content === null) return;
|
|
74
|
+
|
|
75
|
+
const oldCamelName = toCamelCase(oldName);
|
|
76
|
+
const newCamelName = toCamelCase(newName);
|
|
77
|
+
const oldCondition = conditionFor(oldCamelName);
|
|
78
|
+
|
|
79
|
+
if (!content.includes(oldCondition)) {
|
|
80
|
+
log('components.blockMissing', { name: oldCamelName });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
writePageComponents(content.replace(oldCondition, conditionFor(newCamelName)));
|
|
85
|
+
log('components.blockRenamed', { source: oldCamelName, destination: newCamelName });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { addPageBlock, removePageBlock, renamePageBlock };
|