nibula 1.2.3 → 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/CHANGELOG.md +13 -0
- package/README.md +1 -1
- package/bin/create.js +1 -1
- package/bin/nibula.js +3 -6
- package/package.json +1 -1
- 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 -133
- package/tools/modules/updatePage.js +0 -162
- package/tools/modules/utils.js +0 -27
- package/tools/modules/validation.js +0 -30
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": {
|
|
3
|
+
"notInside": "Not inside a Nibula project."
|
|
4
|
+
},
|
|
5
|
+
"validation": {
|
|
6
|
+
"invalidName": "Invalid name.",
|
|
7
|
+
"invalidNameCharacters": "Page name can only contain lowercase letters, numbers, and hyphens.",
|
|
8
|
+
"nameStartsWithNumber": "Page name cannot start with a number.",
|
|
9
|
+
"protectedName": "\"{name}\" is a protected page name.",
|
|
10
|
+
"invalidPath": "Invalid path.",
|
|
11
|
+
"invalidPathCharacters": "Path contains invalid characters.",
|
|
12
|
+
"templatesLabel": "CLI page templates"
|
|
13
|
+
},
|
|
14
|
+
"page": {
|
|
15
|
+
"alreadyExists": "[skip] page \"{name}\" already exists",
|
|
16
|
+
"doesNotExist": "[skip] page \"{name}\" does not exist",
|
|
17
|
+
"targetExists": "[skip] target page \"{name}\" already exists",
|
|
18
|
+
"templateMissing": "[skip] template not found: {path}",
|
|
19
|
+
"fileMissing": "[skip] not found: {path}",
|
|
20
|
+
"fileCreated": "[created] {path}",
|
|
21
|
+
"fileRenamed": "[renamed] {source} \u2192 {destination}",
|
|
22
|
+
"fileDeleted": "[deleted] {path}",
|
|
23
|
+
"createFailed": "[error] could not create page files: {error}",
|
|
24
|
+
"renameFailed": "[error] could not rename page files: {error}",
|
|
25
|
+
"deleteFailed": "[error] {path}: {error}"
|
|
26
|
+
},
|
|
27
|
+
"components": {
|
|
28
|
+
"fileMissing": "[skip] not found: {path}",
|
|
29
|
+
"anchorMissing": "[skip] no {anchor} anchor in {file}",
|
|
30
|
+
"blockAdded": "[updated] page block added for \"{name}\"",
|
|
31
|
+
"blockMissing": "[skip] page block for \"{name}\" not found",
|
|
32
|
+
"blockRemoved": "[cleaned] page block removed for \"{name}\"",
|
|
33
|
+
"blockRenamed": "[renamed] page block \"{source}\" \u2192 \"{destination}\""
|
|
34
|
+
},
|
|
35
|
+
"siteData": {
|
|
36
|
+
"fileMissing": "[skip] not found: {path}",
|
|
37
|
+
"parseFailed": "[error] cannot parse {file}: {error}",
|
|
38
|
+
"recordExists": "[skip] record \"{name}\" already exists",
|
|
39
|
+
"recordMissing": "[skip] record \"{name}\" not found",
|
|
40
|
+
"recordAdded": "[updated] record \"{name}\" added",
|
|
41
|
+
"recordRemoved": "[cleaned] record \"{name}\" removed",
|
|
42
|
+
"recordRenamed": "[updated] record \"{source}\" renamed to \"{destination}\""
|
|
43
|
+
},
|
|
44
|
+
"output": {
|
|
45
|
+
"emptyPath": "[skip] empty output path",
|
|
46
|
+
"readFailed": "[error] cannot read {file}: {error}",
|
|
47
|
+
"updateFailed": "[error] could not update output path: {error}",
|
|
48
|
+
"updating": "\nupdating output path \u2192 \"{path}\"",
|
|
49
|
+
"variableMissing": "[skip] {variable} not found in {file}",
|
|
50
|
+
"keyMissing": "[skip] {key} not found in {file}",
|
|
51
|
+
"fileUpdated": "[updated] {file} \u2192 {path}",
|
|
52
|
+
"directoryDeleted": "[deleted] {path}",
|
|
53
|
+
"refuseDelete": "[skip] refusing to delete \"{path}\" (outside project)",
|
|
54
|
+
"missingConfiguration": "(!) {key} not found in {file}",
|
|
55
|
+
"cleaned": "(\u2713) cleaned \u2192 {path}",
|
|
56
|
+
"nothingToClean": "(i) nothing to clean \u2192 {path}"
|
|
57
|
+
},
|
|
58
|
+
"cli": {
|
|
59
|
+
"createPrompt": "Name of the new page: ",
|
|
60
|
+
"removePrompt": "Name of the page to remove: ",
|
|
61
|
+
"renameFromPrompt": "Page to rename: ",
|
|
62
|
+
"renameToPrompt": "New name: ",
|
|
63
|
+
"outputPathPrompt": "New output path: ",
|
|
64
|
+
"menuPrompt": "Choose an option: ",
|
|
65
|
+
"currentOutputPath": "Current path: \"{path}\"",
|
|
66
|
+
"confirmRemove": "This permanently deletes all files for \"{name}\".",
|
|
67
|
+
"cancelled": "Cancelled.",
|
|
68
|
+
"pageNotFound": "Page \"{name}\" does not exist.",
|
|
69
|
+
"sameName": "Old and new name are the same.",
|
|
70
|
+
"invalidOption": "Invalid option.",
|
|
71
|
+
"unexpectedError": "Unexpected error: {error}",
|
|
72
|
+
"missingFiles": "This project is missing required files:",
|
|
73
|
+
"incompleteProject": "The project may be incomplete or created with a different Nibula version.",
|
|
74
|
+
"exitHint": "CTRL + C to exit"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -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 };
|