@daz4126/swifty 3.1.0 → 3.2.0
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/README.md +2 -2
- package/package.json +11 -5
- package/src/build.js +33 -6
- package/src/cli.js +54 -16
- package/src/config.js +21 -9
- package/src/index.js +3 -0
- package/src/minify.js +15 -6
- package/src/pages.js +61 -24
- package/src/watcher.js +15 -2
package/README.md
CHANGED
|
@@ -59,14 +59,14 @@ npx swifty my-site # Create new site in my-site/ folder
|
|
|
59
59
|
npx swifty build # Build static site to dist/ (for production)
|
|
60
60
|
npx swifty start # Build, watch, and serve at localhost:3000 (for development)
|
|
61
61
|
npx swifty build --out dir # Build to custom output directory
|
|
62
|
-
npx swifty deploy "message" # Build,
|
|
62
|
+
npx swifty deploy "message" # Build, commit the output folder, and push
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
### Development vs Production
|
|
66
66
|
|
|
67
67
|
- **`swifty start`** - For development. Includes live reload (auto-refreshes browser on file changes) and file watching with incremental builds for CSS/JS/images.
|
|
68
68
|
- **`swifty build`** - For production deployment. Produces clean output without any development scripts.
|
|
69
|
-
- **`swifty deploy "message"`** - Builds the site and
|
|
69
|
+
- **`swifty deploy "message"`** - Builds the site, commits only the generated output folder, and pushes it to git.
|
|
70
70
|
|
|
71
71
|
## Documentation
|
|
72
72
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daz4126/swifty",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"main": "index.js",
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"main": "./src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js"
|
|
8
|
+
},
|
|
6
9
|
"bin": {
|
|
7
10
|
"swifty": "./src/cli.js"
|
|
8
11
|
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
9
15
|
"files": [
|
|
10
16
|
"src/"
|
|
11
17
|
],
|
|
@@ -45,10 +51,10 @@
|
|
|
45
51
|
},
|
|
46
52
|
"repository": {
|
|
47
53
|
"type": "git",
|
|
48
|
-
"url": "git+https://github.com/
|
|
54
|
+
"url": "git+https://github.com/daz-codes/swifty.git"
|
|
49
55
|
},
|
|
50
56
|
"bugs": {
|
|
51
|
-
"url": "https://github.com/
|
|
57
|
+
"url": "https://github.com/daz-codes/swifty/issues"
|
|
52
58
|
},
|
|
53
|
-
"homepage": "https://github.com/
|
|
59
|
+
"homepage": "https://github.com/daz-codes/swifty#readme"
|
|
54
60
|
}
|
package/src/build.js
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
import { argv } from "process";
|
|
2
|
+
import path from "path";
|
|
2
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
import fsExtra from "fs-extra";
|
|
6
|
+
|
|
3
7
|
import { copyAssets, optimizeImages } from "./assets.js";
|
|
4
8
|
import { generatePages, createPages, addLinks } from "./pages.js";
|
|
5
9
|
import { generateRssFeeds } from "./rss.js";
|
|
6
10
|
import { generateSeoFiles } from "./sitemap.js";
|
|
7
|
-
import { dirs } from "./config.js";
|
|
11
|
+
import { baseDir, dirs } from "./config.js";
|
|
12
|
+
import { clearDataCache } from "./data.js";
|
|
13
|
+
import { resetCaches } from "./layout.js";
|
|
14
|
+
import { clearCache as clearPartialCache } from "./partials.js";
|
|
15
|
+
|
|
16
|
+
const prepareOutputDirectory = async (outputDir) => {
|
|
17
|
+
const projectPath = path.resolve(baseDir);
|
|
18
|
+
const outputPath = path.resolve(baseDir, outputDir);
|
|
19
|
+
const outputContainsProject =
|
|
20
|
+
projectPath === outputPath || projectPath.startsWith(`${outputPath}${path.sep}`);
|
|
21
|
+
|
|
22
|
+
if (outputContainsProject) {
|
|
23
|
+
throw new Error(`Refusing to empty unsafe output directory: ${outputPath}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await fsExtra.emptyDir(outputPath);
|
|
27
|
+
return outputPath;
|
|
28
|
+
};
|
|
8
29
|
|
|
9
30
|
export default async function build(outputDir = dirs.dist) {
|
|
10
31
|
const startTime = performance.now();
|
|
11
32
|
console.log("🚀 Starting build...");
|
|
33
|
+
const resolvedOutputDir = await prepareOutputDirectory(outputDir);
|
|
34
|
+
clearDataCache();
|
|
35
|
+
clearPartialCache();
|
|
12
36
|
|
|
13
37
|
const copyStart = performance.now();
|
|
14
|
-
await copyAssets(
|
|
38
|
+
await copyAssets(resolvedOutputDir);
|
|
15
39
|
const copyTime = performance.now() - copyStart;
|
|
16
40
|
console.log(`📁 Assets copied in ${(copyTime / 1000).toFixed(2)}s`);
|
|
41
|
+
await resetCaches();
|
|
17
42
|
|
|
18
43
|
const imageStart = performance.now();
|
|
19
|
-
await optimizeImages(
|
|
44
|
+
await optimizeImages(resolvedOutputDir);
|
|
20
45
|
const imageTime = performance.now() - imageStart;
|
|
21
46
|
console.log(`🖼️ Images optimized in ${(imageTime / 1000).toFixed(2)}s`);
|
|
22
47
|
|
|
@@ -31,17 +56,17 @@ export default async function build(outputDir = dirs.dist) {
|
|
|
31
56
|
console.log(`🔗 Links added in ${(linksTime / 1000).toFixed(2)}s`);
|
|
32
57
|
|
|
33
58
|
const createStart = performance.now();
|
|
34
|
-
await createPages(pages,
|
|
59
|
+
await createPages(pages, resolvedOutputDir);
|
|
35
60
|
const createTime = performance.now() - createStart;
|
|
36
61
|
console.log(`✨ Pages created in ${(createTime / 1000).toFixed(2)}s`);
|
|
37
62
|
|
|
38
63
|
const rssStart = performance.now();
|
|
39
|
-
await generateRssFeeds(pages,
|
|
64
|
+
await generateRssFeeds(pages, resolvedOutputDir);
|
|
40
65
|
const rssTime = performance.now() - rssStart;
|
|
41
66
|
console.log(`📡 RSS feeds generated in ${(rssTime / 1000).toFixed(2)}s`);
|
|
42
67
|
|
|
43
68
|
const seoStart = performance.now();
|
|
44
|
-
await generateSeoFiles(pages,
|
|
69
|
+
await generateSeoFiles(pages, resolvedOutputDir);
|
|
45
70
|
const seoTime = performance.now() - seoStart;
|
|
46
71
|
console.log(`🧭 SEO files generated in ${(seoTime / 1000).toFixed(2)}s`);
|
|
47
72
|
|
|
@@ -70,6 +95,8 @@ export default async function build(outputDir = dirs.dist) {
|
|
|
70
95
|
}
|
|
71
96
|
}
|
|
72
97
|
|
|
98
|
+
export { prepareOutputDirectory };
|
|
99
|
+
|
|
73
100
|
// Run the build when invoked directly (e.g. `npm run build`, `npm start`),
|
|
74
101
|
// not when imported as a module (e.g. by cli.js).
|
|
75
102
|
if (argv[1] && fileURLToPath(import.meta.url) === argv[1]) {
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawn,
|
|
3
|
+
import { execFileSync, spawn, spawnSync } from "child_process";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
4
5
|
|
|
5
6
|
const args = process.argv.slice(2);
|
|
6
7
|
let outDir = "dist"; // default
|
|
@@ -14,7 +15,46 @@ if (outIndex !== -1 && args[outIndex + 1]) {
|
|
|
14
15
|
// Pass outDir as an environment variable too (optional, still useful)
|
|
15
16
|
process.env.OUT_DIR = outDir;
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
+
const commitAndPushOutput = (
|
|
19
|
+
outputDir,
|
|
20
|
+
commitMessage,
|
|
21
|
+
{ execFile = execFileSync, spawnFile = spawnSync } = {},
|
|
22
|
+
) => {
|
|
23
|
+
const existingDiff = spawnFile("git", ["diff", "--cached", "--quiet"], {
|
|
24
|
+
stdio: "ignore",
|
|
25
|
+
});
|
|
26
|
+
if (existingDiff.error) throw existingDiff.error;
|
|
27
|
+
if (existingDiff.status === 1) {
|
|
28
|
+
throw new Error("Refusing to deploy while unrelated changes are already staged.");
|
|
29
|
+
}
|
|
30
|
+
if (existingDiff.status !== 0) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Unable to inspect staged changes (git exited ${existingDiff.status}).`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`Adding generated output from ${outputDir} to git...`);
|
|
37
|
+
execFile("git", ["add", "--", outputDir], { stdio: "inherit" });
|
|
38
|
+
|
|
39
|
+
const diffResult = spawnFile("git", ["diff", "--cached", "--quiet"], {
|
|
40
|
+
stdio: "ignore",
|
|
41
|
+
});
|
|
42
|
+
if (diffResult.error) throw diffResult.error;
|
|
43
|
+
if (diffResult.status === 0) {
|
|
44
|
+
console.log("No generated output changes to deploy.");
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (diffResult.status !== 1) {
|
|
48
|
+
throw new Error(`Unable to inspect staged changes (git exited ${diffResult.status}).`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log("Committing generated output...");
|
|
52
|
+
execFile("git", ["commit", "-m", commitMessage], { stdio: "inherit" });
|
|
53
|
+
|
|
54
|
+
console.log("Pushing to remote...");
|
|
55
|
+
execFile("git", ["push"], { stdio: "inherit" });
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
18
58
|
|
|
19
59
|
async function main() {
|
|
20
60
|
const command = args[0];
|
|
@@ -64,7 +104,11 @@ async function main() {
|
|
|
64
104
|
break;
|
|
65
105
|
}
|
|
66
106
|
case "deploy": {
|
|
67
|
-
const
|
|
107
|
+
const commandArgs = args.slice(1).filter((arg, index, values) => {
|
|
108
|
+
if (arg === "--out") return false;
|
|
109
|
+
return index === 0 || values[index - 1] !== "--out";
|
|
110
|
+
});
|
|
111
|
+
const commitMessage = commandArgs[0] || "Deploying latest build";
|
|
68
112
|
|
|
69
113
|
// Build the site
|
|
70
114
|
const build = await import("./build.js");
|
|
@@ -74,18 +118,8 @@ async function main() {
|
|
|
74
118
|
|
|
75
119
|
// Git operations
|
|
76
120
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
console.log("Committing changes...");
|
|
81
|
-
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
|
|
82
|
-
stdio: "inherit",
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
console.log("Pushing to remote...");
|
|
86
|
-
execSync("git push", { stdio: "inherit" });
|
|
87
|
-
|
|
88
|
-
console.log("Deployed successfully!");
|
|
121
|
+
const deployed = commitAndPushOutput(outDir, commitMessage);
|
|
122
|
+
if (deployed) console.log("Deployed successfully!");
|
|
89
123
|
} catch (error) {
|
|
90
124
|
console.error("Deploy failed:", error.message);
|
|
91
125
|
process.exit(1);
|
|
@@ -104,4 +138,8 @@ async function main() {
|
|
|
104
138
|
}
|
|
105
139
|
}
|
|
106
140
|
|
|
107
|
-
|
|
141
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
142
|
+
main();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { commitAndPushOutput, main };
|
package/src/config.js
CHANGED
|
@@ -64,15 +64,27 @@ const builtInDefaults = {
|
|
|
64
64
|
navigation_cache_ttl: 15,
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
const
|
|
68
|
-
const hasConfig = (key) => Object.prototype.hasOwnProperty.call(loadedConfig, key);
|
|
67
|
+
const defaultConfig = {};
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
loadedConfig
|
|
69
|
+
const reloadConfig = async () => {
|
|
70
|
+
const loadedConfig = await loadConfig(baseDir);
|
|
71
|
+
const hasConfig = (key) =>
|
|
72
|
+
Object.prototype.hasOwnProperty.call(loadedConfig, key);
|
|
73
|
+
|
|
74
|
+
if (hasConfig('turbo')) {
|
|
75
|
+
console.warn('The "turbo" config option is deprecated. Use "morphing" and "prefetching" instead.');
|
|
76
|
+
if (!hasConfig('morphing')) {
|
|
77
|
+
loadedConfig.morphing = loadedConfig.turbo;
|
|
78
|
+
}
|
|
74
79
|
}
|
|
75
|
-
}
|
|
76
|
-
const defaultConfig = { ...builtInDefaults, ...loadedConfig };
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
for (const key of Object.keys(defaultConfig)) {
|
|
82
|
+
delete defaultConfig[key];
|
|
83
|
+
}
|
|
84
|
+
Object.assign(defaultConfig, builtInDefaults, loadedConfig);
|
|
85
|
+
return defaultConfig;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
await reloadConfig();
|
|
89
|
+
|
|
90
|
+
export { baseDir, dirs, defaultConfig, loadConfig, reloadConfig };
|
package/src/index.js
ADDED
package/src/minify.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
let protectionSequence = 0;
|
|
2
|
+
|
|
1
3
|
const protectBlocks = (content, pattern) => {
|
|
2
4
|
const blocks = [];
|
|
5
|
+
const tokenPrefix = `__SWIFTY_MINIFY_BLOCK_${protectionSequence++}_`;
|
|
3
6
|
const protectedContent = content.replace(pattern, (match) => {
|
|
4
|
-
const token =
|
|
7
|
+
const token = `${tokenPrefix}${blocks.length}__`;
|
|
5
8
|
blocks.push(match);
|
|
6
9
|
return token;
|
|
7
10
|
});
|
|
@@ -9,7 +12,10 @@ const protectBlocks = (content, pattern) => {
|
|
|
9
12
|
return {
|
|
10
13
|
content: protectedContent,
|
|
11
14
|
restore: (value) =>
|
|
12
|
-
value.replace(
|
|
15
|
+
value.replace(
|
|
16
|
+
new RegExp(`${tokenPrefix}(\\d+)__`, "g"),
|
|
17
|
+
(_, index) => blocks[index],
|
|
18
|
+
),
|
|
13
19
|
};
|
|
14
20
|
};
|
|
15
21
|
|
|
@@ -116,14 +122,17 @@ const minifyHtml = (html) => {
|
|
|
116
122
|
html,
|
|
117
123
|
/<(pre|code|textarea|script|style)\b[^>]*>[\s\S]*?<\/\1>/gi,
|
|
118
124
|
);
|
|
125
|
+
const strings = protectBlocks(
|
|
126
|
+
blocks.content,
|
|
127
|
+
/(["'])(?:\\[\s\S]|(?!\1)[^\\])*\1/g,
|
|
128
|
+
);
|
|
119
129
|
|
|
120
|
-
const minified =
|
|
130
|
+
const minified = strings.content
|
|
121
131
|
.replace(/<!--(?!\[if|<!|>)[\s\S]*?-->/g, "")
|
|
122
|
-
.replace(/\s
|
|
123
|
-
.replace(/>\s+</g, "><")
|
|
132
|
+
.replace(/\s+/g, " ")
|
|
124
133
|
.trim();
|
|
125
134
|
|
|
126
|
-
return blocks.restore(minified);
|
|
135
|
+
return blocks.restore(strings.restore(minified));
|
|
127
136
|
};
|
|
128
137
|
|
|
129
138
|
export { minifyCss, minifyHtml, minifyJs };
|
package/src/pages.js
CHANGED
|
@@ -44,6 +44,54 @@ const parseDate = (dateValue) => {
|
|
|
44
44
|
return isNaN(parsed.getTime()) ? null : parsed;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
const applyMarkdownFileToPage = async (
|
|
48
|
+
page,
|
|
49
|
+
filePath,
|
|
50
|
+
{ notFound = false, folderIndex = false } = {},
|
|
51
|
+
) => {
|
|
52
|
+
const markdownContent = await fs.readFile(filePath, "utf-8");
|
|
53
|
+
const { data, content } = matter(markdownContent);
|
|
54
|
+
Object.assign(page, { meta: { ...page.meta, ...data }, content });
|
|
55
|
+
|
|
56
|
+
if (folderIndex) {
|
|
57
|
+
const stats = await fs.stat(filePath);
|
|
58
|
+
const { dateFormat } = page.meta;
|
|
59
|
+
page.hasIndexContent = true;
|
|
60
|
+
page.indexFilePath = filePath;
|
|
61
|
+
page.createdAtObj = stats.birthtime;
|
|
62
|
+
page.updatedAtObj = stats.mtime;
|
|
63
|
+
page.created_at = new Date(stats.birthtime).toLocaleDateString(
|
|
64
|
+
undefined,
|
|
65
|
+
dateFormat,
|
|
66
|
+
);
|
|
67
|
+
page.updated_at = new Date(stats.mtime).toLocaleDateString(
|
|
68
|
+
undefined,
|
|
69
|
+
dateFormat,
|
|
70
|
+
);
|
|
71
|
+
page.date = new Date(stats.birthtime).toLocaleDateString(undefined, dateFormat);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (notFound && data.sitemap === undefined) {
|
|
75
|
+
page.meta.sitemap = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
page.title = page.meta.title || page.title;
|
|
79
|
+
page.name = page.meta.title || page.name;
|
|
80
|
+
|
|
81
|
+
if (typeof data.nav === "boolean") {
|
|
82
|
+
page.nav = data.nav;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (data.date) {
|
|
86
|
+
const parsedDate = parseDate(data.date);
|
|
87
|
+
if (parsedDate) {
|
|
88
|
+
page.dateObj = parsedDate;
|
|
89
|
+
page.date = parsedDate.toLocaleDateString(undefined, page.meta.dateFormat);
|
|
90
|
+
page.meta.date = page.date;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
47
95
|
const tagsMap = new Map();
|
|
48
96
|
const pageIndex = [];
|
|
49
97
|
const pageIndexUrls = new Set();
|
|
@@ -78,6 +126,8 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
|
|
|
78
126
|
files,
|
|
79
127
|
async (file) => {
|
|
80
128
|
const filePath = path.join(sourceDir, file.name);
|
|
129
|
+
if (parent && file.name === "index.md") return null;
|
|
130
|
+
|
|
81
131
|
const stats = await getValidStats(filePath);
|
|
82
132
|
if (!stats) return null;
|
|
83
133
|
|
|
@@ -130,28 +180,11 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
|
|
|
130
180
|
};
|
|
131
181
|
|
|
132
182
|
if (path.extname(file.name) === ".md") {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
page
|
|
138
|
-
}
|
|
139
|
-
page.title = page.meta.title || page.title;
|
|
140
|
-
page.name = page.meta.title || page.name;
|
|
141
|
-
|
|
142
|
-
// Allow front matter to override nav (opt in or opt out)
|
|
143
|
-
if (typeof data.nav === "boolean") {
|
|
144
|
-
page.nav = data.nav;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// If front matter has a date, parse and format it
|
|
148
|
-
if (data.date) {
|
|
149
|
-
const parsedDate = parseDate(data.date);
|
|
150
|
-
if (parsedDate) {
|
|
151
|
-
page.dateObj = parsedDate;
|
|
152
|
-
page.date = parsedDate.toLocaleDateString(undefined, dateFormat);
|
|
153
|
-
page.meta.date = page.date;
|
|
154
|
-
}
|
|
183
|
+
await applyMarkdownFileToPage(page, filePath, { notFound });
|
|
184
|
+
} else if (isDirectory) {
|
|
185
|
+
const indexPath = path.join(filePath, "index.md");
|
|
186
|
+
if (await fsExtra.pathExists(indexPath)) {
|
|
187
|
+
await applyMarkdownFileToPage(page, indexPath, { folderIndex: true });
|
|
155
188
|
}
|
|
156
189
|
}
|
|
157
190
|
|
|
@@ -208,7 +241,9 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
|
|
|
208
241
|
page.visiblePages = chunks[0];
|
|
209
242
|
|
|
210
243
|
// First page content
|
|
211
|
-
|
|
244
|
+
if (!page.hasIndexContent) {
|
|
245
|
+
page.content = await generateLinkList(page.filename, chunks[0]);
|
|
246
|
+
}
|
|
212
247
|
page.meta.pagination = generatePaginationNav({
|
|
213
248
|
currentPage: 1,
|
|
214
249
|
totalPages,
|
|
@@ -242,7 +277,9 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
|
|
|
242
277
|
page.paginatedPages.push(paginatedPage);
|
|
243
278
|
}
|
|
244
279
|
} else {
|
|
245
|
-
|
|
280
|
+
if (!page.hasIndexContent) {
|
|
281
|
+
page.content = await generateLinkList(page.filename, page.pages);
|
|
282
|
+
}
|
|
246
283
|
page.meta.pagination = '';
|
|
247
284
|
}
|
|
248
285
|
}
|
package/src/watcher.js
CHANGED
|
@@ -2,7 +2,7 @@ import chokidar from "chokidar";
|
|
|
2
2
|
import livereload from "livereload";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import fs from "fs";
|
|
5
|
-
import { defaultConfig } from "./config.js";
|
|
5
|
+
import { defaultConfig, reloadConfig } from "./config.js";
|
|
6
6
|
|
|
7
7
|
// Directory to extension mapping for filtering
|
|
8
8
|
const dirExtensions = {
|
|
@@ -80,6 +80,14 @@ function getChangeType(filePath) {
|
|
|
80
80
|
return "full"; // pages, layouts, partials, config files
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
const isRootConfigFile = (filePath) => {
|
|
84
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
85
|
+
return (
|
|
86
|
+
!relativePath.includes(path.sep) &&
|
|
87
|
+
["config.yaml", "config.yml", "config.json"].includes(relativePath)
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
83
91
|
export default async function watch(outDir = "dist") {
|
|
84
92
|
const build = await import("./build.js");
|
|
85
93
|
const { copySingleAsset, optimizeSingleImage } = await import("./assets.js");
|
|
@@ -94,12 +102,13 @@ export default async function watch(outDir = "dist") {
|
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
// Start livereload server
|
|
105
|
+
const livereloadPort = defaultConfig.livereload_port || 35729;
|
|
97
106
|
const lrServer = livereload.createServer({
|
|
107
|
+
port: livereloadPort,
|
|
98
108
|
usePolling: true,
|
|
99
109
|
delay: defaultConfig.watcher_delay || 100,
|
|
100
110
|
});
|
|
101
111
|
const outPath = path.join(process.cwd(), outDir);
|
|
102
|
-
const livereloadPort = defaultConfig.livereload_port || 35729;
|
|
103
112
|
lrServer.watch(outPath);
|
|
104
113
|
console.log(`LiveReload server started on port ${livereloadPort}`);
|
|
105
114
|
|
|
@@ -128,6 +137,10 @@ export default async function watch(outDir = "dist") {
|
|
|
128
137
|
const filename = path.basename(filePath);
|
|
129
138
|
|
|
130
139
|
try {
|
|
140
|
+
if (isRootConfigFile(filePath)) {
|
|
141
|
+
await reloadConfig();
|
|
142
|
+
}
|
|
143
|
+
|
|
131
144
|
if (event === "deleted") {
|
|
132
145
|
// For deletions, do a full rebuild to clean up
|
|
133
146
|
console.log(`🗑️ File deleted: ${filename}. Running full build...`);
|