@traisetech/autopilot 0.1.3 → 0.1.6
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 +50 -0
- package/README.md +1 -209
- package/bin/autopilot.js +25 -5
- package/docs/TROUBLESHOOTING.md +21 -40
- package/package.json +5 -6
- package/src/commands/init.js +38 -1
- package/src/config/defaults.js +2 -0
- package/src/config/ignore.js +136 -37
- package/src/config/loader.js +47 -47
- package/src/core/watcher.js +98 -133
- package/src/utils/logger.js +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -15,3 +15,53 @@
|
|
|
15
15
|
- Per-repo config and ignore rules
|
|
16
16
|
|
|
17
17
|
Built by Praise Masunga (PraiseTechzw).
|
|
18
|
+
# Changelog
|
|
19
|
+
|
|
20
|
+
All notable changes to this project will be documented in this file.
|
|
21
|
+
This project follows Semantic Versioning (https://semver.org).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## [0.1.4] – 2026-02-01
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **Windows Compatibility**: Fixed critical issue where absolute paths on Windows caused ignore rules to fail.
|
|
29
|
+
- **Watcher Noise**: Fixed infinite commit loops caused by `.vscode/time-analytics.json` and self-logging.
|
|
30
|
+
- Fixed a critical CLI crash where `autopilot start` failed due to miswired Commander action handlers.
|
|
31
|
+
- Improved command registration to ensure all CLI commands are correctly bound and validated at runtime.
|
|
32
|
+
- Prevented undefined command handlers from causing runtime exceptions.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **Release Gates**: Added `npm run verify` and `prepublishOnly` hooks to prevent broken releases.
|
|
36
|
+
- **Integration Tests**: Added full end-to-end test suite using `node:test`.
|
|
37
|
+
- **Smart Init**: `autopilot init` now automatically adds `autopilot.log` to `.gitignore`.
|
|
38
|
+
- Pre-publish verification pipeline to block publishing broken builds.
|
|
39
|
+
- CLI smoke tests to ensure core commands (`init`, `start`, `status`, `doctor`) do not crash.
|
|
40
|
+
- Test-only dry-run mode for watcher to allow safe automated testing.
|
|
41
|
+
- Additional configuration and commit logic unit tests.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- Standardized command exports across all CLI commands.
|
|
45
|
+
- Improved error messages for misconfigured or invalid commands.
|
|
46
|
+
- Strengthened release hygiene and stability guarantees.
|
|
47
|
+
|
|
48
|
+
### Developer Experience
|
|
49
|
+
- Added `prepublishOnly` guard to prevent accidental publishing of failing builds.
|
|
50
|
+
- Improved Windows compatibility during testing and CLI execution.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## [0.1.3] – 2026-01-31
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
- Initial public release of Autopilot CLI.
|
|
58
|
+
- Intelligent Git automation with smart commit messages.
|
|
59
|
+
- Background watcher with debouncing and safety rails.
|
|
60
|
+
- Branch protection and remote-ahead detection.
|
|
61
|
+
- `doctor` command for environment diagnostics.
|
|
62
|
+
- Per-project configuration via `.autopilotrc.json`.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## [0.1.0] – Initial Development
|
|
67
|
+
- Core architecture and foundational CLI commands.
|
package/README.md
CHANGED
|
@@ -1,210 +1,2 @@
|
|
|
1
|
-
# 🚀 Autopilot CLI
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
**Intelligent Git automation that commits and pushes your code, so you can focus on building.**
|
|
8
|
-
|
|
9
|
-
[](https://www.npmjs.com/package/autopilot-cli)
|
|
10
|
-
[](https://opensource.org/licenses/MIT)
|
|
11
|
-
[](https://nodejs.org)
|
|
12
|
-
[](https://www.npmjs.com/package/autopilot-cli)
|
|
13
|
-
[](https://github.com/PraiseTechzw/autopilot-cli/stargazers)
|
|
14
|
-
[](https://github.com/PraiseTechzw/autopilot-cli/actions)
|
|
15
|
-
[](http://makeapullrequest.com)
|
|
16
|
-
|
|
17
|
-
**Built by [Praise Masunga](https://github.com/PraiseTechzw) (PraiseTechzw)**
|
|
18
|
-
|
|
19
|
-
[Features](#-features) • [Quick Start](#-quick-start) • [Configuration](#-configuration) • [Commands](#-commands) • [Safety](#-safety-features)
|
|
20
|
-
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 📖 Table of Contents
|
|
26
|
-
|
|
27
|
-
- [Why Autopilot?](#-why-autopilot)
|
|
28
|
-
- [Features](#-features)
|
|
29
|
-
- [Quick Start](#-quick-start)
|
|
30
|
-
- [Installation](#-installation)
|
|
31
|
-
- [Commands](#-commands)
|
|
32
|
-
- [Configuration](#-configuration)
|
|
33
|
-
- [Safety Features](#-safety-features)
|
|
34
|
-
- [Troubleshooting](#-troubleshooting)
|
|
35
|
-
- [Contributing](#-contributing)
|
|
36
|
-
- [License](#-license)
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## 🎯 Why Autopilot?
|
|
41
|
-
|
|
42
|
-
<table>
|
|
43
|
-
<tr>
|
|
44
|
-
<td width="50%">
|
|
45
|
-
|
|
46
|
-
### ❌ Before Autopilot
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# Every. Single. Time.
|
|
50
|
-
git add .
|
|
51
|
-
git commit -m "update stuff"
|
|
52
|
-
git push
|
|
53
|
-
|
|
54
|
-
# Repeat 50+ times a day...
|
|
55
|
-
# Lose focus on coding
|
|
56
|
-
# Forget to commit
|
|
57
|
-
# Inconsistent messages
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
</td>
|
|
61
|
-
<td width="50%">
|
|
62
|
-
|
|
63
|
-
### ✅ With Autopilot
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
# One time setup
|
|
67
|
-
autopilot init
|
|
68
|
-
autopilot start
|
|
69
|
-
|
|
70
|
-
# That's it!
|
|
71
|
-
# Focus on coding
|
|
72
|
-
# Auto-commits with smart messages
|
|
73
|
-
# Never lose work again
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
</td>
|
|
77
|
-
</tr>
|
|
78
|
-
</table>
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## ✨ Features
|
|
83
|
-
|
|
84
|
-
- **🧠 Smart Commits**: Generates professional conventional commit messages automatically.
|
|
85
|
-
- **⚡ Watcher Engine**: Real-time file monitoring with smart debouncing using `chokidar`.
|
|
86
|
-
- **🛡️ Safety First**: Blocks commits on protected branches and checks remote status.
|
|
87
|
-
- **🔄 Automated Flow**: Fetches, stages, commits, and pushes (optional) automatically.
|
|
88
|
-
- **⚙️ Zero Config**: Works out of the box, but fully configurable via `.autopilotrc.json`.
|
|
89
|
-
- **🩺 Self-Healing**: Includes a `doctor` command to diagnose and fix issues.
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## 🚀 Quick Start
|
|
94
|
-
|
|
95
|
-
### Installation
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
# Install globally via npm
|
|
99
|
-
npm install -g autopilot-cli
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Usage
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
# 1. Initialize in your project
|
|
106
|
-
cd my-project
|
|
107
|
-
autopilot init
|
|
108
|
-
|
|
109
|
-
# 2. Start the background watcher
|
|
110
|
-
autopilot start
|
|
111
|
-
|
|
112
|
-
# 3. Check status
|
|
113
|
-
autopilot status
|
|
114
|
-
|
|
115
|
-
# 4. Stop when done
|
|
116
|
-
autopilot stop
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## 💻 Commands
|
|
122
|
-
|
|
123
|
-
| Command | Description |
|
|
124
|
-
|---------|-------------|
|
|
125
|
-
| `autopilot init` | Initializes configuration and ignore files in the current directory. |
|
|
126
|
-
| `autopilot start` | Starts the background watcher daemon. |
|
|
127
|
-
| `autopilot stop` | Stops the running watcher daemon. |
|
|
128
|
-
| `autopilot status` | Shows the current status of the watcher process. |
|
|
129
|
-
| `autopilot doctor` | Runs diagnostics to verify environment and configuration. |
|
|
130
|
-
| `autopilot --help` | Displays help information. |
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## ⚙️ Configuration
|
|
135
|
-
|
|
136
|
-
Autopilot uses a `.autopilotrc.json` file for configuration.
|
|
137
|
-
|
|
138
|
-
```json
|
|
139
|
-
{
|
|
140
|
-
"minInterval": 30,
|
|
141
|
-
"autoPush": true,
|
|
142
|
-
"blockedBranches": ["main", "production"],
|
|
143
|
-
"requireChecks": false,
|
|
144
|
-
"ignore": [
|
|
145
|
-
"*.log",
|
|
146
|
-
"temp/",
|
|
147
|
-
"dist/",
|
|
148
|
-
"node_modules"
|
|
149
|
-
]
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
| Option | Type | Default | Description |
|
|
154
|
-
|--------|------|---------|-------------|
|
|
155
|
-
| `minInterval` | `number` | `30` | Minimum seconds between commits. |
|
|
156
|
-
| `autoPush` | `boolean` | `true` | Whether to push changes automatically after commit. |
|
|
157
|
-
| `blockedBranches` | `array` | `[]` | List of branches to disable auto-commit on. |
|
|
158
|
-
| `requireChecks` | `boolean` | `false` | Run custom checks before committing. |
|
|
159
|
-
| `ignore` | `array` | `[]` | Additional glob patterns to ignore. |
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## 🛡️ Safety Features
|
|
164
|
-
|
|
165
|
-
Autopilot includes several safety mechanisms to prevent accidents:
|
|
166
|
-
|
|
167
|
-
1. **Branch Protection**: Will not run on branches listed in `blockedBranches`.
|
|
168
|
-
2. **Remote Sync**: Checks if local branch is behind remote before acting.
|
|
169
|
-
3. **Debouncing**: Waits for file changes to settle before committing.
|
|
170
|
-
4. **PID Management**: Ensures only one instance runs per repository.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## 🔧 Troubleshooting
|
|
175
|
-
|
|
176
|
-
If you encounter issues, run the doctor command:
|
|
177
|
-
|
|
178
|
-
```bash
|
|
179
|
-
autopilot doctor
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
This will check for:
|
|
183
|
-
- Git repository status
|
|
184
|
-
- Configuration validity
|
|
185
|
-
- Node.js version
|
|
186
|
-
- Permissions
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## 🤝 Contributing
|
|
191
|
-
|
|
192
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
193
|
-
|
|
194
|
-
1. Fork the repository
|
|
195
|
-
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
196
|
-
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
197
|
-
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
198
|
-
5. Open a Pull Request
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
## 📜 License
|
|
203
|
-
|
|
204
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
<div align="center">
|
|
209
|
-
<b>Built with ❤️ by <a href="https://github.com/PraiseTechzw">Praise Masunga (PraiseTechzw)</a></b>
|
|
210
|
-
</div>
|
|
2
|
+
<!-- Autopilot test run: 2026-02-01 -->
|
package/bin/autopilot.js
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
4
|
+
const initRepo = require('../src/commands/init');
|
|
5
|
+
const startWatcher = require('../src/commands/start');
|
|
6
|
+
const stopWatcher = require('../src/commands/stop');
|
|
7
|
+
const statusWatcher = require('../src/commands/status');
|
|
8
|
+
const doctor = require('../src/commands/doctor');
|
|
9
9
|
const pkg = require('../package.json');
|
|
10
|
+
const logger = require('../src/utils/logger');
|
|
11
|
+
|
|
12
|
+
// Validate command handlers
|
|
13
|
+
const commands = {
|
|
14
|
+
init: initRepo,
|
|
15
|
+
start: startWatcher,
|
|
16
|
+
stop: stopWatcher,
|
|
17
|
+
status: statusWatcher,
|
|
18
|
+
doctor: doctor
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Runtime assertion to prevent wiring errors
|
|
22
|
+
Object.entries(commands).forEach(([name, handler]) => {
|
|
23
|
+
if (typeof handler !== 'function') {
|
|
24
|
+
console.error(`\n❌ FATAL ERROR: Command handler for '${name}' is not a function.`);
|
|
25
|
+
console.error(`Received: ${typeof handler}`);
|
|
26
|
+
console.error('Please report this issue to the maintainer.\n');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
10
30
|
|
|
11
31
|
const program = new Command();
|
|
12
32
|
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -1,40 +1,21 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## It won’t push
|
|
26
|
-
|
|
27
|
-
- Verify `autoPush` is true
|
|
28
|
-
- Check your `origin` remote and authentication
|
|
29
|
-
- Ensure you have access to the remote
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Logs
|
|
34
|
-
|
|
35
|
-
- `autopilot.log` is created in the repo root
|
|
36
|
-
- `autopilot status` shows the last log line
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
**Built by Praise Masunga (PraiseTechzw)**
|
|
1
|
+
# TROUBLESHOOTING
|
|
2
|
+
|
|
3
|
+
## Common Issues
|
|
4
|
+
|
|
5
|
+
### Watcher Loop / High CPU Usage (Windows)
|
|
6
|
+
If Autopilot seems to be constantly triggering or consuming high CPU on Windows, it is likely due to "noisy" files that change frequently but should be ignored.
|
|
7
|
+
|
|
8
|
+
**Common Culprits:**
|
|
9
|
+
- `.vscode/time-analytics.json`: Some VS Code extensions write to this file constantly.
|
|
10
|
+
- `autopilot.log`: If the watcher logs to a file that it is also watching, it creates a feedback loop.
|
|
11
|
+
|
|
12
|
+
**Solution:**
|
|
13
|
+
Autopilot v0.1.4+ includes enhanced Windows path handling to automatically ignore these files. If you still see issues:
|
|
14
|
+
1. Ensure `.vscode/` and `*.log` are in your `.autopilotignore` file.
|
|
15
|
+
2. Run `autopilot doctor` to verify your configuration.
|
|
16
|
+
|
|
17
|
+
### "git.status is not a function"
|
|
18
|
+
This was a known issue in older versions. Please upgrade to the latest version.
|
|
19
|
+
|
|
20
|
+
### Permissions Errors
|
|
21
|
+
Ensure you have write access to the repository and that no other process has locked the files (common on Windows with anti-virus software).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@traisetech/autopilot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -32,12 +32,11 @@
|
|
|
32
32
|
"main": "src/index.js",
|
|
33
33
|
"scripts": {
|
|
34
34
|
"dev": "node bin/autopilot.js",
|
|
35
|
-
"lint": "echo \"No lint configured\"",
|
|
36
35
|
"test": "node --test",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"release:
|
|
36
|
+
"lint": "node -c bin/autopilot.js && node -c src/index.js",
|
|
37
|
+
"verify": "node bin/autopilot.js --help && npm run test && node bin/autopilot.js doctor",
|
|
38
|
+
"prepublishOnly": "npm run verify",
|
|
39
|
+
"release:patch": "npm run verify && npm version patch && git push --follow-tags && echo \"\n🚀 Ready to publish! Run: npm publish --access public\""
|
|
41
40
|
},
|
|
42
41
|
"engines": {
|
|
43
42
|
"node": ">=18.0.0"
|
package/src/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
7
8
|
const logger = require('../utils/logger');
|
|
8
9
|
const { getConfigPath, getIgnorePath, getGitPath } = require('../utils/paths');
|
|
9
10
|
const { DEFAULT_CONFIG, DEFAULT_IGNORE_PATTERNS } = require('../config/defaults');
|
|
@@ -54,6 +55,41 @@ async function createConfigFile(repoPath) {
|
|
|
54
55
|
return true;
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Update .gitignore with Autopilot specific files
|
|
60
|
+
* @param {string} repoPath
|
|
61
|
+
*/
|
|
62
|
+
async function updateGitIgnore(repoPath) {
|
|
63
|
+
const gitIgnorePath = path.join(repoPath, '.gitignore');
|
|
64
|
+
const toIgnore = ['autopilot.log', '.autopilot.pid'];
|
|
65
|
+
let content = '';
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
if (await fs.pathExists(gitIgnorePath)) {
|
|
69
|
+
content = await fs.readFile(gitIgnorePath, 'utf-8');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
73
|
+
const newLines = [];
|
|
74
|
+
let added = false;
|
|
75
|
+
|
|
76
|
+
for (const item of toIgnore) {
|
|
77
|
+
if (!lines.includes(item)) {
|
|
78
|
+
newLines.push(item);
|
|
79
|
+
added = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (added) {
|
|
84
|
+
const newContent = content + (content && !content.endsWith('\n') ? '\n' : '') + newLines.join('\n') + '\n';
|
|
85
|
+
await fs.writeFile(gitIgnorePath, newContent);
|
|
86
|
+
logger.success('Updated .gitignore');
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logger.warn(`Could not update .gitignore: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
57
93
|
/**
|
|
58
94
|
* Initialize Autopilot in current repository
|
|
59
95
|
*/
|
|
@@ -76,6 +112,7 @@ async function initRepo() {
|
|
|
76
112
|
// Create files
|
|
77
113
|
await createIgnoreFile(repoPath);
|
|
78
114
|
await createConfigFile(repoPath);
|
|
115
|
+
await updateGitIgnore(repoPath);
|
|
79
116
|
|
|
80
117
|
logger.section('✨ Initialization Complete');
|
|
81
118
|
logger.info('Next steps:');
|
|
@@ -89,4 +126,4 @@ async function initRepo() {
|
|
|
89
126
|
}
|
|
90
127
|
}
|
|
91
128
|
|
|
92
|
-
module.exports =
|
|
129
|
+
module.exports = initRepo;
|
package/src/config/defaults.js
CHANGED
package/src/config/ignore.js
CHANGED
|
@@ -1,37 +1,136 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const logger = require('../utils/logger');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const logger = require('../utils/logger');
|
|
4
|
+
const { getIgnorePath } = require('../utils/paths');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Standardize path to use forward slashes
|
|
8
|
+
* @param {string} p - Path to normalize
|
|
9
|
+
* @returns {string} Normalized path
|
|
10
|
+
*/
|
|
11
|
+
const normalizePath = (p) => p.split(path.sep).join('/');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read ignore file patterns
|
|
15
|
+
* @param {string} repoPath
|
|
16
|
+
* @returns {Promise<string[]>} Array of ignore patterns
|
|
17
|
+
*/
|
|
18
|
+
const readIgnoreFile = async (repoPath) => {
|
|
19
|
+
const ignorePath = getIgnorePath(repoPath);
|
|
20
|
+
try {
|
|
21
|
+
if (await fs.pathExists(ignorePath)) {
|
|
22
|
+
const content = await fs.readFile(ignorePath, 'utf-8');
|
|
23
|
+
return content
|
|
24
|
+
.split('\n')
|
|
25
|
+
.map((line) => line.trim())
|
|
26
|
+
.filter((line) => line && !line.startsWith('#'));
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
logger.debug(`Error reading ignore file: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
return [];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create ignore file
|
|
36
|
+
*/
|
|
37
|
+
const createIgnoreFile = async (repoPath, patterns = []) => {
|
|
38
|
+
const ignorePath = getIgnorePath(repoPath);
|
|
39
|
+
try {
|
|
40
|
+
const content =
|
|
41
|
+
`# Autopilot Ignore File\n# Add patterns to exclude from autopilot watching\n\n` +
|
|
42
|
+
patterns.join('\n');
|
|
43
|
+
await fs.writeFile(ignorePath, content);
|
|
44
|
+
logger.success('Created .autopilotignore file');
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.error(`Failed to create ignore file: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a filter function for Chokidar
|
|
52
|
+
* @param {string} repoPath - Root of the repository
|
|
53
|
+
* @param {string[]} userPatterns - Custom ignore patterns
|
|
54
|
+
* @returns {function} Filter function (path => boolean)
|
|
55
|
+
*/
|
|
56
|
+
const createIgnoredFilter = (repoPath, userPatterns = []) => {
|
|
57
|
+
const normalizedRepoPath = normalizePath(repoPath);
|
|
58
|
+
|
|
59
|
+
// Always ignore these critical paths to prevent loops and noise
|
|
60
|
+
const criticalIgnores = [
|
|
61
|
+
'.git',
|
|
62
|
+
'node_modules',
|
|
63
|
+
'.vscode',
|
|
64
|
+
'.idea',
|
|
65
|
+
'dist',
|
|
66
|
+
'build',
|
|
67
|
+
'coverage',
|
|
68
|
+
'.next'
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const criticalFiles = [
|
|
72
|
+
'autopilot.log',
|
|
73
|
+
'.autopilot.pid',
|
|
74
|
+
'.DS_Store'
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return (absolutePath) => {
|
|
78
|
+
// 1. Normalize paths
|
|
79
|
+
const normalizedAbs = normalizePath(absolutePath);
|
|
80
|
+
|
|
81
|
+
// 2. Get relative path
|
|
82
|
+
let relativePath = normalizedAbs;
|
|
83
|
+
if (normalizedAbs.startsWith(normalizedRepoPath)) {
|
|
84
|
+
relativePath = normalizedAbs.slice(normalizedRepoPath.length);
|
|
85
|
+
if (relativePath.startsWith('/')) {
|
|
86
|
+
relativePath = relativePath.slice(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle root path case
|
|
91
|
+
if (!relativePath) return false;
|
|
92
|
+
|
|
93
|
+
// 3. Check critical directory prefixes
|
|
94
|
+
// We check if any path segment matches a critical ignore
|
|
95
|
+
const parts = relativePath.split('/');
|
|
96
|
+
for (const part of parts) {
|
|
97
|
+
if (criticalIgnores.includes(part)) return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 4. Check file extensions and exact matches
|
|
101
|
+
if (relativePath.endsWith('.log')) return true;
|
|
102
|
+
if (criticalFiles.some(f => relativePath.endsWith(f))) return true;
|
|
103
|
+
|
|
104
|
+
// 5. Check user patterns (simple prefix/suffix matching for now)
|
|
105
|
+
// For robust glob support without adding dependencies, we rely on basic checks
|
|
106
|
+
// Most users use simple dir/ or *.ext patterns
|
|
107
|
+
for (const pattern of userPatterns) {
|
|
108
|
+
// Remove leading/trailing slashes for comparison
|
|
109
|
+
const cleanPattern = pattern.replace(/^\/+|\/+$/g, '');
|
|
110
|
+
|
|
111
|
+
// Directory match (e.g., "temp/")
|
|
112
|
+
if (pattern.endsWith('/') && (relativePath === cleanPattern || relativePath.startsWith(cleanPattern + '/'))) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Extension match (e.g., "*.tmp")
|
|
117
|
+
if (pattern.startsWith('*.') && relativePath.endsWith(pattern.slice(1))) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Exact match
|
|
122
|
+
if (relativePath === cleanPattern) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return false;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
readIgnoreFile,
|
|
133
|
+
createIgnoreFile,
|
|
134
|
+
createIgnoredFilter,
|
|
135
|
+
normalizePath
|
|
136
|
+
};
|
package/src/config/loader.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const logger = require('../utils/logger');
|
|
4
|
-
const
|
|
5
|
-
const { getConfigPath } = require('../utils/paths');
|
|
6
|
-
|
|
7
|
-
const loadConfig = async (repoPath) => {
|
|
8
|
-
const configPath = getConfigPath(repoPath);
|
|
9
|
-
try {
|
|
10
|
-
if (await fs.pathExists(configPath)) {
|
|
11
|
-
const config = await fs.readJson(configPath);
|
|
12
|
-
logger.debug(`Loaded config from ${configPath}`);
|
|
13
|
-
return { ...
|
|
14
|
-
}
|
|
15
|
-
} catch (error) {
|
|
16
|
-
logger.warn(`Error loading config: ${error.message}`);
|
|
17
|
-
}
|
|
18
|
-
return
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const saveConfig = async (repoPath, config) => {
|
|
22
|
-
const configPath = getConfigPath(repoPath);
|
|
23
|
-
try {
|
|
24
|
-
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
25
|
-
logger.success(`Config saved to ${configPath}`);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
logger.error(`Failed to save config: ${error.message}`);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const createDefaultConfig = async (repoPath) => {
|
|
32
|
-
const configPath = getConfigPath(repoPath);
|
|
33
|
-
try {
|
|
34
|
-
if (!(await fs.pathExists(configPath))) {
|
|
35
|
-
await fs.writeJson(configPath,
|
|
36
|
-
logger.success(`Created default config at ${configPath}`);
|
|
37
|
-
}
|
|
38
|
-
} catch (error) {
|
|
39
|
-
logger.error(`Failed to create config: ${error.message}`);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
loadConfig,
|
|
45
|
-
saveConfig,
|
|
46
|
-
createDefaultConfig,
|
|
47
|
-
};
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const logger = require('../utils/logger');
|
|
4
|
+
const { DEFAULT_CONFIG } = require('./defaults');
|
|
5
|
+
const { getConfigPath } = require('../utils/paths');
|
|
6
|
+
|
|
7
|
+
const loadConfig = async (repoPath) => {
|
|
8
|
+
const configPath = getConfigPath(repoPath);
|
|
9
|
+
try {
|
|
10
|
+
if (await fs.pathExists(configPath)) {
|
|
11
|
+
const config = await fs.readJson(configPath);
|
|
12
|
+
logger.debug(`Loaded config from ${configPath}`);
|
|
13
|
+
return { ...DEFAULT_CONFIG, ...config };
|
|
14
|
+
}
|
|
15
|
+
} catch (error) {
|
|
16
|
+
logger.warn(`Error loading config: ${error.message}`);
|
|
17
|
+
}
|
|
18
|
+
return { ...DEFAULT_CONFIG };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const saveConfig = async (repoPath, config) => {
|
|
22
|
+
const configPath = getConfigPath(repoPath);
|
|
23
|
+
try {
|
|
24
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
25
|
+
logger.success(`Config saved to ${configPath}`);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
logger.error(`Failed to save config: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const createDefaultConfig = async (repoPath) => {
|
|
32
|
+
const configPath = getConfigPath(repoPath);
|
|
33
|
+
try {
|
|
34
|
+
if (!(await fs.pathExists(configPath))) {
|
|
35
|
+
await fs.writeJson(configPath, DEFAULT_CONFIG, { spaces: 2 });
|
|
36
|
+
logger.success(`Created default config at ${configPath}`);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.error(`Failed to create config: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
loadConfig,
|
|
45
|
+
saveConfig,
|
|
46
|
+
createDefaultConfig,
|
|
47
|
+
};
|
package/src/core/watcher.js
CHANGED
|
@@ -6,13 +6,12 @@
|
|
|
6
6
|
const fs = require('fs-extra');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const chokidar = require('chokidar');
|
|
9
|
-
const { execa } = require('execa');
|
|
10
9
|
const logger = require('../utils/logger');
|
|
11
10
|
const git = require('./git');
|
|
12
11
|
const { generateCommitMessage } = require('./commit');
|
|
13
|
-
const { getIgnorePath } = require('../utils/paths');
|
|
14
12
|
const { savePid, removePid, registerProcessHandlers } = require('../utils/process');
|
|
15
13
|
const { loadConfig } = require('../config/loader');
|
|
14
|
+
const { readIgnoreFile, createIgnoredFilter, normalizePath } = require('../config/ignore');
|
|
16
15
|
|
|
17
16
|
class Watcher {
|
|
18
17
|
constructor(repoPath) {
|
|
@@ -27,6 +26,23 @@ class Watcher {
|
|
|
27
26
|
this.ignorePatterns = [];
|
|
28
27
|
}
|
|
29
28
|
|
|
29
|
+
logVerbose(message) {
|
|
30
|
+
// Helper to log debug messages to file/stdout if needed
|
|
31
|
+
// For now, we use logger.debug which writes to stdout if DEBUG is set
|
|
32
|
+
// We also want to ensure we don't create a loop by logging to the file we watch
|
|
33
|
+
// But since we ignore autopilot.log, it should be fine.
|
|
34
|
+
logger.debug(message);
|
|
35
|
+
// TODO: Append to log file if configured
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async reloadConfig() {
|
|
39
|
+
this.config = await loadConfig(this.repoPath);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async reloadIgnore() {
|
|
43
|
+
this.ignorePatterns = await readIgnoreFile(this.repoPath);
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
/**
|
|
31
47
|
* Initialize and start the watcher
|
|
32
48
|
*/
|
|
@@ -41,7 +57,7 @@ class Watcher {
|
|
|
41
57
|
await fs.ensureFile(this.logFilePath);
|
|
42
58
|
await savePid(this.repoPath);
|
|
43
59
|
|
|
44
|
-
|
|
60
|
+
logger.info('Starting Autopilot watcher...');
|
|
45
61
|
|
|
46
62
|
// Load configuration
|
|
47
63
|
await this.reloadConfig();
|
|
@@ -51,23 +67,16 @@ class Watcher {
|
|
|
51
67
|
const currentBranch = await git.getBranch(this.repoPath);
|
|
52
68
|
if (currentBranch && this.config.blockedBranches?.includes(currentBranch)) {
|
|
53
69
|
logger.error(`Branch '${currentBranch}' is blocked in config. Stopping.`);
|
|
54
|
-
this.logVerbose(`Blocked branch detected: ${currentBranch}`);
|
|
55
70
|
await this.stop();
|
|
56
71
|
return;
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
...this.ignorePatterns,
|
|
62
|
-
/(^|[\/\\])\.git([\/\\]|$)/, // Regex for .git folder
|
|
63
|
-
'**/autopilot.log',
|
|
64
|
-
'**/.autopilot.pid',
|
|
65
|
-
'node_modules' // Sensible default
|
|
66
|
-
];
|
|
74
|
+
// Create robust ignore filter
|
|
75
|
+
const ignoredFilter = createIgnoredFilter(this.repoPath, this.ignorePatterns);
|
|
67
76
|
|
|
68
|
-
// Start Chokidar
|
|
77
|
+
// Start Chokidar with function-based ignore
|
|
69
78
|
this.watcher = chokidar.watch(this.repoPath, {
|
|
70
|
-
ignored:
|
|
79
|
+
ignored: ignoredFilter,
|
|
71
80
|
ignoreInitial: true,
|
|
72
81
|
persistent: true,
|
|
73
82
|
awaitWriteFinish: {
|
|
@@ -91,9 +100,18 @@ class Watcher {
|
|
|
91
100
|
logger.success(`Autopilot is watching ${this.repoPath}`);
|
|
92
101
|
logger.info(`Logs: ${this.logFilePath}`);
|
|
93
102
|
|
|
103
|
+
// Test Mode Support
|
|
104
|
+
if (process.env.AUTOPILOT_TEST_MODE) {
|
|
105
|
+
logger.warn('TEST MODE: Running in dry-run mode for 8 seconds...');
|
|
106
|
+
setTimeout(async () => {
|
|
107
|
+
logger.info('TEST MODE: Auto-stopping watcher...');
|
|
108
|
+
await this.stop();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}, 8000);
|
|
111
|
+
}
|
|
112
|
+
|
|
94
113
|
} catch (error) {
|
|
95
114
|
logger.error(`Failed to start watcher: ${error.message}`);
|
|
96
|
-
this.logVerbose(`Start error: ${error.stack}`);
|
|
97
115
|
await this.stop();
|
|
98
116
|
}
|
|
99
117
|
}
|
|
@@ -127,10 +145,15 @@ class Watcher {
|
|
|
127
145
|
onFsEvent(type, filePath) {
|
|
128
146
|
if (this.isProcessing) return;
|
|
129
147
|
|
|
130
|
-
//
|
|
131
|
-
|
|
148
|
+
// Normalize path relative to repo for logging and checks
|
|
149
|
+
const relativePath = normalizePath(path.relative(this.repoPath, filePath));
|
|
150
|
+
|
|
151
|
+
// Double check ignore (safety net) - although chokidar should catch most
|
|
152
|
+
if (relativePath.includes('.git/') || relativePath.endsWith('autopilot.log') || relativePath.includes('.vscode/')) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
132
155
|
|
|
133
|
-
this.logVerbose(`File event: ${type} ${
|
|
156
|
+
this.logVerbose(`File event: ${type} ${relativePath}`);
|
|
134
157
|
this.scheduleProcess();
|
|
135
158
|
}
|
|
136
159
|
|
|
@@ -144,11 +167,22 @@ class Watcher {
|
|
|
144
167
|
clearTimeout(this.debounceTimer);
|
|
145
168
|
}
|
|
146
169
|
|
|
170
|
+
logger.debug('Debounce fired. Waiting...');
|
|
171
|
+
|
|
147
172
|
this.debounceTimer = setTimeout(() => {
|
|
148
173
|
this.processChanges();
|
|
149
174
|
}, debounceMs);
|
|
150
175
|
}
|
|
151
176
|
|
|
177
|
+
handleError(error) {
|
|
178
|
+
logger.error(`Watcher error: ${error.message}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async runChecks() {
|
|
182
|
+
// Placeholder for custom checks
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
152
186
|
/**
|
|
153
187
|
* Main processing loop
|
|
154
188
|
*/
|
|
@@ -157,153 +191,84 @@ class Watcher {
|
|
|
157
191
|
this.isProcessing = true;
|
|
158
192
|
|
|
159
193
|
try {
|
|
194
|
+
logger.debug('Checking git status...');
|
|
195
|
+
|
|
160
196
|
// 1. Min interval check
|
|
161
197
|
const now = Date.now();
|
|
162
|
-
const minInterval = (this.config?.
|
|
163
|
-
if (now - this.lastCommitAt < minInterval) {
|
|
164
|
-
|
|
198
|
+
const minInterval = (this.config?.minSecondsBetweenCommits || 180) * 1000;
|
|
199
|
+
if (this.lastCommitAt > 0 && now - this.lastCommitAt < minInterval) {
|
|
200
|
+
logger.debug(`Skip commit: Minimum interval not met (${Math.round((minInterval - (now - this.lastCommitAt))/1000)}s remaining)`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 2. Check if dirty
|
|
205
|
+
const statusObj = await git.getPorcelainStatus(this.repoPath);
|
|
206
|
+
const isDirty = statusObj.ok && statusObj.files.length > 0;
|
|
207
|
+
logger.debug(`Git dirty: ${isDirty}`);
|
|
208
|
+
|
|
209
|
+
if (!isDirty) {
|
|
165
210
|
return;
|
|
166
211
|
}
|
|
167
212
|
|
|
168
|
-
//
|
|
213
|
+
// 3. Safety: Branch check
|
|
169
214
|
const branch = await git.getBranch(this.repoPath);
|
|
170
215
|
if (this.config?.blockedBranches?.includes(branch)) {
|
|
171
|
-
logger.warn(`
|
|
216
|
+
logger.warn(`Skip commit: Branch '${branch}' is blocked`);
|
|
172
217
|
return;
|
|
173
218
|
}
|
|
174
219
|
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
220
|
+
// 4. Safety: Remote check (fetch -> behind?)
|
|
221
|
+
logger.debug('Checking remote status...');
|
|
222
|
+
// Note: isRemoteAhead might need network, timeout safely?
|
|
223
|
+
try {
|
|
224
|
+
const remoteStatus = await git.isRemoteAhead(this.repoPath);
|
|
225
|
+
if (remoteStatus.behind) {
|
|
226
|
+
logger.warn('Skip commit: Local branch is behind remote. Please pull changes.');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {
|
|
230
|
+
logger.debug(`Remote check failed (offline?): ${e.message}`);
|
|
182
231
|
}
|
|
183
232
|
|
|
184
|
-
//
|
|
233
|
+
// 5. Safety: Custom checks
|
|
185
234
|
if (this.config?.requireChecks) {
|
|
186
235
|
const checksPassed = await this.runChecks();
|
|
187
236
|
if (!checksPassed) {
|
|
188
|
-
logger.warn('Checks failed
|
|
237
|
+
logger.warn('Skip commit: Checks failed');
|
|
189
238
|
return;
|
|
190
239
|
}
|
|
191
240
|
}
|
|
192
241
|
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
if (!status.ok || status.files.length === 0) {
|
|
196
|
-
this.logVerbose('No changes to commit');
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
this.logVerbose(`Detecting ${status.files.length} changed files`);
|
|
242
|
+
// 6. Commit
|
|
243
|
+
logger.info('Committing changes...');
|
|
201
244
|
|
|
202
|
-
// Add all
|
|
245
|
+
// Add all changes
|
|
203
246
|
await git.addAll(this.repoPath);
|
|
204
247
|
|
|
205
248
|
// Generate message
|
|
206
|
-
const
|
|
207
|
-
this.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
logger.success('Pushed successfully');
|
|
221
|
-
} else {
|
|
222
|
-
logger.error(`Push failed: ${pushResult.stderr}`);
|
|
223
|
-
this.logVerbose(`Push error: ${pushResult.stderr}`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
logger.error(`Commit failed: ${commitResult.stderr}`);
|
|
228
|
-
this.logVerbose(`Commit error: ${commitResult.stderr}`);
|
|
249
|
+
const changedFiles = statusObj.files;
|
|
250
|
+
const message = this.config?.commitMessageMode === 'simple'
|
|
251
|
+
? 'chore: auto-commit changes'
|
|
252
|
+
: generateCommitMessage(changedFiles);
|
|
253
|
+
|
|
254
|
+
await git.commit(this.repoPath, message);
|
|
255
|
+
this.lastCommitAt = Date.now();
|
|
256
|
+
logger.success('Commit done');
|
|
257
|
+
|
|
258
|
+
// 7. Auto-push
|
|
259
|
+
if (this.config?.autoPush) {
|
|
260
|
+
logger.info('Pushing to remote...');
|
|
261
|
+
await git.push(this.repoPath);
|
|
262
|
+
logger.success('Push complete');
|
|
229
263
|
}
|
|
230
264
|
|
|
231
265
|
} catch (error) {
|
|
232
266
|
logger.error(`Process error: ${error.message}`);
|
|
233
|
-
this.logVerbose(`Process exception: ${error.stack}`);
|
|
234
267
|
} finally {
|
|
235
268
|
this.isProcessing = false;
|
|
236
269
|
this.debounceTimer = null;
|
|
237
270
|
}
|
|
238
271
|
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Run user-defined checks
|
|
242
|
-
*/
|
|
243
|
-
async runChecks() {
|
|
244
|
-
const checks = this.config?.checks || [];
|
|
245
|
-
if (checks.length === 0) return true;
|
|
246
|
-
|
|
247
|
-
this.logVerbose(`Running checks: ${checks.join(', ')}`);
|
|
248
|
-
|
|
249
|
-
for (const cmd of checks) {
|
|
250
|
-
try {
|
|
251
|
-
logger.info(`Running check: ${cmd}`);
|
|
252
|
-
await execa(cmd, { cwd: this.repoPath, shell: true });
|
|
253
|
-
} catch (error) {
|
|
254
|
-
logger.error(`Check failed: ${cmd}`);
|
|
255
|
-
this.logVerbose(`Check output: ${error.message}`);
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Reload configuration
|
|
264
|
-
*/
|
|
265
|
-
async reloadConfig() {
|
|
266
|
-
this.config = await loadConfig(this.repoPath);
|
|
267
|
-
this.logVerbose('Config reloaded');
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Reload ignore patterns
|
|
272
|
-
*/
|
|
273
|
-
async reloadIgnore() {
|
|
274
|
-
const ignorePath = getIgnorePath(this.repoPath);
|
|
275
|
-
this.ignorePatterns = [];
|
|
276
|
-
|
|
277
|
-
if (await fs.pathExists(ignorePath)) {
|
|
278
|
-
try {
|
|
279
|
-
const content = await fs.readFile(ignorePath, 'utf-8');
|
|
280
|
-
const lines = content.split('\n')
|
|
281
|
-
.map(l => l.trim())
|
|
282
|
-
.filter(l => l && !l.startsWith('#'));
|
|
283
|
-
|
|
284
|
-
this.ignorePatterns.push(...lines);
|
|
285
|
-
this.logVerbose(`Loaded ${lines.length} ignore patterns`);
|
|
286
|
-
} catch (error) {
|
|
287
|
-
logger.warn(`Failed to load ignore file: ${error.message}`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Also load ignore patterns from config json if they exist
|
|
292
|
-
if (this.config?.ignore && Array.isArray(this.config.ignore)) {
|
|
293
|
-
this.ignorePatterns.push(...this.config.ignore);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
handleError(error) {
|
|
298
|
-
logger.error(`Watcher error: ${error.message}`);
|
|
299
|
-
this.logVerbose(`Watcher error: ${error.stack}`);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
logVerbose(message) {
|
|
303
|
-
const timestamp = new Date().toISOString();
|
|
304
|
-
const logLine = `[${timestamp}] ${message}\n`;
|
|
305
|
-
fs.appendFile(this.logFilePath, logLine).catch(() => {});
|
|
306
|
-
}
|
|
307
272
|
}
|
|
308
273
|
|
|
309
274
|
module.exports = Watcher;
|
package/src/utils/logger.js
CHANGED
|
@@ -12,6 +12,16 @@ const logger = {
|
|
|
12
12
|
console.log(`ℹ️ ${message}`);
|
|
13
13
|
},
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Log debug message (only visible if DEBUG env var is set)
|
|
17
|
+
* @param {string} message - Message to log
|
|
18
|
+
*/
|
|
19
|
+
debug: (message) => {
|
|
20
|
+
if (process.env.DEBUG) {
|
|
21
|
+
console.log(`🔍 ${message}`);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
15
25
|
/**
|
|
16
26
|
* Log success message
|
|
17
27
|
* @param {string} message - Message to log
|