nibula 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.eleventy.js +81 -81
  2. package/CHANGELOG.md +384 -377
  3. package/LICENSE +169 -169
  4. package/NOTICE +4 -4
  5. package/README.md +110 -110
  6. package/bin/create.js +505 -505
  7. package/bin/delegate.js +57 -57
  8. package/bin/nibula.js +302 -302
  9. package/docs/Assistant CLI.md +65 -65
  10. package/docs/Backend.md +201 -201
  11. package/docs/Components.md +140 -140
  12. package/docs/Creating pages.md +69 -69
  13. package/docs/Deploy.md +210 -210
  14. package/docs/Head and SEO.md +139 -139
  15. package/docs/Javascript.md +126 -126
  16. package/docs/Styling with SCSS.md +139 -139
  17. package/nginx.conf +113 -113
  18. package/package.json +69 -69
  19. package/src/backend/.htaccess +6 -6
  20. package/src/backend/_core/composer.json +5 -5
  21. package/src/backend/_core/composer.lock +492 -492
  22. package/src/backend/_core/index.js +267 -267
  23. package/src/backend/_core/index.php +160 -160
  24. package/src/backend/_core/init.js +52 -52
  25. package/src/backend/_core/init.php +33 -33
  26. package/src/backend/_core/modules/RateLimiter.js +58 -58
  27. package/src/backend/_core/modules/RateLimiter.php +30 -30
  28. package/src/backend/_core/modules/Response.js +59 -59
  29. package/src/backend/_core/modules/Response.php +48 -48
  30. package/src/backend/api/protected/example-protected.js +23 -23
  31. package/src/backend/api/protected/example-protected.php +16 -16
  32. package/src/backend/api/public/example-public.js +24 -24
  33. package/src/backend/api/public/example-public.php +16 -16
  34. package/src/backend/database/Database.js +46 -46
  35. package/src/backend/database/Database.php +23 -23
  36. package/src/backend/example.config.js +37 -37
  37. package/src/backend/example.config.php +37 -37
  38. package/src/backend/package.json +17 -17
  39. package/src/backend/web.config +43 -43
  40. package/src/frontend/.htaccess +51 -51
  41. package/src/frontend/404.njk +17 -17
  42. package/src/frontend/assets/brand/favicon.svg +54 -54
  43. package/src/frontend/assets/brand/logo.svg +54 -54
  44. package/src/frontend/components/global/footer.njk +25 -25
  45. package/src/frontend/components/global/header.njk +6 -6
  46. package/src/frontend/components/welcome.njk +115 -115
  47. package/src/frontend/index.njk +8 -8
  48. package/src/frontend/js/global.js +10 -10
  49. package/src/frontend/js/modules/exampleModule.js +2 -2
  50. package/src/frontend/js/pages/404.js +8 -8
  51. package/src/frontend/js/pages/homepage.js +8 -8
  52. package/src/frontend/layouts/base.njk +136 -136
  53. package/src/frontend/layouts/page-components.njk +13 -13
  54. package/src/frontend/llms.njk +17 -17
  55. package/src/frontend/robots.njk +7 -7
  56. package/src/frontend/scss/_global.scss +46 -46
  57. package/src/frontend/scss/_root.scss +13 -13
  58. package/src/frontend/scss/modules/_animations.scss +24 -24
  59. package/src/frontend/scss/modules/_footer.scss +28 -28
  60. package/src/frontend/scss/modules/_header.scss +26 -26
  61. package/src/frontend/scss/modules/_mobile.scss +29 -29
  62. package/src/frontend/scss/modules/_typography.scss +18 -18
  63. package/src/frontend/scss/modules/frameworks/_bootstrap.scss +109 -109
  64. package/src/frontend/scss/modules/frameworks/_bulma.scss +108 -108
  65. package/src/frontend/scss/modules/frameworks/_foundation.scss +137 -137
  66. package/src/frontend/scss/modules/frameworks/_uikit.scss +109 -109
  67. package/src/frontend/scss/pages/404.scss +27 -27
  68. package/src/frontend/scss/pages/homepage.scss +22 -22
  69. package/src/frontend/sitemap.njk +17 -17
  70. package/src/frontend/ts/global.ts +11 -11
  71. package/src/frontend/ts/modules/exampleModule.ts +2 -2
  72. package/src/frontend/ts/pages/404.ts +8 -8
  73. package/src/frontend/ts/pages/homepage.ts +8 -8
  74. package/src/frontend/web.config +51 -51
  75. package/tools/assistant.js +122 -122
  76. package/tools/buildJs.js +39 -39
  77. package/tools/cleanOutput.js +23 -23
  78. package/tools/cli/prompt.js +40 -40
  79. package/tools/cli/ui.js +87 -87
  80. package/tools/config/messages.json +80 -80
  81. package/tools/config/settings.json +130 -130
  82. package/tools/lib/colors.js +17 -17
  83. package/tools/lib/files.js +75 -75
  84. package/tools/lib/logger.js +22 -22
  85. package/tools/lib/outputPath.js +141 -141
  86. package/tools/lib/pageActions.js +146 -146
  87. package/tools/lib/pageArtifacts.js +46 -46
  88. package/tools/lib/pageComponents.js +88 -88
  89. package/tools/lib/pagesData.js +87 -87
  90. package/tools/lib/paths.js +63 -63
  91. package/tools/lib/project.js +54 -54
  92. package/tools/lib/text.js +47 -47
  93. package/tools/lib/validation.js +40 -40
  94. package/tools/res/templates/template.js +7 -7
  95. package/tools/res/templates/template.njk +8 -8
  96. package/tools/res/templates/template.scss +22 -22
  97. package/tools/res/templates/template.ts +8 -8
  98. package/tsconfig.json +24 -24
  99. package/src/frontend/scss/pages/prova.scss +0 -23
