gitdone-agent 0.2.1 → 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 +93 -6
- 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
|
|
@@ -274,20 +345,26 @@ async function reportCommandResult(cfg, id, status, result) {
|
|
|
274
345
|
async function executeCommand(cfg, cmd, repoPath) {
|
|
275
346
|
let result = 'ok'
|
|
276
347
|
let status = 'done'
|
|
348
|
+
const auth = cfg.auth?.[repoPath] ?? null
|
|
277
349
|
try {
|
|
278
350
|
if (cmd.type === 'commit') {
|
|
279
351
|
const msg = (cmd.payload?.message ?? 'commit').replace(/"/g, '\\"')
|
|
280
352
|
git('git add -A', repoPath)
|
|
281
353
|
result = git(`git commit -m "${msg}"`, repoPath) || 'committed'
|
|
282
|
-
} else if (cmd.type === 'push') {
|
|
283
|
-
result =
|
|
284
|
-
} else if (cmd.type === 'pull') {
|
|
285
|
-
result = git('git pull', repoPath) || 'pulled'
|
|
354
|
+
} else if (cmd.type === 'push' || cmd.type === 'pull') {
|
|
355
|
+
result = pushOrPull(cmd.type, repoPath, auth)
|
|
286
356
|
}
|
|
287
|
-
log(`✓ ${cmd.type} @ ${repoPath}: ${result}`)
|
|
357
|
+
log(`✓ ${cmd.type} @ ${repoPath}: ${redact(result, auth?.token)}`)
|
|
288
358
|
} catch (err) {
|
|
289
359
|
status = 'error'
|
|
290
|
-
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
|
+
}
|
|
291
368
|
log(`✗ ${cmd.type} failed @ ${repoPath}: ${result}`)
|
|
292
369
|
}
|
|
293
370
|
await reportCommandResult(cfg, cmd.id, status, result)
|
|
@@ -298,6 +375,7 @@ async function sync(cfg, discovered) {
|
|
|
298
375
|
const data = await api(cfg, '/api/v1/agent/sync', {
|
|
299
376
|
machineId: cfg.machineId,
|
|
300
377
|
hostname: cfg.hostname,
|
|
378
|
+
agentVersion: AGENT_VERSION,
|
|
301
379
|
roots: cfg.roots,
|
|
302
380
|
repos: discovered,
|
|
303
381
|
})
|
|
@@ -311,8 +389,17 @@ async function pushSnapshot(cfg, repo) {
|
|
|
311
389
|
machineId: cfg.machineId,
|
|
312
390
|
path: repo.path,
|
|
313
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],
|
|
314
395
|
...snapshot,
|
|
315
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
|
+
}
|
|
316
403
|
for (const cmd of data.commands ?? []) {
|
|
317
404
|
await executeCommand(cfg, cmd, repo.path)
|
|
318
405
|
}
|