firefox-css-theme 0.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/LICENSE +21 -0
- package/README.md +181 -0
- package/dist/cli.js +101 -0
- package/dist/commands/create.js +34 -0
- package/dist/commands/mcp.js +219 -0
- package/dist/commands/profiles.js +17 -0
- package/dist/commands/save.js +91 -0
- package/dist/commands/start.js +89 -0
- package/dist/commands/styles.js +1 -0
- package/dist/firefox.js +366 -0
- package/dist/index.js +11 -0
- package/dist/processor.js +37 -0
- package/dist/profiles.js +98 -0
- package/dist/registry.js +48 -0
- package/dist/types.js +1 -0
- package/dist/watcher.js +111 -0
- package/package.json +49 -0
package/dist/watcher.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { watch } from "chokidar";
|
|
3
|
+
export class FileWatcher {
|
|
4
|
+
fileSystemWatcher = null;
|
|
5
|
+
targets = new Set();
|
|
6
|
+
timers = new Map();
|
|
7
|
+
/** Registers a target with its file paths to be watched for changes. */
|
|
8
|
+
addTarget(target) {
|
|
9
|
+
this.targets.add(target);
|
|
10
|
+
if (this.fileSystemWatcher) {
|
|
11
|
+
for (const filePath of target.filePaths) {
|
|
12
|
+
this.fileSystemWatcher.add(path.resolve(process.cwd(), filePath));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Updates the list of file paths watched for a registered target. */
|
|
17
|
+
updateFilePaths(target, nextFilePaths) {
|
|
18
|
+
const previousAbsolutePaths = target.filePaths.map((filePath) => path.resolve(process.cwd(), filePath));
|
|
19
|
+
const nextAbsolutePaths = nextFilePaths.map((filePath) => path.resolve(process.cwd(), filePath));
|
|
20
|
+
target.filePaths = nextFilePaths;
|
|
21
|
+
if (this.fileSystemWatcher) {
|
|
22
|
+
for (const previousPath of previousAbsolutePaths) {
|
|
23
|
+
if (!nextAbsolutePaths.includes(previousPath)) {
|
|
24
|
+
this.fileSystemWatcher.unwatch(previousPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const nextPath of nextAbsolutePaths) {
|
|
28
|
+
if (!previousAbsolutePaths.includes(nextPath)) {
|
|
29
|
+
this.fileSystemWatcher.add(nextPath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Removes a target from the active watch list. */
|
|
35
|
+
removeTarget(target) {
|
|
36
|
+
this.targets.delete(target);
|
|
37
|
+
const debounceTimer = this.timers.get(target);
|
|
38
|
+
if (debounceTimer) {
|
|
39
|
+
clearTimeout(debounceTimer);
|
|
40
|
+
this.timers.delete(target);
|
|
41
|
+
}
|
|
42
|
+
if (this.fileSystemWatcher) {
|
|
43
|
+
for (const filePath of target.filePaths) {
|
|
44
|
+
this.fileSystemWatcher.unwatch(path.resolve(process.cwd(), filePath));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Initialises the file watcher and begins monitoring all registered target
|
|
50
|
+
* file paths.
|
|
51
|
+
*/
|
|
52
|
+
async start() {
|
|
53
|
+
if (this.fileSystemWatcher)
|
|
54
|
+
return;
|
|
55
|
+
const targetPaths = [];
|
|
56
|
+
for (const target of this.targets) {
|
|
57
|
+
for (const filePath of target.filePaths) {
|
|
58
|
+
const absolutePath = path.resolve(process.cwd(), filePath);
|
|
59
|
+
if (!targetPaths.includes(absolutePath)) {
|
|
60
|
+
targetPaths.push(absolutePath);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.fileSystemWatcher = watch(targetPaths, {
|
|
65
|
+
awaitWriteFinish: { pollInterval: 50, stabilityThreshold: 100 },
|
|
66
|
+
ignoreInitial: true,
|
|
67
|
+
persistent: true,
|
|
68
|
+
});
|
|
69
|
+
this.fileSystemWatcher.on("change", (changedFilePath) => {
|
|
70
|
+
this.handleFileChange("change", changedFilePath);
|
|
71
|
+
});
|
|
72
|
+
this.fileSystemWatcher.on("add", (addedFilePath) => {
|
|
73
|
+
this.handleFileChange("add", addedFilePath);
|
|
74
|
+
});
|
|
75
|
+
this.fileSystemWatcher.on("unlink", (unlinkedFilePath) => {
|
|
76
|
+
this.handleFileChange("unlink", unlinkedFilePath);
|
|
77
|
+
});
|
|
78
|
+
await new Promise((resolve) => {
|
|
79
|
+
this.fileSystemWatcher.on("ready", () => resolve());
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Closes the file system watcher and cancels any pending debounce timers. */
|
|
83
|
+
async close() {
|
|
84
|
+
for (const timer of this.timers.values()) {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
}
|
|
87
|
+
this.timers.clear();
|
|
88
|
+
if (this.fileSystemWatcher) {
|
|
89
|
+
await this.fileSystemWatcher.close();
|
|
90
|
+
this.fileSystemWatcher = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
handleFileChange(eventType, changedFilePath) {
|
|
94
|
+
const absoluteChangedPath = path.resolve(process.cwd(), changedFilePath);
|
|
95
|
+
for (const target of this.targets) {
|
|
96
|
+
const targetAbsolutePaths = target.filePaths.map((filePath) => path.resolve(process.cwd(), filePath));
|
|
97
|
+
const isMatch = targetAbsolutePaths.includes(absoluteChangedPath);
|
|
98
|
+
if (isMatch) {
|
|
99
|
+
const existingTimer = this.timers.get(target);
|
|
100
|
+
if (existingTimer)
|
|
101
|
+
clearTimeout(existingTimer);
|
|
102
|
+
const newTimer = setTimeout(async () => {
|
|
103
|
+
this.timers.delete(target);
|
|
104
|
+
await target.onChange(eventType, absoluteChangedPath);
|
|
105
|
+
}, 100);
|
|
106
|
+
this.timers.set(target, newTimer);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export const fileWatcher = new FileWatcher();
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "firefox-css-theme",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CLI toolkit and Model Context Protocol server for building, inspecting, and live-debugging Firefox UI and CSS themes.",
|
|
5
|
+
"homepage": "https://github.com/easonwong-de/Firefox-CSS-Theme-MCP#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/easonwong-de/Firefox-CSS-Theme-MCP/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/easonwong-de/Firefox-CSS-Theme-MCP.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Eason Wong <me@easonwong.de> (https://easonwong.de/)",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"bin": {
|
|
18
|
+
"firefox-css-theme": "dist/cli.js",
|
|
19
|
+
"firefox-css-theme-mcp": "dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"format": "prettier --write --list-different .",
|
|
27
|
+
"prepublishOnly": "npm run build",
|
|
28
|
+
"start": "node dist/cli.js start",
|
|
29
|
+
"test": "tsx tests/run.ts --headless"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
33
|
+
"chokidar": "^4.0.3",
|
|
34
|
+
"commander": "^15.0.0",
|
|
35
|
+
"postcss": "^8.5.3",
|
|
36
|
+
"postcss-import": "^16.1.0",
|
|
37
|
+
"selenium-webdriver": "^4.47.0",
|
|
38
|
+
"zod": "^4.4.3"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^26.2.0",
|
|
42
|
+
"@types/postcss-import": "^14.0.3",
|
|
43
|
+
"@types/selenium-webdriver": "^4.35.6",
|
|
44
|
+
"prettier": "^3.9.6",
|
|
45
|
+
"prettier-plugin-jsdoc": "^1.8.1",
|
|
46
|
+
"tsx": "^4.23.12",
|
|
47
|
+
"typescript": "^7.0.2"
|
|
48
|
+
}
|
|
49
|
+
}
|