gitdone-agent 0.2.0 → 0.3.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.
- package/index.js +97 -7
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -24,6 +24,11 @@ import { randomUUID } from 'node:crypto'
|
|
|
24
24
|
// truth for the running agent), and a log file (so failures are visible even
|
|
25
25
|
// when the agent runs in a hidden window).
|
|
26
26
|
|
|
27
|
+
// Reported to the server on every sync so the web UI can flag outdated agents.
|
|
28
|
+
// Keep in lockstep with packages/agent/package.json "version" AND
|
|
29
|
+
// src/lib/agentVersion.ts LATEST_AGENT_VERSION.
|
|
30
|
+
const AGENT_VERSION = '0.3.1'
|
|
31
|
+
|
|
27
32
|
const AGENT_DIR = join(homedir(), '.gitdone-agent')
|
|
28
33
|
const CONFIG_PATH = join(AGENT_DIR, 'config.json')
|
|
29
34
|
const STABLE_AGENT = join(AGENT_DIR, 'agent.mjs')
|
|
@@ -102,10 +107,33 @@ function getNodePath() {
|
|
|
102
107
|
return process.execPath
|
|
103
108
|
}
|
|
104
109
|
|
|
110
|
+
// Kill any previously-running agent processes so an update applies WITHOUT a
|
|
111
|
+
// Windows re-login. Targets node processes launched from the agent dir
|
|
112
|
+
// (~/.gitdone-agent), excluding ourselves — the npx installer runs from the
|
|
113
|
+
// npx cache, so it won't match. Best-effort; failures are non-fatal.
|
|
114
|
+
function stopRunningAgents() {
|
|
115
|
+
const ps = [
|
|
116
|
+
"$ErrorActionPreference='SilentlyContinue'",
|
|
117
|
+
"Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" |",
|
|
118
|
+
` Where-Object { $_.CommandLine -like '*.gitdone-agent*' -and $_.ProcessId -ne ${process.pid} } |`,
|
|
119
|
+
' ForEach-Object { Stop-Process -Id $_.ProcessId -Force }',
|
|
120
|
+
].join('\n')
|
|
121
|
+
try {
|
|
122
|
+
ensureAgentDir()
|
|
123
|
+
const scriptPath = join(AGENT_DIR, 'stop-agents.ps1')
|
|
124
|
+
writeFileSync(scriptPath, ps, 'utf8')
|
|
125
|
+
execSync(`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${scriptPath}"`, { stdio: 'ignore' })
|
|
126
|
+
} catch { /* best-effort — at worst the old agent lingers until next login */ }
|
|
127
|
+
}
|
|
128
|
+
|
|
105
129
|
function installStartup() {
|
|
106
130
|
ensureAgentDir()
|
|
107
131
|
const nodePath = getNodePath()
|
|
108
132
|
|
|
133
|
+
// Stop the previously-running agent(s) first so we don't end up with the old
|
|
134
|
+
// build still reporting alongside the new one until the next Windows login.
|
|
135
|
+
stopRunningAgents()
|
|
136
|
+
|
|
109
137
|
// Copy this script to a stable location. process.argv[1] may point inside an
|
|
110
138
|
// npx cache dir that gets purged later — referencing it from autostart would
|
|
111
139
|
// silently break on the next login. A stable copy survives.
|
|
@@ -171,6 +199,49 @@ function git(cmd, cwd) {
|
|
|
171
199
|
}
|
|
172
200
|
}
|
|
173
201
|
|
|
202
|
+
// Like git(), but surfaces failures (with stderr) instead of swallowing them —
|
|
203
|
+
// used for push/pull where we need to detect auth rejection.
|
|
204
|
+
function gitTry(cmd, cwd) {
|
|
205
|
+
try {
|
|
206
|
+
const out = execSync(cmd, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
207
|
+
return { ok: true, out }
|
|
208
|
+
} catch (err) {
|
|
209
|
+
const out = (err.stderr || err.stdout || err.message || '').toString().trim()
|
|
210
|
+
return { ok: false, out }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Never let a token reach the log file or the server's command-result.
|
|
215
|
+
function redact(text, token) {
|
|
216
|
+
if (!text || !token) return text
|
|
217
|
+
return text.split(token).join('***')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// https://x-access-token:<token>@github.com/<owner>/<repo>.git — an ad-hoc URL
|
|
221
|
+
// passed straight to push/pull, so it never gets written into .git/config.
|
|
222
|
+
function authUrl(auth) {
|
|
223
|
+
const repo = auth.repo.replace(/\.git$/i, '')
|
|
224
|
+
return `https://x-access-token:${auth.token}@github.com/${repo}.git`
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Push/pull using the server-issued token when we have one; otherwise fall back
|
|
228
|
+
// to the machine's own git credentials (old behaviour). Throws on failure.
|
|
229
|
+
function pushOrPull(type, repoPath, auth) {
|
|
230
|
+
const branch = git('git branch --show-current', repoPath) || 'HEAD'
|
|
231
|
+
let cmd
|
|
232
|
+
if (auth) {
|
|
233
|
+
const url = authUrl(auth)
|
|
234
|
+
cmd = type === 'push' ? `git push "${url}" HEAD:${branch}` : `git pull "${url}" ${branch}`
|
|
235
|
+
} else {
|
|
236
|
+
cmd = type === 'push' ? 'git push' : 'git pull'
|
|
237
|
+
}
|
|
238
|
+
const r = gitTry(cmd, repoPath)
|
|
239
|
+
if (!r.ok) throw new Error(r.out || `${type} failed`)
|
|
240
|
+
return r.out || (type === 'push' ? 'pushed' : 'pulled')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const AUTH_FAIL = /authentication|authorization|403|401|denied|could not read Username|invalid username or password|terminal prompts disabled/i
|
|
244
|
+
|
|
174
245
|
function parseDiffByFile(diffOutput) {
|
|
175
246
|
const files = {}
|
|
176
247
|
if (!diffOutput) return files
|
|
@@ -210,7 +281,10 @@ function getSnapshot(repoPath) {
|
|
|
210
281
|
|
|
211
282
|
const diffs = parseDiffByFile(git('git diff HEAD', repoPath))
|
|
212
283
|
|
|
213
|
-
|
|
284
|
+
// Origin remote → lets the server auto-detect the GitHub repo for the History tab.
|
|
285
|
+
const remoteUrl = git('git config --get remote.origin.url', repoPath) || null
|
|
286
|
+
|
|
287
|
+
return { branch, modified, staged, aheadBy, behindBy, lastCommit, statuses, diffs, remoteUrl }
|
|
214
288
|
}
|
|
215
289
|
|
|
216
290
|
// ─── Repo discovery ─────────────────────────────────────────────────────────────
|
|
@@ -271,20 +345,26 @@ async function reportCommandResult(cfg, id, status, result) {
|
|
|
271
345
|
async function executeCommand(cfg, cmd, repoPath) {
|
|
272
346
|
let result = 'ok'
|
|
273
347
|
let status = 'done'
|
|
348
|
+
const auth = cfg.auth?.[repoPath] ?? null
|
|
274
349
|
try {
|
|
275
350
|
if (cmd.type === 'commit') {
|
|
276
351
|
const msg = (cmd.payload?.message ?? 'commit').replace(/"/g, '\\"')
|
|
277
352
|
git('git add -A', repoPath)
|
|
278
353
|
result = git(`git commit -m "${msg}"`, repoPath) || 'committed'
|
|
279
|
-
} else if (cmd.type === 'push') {
|
|
280
|
-
result =
|
|
281
|
-
} else if (cmd.type === 'pull') {
|
|
282
|
-
result = git('git pull', repoPath) || 'pulled'
|
|
354
|
+
} else if (cmd.type === 'push' || cmd.type === 'pull') {
|
|
355
|
+
result = pushOrPull(cmd.type, repoPath, auth)
|
|
283
356
|
}
|
|
284
|
-
log(`✓ ${cmd.type} @ ${repoPath}: ${result}`)
|
|
357
|
+
log(`✓ ${cmd.type} @ ${repoPath}: ${redact(result, auth?.token)}`)
|
|
285
358
|
} catch (err) {
|
|
286
359
|
status = 'error'
|
|
287
|
-
result = err.message
|
|
360
|
+
result = redact(err.message, auth?.token)
|
|
361
|
+
// Cached token rejected (e.g. rotated/expired on the server) → drop it so
|
|
362
|
+
// the next snapshot reports hasGithubAuth:false and the server reissues one.
|
|
363
|
+
if (auth && AUTH_FAIL.test(err.message)) {
|
|
364
|
+
delete cfg.auth[repoPath]
|
|
365
|
+
writeConfig(cfg)
|
|
366
|
+
result += ' — токенът е изчистен, пробвай командата пак'
|
|
367
|
+
}
|
|
288
368
|
log(`✗ ${cmd.type} failed @ ${repoPath}: ${result}`)
|
|
289
369
|
}
|
|
290
370
|
await reportCommandResult(cfg, cmd.id, status, result)
|
|
@@ -295,6 +375,7 @@ async function sync(cfg, discovered) {
|
|
|
295
375
|
const data = await api(cfg, '/api/v1/agent/sync', {
|
|
296
376
|
machineId: cfg.machineId,
|
|
297
377
|
hostname: cfg.hostname,
|
|
378
|
+
agentVersion: AGENT_VERSION,
|
|
298
379
|
roots: cfg.roots,
|
|
299
380
|
repos: discovered,
|
|
300
381
|
})
|
|
@@ -308,8 +389,17 @@ async function pushSnapshot(cfg, repo) {
|
|
|
308
389
|
machineId: cfg.machineId,
|
|
309
390
|
path: repo.path,
|
|
310
391
|
repoName: repo.name,
|
|
392
|
+
// Tells the server whether we already hold a push token for this repo.
|
|
393
|
+
// Always a boolean → marks us as a token-capable agent.
|
|
394
|
+
hasGithubAuth: !!cfg.auth?.[repo.path],
|
|
311
395
|
...snapshot,
|
|
312
396
|
})
|
|
397
|
+
// Server issued a (fresh) push token — cache it on disk so we ask only once.
|
|
398
|
+
if (data.githubAuth?.token && data.githubAuth?.repo) {
|
|
399
|
+
cfg.auth = cfg.auth ?? {}
|
|
400
|
+
cfg.auth[repo.path] = { token: data.githubAuth.token, repo: data.githubAuth.repo }
|
|
401
|
+
writeConfig(cfg)
|
|
402
|
+
}
|
|
313
403
|
for (const cmd of data.commands ?? []) {
|
|
314
404
|
await executeCommand(cfg, cmd, repo.path)
|
|
315
405
|
}
|