@@ -1,141 +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 };
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 };
@@ -1,147 +1,147 @@
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, removeDirectoryIfEmpty } = 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 { readPagesData, addPageData, removePageData, renamePageData } = require('./pagesData');
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, keys = Object.keys(FRONT_MATTER_PATTERNS)) {
22
- let content = readText(filePath);
23
-
24
- for (const key of keys) {
25
- content = content.replace(FRONT_MATTER_PATTERNS[key], 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
- addPageData(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
- if (!readPagesData()) return;
79
-
80
- const current = getPageArtifacts(oldName);
81
- const next = getPageArtifacts(newName);
82
- const values = { camelName: next.camelName, pageName: newName };
83
-
84
- const moves = [
85
- { source: current.source.style, destination: next.source.style, isRoute: false },
86
- { source: current.source.script, destination: next.source.script, isRoute: false },
87
- { source: current.source.route, destination: next.source.route, isRoute: true },
88
- ];
89
-
90
- try {
91
- for (const { source, destination, isRoute } of moves) {
92
- if (!exists(source)) {
93
- log('page.fileMissing', { path: source });
94
- continue;
95
- }
96
-
97
- moveFile(source, destination);
98
- log('page.fileRenamed', { source, destination });
99
-
100
- if (isRoute) applyFrontMatter(destination, values, ['permalink']);
101
- }
102
- } catch (error) {
103
- log('page.renameFailed', { error: error.message });
104
- return;
105
- }
106
-
107
- renamePageBlock(oldName, newName);
108
- renamePageData(oldName, newName);
109
- }
110
-
111
- function removePage(pageName) {
112
- if (!pageExists(pageName)) {
113
- log('page.doesNotExist', { name: pageName });
114
- return;
115
- }
116
- if (!readPagesData()) return;
117
-
118
- const { source, output } = getPageArtifacts(pageName);
119
- const files = [source.style, source.script, source.route, ...output.files];
120
-
121
- for (const file of files) {
122
- try {
123
- if (removeFile(file)) log('page.fileDeleted', { path: file });
124
- } catch (error) {
125
- log('page.deleteFailed', { path: file, error: error.message });
126
- }
127
- }
128
-
129
- for (const directory of output.directories) {
130
- try {
131
- if (removeDirectory(directory)) log('page.fileDeleted', { path: directory });
132
- } catch (error) {
133
- log('page.deleteFailed', { path: directory, error: error.message });
134
- }
135
- }
136
-
137
- removePageBlock(pageName);
138
- removePageData(pageName);
139
-
140
- const sourceDirectories = [source.style, source.script, source.route].map(file => path.dirname(file));
141
-
142
- for (const directory of sourceDirectories) {
143
- if (removeDirectoryIfEmpty(directory)) log('page.fileDeleted', { path: directory });
144
- }
145
- }
146
-
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, removeDirectoryIfEmpty } = 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 { readPagesData, addPageData, removePageData, renamePageData } = require('./pagesData');
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, keys = Object.keys(FRONT_MATTER_PATTERNS)) {
22
+ let content = readText(filePath);
23
+
24
+ for (const key of keys) {
25
+ content = content.replace(FRONT_MATTER_PATTERNS[key], 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
+ addPageData(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
+ if (!readPagesData()) return;
79
+
80
+ const current = getPageArtifacts(oldName);
81
+ const next = getPageArtifacts(newName);
82
+ const values = { camelName: next.camelName, pageName: newName };
83
+
84
+ const moves = [
85
+ { source: current.source.style, destination: next.source.style, isRoute: false },
86
+ { source: current.source.script, destination: next.source.script, isRoute: false },
87
+ { source: current.source.route, destination: next.source.route, isRoute: true },
88
+ ];
89
+
90
+ try {
91
+ for (const { source, destination, isRoute } of moves) {
92
+ if (!exists(source)) {
93
+ log('page.fileMissing', { path: source });
94
+ continue;
95
+ }
96
+
97
+ moveFile(source, destination);
98
+ log('page.fileRenamed', { source, destination });
99
+
100
+ if (isRoute) applyFrontMatter(destination, values, ['permalink']);
101
+ }
102
+ } catch (error) {
103
+ log('page.renameFailed', { error: error.message });
104
+ return;
105
+ }
106
+
107
+ renamePageBlock(oldName, newName);
108
+ renamePageData(oldName, newName);
109
+ }
110
+
111
+ function removePage(pageName) {
112
+ if (!pageExists(pageName)) {
113
+ log('page.doesNotExist', { name: pageName });
114
+ return;
115
+ }
116
+ if (!readPagesData()) return;
117
+
118
+ const { source, output } = getPageArtifacts(pageName);
119
+ const files = [source.style, source.script, source.route, ...output.files];
120
+
121
+ for (const file of files) {
122
+ try {
123
+ if (removeFile(file)) log('page.fileDeleted', { path: file });
124
+ } catch (error) {
125
+ log('page.deleteFailed', { path: file, error: error.message });
126
+ }
127
+ }
128
+
129
+ for (const directory of output.directories) {
130
+ try {
131
+ if (removeDirectory(directory)) log('page.fileDeleted', { path: directory });
132
+ } catch (error) {
133
+ log('page.deleteFailed', { path: directory, error: error.message });
134
+ }
135
+ }
136
+
137
+ removePageBlock(pageName);
138
+ removePageData(pageName);
139
+
140
+ const sourceDirectories = [source.style, source.script, source.route].map(file => path.dirname(file));
141
+
142
+ for (const directory of sourceDirectories) {
143
+ if (removeDirectoryIfEmpty(directory)) log('page.fileDeleted', { path: directory });
144
+ }
145
+ }
146
+
147
147
  module.exports = { addPage, removePage, renamePage };
@@ -1,46 +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 };
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 };