repowise 0.1.82 → 0.1.84
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/dist/bin/repowise.js +1549 -763
- package/package.json +4 -2
package/dist/bin/repowise.js
CHANGED
|
@@ -3,63 +3,100 @@
|
|
|
3
3
|
// bin/repowise.ts
|
|
4
4
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5
5
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6
|
-
import { dirname as
|
|
6
|
+
import { dirname as dirname9, join as join22 } from "path";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
9
9
|
// ../listener/dist/main.js
|
|
10
|
-
import { readFile as
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { readFile as readFile5, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
|
|
11
|
+
import { join as join12, dirname as dirname4 } from "path";
|
|
12
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13
|
+
import lockfile2 from "proper-lockfile";
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
-
import { readFile, writeFile, rename, mkdir, chmod } from "fs/promises";
|
|
15
|
+
// ../../packages/shared/src/config-dir.ts
|
|
16
16
|
import { homedir } from "os";
|
|
17
17
|
import { join } from "path";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
function getConfigDir() {
|
|
19
|
+
const isStaging = true ? false : false;
|
|
20
|
+
return join(homedir(), isStaging ? ".repowise-staging" : ".repowise");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ../listener/dist/lib/config.js
|
|
24
|
+
import { readFile, writeFile, rename, unlink, mkdir, chmod } from "fs/promises";
|
|
25
|
+
import { join as join2 } from "path";
|
|
26
|
+
import lockfile from "proper-lockfile";
|
|
27
|
+
var DEFAULT_API_URL = false ? "https://staging-api.repowise.ai" : "https://api.repowise.ai";
|
|
22
28
|
async function getListenerConfig() {
|
|
29
|
+
const configDir = getConfigDir();
|
|
30
|
+
const configPath = join2(configDir, "config.json");
|
|
23
31
|
const apiUrl = process.env["REPOWISE_API_URL"] ?? DEFAULT_API_URL;
|
|
24
32
|
try {
|
|
25
|
-
const data = await readFile(
|
|
33
|
+
const data = await readFile(configPath, "utf-8");
|
|
26
34
|
const raw = JSON.parse(data);
|
|
27
|
-
rawConfigCache = raw;
|
|
28
35
|
const validRepos = (raw.repos ?? []).filter((r) => typeof r === "object" && r !== null && typeof r.repoId === "string" && typeof r.localPath === "string");
|
|
29
36
|
return {
|
|
30
37
|
defaultApiUrl: raw.apiUrl ?? apiUrl,
|
|
31
|
-
repos: validRepos
|
|
38
|
+
repos: validRepos,
|
|
39
|
+
autoDiscoverRepos: raw.autoDiscoverRepos ?? false,
|
|
40
|
+
noAutoUpdate: raw.noAutoUpdate ?? false
|
|
32
41
|
};
|
|
33
42
|
} catch {
|
|
34
|
-
return { defaultApiUrl: apiUrl, repos: [] };
|
|
43
|
+
return { defaultApiUrl: apiUrl, repos: [], autoDiscoverRepos: false, noAutoUpdate: false };
|
|
35
44
|
}
|
|
36
45
|
}
|
|
37
46
|
async function saveListenerConfig(config2) {
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const configDir = getConfigDir();
|
|
48
|
+
const configPath = join2(configDir, "config.json");
|
|
49
|
+
await mkdir(configDir, { recursive: true, mode: 448 });
|
|
50
|
+
try {
|
|
51
|
+
await writeFile(configPath, "", { flag: "a" });
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
let release = null;
|
|
55
|
+
try {
|
|
56
|
+
release = await lockfile.lock(configPath, { stale: 1e4, retries: 3, realpath: false });
|
|
57
|
+
let raw = {};
|
|
58
|
+
try {
|
|
59
|
+
const data = await readFile(configPath, "utf-8");
|
|
60
|
+
raw = JSON.parse(data);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
const output = {
|
|
64
|
+
...raw,
|
|
65
|
+
apiUrl: config2.defaultApiUrl,
|
|
66
|
+
repos: config2.repos
|
|
67
|
+
};
|
|
68
|
+
const tmpPath = configPath + ".tmp";
|
|
69
|
+
try {
|
|
70
|
+
await writeFile(tmpPath, JSON.stringify(output, null, 2));
|
|
71
|
+
await chmod(tmpPath, 384);
|
|
72
|
+
await rename(tmpPath, configPath);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
try {
|
|
75
|
+
await unlink(tmpPath);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
} finally {
|
|
81
|
+
if (release) {
|
|
82
|
+
try {
|
|
83
|
+
await release();
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
49
88
|
}
|
|
50
89
|
|
|
51
90
|
// ../listener/dist/lib/state.js
|
|
52
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
53
|
-
import {
|
|
54
|
-
import { join as join2 } from "path";
|
|
55
|
-
var CONFIG_DIR2 = join2(homedir2(), ".repowise");
|
|
56
|
-
var STATE_PATH = join2(CONFIG_DIR2, "listener-state.json");
|
|
91
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2, rename as rename2, unlink as unlink2 } from "fs/promises";
|
|
92
|
+
import { join as join3 } from "path";
|
|
57
93
|
function emptyState() {
|
|
58
94
|
return { repos: {} };
|
|
59
95
|
}
|
|
60
96
|
async function loadState() {
|
|
97
|
+
const statePath = join3(getConfigDir(), "listener-state.json");
|
|
61
98
|
try {
|
|
62
|
-
const data = await readFile2(
|
|
99
|
+
const data = await readFile2(statePath, "utf-8");
|
|
63
100
|
return JSON.parse(data);
|
|
64
101
|
} catch (err) {
|
|
65
102
|
if (err.code === "ENOENT" || err instanceof SyntaxError) {
|
|
@@ -69,19 +106,34 @@ async function loadState() {
|
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
108
|
async function saveState(state) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
await
|
|
109
|
+
const configDir = getConfigDir();
|
|
110
|
+
const statePath = join3(configDir, "listener-state.json");
|
|
111
|
+
await mkdir2(configDir, { recursive: true, mode: 448 });
|
|
112
|
+
const tmpPath = statePath + ".tmp";
|
|
113
|
+
try {
|
|
114
|
+
await writeFile2(tmpPath, JSON.stringify(state, null, 2));
|
|
115
|
+
await chmod2(tmpPath, 384);
|
|
116
|
+
await rename2(tmpPath, statePath);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
try {
|
|
119
|
+
await unlink2(tmpPath);
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
75
124
|
}
|
|
76
125
|
|
|
77
126
|
// ../listener/dist/lib/reconcile.js
|
|
78
|
-
import {
|
|
79
|
-
|
|
127
|
+
import { statSync, readdirSync } from "fs";
|
|
128
|
+
import { basename, dirname, join as join4 } from "path";
|
|
129
|
+
function reconcileRepos(configRepos, activeRepos, state, apiUrl, options) {
|
|
80
130
|
if (activeRepos.length === 0) {
|
|
81
|
-
return { updated: false, repos: configRepos, changes: [] };
|
|
131
|
+
return { updated: false, repos: configRepos, changes: [], addedRepos: [] };
|
|
82
132
|
}
|
|
83
133
|
const changes = [];
|
|
84
134
|
let updated = false;
|
|
135
|
+
const addedRepos = [];
|
|
136
|
+
const usedPaths = new Set(configRepos.map((r) => r.localPath));
|
|
85
137
|
const updatedRepos = configRepos.map((repo) => {
|
|
86
138
|
if (repo.apiUrl && repo.apiUrl !== apiUrl) {
|
|
87
139
|
return repo;
|
|
@@ -151,19 +203,78 @@ function reconcileRepos(configRepos, activeRepos, state, apiUrl) {
|
|
|
151
203
|
}
|
|
152
204
|
return repo;
|
|
153
205
|
});
|
|
154
|
-
|
|
206
|
+
if (options?.autoDiscover) {
|
|
207
|
+
const configuredRepoIds = new Set(updatedRepos.map((r) => r.repoId));
|
|
208
|
+
const unmatchedActiveRepos = activeRepos.filter((ar) => !configuredRepoIds.has(ar.repoId));
|
|
209
|
+
if (unmatchedActiveRepos.length > 0) {
|
|
210
|
+
const parentDirs = /* @__PURE__ */ new Set();
|
|
211
|
+
for (const repo of updatedRepos) {
|
|
212
|
+
parentDirs.add(dirname(repo.localPath));
|
|
213
|
+
}
|
|
214
|
+
for (const activeRepo of unmatchedActiveRepos) {
|
|
215
|
+
const found = findLocalRepo(activeRepo, parentDirs, usedPaths);
|
|
216
|
+
if (found) {
|
|
217
|
+
const newRepo = {
|
|
218
|
+
repoId: activeRepo.repoId,
|
|
219
|
+
localPath: found,
|
|
220
|
+
apiUrl,
|
|
221
|
+
platform: activeRepo.platform,
|
|
222
|
+
externalId: activeRepo.externalId
|
|
223
|
+
};
|
|
224
|
+
updatedRepos.push(newRepo);
|
|
225
|
+
addedRepos.push(newRepo);
|
|
226
|
+
usedPaths.add(found);
|
|
227
|
+
configuredRepoIds.add(activeRepo.repoId);
|
|
228
|
+
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
229
|
+
state.repos[activeRepo.repoId] = {
|
|
230
|
+
...state.repos[activeRepo.repoId],
|
|
231
|
+
lastSyncTimestamp: twentyFourHoursAgo,
|
|
232
|
+
lastSyncCommitSha: null
|
|
233
|
+
};
|
|
234
|
+
changes.push(`[self-heal] Auto-added ${activeRepo.fullName} from ${found}`);
|
|
235
|
+
updated = true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return { updated, repos: updatedRepos, changes, addedRepos };
|
|
241
|
+
}
|
|
242
|
+
function findLocalRepo(activeRepo, parentDirs, usedPaths) {
|
|
243
|
+
const nameParts = activeRepo.fullName.split("/");
|
|
244
|
+
const repoName = nameParts[nameParts.length - 1];
|
|
245
|
+
for (const parentDir of parentDirs) {
|
|
246
|
+
const candidate = join4(parentDir, repoName);
|
|
247
|
+
if (!usedPaths.has(candidate) && hasContextFolder(candidate)) {
|
|
248
|
+
return candidate;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
function hasContextFolder(dirPath) {
|
|
254
|
+
try {
|
|
255
|
+
const contextPath = join4(dirPath, "repowise-context");
|
|
256
|
+
const s = statSync(contextPath);
|
|
257
|
+
if (!s.isDirectory())
|
|
258
|
+
return false;
|
|
259
|
+
const entries = readdirSync(contextPath);
|
|
260
|
+
return entries.length > 0;
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
155
264
|
}
|
|
156
265
|
function migrateState(state, oldId, newId) {
|
|
157
266
|
const oldState = state.repos[oldId];
|
|
158
267
|
if (oldState) {
|
|
159
268
|
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
160
269
|
state.repos[newId] = {
|
|
270
|
+
...state.repos[newId],
|
|
161
271
|
lastSyncTimestamp: twentyFourHoursAgo,
|
|
162
272
|
lastSyncCommitSha: null
|
|
163
273
|
};
|
|
164
274
|
delete state.repos[oldId];
|
|
165
275
|
} else {
|
|
166
276
|
state.repos[newId] = {
|
|
277
|
+
...state.repos[newId],
|
|
167
278
|
lastSyncTimestamp: new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString(),
|
|
168
279
|
lastSyncCommitSha: null
|
|
169
280
|
};
|
|
@@ -172,10 +283,7 @@ function migrateState(state, oldId, newId) {
|
|
|
172
283
|
|
|
173
284
|
// ../listener/dist/lib/auth.js
|
|
174
285
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, chmod as chmod3 } from "fs/promises";
|
|
175
|
-
import {
|
|
176
|
-
import { join as join3 } from "path";
|
|
177
|
-
var CONFIG_DIR3 = join3(homedir3(), ".repowise");
|
|
178
|
-
var CREDENTIALS_PATH = join3(CONFIG_DIR3, "credentials.json");
|
|
286
|
+
import { join as join5 } from "path";
|
|
179
287
|
function getTokenUrl(creds) {
|
|
180
288
|
const cognito = creds?.cognito;
|
|
181
289
|
const domain = process.env["REPOWISE_COGNITO_DOMAIN"] ?? cognito?.domain ?? "auth-repowise-dev";
|
|
@@ -214,7 +322,8 @@ async function refreshTokens(refreshToken, creds) {
|
|
|
214
322
|
}
|
|
215
323
|
async function getStoredCredentials() {
|
|
216
324
|
try {
|
|
217
|
-
const
|
|
325
|
+
const credPath = join5(getConfigDir(), "credentials.json");
|
|
326
|
+
const data = await readFile3(credPath, "utf-8");
|
|
218
327
|
return JSON.parse(data);
|
|
219
328
|
} catch (err) {
|
|
220
329
|
if (err.code === "ENOENT" || err instanceof SyntaxError) {
|
|
@@ -224,9 +333,11 @@ async function getStoredCredentials() {
|
|
|
224
333
|
}
|
|
225
334
|
}
|
|
226
335
|
async function storeCredentials(credentials) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
await
|
|
336
|
+
const dir = getConfigDir();
|
|
337
|
+
const credPath = join5(dir, "credentials.json");
|
|
338
|
+
await mkdir3(dir, { recursive: true, mode: 448 });
|
|
339
|
+
await writeFile3(credPath, JSON.stringify(credentials, null, 2));
|
|
340
|
+
await chmod3(credPath, 384);
|
|
230
341
|
}
|
|
231
342
|
async function getValidCredentials(options) {
|
|
232
343
|
const creds = await getStoredCredentials();
|
|
@@ -377,15 +488,15 @@ function notifyContextUpdated(repoId, fileCount) {
|
|
|
377
488
|
// ../listener/dist/context-fetcher.js
|
|
378
489
|
import { execFile } from "child_process";
|
|
379
490
|
import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
|
|
380
|
-
import { dirname as dirname2, join as
|
|
491
|
+
import { dirname as dirname2, join as join7 } from "path";
|
|
381
492
|
import { promisify } from "util";
|
|
382
493
|
|
|
383
494
|
// ../listener/dist/file-writer.js
|
|
384
495
|
import { access } from "fs/promises";
|
|
385
|
-
import { join as
|
|
496
|
+
import { join as join6 } from "path";
|
|
386
497
|
async function verifyContextFolder(localPath) {
|
|
387
498
|
try {
|
|
388
|
-
await access(
|
|
499
|
+
await access(join6(localPath, "repowise-context"));
|
|
389
500
|
return true;
|
|
390
501
|
} catch {
|
|
391
502
|
return false;
|
|
@@ -473,7 +584,7 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
473
584
|
if (files.length === 0) {
|
|
474
585
|
return { success: true, updatedFiles: [] };
|
|
475
586
|
}
|
|
476
|
-
const contextDir =
|
|
587
|
+
const contextDir = join7(localPath, "repowise-context");
|
|
477
588
|
await mkdir4(contextDir, { recursive: true });
|
|
478
589
|
const updatedFiles = [];
|
|
479
590
|
for (const file of files) {
|
|
@@ -498,7 +609,7 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
498
609
|
continue;
|
|
499
610
|
}
|
|
500
611
|
const content = await contentRes.text();
|
|
501
|
-
const filePath =
|
|
612
|
+
const filePath = join7(contextDir, file.fileName);
|
|
502
613
|
await mkdir4(dirname2(filePath), { recursive: true });
|
|
503
614
|
await writeFile4(filePath, content, "utf-8");
|
|
504
615
|
updatedFiles.push(file.fileName);
|
|
@@ -512,175 +623,820 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
512
623
|
}
|
|
513
624
|
}
|
|
514
625
|
|
|
515
|
-
// ../listener/dist/
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
626
|
+
// ../listener/dist/process-manager.js
|
|
627
|
+
import { spawn } from "child_process";
|
|
628
|
+
import { openSync, closeSync } from "fs";
|
|
629
|
+
import { readFile as readFile4, writeFile as writeFile5, mkdir as mkdir5, unlink as unlink3 } from "fs/promises";
|
|
630
|
+
import { homedir as homedir2 } from "os";
|
|
631
|
+
import { join as join8 } from "path";
|
|
632
|
+
import { createRequire } from "module";
|
|
633
|
+
import { fileURLToPath } from "url";
|
|
634
|
+
function repowiseDir() {
|
|
635
|
+
return getConfigDir();
|
|
523
636
|
}
|
|
524
|
-
|
|
637
|
+
function pidPath() {
|
|
638
|
+
return join8(repowiseDir(), "listener.pid");
|
|
639
|
+
}
|
|
640
|
+
function logDirPath() {
|
|
641
|
+
return join8(repowiseDir(), "logs");
|
|
642
|
+
}
|
|
643
|
+
function resolveListenerCommand() {
|
|
525
644
|
try {
|
|
526
|
-
|
|
645
|
+
const require2 = createRequire(import.meta.url);
|
|
646
|
+
const mainPath = require2.resolve("@repowise/listener/main");
|
|
647
|
+
return { script: mainPath, args: [] };
|
|
527
648
|
} catch {
|
|
649
|
+
const bundlePath = fileURLToPath(import.meta.url);
|
|
650
|
+
return { script: bundlePath, args: ["__listener"] };
|
|
528
651
|
}
|
|
529
652
|
}
|
|
530
|
-
async function
|
|
653
|
+
async function readPid() {
|
|
531
654
|
try {
|
|
532
|
-
const content = await readFile4(
|
|
655
|
+
const content = await readFile4(pidPath(), "utf-8");
|
|
533
656
|
const pid = parseInt(content.trim(), 10);
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return true;
|
|
540
|
-
} catch {
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
} catch {
|
|
657
|
+
return Number.isNaN(pid) ? null : pid;
|
|
658
|
+
} catch (err) {
|
|
659
|
+
if (err.code === "ENOENT")
|
|
660
|
+
return null;
|
|
661
|
+
throw err;
|
|
544
662
|
}
|
|
545
|
-
return false;
|
|
546
663
|
}
|
|
547
|
-
function
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
664
|
+
function isAlive(pid) {
|
|
665
|
+
try {
|
|
666
|
+
process.kill(pid, 0);
|
|
667
|
+
return true;
|
|
668
|
+
} catch {
|
|
669
|
+
return false;
|
|
552
670
|
}
|
|
553
671
|
}
|
|
554
|
-
function
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
672
|
+
async function startBackground() {
|
|
673
|
+
if (await isRunning()) {
|
|
674
|
+
const pid2 = await readPid();
|
|
675
|
+
if (pid2)
|
|
676
|
+
return pid2;
|
|
677
|
+
}
|
|
678
|
+
const logDir2 = logDirPath();
|
|
679
|
+
await mkdir5(logDir2, { recursive: true });
|
|
680
|
+
const cmd = resolveListenerCommand();
|
|
681
|
+
const stdoutFd = openSync(join8(logDir2, "listener-stdout.log"), "a");
|
|
682
|
+
const stderrFd = openSync(join8(logDir2, "listener-stderr.log"), "a");
|
|
683
|
+
const child = spawn(process.execPath, [cmd.script, ...cmd.args], {
|
|
684
|
+
detached: true,
|
|
685
|
+
stdio: ["ignore", stdoutFd, stderrFd],
|
|
686
|
+
cwd: homedir2(),
|
|
687
|
+
env: { ...process.env }
|
|
561
688
|
});
|
|
689
|
+
child.unref();
|
|
690
|
+
closeSync(stdoutFd);
|
|
691
|
+
closeSync(stderrFd);
|
|
692
|
+
const pid = child.pid;
|
|
693
|
+
if (!pid)
|
|
694
|
+
throw new Error("Failed to spawn listener process");
|
|
695
|
+
await writeFile5(pidPath(), String(pid));
|
|
696
|
+
return pid;
|
|
562
697
|
}
|
|
563
|
-
function
|
|
698
|
+
async function stopProcess() {
|
|
699
|
+
const pid = await readPid();
|
|
700
|
+
if (pid === null)
|
|
701
|
+
return;
|
|
702
|
+
if (!isAlive(pid)) {
|
|
703
|
+
await removePidFile();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
564
706
|
try {
|
|
565
|
-
|
|
566
|
-
return typeof payload.email === "string" ? payload.email : null;
|
|
707
|
+
process.kill(pid, "SIGTERM");
|
|
567
708
|
} catch {
|
|
568
|
-
return null;
|
|
569
709
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
if (notif.type === "sync.completed") {
|
|
579
|
-
const localPath = repoLocalPaths.get(notif.repoId);
|
|
580
|
-
if (localPath) {
|
|
581
|
-
let result;
|
|
582
|
-
if (apiUrl) {
|
|
583
|
-
result = await fetchContextFromServer(notif.repoId, localPath, apiUrl);
|
|
584
|
-
} else {
|
|
585
|
-
result = await fetchContextUpdates(localPath);
|
|
586
|
-
}
|
|
587
|
-
if (result.success) {
|
|
588
|
-
updateCount++;
|
|
589
|
-
notifyContextUpdated(notif.repoId, result.updatedFiles.length);
|
|
590
|
-
state.repos[notif.repoId] = {
|
|
591
|
-
lastSyncTimestamp: notif.createdAt,
|
|
592
|
-
lastSyncCommitSha: notif.commitSha
|
|
593
|
-
};
|
|
594
|
-
} else {
|
|
595
|
-
console.error(`Download failed for repo ${notif.repoId} \u2014 will retry on next poll`);
|
|
596
|
-
}
|
|
597
|
-
} else {
|
|
598
|
-
state.repos[notif.repoId] = {
|
|
599
|
-
lastSyncTimestamp: notif.createdAt,
|
|
600
|
-
lastSyncCommitSha: notif.commitSha
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
} else {
|
|
604
|
-
state.repos[notif.repoId] = {
|
|
605
|
-
lastSyncTimestamp: notif.createdAt,
|
|
606
|
-
lastSyncCommitSha: notif.commitSha
|
|
607
|
-
};
|
|
710
|
+
const deadline = Date.now() + 5e3;
|
|
711
|
+
while (Date.now() < deadline && isAlive(pid)) {
|
|
712
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
713
|
+
}
|
|
714
|
+
if (isAlive(pid)) {
|
|
715
|
+
try {
|
|
716
|
+
process.kill(pid, "SIGKILL");
|
|
717
|
+
} catch {
|
|
608
718
|
}
|
|
609
719
|
}
|
|
610
|
-
|
|
720
|
+
await removePidFile();
|
|
611
721
|
}
|
|
612
|
-
async function
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
} else {
|
|
623
|
-
result = await fetchContextUpdates(localPath);
|
|
624
|
-
}
|
|
625
|
-
if (result.success)
|
|
626
|
-
syncCount++;
|
|
627
|
-
}
|
|
628
|
-
notifyBackOnline(syncCount);
|
|
629
|
-
} else {
|
|
630
|
-
const sinceTimestamp = offlineState.offlineSince;
|
|
631
|
-
const response = await pollClient.poll(repoIds, sinceTimestamp);
|
|
632
|
-
const updateCount = await processNotifications(response.notifications, state, repoLocalPaths, apiUrl);
|
|
633
|
-
await saveState(state);
|
|
634
|
-
notifyBackOnline(updateCount);
|
|
722
|
+
async function isRunning() {
|
|
723
|
+
const pid = await readPid();
|
|
724
|
+
if (pid === null)
|
|
725
|
+
return false;
|
|
726
|
+
return isAlive(pid);
|
|
727
|
+
}
|
|
728
|
+
async function removePidFile() {
|
|
729
|
+
try {
|
|
730
|
+
await unlink3(pidPath());
|
|
731
|
+
} catch {
|
|
635
732
|
}
|
|
636
733
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
734
|
+
|
|
735
|
+
// ../listener/dist/lib/auto-updater.js
|
|
736
|
+
import { execFile as execFile2 } from "child_process";
|
|
737
|
+
import { access as access2, constants, realpath } from "fs/promises";
|
|
738
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
739
|
+
import { promisify as promisify2 } from "util";
|
|
740
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
741
|
+
async function installUpdate(currentVersion, packageName, targetVersion) {
|
|
742
|
+
if (process.env["CI"] === "true")
|
|
743
|
+
return { updated: false };
|
|
744
|
+
if (!/^\d+\.\d+\.\d+/.test(targetVersion))
|
|
745
|
+
return { updated: false };
|
|
746
|
+
if (!isNewer(targetVersion, currentVersion)) {
|
|
747
|
+
console.log(`[auto-update] ${targetVersion} is not newer than ${currentVersion} \u2014 skipping`);
|
|
748
|
+
return { updated: false };
|
|
749
|
+
}
|
|
750
|
+
const npmWrapper = join9(dirname3(process.execPath), "npm");
|
|
751
|
+
const npmScript = await realpath(npmWrapper);
|
|
752
|
+
const runNpm = (args) => execFileAsync2(process.execPath, [npmScript, ...args], { timeout: 6e4 });
|
|
753
|
+
try {
|
|
754
|
+
const { stdout: prefix } = await runNpm(["prefix", "-g"]);
|
|
755
|
+
const npmDir = join9(prefix.trim(), "lib", "node_modules");
|
|
756
|
+
const checkDir = process.platform === "win32" ? prefix.trim() : npmDir;
|
|
757
|
+
await access2(checkDir, constants.W_OK);
|
|
758
|
+
} catch (err) {
|
|
759
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
760
|
+
console.log(`[auto-update] npm global prefix not writable \u2014 skipping update (${msg})`);
|
|
761
|
+
return { updated: false };
|
|
643
762
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
console.
|
|
648
|
-
|
|
649
|
-
|
|
763
|
+
console.log(`[auto-update] Updating ${packageName} from ${currentVersion} to ${targetVersion}...`);
|
|
764
|
+
try {
|
|
765
|
+
await runNpm(["install", "-g", "--ignore-scripts", `${packageName}@${targetVersion}`]);
|
|
766
|
+
console.log(`[auto-update] Successfully updated to ${targetVersion}`);
|
|
767
|
+
return { updated: true, latestVersion: targetVersion };
|
|
768
|
+
} catch (err) {
|
|
769
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
770
|
+
console.error(`[auto-update] Installation failed: ${msg}`);
|
|
771
|
+
return { updated: false, error: msg };
|
|
650
772
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
773
|
+
}
|
|
774
|
+
function isNewer(a, b) {
|
|
775
|
+
const parseVer = (v) => {
|
|
776
|
+
const [main, pre] = v.split("-", 2);
|
|
777
|
+
const parts = main.split(".").map(Number);
|
|
778
|
+
return { parts, pre: pre ?? "" };
|
|
779
|
+
};
|
|
780
|
+
const va = parseVer(a);
|
|
781
|
+
const vb = parseVer(b);
|
|
782
|
+
for (let i = 0; i < Math.max(va.parts.length, vb.parts.length); i++) {
|
|
783
|
+
const na = va.parts[i] ?? 0;
|
|
784
|
+
const nb = vb.parts[i] ?? 0;
|
|
785
|
+
if (na > nb)
|
|
786
|
+
return true;
|
|
787
|
+
if (na < nb)
|
|
788
|
+
return false;
|
|
656
789
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
790
|
+
if (vb.pre && !va.pre)
|
|
791
|
+
return true;
|
|
792
|
+
if (va.pre && vb.pre)
|
|
793
|
+
return comparePrerelease(va.pre, vb.pre) > 0;
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
function comparePrerelease(a, b) {
|
|
797
|
+
const aParts = a.split(".");
|
|
798
|
+
const bParts = b.split(".");
|
|
799
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
800
|
+
const ap = aParts[i];
|
|
801
|
+
const bp = bParts[i];
|
|
802
|
+
if (ap === void 0)
|
|
803
|
+
return -1;
|
|
804
|
+
if (bp === void 0)
|
|
805
|
+
return 1;
|
|
806
|
+
const aNum = /^\d+$/.test(ap) ? parseInt(ap, 10) : NaN;
|
|
807
|
+
const bNum = /^\d+$/.test(bp) ? parseInt(bp, 10) : NaN;
|
|
808
|
+
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
809
|
+
if (aNum > bNum)
|
|
810
|
+
return 1;
|
|
811
|
+
if (aNum < bNum)
|
|
812
|
+
return -1;
|
|
813
|
+
} else {
|
|
814
|
+
if (ap > bp)
|
|
815
|
+
return 1;
|
|
816
|
+
if (ap < bp)
|
|
817
|
+
return -1;
|
|
672
818
|
}
|
|
673
|
-
group.repoIds.push(repo.repoId);
|
|
674
|
-
group.repoLocalPaths.set(repo.repoId, repo.localPath);
|
|
675
819
|
}
|
|
676
|
-
|
|
677
|
-
|
|
820
|
+
return 0;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// ../listener/dist/lifecycle.js
|
|
824
|
+
import { unlink as unlink5 } from "fs/promises";
|
|
825
|
+
import { join as join11 } from "path";
|
|
826
|
+
|
|
827
|
+
// ../listener/dist/service-installer.js
|
|
828
|
+
import { execFile as execFile3 } from "child_process";
|
|
829
|
+
import { writeFile as writeFile6, mkdir as mkdir6, unlink as unlink4 } from "fs/promises";
|
|
830
|
+
import { homedir as homedir3 } from "os";
|
|
831
|
+
import { join as join10 } from "path";
|
|
832
|
+
var IS_STAGING = true ? false : false;
|
|
833
|
+
function exec(cmd, args) {
|
|
834
|
+
return new Promise((resolve, reject) => {
|
|
835
|
+
execFile3(cmd, args, (err, stdout) => {
|
|
836
|
+
if (err) {
|
|
837
|
+
reject(err);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
resolve(String(stdout ?? ""));
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
var PLIST_LABEL = IS_STAGING ? "com.repowise-staging.listener" : "com.repowise.listener";
|
|
845
|
+
function plistPath() {
|
|
846
|
+
return join10(homedir3(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
847
|
+
}
|
|
848
|
+
function logDir() {
|
|
849
|
+
return join10(getConfigDir(), "logs");
|
|
850
|
+
}
|
|
851
|
+
function buildPlist() {
|
|
852
|
+
const cmd = resolveListenerCommand();
|
|
853
|
+
const logs = logDir();
|
|
854
|
+
const programArgs = [process.execPath, cmd.script, ...cmd.args].map((a) => ` <string>${a}</string>`).join("\n");
|
|
855
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
856
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
857
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
858
|
+
<plist version="1.0">
|
|
859
|
+
<dict>
|
|
860
|
+
<key>Label</key>
|
|
861
|
+
<string>${PLIST_LABEL}</string>
|
|
862
|
+
<key>ProgramArguments</key>
|
|
863
|
+
<array>
|
|
864
|
+
${programArgs}
|
|
865
|
+
</array>
|
|
866
|
+
<key>RunAtLoad</key>
|
|
867
|
+
<true/>
|
|
868
|
+
<key>KeepAlive</key>
|
|
869
|
+
<true/>
|
|
870
|
+
<key>StandardOutPath</key>
|
|
871
|
+
<string>${join10(logs, "listener-stdout.log")}</string>
|
|
872
|
+
<key>StandardErrorPath</key>
|
|
873
|
+
<string>${join10(logs, "listener-stderr.log")}</string>
|
|
874
|
+
<key>ProcessType</key>
|
|
875
|
+
<string>Background</string>
|
|
876
|
+
</dict>
|
|
877
|
+
</plist>`;
|
|
878
|
+
}
|
|
879
|
+
async function darwinInstall() {
|
|
880
|
+
await mkdir6(logDir(), { recursive: true });
|
|
881
|
+
await mkdir6(join10(homedir3(), "Library", "LaunchAgents"), { recursive: true });
|
|
882
|
+
try {
|
|
883
|
+
await exec("launchctl", ["unload", plistPath()]);
|
|
884
|
+
} catch {
|
|
885
|
+
}
|
|
886
|
+
await writeFile6(plistPath(), buildPlist());
|
|
887
|
+
await exec("launchctl", ["load", plistPath()]);
|
|
888
|
+
}
|
|
889
|
+
async function darwinUninstall() {
|
|
890
|
+
try {
|
|
891
|
+
await exec("launchctl", ["unload", plistPath()]);
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
894
|
+
try {
|
|
895
|
+
await unlink4(plistPath());
|
|
896
|
+
} catch {
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
async function darwinIsInstalled() {
|
|
900
|
+
try {
|
|
901
|
+
const stdout = await exec("launchctl", ["list"]);
|
|
902
|
+
return stdout.includes(PLIST_LABEL);
|
|
903
|
+
} catch {
|
|
904
|
+
return false;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
var SYSTEMD_SERVICE = IS_STAGING ? "repowise-staging-listener" : "repowise-listener";
|
|
908
|
+
function unitPath() {
|
|
909
|
+
return join10(homedir3(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
|
|
910
|
+
}
|
|
911
|
+
function buildUnit() {
|
|
912
|
+
const cmd = resolveListenerCommand();
|
|
913
|
+
const execStart = [process.execPath, cmd.script, ...cmd.args].join(" ");
|
|
914
|
+
const logs = logDir();
|
|
915
|
+
return `[Unit]
|
|
916
|
+
Description=RepoWise Listener
|
|
917
|
+
After=network-online.target
|
|
918
|
+
Wants=network-online.target
|
|
919
|
+
|
|
920
|
+
[Service]
|
|
921
|
+
Type=simple
|
|
922
|
+
ExecStart=${execStart}
|
|
923
|
+
Restart=always
|
|
924
|
+
RestartSec=10
|
|
925
|
+
StandardOutput=append:${join10(logs, "listener-stdout.log")}
|
|
926
|
+
StandardError=append:${join10(logs, "listener-stderr.log")}
|
|
927
|
+
|
|
928
|
+
[Install]
|
|
929
|
+
WantedBy=default.target`;
|
|
930
|
+
}
|
|
931
|
+
async function linuxInstall() {
|
|
932
|
+
await mkdir6(logDir(), { recursive: true });
|
|
933
|
+
await mkdir6(join10(homedir3(), ".config", "systemd", "user"), { recursive: true });
|
|
934
|
+
await writeFile6(unitPath(), buildUnit());
|
|
935
|
+
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
936
|
+
await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
|
|
937
|
+
await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
|
|
938
|
+
}
|
|
939
|
+
async function linuxUninstall() {
|
|
940
|
+
try {
|
|
941
|
+
await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
|
|
942
|
+
} catch {
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
await exec("systemctl", ["--user", "disable", SYSTEMD_SERVICE]);
|
|
946
|
+
} catch {
|
|
947
|
+
}
|
|
948
|
+
try {
|
|
949
|
+
await unlink4(unitPath());
|
|
950
|
+
} catch {
|
|
951
|
+
}
|
|
952
|
+
try {
|
|
953
|
+
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
954
|
+
} catch {
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
async function linuxIsInstalled() {
|
|
958
|
+
try {
|
|
959
|
+
const stdout = await exec("systemctl", ["--user", "is-enabled", SYSTEMD_SERVICE]);
|
|
960
|
+
return stdout.trim() === "enabled";
|
|
961
|
+
} catch {
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
var TASK_NAME = IS_STAGING ? "RepoWise Staging Listener" : "RepoWise Listener";
|
|
966
|
+
async function win32Install() {
|
|
967
|
+
await mkdir6(logDir(), { recursive: true });
|
|
968
|
+
const cmd = resolveListenerCommand();
|
|
969
|
+
const taskCmd = [process.execPath, cmd.script, ...cmd.args].map((a) => `"${a}"`).join(" ");
|
|
970
|
+
await exec("schtasks", [
|
|
971
|
+
"/create",
|
|
972
|
+
"/tn",
|
|
973
|
+
TASK_NAME,
|
|
974
|
+
"/tr",
|
|
975
|
+
taskCmd,
|
|
976
|
+
"/sc",
|
|
977
|
+
"onlogon",
|
|
978
|
+
"/ru",
|
|
979
|
+
process.env.USERNAME ?? "",
|
|
980
|
+
"/f"
|
|
981
|
+
]);
|
|
982
|
+
await exec("schtasks", ["/run", "/tn", TASK_NAME]);
|
|
983
|
+
}
|
|
984
|
+
async function win32Uninstall() {
|
|
985
|
+
try {
|
|
986
|
+
await exec("schtasks", ["/end", "/tn", TASK_NAME]);
|
|
987
|
+
} catch {
|
|
988
|
+
}
|
|
989
|
+
try {
|
|
990
|
+
await exec("schtasks", ["/delete", "/tn", TASK_NAME, "/f"]);
|
|
991
|
+
} catch {
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
async function win32IsInstalled() {
|
|
995
|
+
try {
|
|
996
|
+
await exec("schtasks", ["/query", "/tn", TASK_NAME]);
|
|
997
|
+
return true;
|
|
998
|
+
} catch {
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
async function darwinStop() {
|
|
1003
|
+
try {
|
|
1004
|
+
await exec("launchctl", ["unload", plistPath()]);
|
|
1005
|
+
} catch {
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
async function linuxStop() {
|
|
1009
|
+
await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
|
|
1010
|
+
}
|
|
1011
|
+
async function win32Stop() {
|
|
1012
|
+
await exec("schtasks", ["/end", "/tn", TASK_NAME]);
|
|
1013
|
+
}
|
|
1014
|
+
async function darwinIsServiceRunning() {
|
|
1015
|
+
try {
|
|
1016
|
+
const stdout = await exec("launchctl", ["list", PLIST_LABEL]);
|
|
1017
|
+
const pidMatch = stdout.match(/"PID"\s*=\s*(\d+)/);
|
|
1018
|
+
return pidMatch !== null && parseInt(pidMatch[1], 10) > 0;
|
|
1019
|
+
} catch {
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
async function linuxIsServiceRunning() {
|
|
1024
|
+
try {
|
|
1025
|
+
const stdout = await exec("systemctl", ["--user", "is-active", SYSTEMD_SERVICE]);
|
|
1026
|
+
return stdout.trim() === "active";
|
|
1027
|
+
} catch {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
async function win32IsServiceRunning() {
|
|
1032
|
+
try {
|
|
1033
|
+
const stdout = await exec("schtasks", ["/query", "/tn", TASK_NAME, "/fo", "CSV"]);
|
|
1034
|
+
return stdout.includes("Running");
|
|
1035
|
+
} catch {
|
|
1036
|
+
return false;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
async function stopService() {
|
|
1040
|
+
switch (process.platform) {
|
|
1041
|
+
case "darwin":
|
|
1042
|
+
await darwinStop();
|
|
1043
|
+
break;
|
|
1044
|
+
case "linux":
|
|
1045
|
+
await linuxStop();
|
|
1046
|
+
break;
|
|
1047
|
+
case "win32":
|
|
1048
|
+
await win32Stop();
|
|
1049
|
+
break;
|
|
1050
|
+
default:
|
|
1051
|
+
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
async function install() {
|
|
1055
|
+
switch (process.platform) {
|
|
1056
|
+
case "darwin":
|
|
1057
|
+
await darwinInstall();
|
|
1058
|
+
break;
|
|
1059
|
+
case "linux":
|
|
1060
|
+
await linuxInstall();
|
|
1061
|
+
break;
|
|
1062
|
+
case "win32":
|
|
1063
|
+
await win32Install();
|
|
1064
|
+
break;
|
|
1065
|
+
default:
|
|
1066
|
+
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
async function uninstall() {
|
|
1070
|
+
switch (process.platform) {
|
|
1071
|
+
case "darwin":
|
|
1072
|
+
await darwinUninstall();
|
|
1073
|
+
break;
|
|
1074
|
+
case "linux":
|
|
1075
|
+
await linuxUninstall();
|
|
1076
|
+
break;
|
|
1077
|
+
case "win32":
|
|
1078
|
+
await win32Uninstall();
|
|
1079
|
+
break;
|
|
1080
|
+
default:
|
|
1081
|
+
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
async function isInstalled() {
|
|
1085
|
+
switch (process.platform) {
|
|
1086
|
+
case "darwin":
|
|
1087
|
+
return darwinIsInstalled();
|
|
1088
|
+
case "linux":
|
|
1089
|
+
return linuxIsInstalled();
|
|
1090
|
+
case "win32":
|
|
1091
|
+
return win32IsInstalled();
|
|
1092
|
+
default:
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
async function isServiceRunning() {
|
|
1097
|
+
switch (process.platform) {
|
|
1098
|
+
case "darwin":
|
|
1099
|
+
return darwinIsServiceRunning();
|
|
1100
|
+
case "linux":
|
|
1101
|
+
return linuxIsServiceRunning();
|
|
1102
|
+
case "win32":
|
|
1103
|
+
return win32IsServiceRunning();
|
|
1104
|
+
default:
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// ../listener/dist/lifecycle.js
|
|
1110
|
+
async function getListenerStatus() {
|
|
1111
|
+
const pid = await readPid();
|
|
1112
|
+
if (pid !== null) {
|
|
1113
|
+
if (isAlive(pid)) {
|
|
1114
|
+
const serviceInstalled2 = await isInstalled();
|
|
1115
|
+
return { running: true, method: "pid", pid, serviceInstalled: serviceInstalled2 };
|
|
1116
|
+
}
|
|
1117
|
+
try {
|
|
1118
|
+
await unlink5(join11(getConfigDir(), "listener.pid"));
|
|
1119
|
+
} catch {
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
try {
|
|
1123
|
+
if (await isServiceRunning()) {
|
|
1124
|
+
return { running: true, method: "service", serviceInstalled: true };
|
|
1125
|
+
}
|
|
1126
|
+
} catch {
|
|
1127
|
+
}
|
|
1128
|
+
const serviceInstalled = await isInstalled();
|
|
1129
|
+
return { running: false, method: "none", serviceInstalled };
|
|
1130
|
+
}
|
|
1131
|
+
async function ensureListenerRunning() {
|
|
1132
|
+
const currentStatus = await getListenerStatus();
|
|
1133
|
+
if (currentStatus.running)
|
|
1134
|
+
return currentStatus;
|
|
1135
|
+
try {
|
|
1136
|
+
await install();
|
|
1137
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
1138
|
+
const postInstall = await getListenerStatus();
|
|
1139
|
+
if (postInstall.running)
|
|
1140
|
+
return postInstall;
|
|
1141
|
+
} catch {
|
|
1142
|
+
}
|
|
1143
|
+
const pid = await startBackground();
|
|
1144
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1145
|
+
if (!isAlive(pid)) {
|
|
1146
|
+
return { running: false, method: "none", serviceInstalled: await isInstalled() };
|
|
1147
|
+
}
|
|
1148
|
+
return { running: true, method: "pid", pid, serviceInstalled: await isInstalled() };
|
|
1149
|
+
}
|
|
1150
|
+
async function stopListener() {
|
|
1151
|
+
try {
|
|
1152
|
+
await stopProcess();
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1155
|
+
try {
|
|
1156
|
+
await stopService();
|
|
1157
|
+
} catch {
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// ../listener/dist/main.js
|
|
1162
|
+
var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
|
|
1163
|
+
var STALE_CHECK_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1164
|
+
var CRASH_LOOP_WINDOW_MS = 3e4;
|
|
1165
|
+
var CRASH_LOOP_THRESHOLD = 3;
|
|
1166
|
+
var running = false;
|
|
1167
|
+
var sleepResolve = null;
|
|
1168
|
+
var releaseLock = null;
|
|
1169
|
+
function stop() {
|
|
1170
|
+
running = false;
|
|
1171
|
+
if (sleepResolve) {
|
|
1172
|
+
sleepResolve();
|
|
1173
|
+
sleepResolve = null;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function sleep(ms) {
|
|
1177
|
+
return new Promise((resolve) => {
|
|
1178
|
+
sleepResolve = resolve;
|
|
1179
|
+
setTimeout(() => {
|
|
1180
|
+
sleepResolve = null;
|
|
1181
|
+
resolve();
|
|
1182
|
+
}, ms);
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
function decodeEmailFromIdToken(idToken) {
|
|
1186
|
+
try {
|
|
1187
|
+
const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
|
|
1188
|
+
return typeof payload.email === "string" ? payload.email : null;
|
|
1189
|
+
} catch {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
async function processNotifications(notifications, state, repoLocalPaths, apiUrl) {
|
|
1194
|
+
let updateCount = 0;
|
|
1195
|
+
for (const notif of notifications) {
|
|
1196
|
+
const repoState = state.repos[notif.repoId];
|
|
1197
|
+
if (repoState && notif.createdAt <= repoState.lastSyncTimestamp) {
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (notif.type === "sync.completed") {
|
|
1201
|
+
if (!notif.commitSha) {
|
|
1202
|
+
console.warn(`sync.completed for ${notif.repoId} has no commitSha`);
|
|
1203
|
+
}
|
|
1204
|
+
const localPath = repoLocalPaths.get(notif.repoId);
|
|
1205
|
+
if (localPath) {
|
|
1206
|
+
let result;
|
|
1207
|
+
if (apiUrl) {
|
|
1208
|
+
result = await fetchContextFromServer(notif.repoId, localPath, apiUrl);
|
|
1209
|
+
} else {
|
|
1210
|
+
result = await fetchContextUpdates(localPath);
|
|
1211
|
+
}
|
|
1212
|
+
if (result.success) {
|
|
1213
|
+
updateCount++;
|
|
1214
|
+
notifyContextUpdated(notif.repoId, result.updatedFiles.length);
|
|
1215
|
+
state.repos[notif.repoId] = {
|
|
1216
|
+
...state.repos[notif.repoId],
|
|
1217
|
+
lastSyncTimestamp: notif.createdAt,
|
|
1218
|
+
lastSyncCommitSha: notif.commitSha
|
|
1219
|
+
};
|
|
1220
|
+
} else {
|
|
1221
|
+
console.error(`Download failed for repo ${notif.repoId} \u2014 will retry on next poll`);
|
|
1222
|
+
}
|
|
1223
|
+
} else {
|
|
1224
|
+
state.repos[notif.repoId] = {
|
|
1225
|
+
...state.repos[notif.repoId],
|
|
1226
|
+
lastSyncTimestamp: notif.createdAt,
|
|
1227
|
+
lastSyncCommitSha: notif.commitSha
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
} else {
|
|
1231
|
+
state.repos[notif.repoId] = {
|
|
1232
|
+
...state.repos[notif.repoId],
|
|
1233
|
+
lastSyncTimestamp: notif.createdAt,
|
|
1234
|
+
lastSyncCommitSha: notif.commitSha
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return updateCount;
|
|
1239
|
+
}
|
|
1240
|
+
async function handleCatchUp(offlineState, pollClient, repoIds, state, repoLocalPaths, apiUrl) {
|
|
1241
|
+
if (!offlineState.offlineSince)
|
|
1242
|
+
return;
|
|
1243
|
+
const offlineDuration = Date.now() - new Date(offlineState.offlineSince).getTime();
|
|
1244
|
+
if (offlineDuration >= TWENTY_FOUR_HOURS_MS) {
|
|
1245
|
+
let syncCount = 0;
|
|
1246
|
+
for (const [repoId, localPath] of repoLocalPaths) {
|
|
1247
|
+
let result;
|
|
1248
|
+
if (apiUrl) {
|
|
1249
|
+
result = await fetchContextFromServer(repoId, localPath, apiUrl);
|
|
1250
|
+
} else {
|
|
1251
|
+
result = await fetchContextUpdates(localPath);
|
|
1252
|
+
}
|
|
1253
|
+
if (result.success) {
|
|
1254
|
+
syncCount++;
|
|
1255
|
+
state.repos[repoId] = {
|
|
1256
|
+
...state.repos[repoId],
|
|
1257
|
+
lastSyncTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1258
|
+
lastSyncCommitSha: null
|
|
1259
|
+
// unknown from catch-up, but timestamp prevents re-download
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
await saveState(state);
|
|
1264
|
+
notifyBackOnline(syncCount);
|
|
1265
|
+
} else {
|
|
1266
|
+
const sinceTimestamp = offlineState.offlineSince;
|
|
1267
|
+
const response = await pollClient.poll(repoIds, sinceTimestamp);
|
|
1268
|
+
const updateCount = await processNotifications(response.notifications, state, repoLocalPaths, apiUrl);
|
|
1269
|
+
await saveState(state);
|
|
1270
|
+
notifyBackOnline(updateCount);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
async function checkStaleContext(repos, state, groups) {
|
|
1274
|
+
const now = Date.now();
|
|
1275
|
+
let dirty = false;
|
|
1276
|
+
for (const repo of repos) {
|
|
1277
|
+
const repoState = state.repos[repo.repoId];
|
|
1278
|
+
if (!repoState)
|
|
1279
|
+
continue;
|
|
1280
|
+
if (repoState.lastContextCheckAt) {
|
|
1281
|
+
const lastCheck = new Date(repoState.lastContextCheckAt).getTime();
|
|
1282
|
+
if (now - lastCheck < STALE_CHECK_COOLDOWN_MS)
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
const apiUrl = repo.apiUrl;
|
|
1286
|
+
const group = groups.find((g) => g.apiUrl === apiUrl);
|
|
1287
|
+
if (group?.offline.isOffline)
|
|
1288
|
+
continue;
|
|
1289
|
+
const { statSync: statSync2, readdirSync: readdirSync2 } = await import("fs");
|
|
1290
|
+
const contextPath = join12(repo.localPath, "repowise-context");
|
|
1291
|
+
let isMissingOrEmpty = false;
|
|
1292
|
+
try {
|
|
1293
|
+
const s = statSync2(contextPath);
|
|
1294
|
+
if (!s.isDirectory()) {
|
|
1295
|
+
isMissingOrEmpty = true;
|
|
1296
|
+
} else {
|
|
1297
|
+
const entries = readdirSync2(contextPath);
|
|
1298
|
+
isMissingOrEmpty = entries.length === 0;
|
|
1299
|
+
}
|
|
1300
|
+
} catch {
|
|
1301
|
+
isMissingOrEmpty = true;
|
|
1302
|
+
}
|
|
1303
|
+
state.repos[repo.repoId] = {
|
|
1304
|
+
...state.repos[repo.repoId],
|
|
1305
|
+
lastContextCheckAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1306
|
+
};
|
|
1307
|
+
dirty = true;
|
|
1308
|
+
if (isMissingOrEmpty && apiUrl) {
|
|
1309
|
+
console.log(`[self-heal] Context missing/empty for ${repo.repoId}, re-downloading...`);
|
|
1310
|
+
const result = await fetchContextFromServer(repo.repoId, repo.localPath, apiUrl);
|
|
1311
|
+
if (result.success && result.updatedFiles.length > 0) {
|
|
1312
|
+
console.log(`[self-heal] Re-downloaded ${result.updatedFiles.length} files for ${repo.repoId}`);
|
|
1313
|
+
notifyContextUpdated(repo.repoId, result.updatedFiles.length);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return dirty;
|
|
1318
|
+
}
|
|
1319
|
+
async function startListener() {
|
|
1320
|
+
running = true;
|
|
1321
|
+
const configDir = getConfigDir();
|
|
1322
|
+
await mkdir7(configDir, { recursive: true });
|
|
1323
|
+
const lockPath = join12(configDir, "listener.lock");
|
|
1324
|
+
await writeFile7(lockPath, "", { flag: "a" });
|
|
1325
|
+
let lockIsHeld = false;
|
|
1326
|
+
try {
|
|
1327
|
+
lockIsHeld = await lockfile2.check(lockPath, { stale: 3e4, realpath: false });
|
|
1328
|
+
} catch {
|
|
1329
|
+
}
|
|
1330
|
+
if (!lockIsHeld) {
|
|
1331
|
+
const existingPid = await readPid();
|
|
1332
|
+
if (existingPid && existingPid !== process.pid && isAlive(existingPid)) {
|
|
1333
|
+
try {
|
|
1334
|
+
process.kill(existingPid, "SIGTERM");
|
|
1335
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1336
|
+
} catch {
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
try {
|
|
1341
|
+
releaseLock = await lockfile2.lock(lockPath, { stale: 3e4, realpath: false });
|
|
1342
|
+
} catch {
|
|
1343
|
+
console.error(`Listener already running. Stop it first with \`${true ? "repowise" : "repowise"} stop\`.`);
|
|
1344
|
+
process.exitCode = 1;
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const releaseLockAndExit = async () => {
|
|
1348
|
+
if (releaseLock) {
|
|
1349
|
+
try {
|
|
1350
|
+
await releaseLock();
|
|
1351
|
+
} catch {
|
|
1352
|
+
}
|
|
1353
|
+
releaseLock = null;
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
let config2;
|
|
1357
|
+
try {
|
|
1358
|
+
config2 = await getListenerConfig();
|
|
1359
|
+
} catch (err) {
|
|
1360
|
+
console.error("Failed to load config:", err);
|
|
1361
|
+
await releaseLockAndExit();
|
|
1362
|
+
process.exitCode = 1;
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (config2.repos.length === 0 && !config2.autoDiscoverRepos) {
|
|
1366
|
+
console.error(`No repos configured. Add repos to ${join12(configDir, "config.json")}`);
|
|
1367
|
+
await releaseLockAndExit();
|
|
1368
|
+
process.exitCode = 1;
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
const credentials = await getValidCredentials();
|
|
1372
|
+
if (!credentials) {
|
|
1373
|
+
console.error("Not logged in. Run `repowise login` first.");
|
|
1374
|
+
await releaseLockAndExit();
|
|
1375
|
+
process.exitCode = 1;
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
let state;
|
|
1379
|
+
try {
|
|
1380
|
+
state = await loadState();
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
console.error("Failed to load state:", err);
|
|
1383
|
+
await releaseLockAndExit();
|
|
1384
|
+
process.exitCode = 1;
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
1388
|
+
const nowMs = Date.now();
|
|
1389
|
+
if (state.lastBootAt) {
|
|
1390
|
+
const lastBootMs = new Date(state.lastBootAt).getTime();
|
|
1391
|
+
if (nowMs - lastBootMs < CRASH_LOOP_WINDOW_MS) {
|
|
1392
|
+
state.crashCount = (state.crashCount ?? 0) + 1;
|
|
1393
|
+
} else {
|
|
1394
|
+
state.crashCount = 0;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
state.lastBootAt = nowIso;
|
|
1398
|
+
const crashLoopDetected = (state.crashCount ?? 0) >= CRASH_LOOP_THRESHOLD;
|
|
1399
|
+
if (crashLoopDetected) {
|
|
1400
|
+
console.warn(`[self-heal] Crash-loop detected (${state.crashCount} rapid restarts). Auto-update disabled until stable.`);
|
|
1401
|
+
}
|
|
1402
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
1403
|
+
for (const repo of config2.repos) {
|
|
1404
|
+
const apiUrl = repo.apiUrl ?? config2.defaultApiUrl;
|
|
1405
|
+
let group = groupMap.get(apiUrl);
|
|
1406
|
+
if (!group) {
|
|
1407
|
+
group = {
|
|
1408
|
+
apiUrl,
|
|
1409
|
+
pollClient: new PollClient(apiUrl),
|
|
1410
|
+
backoff: new BackoffCalculator(),
|
|
1411
|
+
repoIds: [],
|
|
1412
|
+
repoLocalPaths: /* @__PURE__ */ new Map(),
|
|
1413
|
+
offline: { isOffline: false, offlineSince: null, attemptCount: 0, nextRetryAt: 0 }
|
|
1414
|
+
};
|
|
1415
|
+
groupMap.set(apiUrl, group);
|
|
1416
|
+
}
|
|
1417
|
+
group.repoIds.push(repo.repoId);
|
|
1418
|
+
group.repoLocalPaths.set(repo.repoId, repo.localPath);
|
|
1419
|
+
}
|
|
1420
|
+
const groups = Array.from(groupMap.values());
|
|
1421
|
+
const allRepoIds = config2.repos.map((r) => r.repoId);
|
|
678
1422
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1423
|
+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
679
1424
|
for (const repoId of allRepoIds) {
|
|
680
1425
|
if (!state.repos[repoId]) {
|
|
681
|
-
state.repos[repoId] = { lastSyncTimestamp:
|
|
1426
|
+
state.repos[repoId] = { lastSyncTimestamp: oneDayAgo, lastSyncCommitSha: null };
|
|
682
1427
|
}
|
|
683
1428
|
}
|
|
1429
|
+
await saveState(state);
|
|
1430
|
+
const packageName = true ? "repowise" : "repowise";
|
|
1431
|
+
let currentVersion = "";
|
|
1432
|
+
try {
|
|
1433
|
+
const selfDir = dirname4(fileURLToPath2(import.meta.url));
|
|
1434
|
+
const pkgJsonPath = join12(selfDir, "..", "..", "package.json");
|
|
1435
|
+
const pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf-8"));
|
|
1436
|
+
currentVersion = pkgJson.version;
|
|
1437
|
+
} catch (err) {
|
|
1438
|
+
console.log(`[auto-update] Version detection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1439
|
+
}
|
|
684
1440
|
let pollIntervalMs = 5e3;
|
|
685
1441
|
let pollCycleCount = 0;
|
|
686
1442
|
const RECONCILE_EVERY_N_CYCLES = 60;
|
|
@@ -700,10 +1456,24 @@ async function startListener(options) {
|
|
|
700
1456
|
console.log("Shutting down...");
|
|
701
1457
|
stop();
|
|
702
1458
|
await saveState(state);
|
|
703
|
-
|
|
1459
|
+
if (releaseLock) {
|
|
1460
|
+
try {
|
|
1461
|
+
await releaseLock();
|
|
1462
|
+
} catch {
|
|
1463
|
+
}
|
|
1464
|
+
releaseLock = null;
|
|
1465
|
+
}
|
|
704
1466
|
};
|
|
705
|
-
process.on("SIGTERM", () =>
|
|
706
|
-
|
|
1467
|
+
process.on("SIGTERM", () => {
|
|
1468
|
+
shutdown().catch((err) => {
|
|
1469
|
+
console.error("Shutdown error:", err);
|
|
1470
|
+
}).finally(() => process.exit(0));
|
|
1471
|
+
});
|
|
1472
|
+
process.on("SIGINT", () => {
|
|
1473
|
+
shutdown().catch((err) => {
|
|
1474
|
+
console.error("Shutdown error:", err);
|
|
1475
|
+
}).finally(() => process.exit(0));
|
|
1476
|
+
});
|
|
707
1477
|
while (running) {
|
|
708
1478
|
let anyAuthError = null;
|
|
709
1479
|
let authErrorGroup = null;
|
|
@@ -711,6 +1481,7 @@ async function startListener(options) {
|
|
|
711
1481
|
let connectionLostNotified = false;
|
|
712
1482
|
pollCycleCount++;
|
|
713
1483
|
const shouldReconcile = pollCycleCount % RECONCILE_EVERY_N_CYCLES === 0;
|
|
1484
|
+
let latestCliVersion;
|
|
714
1485
|
for (const group of groups) {
|
|
715
1486
|
if (!running)
|
|
716
1487
|
break;
|
|
@@ -725,9 +1496,14 @@ async function startListener(options) {
|
|
|
725
1496
|
const response = await group.pollClient.poll(group.repoIds, sinceTimestamp, {
|
|
726
1497
|
includeActiveRepos: shouldReconcile
|
|
727
1498
|
});
|
|
1499
|
+
if (response.latestCliVersion) {
|
|
1500
|
+
latestCliVersion = response.latestCliVersion;
|
|
1501
|
+
}
|
|
728
1502
|
if (response.activeRepos && response.activeRepos.length > 0) {
|
|
729
1503
|
try {
|
|
730
|
-
const result = reconcileRepos(config2.repos, response.activeRepos, state, group.apiUrl
|
|
1504
|
+
const result = reconcileRepos(config2.repos, response.activeRepos, state, group.apiUrl, {
|
|
1505
|
+
autoDiscover: config2.autoDiscoverRepos
|
|
1506
|
+
});
|
|
731
1507
|
if (result.updated) {
|
|
732
1508
|
config2.repos = result.repos;
|
|
733
1509
|
await saveListenerConfig(config2);
|
|
@@ -742,6 +1518,19 @@ async function startListener(options) {
|
|
|
742
1518
|
for (const change of result.changes) {
|
|
743
1519
|
console.log(`[reconcile] ${change}`);
|
|
744
1520
|
}
|
|
1521
|
+
if (result.addedRepos.length > 0) {
|
|
1522
|
+
for (const addedRepo of result.addedRepos) {
|
|
1523
|
+
const apiUrl = addedRepo.apiUrl ?? config2.defaultApiUrl;
|
|
1524
|
+
try {
|
|
1525
|
+
const fetchResult = await fetchContextFromServer(addedRepo.repoId, addedRepo.localPath, apiUrl);
|
|
1526
|
+
if (fetchResult.success) {
|
|
1527
|
+
console.log(`[self-heal] Downloaded ${fetchResult.updatedFiles.length} context files for ${addedRepo.repoId}`);
|
|
1528
|
+
}
|
|
1529
|
+
} catch (fetchErr) {
|
|
1530
|
+
console.warn(`[self-heal] Context download failed for ${addedRepo.repoId}:`, fetchErr instanceof Error ? fetchErr.message : String(fetchErr));
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
745
1534
|
}
|
|
746
1535
|
} catch (reconcileErr) {
|
|
747
1536
|
console.warn("Repo reconciliation failed \u2014 will retry next cycle", reconcileErr instanceof Error ? reconcileErr.message : String(reconcileErr));
|
|
@@ -783,6 +1572,49 @@ async function startListener(options) {
|
|
|
783
1572
|
console.error(`Poll failed for ${group.apiUrl} (attempt ${group.offline.attemptCount}): ${message}. Retrying in ${Math.round(delay2 / 1e3)}s`);
|
|
784
1573
|
}
|
|
785
1574
|
}
|
|
1575
|
+
if (shouldReconcile) {
|
|
1576
|
+
try {
|
|
1577
|
+
const staleDirty = await checkStaleContext(config2.repos, state, groups);
|
|
1578
|
+
if (staleDirty) {
|
|
1579
|
+
await saveState(state);
|
|
1580
|
+
}
|
|
1581
|
+
} catch (err) {
|
|
1582
|
+
console.warn("[self-heal] Stale context check failed:", err instanceof Error ? err.message : String(err));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
if (latestCliVersion && currentVersion && !config2.noAutoUpdate && (state.crashCount ?? 0) < CRASH_LOOP_THRESHOLD) {
|
|
1586
|
+
if (latestCliVersion !== state.lastUpdateTargetVersion) {
|
|
1587
|
+
state.updateFailCount = 0;
|
|
1588
|
+
state.lastUpdateAttemptAt = void 0;
|
|
1589
|
+
state.lastUpdateTargetVersion = latestCliVersion;
|
|
1590
|
+
}
|
|
1591
|
+
const backoffMs = Math.min(5 * 60 * 1e3 * Math.pow(2, state.updateFailCount ?? 0), 60 * 60 * 1e3);
|
|
1592
|
+
const lastAttempt = state.lastUpdateAttemptAt ? new Date(state.lastUpdateAttemptAt).getTime() : 0;
|
|
1593
|
+
if (Date.now() - lastAttempt >= backoffMs) {
|
|
1594
|
+
const listenerStatus = await getListenerStatus();
|
|
1595
|
+
state.lastUpdateAttemptAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1596
|
+
const updateResult = await installUpdate(currentVersion, packageName, latestCliVersion);
|
|
1597
|
+
if (updateResult.updated) {
|
|
1598
|
+
state.updateFailCount = 0;
|
|
1599
|
+
await saveState(state);
|
|
1600
|
+
if (listenerStatus.serviceInstalled) {
|
|
1601
|
+
console.log("[auto-update] Restarting with new version...");
|
|
1602
|
+
await shutdown();
|
|
1603
|
+
process.exit(0);
|
|
1604
|
+
} else {
|
|
1605
|
+
console.log("[auto-update] Spawning new listener with updated version...");
|
|
1606
|
+
await shutdown();
|
|
1607
|
+
await startBackground();
|
|
1608
|
+
process.exit(0);
|
|
1609
|
+
}
|
|
1610
|
+
} else if (updateResult.error) {
|
|
1611
|
+
state.updateFailCount = (state.updateFailCount ?? 0) + 1;
|
|
1612
|
+
await saveState(state);
|
|
1613
|
+
} else {
|
|
1614
|
+
await saveState(state);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
786
1618
|
if (anyAuthError) {
|
|
787
1619
|
let recovered = false;
|
|
788
1620
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
@@ -812,8 +1644,19 @@ async function startListener(options) {
|
|
|
812
1644
|
} catch {
|
|
813
1645
|
}
|
|
814
1646
|
}
|
|
1647
|
+
const credentialsPath = join12(getConfigDir(), "credentials.json");
|
|
1648
|
+
let credentialsChanged = false;
|
|
1649
|
+
let watcher = null;
|
|
1650
|
+
try {
|
|
1651
|
+
const fs = await import("fs");
|
|
1652
|
+
watcher = fs.watch(credentialsPath, () => {
|
|
1653
|
+
credentialsChanged = true;
|
|
1654
|
+
});
|
|
1655
|
+
} catch {
|
|
1656
|
+
}
|
|
815
1657
|
while (running) {
|
|
816
|
-
await sleep(
|
|
1658
|
+
await sleep(credentialsChanged ? 0 : 1e4);
|
|
1659
|
+
credentialsChanged = false;
|
|
817
1660
|
if (!running)
|
|
818
1661
|
break;
|
|
819
1662
|
const fresh = await getValidCredentials({ forceRefresh: true });
|
|
@@ -822,452 +1665,170 @@ async function startListener(options) {
|
|
|
822
1665
|
break;
|
|
823
1666
|
}
|
|
824
1667
|
}
|
|
1668
|
+
if (watcher)
|
|
1669
|
+
watcher.close();
|
|
825
1670
|
continue;
|
|
826
|
-
}
|
|
827
|
-
pollIntervalMs = minPollInterval;
|
|
828
|
-
await sleep(pollIntervalMs);
|
|
829
|
-
}
|
|
830
|
-
await removePidFile();
|
|
831
|
-
}
|
|
832
|
-
var isDirectRun = process.argv[1]?.endsWith("main.js") || process.argv[1]?.endsWith("main.ts");
|
|
833
|
-
if (isDirectRun) {
|
|
834
|
-
startListener().catch((err) => {
|
|
835
|
-
console.error("Listener fatal error:", err);
|
|
836
|
-
process.exitCode = 1;
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// src/lib/env.ts
|
|
841
|
-
var staging = false;
|
|
842
|
-
function setStagingMode(value) {
|
|
843
|
-
staging = value;
|
|
844
|
-
}
|
|
845
|
-
function isStagingMode() {
|
|
846
|
-
return staging;
|
|
847
|
-
}
|
|
848
|
-
var PRODUCTION = {
|
|
849
|
-
apiUrl: "https://api.repowise.ai",
|
|
850
|
-
cognitoDomain: "auth.repowise.ai",
|
|
851
|
-
cognitoClientId: "50so1fkmjbqt1ufnsmbhsjbtst",
|
|
852
|
-
cognitoRegion: "us-east-1",
|
|
853
|
-
customDomain: true
|
|
854
|
-
};
|
|
855
|
-
var STAGING = {
|
|
856
|
-
apiUrl: "https://staging-api.repowise.ai",
|
|
857
|
-
cognitoDomain: "auth-staging.repowise.ai",
|
|
858
|
-
cognitoClientId: "7h0l0dhjcb1v5erer0gaclv0q6",
|
|
859
|
-
cognitoRegion: "us-east-1",
|
|
860
|
-
customDomain: true
|
|
861
|
-
};
|
|
862
|
-
function getEnvConfig() {
|
|
863
|
-
return staging ? STAGING : PRODUCTION;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// src/lib/welcome.ts
|
|
867
|
-
import chalk from "chalk";
|
|
868
|
-
|
|
869
|
-
// src/lib/config.ts
|
|
870
|
-
import { readFile as readFile5, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
|
|
871
|
-
import { homedir as homedir5 } from "os";
|
|
872
|
-
import { join as join7 } from "path";
|
|
873
|
-
var CONFIG_DIR4 = join7(homedir5(), ".repowise");
|
|
874
|
-
var CONFIG_PATH2 = join7(CONFIG_DIR4, "config.json");
|
|
875
|
-
async function getConfig() {
|
|
876
|
-
try {
|
|
877
|
-
const data = await readFile5(CONFIG_PATH2, "utf-8");
|
|
878
|
-
return JSON.parse(data);
|
|
879
|
-
} catch {
|
|
880
|
-
return {};
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
async function saveConfig(config2) {
|
|
884
|
-
await mkdir6(CONFIG_DIR4, { recursive: true });
|
|
885
|
-
await writeFile6(CONFIG_PATH2, JSON.stringify(config2, null, 2));
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// src/lib/welcome.ts
|
|
889
|
-
var W = 41;
|
|
890
|
-
function row(styled, visible) {
|
|
891
|
-
return chalk.cyan(" \u2502") + styled + " ".repeat(W - visible) + chalk.cyan("\u2502");
|
|
892
|
-
}
|
|
893
|
-
async function showWelcome(currentVersion) {
|
|
894
|
-
try {
|
|
895
|
-
const config2 = await getConfig();
|
|
896
|
-
if (config2.lastSeenVersion === currentVersion) return;
|
|
897
|
-
const isUpgrade = !!config2.lastSeenVersion;
|
|
898
|
-
const tag = isUpgrade ? "updated" : "installed";
|
|
899
|
-
const titleText = `RepoWise v${currentVersion}`;
|
|
900
|
-
const titleStyled = " " + chalk.bold(titleText) + chalk.green(` \u2713 ${tag}`);
|
|
901
|
-
const titleVisible = 3 + titleText.length + 3 + tag.length;
|
|
902
|
-
const border = "\u2500".repeat(W);
|
|
903
|
-
console.log("");
|
|
904
|
-
console.log(chalk.cyan(` \u256D${border}\u256E`));
|
|
905
|
-
console.log(row("", 0));
|
|
906
|
-
console.log(row(titleStyled, titleVisible));
|
|
907
|
-
console.log(row("", 0));
|
|
908
|
-
console.log(row(" " + chalk.dim("Get started:"), 15));
|
|
909
|
-
console.log(row(" $ " + chalk.bold("repowise create"), 22));
|
|
910
|
-
console.log(row("", 0));
|
|
911
|
-
console.log(row(" " + chalk.dim("Thank you for using RepoWise!"), 32));
|
|
912
|
-
console.log(row(" " + chalk.dim("https://repowise.ai"), 22));
|
|
913
|
-
console.log(row("", 0));
|
|
914
|
-
console.log(chalk.cyan(` \u2570${border}\u256F`));
|
|
915
|
-
console.log("");
|
|
916
|
-
await saveConfig({ ...config2, lastSeenVersion: currentVersion });
|
|
917
|
-
} catch {
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// src/commands/create.ts
|
|
922
|
-
import { execSync } from "child_process";
|
|
923
|
-
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
924
|
-
import { dirname as dirname4, join as join13 } from "path";
|
|
925
|
-
|
|
926
|
-
// ../listener/dist/process-manager.js
|
|
927
|
-
import { spawn } from "child_process";
|
|
928
|
-
import { openSync, closeSync } from "fs";
|
|
929
|
-
import { readFile as readFile6, writeFile as writeFile7, mkdir as mkdir7, unlink as unlink2 } from "fs/promises";
|
|
930
|
-
import { homedir as homedir6 } from "os";
|
|
931
|
-
import { join as join8 } from "path";
|
|
932
|
-
import { createRequire } from "module";
|
|
933
|
-
import { fileURLToPath } from "url";
|
|
934
|
-
var REPOWISE_DIR = join8(homedir6(), ".repowise");
|
|
935
|
-
var PID_PATH2 = join8(REPOWISE_DIR, "listener.pid");
|
|
936
|
-
var LOG_DIR = join8(REPOWISE_DIR, "logs");
|
|
937
|
-
function resolveListenerCommand() {
|
|
938
|
-
try {
|
|
939
|
-
const require2 = createRequire(import.meta.url);
|
|
940
|
-
const mainPath = require2.resolve("@repowise/listener/main");
|
|
941
|
-
return { script: mainPath, args: [] };
|
|
942
|
-
} catch {
|
|
943
|
-
const bundlePath = fileURLToPath(import.meta.url);
|
|
944
|
-
return { script: bundlePath, args: ["__listener"] };
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
async function readPid() {
|
|
948
|
-
try {
|
|
949
|
-
const content = await readFile6(PID_PATH2, "utf-8");
|
|
950
|
-
const pid = parseInt(content.trim(), 10);
|
|
951
|
-
return Number.isNaN(pid) ? null : pid;
|
|
952
|
-
} catch (err) {
|
|
953
|
-
if (err.code === "ENOENT")
|
|
954
|
-
return null;
|
|
955
|
-
throw err;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
function isAlive(pid) {
|
|
959
|
-
try {
|
|
960
|
-
process.kill(pid, 0);
|
|
961
|
-
return true;
|
|
962
|
-
} catch {
|
|
963
|
-
return false;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
async function startBackground() {
|
|
967
|
-
await mkdir7(LOG_DIR, { recursive: true });
|
|
968
|
-
const cmd = resolveListenerCommand();
|
|
969
|
-
const stdoutFd = openSync(join8(LOG_DIR, "listener-stdout.log"), "a");
|
|
970
|
-
const stderrFd = openSync(join8(LOG_DIR, "listener-stderr.log"), "a");
|
|
971
|
-
const child = spawn(process.execPath, [cmd.script, ...cmd.args], {
|
|
972
|
-
detached: true,
|
|
973
|
-
stdio: ["ignore", stdoutFd, stderrFd],
|
|
974
|
-
cwd: homedir6(),
|
|
975
|
-
env: { ...process.env }
|
|
976
|
-
});
|
|
977
|
-
child.unref();
|
|
978
|
-
closeSync(stdoutFd);
|
|
979
|
-
closeSync(stderrFd);
|
|
980
|
-
const pid = child.pid;
|
|
981
|
-
if (!pid)
|
|
982
|
-
throw new Error("Failed to spawn listener process");
|
|
983
|
-
await writeFile7(PID_PATH2, String(pid));
|
|
984
|
-
return pid;
|
|
985
|
-
}
|
|
986
|
-
async function stopProcess() {
|
|
987
|
-
const pid = await readPid();
|
|
988
|
-
if (pid === null)
|
|
989
|
-
return;
|
|
990
|
-
if (!isAlive(pid)) {
|
|
991
|
-
await removePidFile2();
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
try {
|
|
995
|
-
process.kill(pid, "SIGTERM");
|
|
996
|
-
} catch {
|
|
997
|
-
}
|
|
998
|
-
const deadline = Date.now() + 5e3;
|
|
999
|
-
while (Date.now() < deadline && isAlive(pid)) {
|
|
1000
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
1671
|
+
}
|
|
1672
|
+
pollIntervalMs = minPollInterval;
|
|
1673
|
+
await sleep(pollIntervalMs);
|
|
1001
1674
|
}
|
|
1002
|
-
if (
|
|
1675
|
+
if (releaseLock) {
|
|
1003
1676
|
try {
|
|
1004
|
-
|
|
1677
|
+
await releaseLock();
|
|
1005
1678
|
} catch {
|
|
1006
1679
|
}
|
|
1007
|
-
|
|
1008
|
-
await removePidFile2();
|
|
1009
|
-
}
|
|
1010
|
-
async function isRunning() {
|
|
1011
|
-
const pid = await readPid();
|
|
1012
|
-
if (pid === null)
|
|
1013
|
-
return false;
|
|
1014
|
-
return isAlive(pid);
|
|
1015
|
-
}
|
|
1016
|
-
async function getStatus() {
|
|
1017
|
-
const pid = await readPid();
|
|
1018
|
-
if (pid === null)
|
|
1019
|
-
return { running: false, pid: null };
|
|
1020
|
-
const alive = isAlive(pid);
|
|
1021
|
-
return { running: alive, pid: alive ? pid : null };
|
|
1022
|
-
}
|
|
1023
|
-
async function removePidFile2() {
|
|
1024
|
-
try {
|
|
1025
|
-
await unlink2(PID_PATH2);
|
|
1026
|
-
} catch {
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// ../listener/dist/service-installer.js
|
|
1031
|
-
import { execFile as execFile2 } from "child_process";
|
|
1032
|
-
import { writeFile as writeFile8, mkdir as mkdir8, unlink as unlink3 } from "fs/promises";
|
|
1033
|
-
import { homedir as homedir7 } from "os";
|
|
1034
|
-
import { join as join9 } from "path";
|
|
1035
|
-
import { createRequire as createRequire2 } from "module";
|
|
1036
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1037
|
-
function resolveListenerCommand2() {
|
|
1038
|
-
try {
|
|
1039
|
-
const require2 = createRequire2(import.meta.url);
|
|
1040
|
-
const mainPath = require2.resolve("@repowise/listener/main");
|
|
1041
|
-
return { script: mainPath, args: [] };
|
|
1042
|
-
} catch {
|
|
1043
|
-
const bundlePath = fileURLToPath2(import.meta.url);
|
|
1044
|
-
return { script: bundlePath, args: ["__listener"] };
|
|
1680
|
+
releaseLock = null;
|
|
1045
1681
|
}
|
|
1046
1682
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
resolve(String(stdout ?? ""));
|
|
1055
|
-
});
|
|
1683
|
+
var isDirectRun = process.argv[1]?.endsWith("main.js") || process.argv[1]?.endsWith("main.ts");
|
|
1684
|
+
if (isDirectRun) {
|
|
1685
|
+
startListener().catch((err) => {
|
|
1686
|
+
console.error("Listener fatal error:", err);
|
|
1687
|
+
process.exitCode = 1;
|
|
1056
1688
|
});
|
|
1057
1689
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
"
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
</array>
|
|
1080
|
-
<key>RunAtLoad</key>
|
|
1081
|
-
<true/>
|
|
1082
|
-
<key>KeepAlive</key>
|
|
1083
|
-
<true/>
|
|
1084
|
-
<key>StandardOutPath</key>
|
|
1085
|
-
<string>${join9(logs, "listener-stdout.log")}</string>
|
|
1086
|
-
<key>StandardErrorPath</key>
|
|
1087
|
-
<string>${join9(logs, "listener-stderr.log")}</string>
|
|
1088
|
-
<key>ProcessType</key>
|
|
1089
|
-
<string>Background</string>
|
|
1090
|
-
</dict>
|
|
1091
|
-
</plist>`;
|
|
1092
|
-
}
|
|
1093
|
-
async function darwinInstall() {
|
|
1094
|
-
await mkdir8(logDir(), { recursive: true });
|
|
1095
|
-
await mkdir8(join9(homedir7(), "Library", "LaunchAgents"), { recursive: true });
|
|
1096
|
-
try {
|
|
1097
|
-
await exec("launchctl", ["unload", plistPath()]);
|
|
1098
|
-
} catch {
|
|
1099
|
-
}
|
|
1100
|
-
await writeFile8(plistPath(), buildPlist());
|
|
1101
|
-
await exec("launchctl", ["load", plistPath()]);
|
|
1102
|
-
}
|
|
1103
|
-
async function darwinUninstall() {
|
|
1104
|
-
try {
|
|
1105
|
-
await exec("launchctl", ["unload", plistPath()]);
|
|
1106
|
-
} catch {
|
|
1107
|
-
}
|
|
1108
|
-
try {
|
|
1109
|
-
await unlink3(plistPath());
|
|
1110
|
-
} catch {
|
|
1111
|
-
}
|
|
1690
|
+
|
|
1691
|
+
// src/lib/env.ts
|
|
1692
|
+
import { homedir as homedir4 } from "os";
|
|
1693
|
+
import { join as join13 } from "path";
|
|
1694
|
+
var IS_STAGING2 = true ? false : false;
|
|
1695
|
+
var PRODUCTION = {
|
|
1696
|
+
apiUrl: "https://api.repowise.ai",
|
|
1697
|
+
cognitoDomain: "auth.repowise.ai",
|
|
1698
|
+
cognitoClientId: "50so1fkmjbqt1ufnsmbhsjbtst",
|
|
1699
|
+
cognitoRegion: "us-east-1",
|
|
1700
|
+
customDomain: true
|
|
1701
|
+
};
|
|
1702
|
+
var STAGING = {
|
|
1703
|
+
apiUrl: "https://staging-api.repowise.ai",
|
|
1704
|
+
cognitoDomain: "auth-staging.repowise.ai",
|
|
1705
|
+
cognitoClientId: "7h0l0dhjcb1v5erer0gaclv0q6",
|
|
1706
|
+
cognitoRegion: "us-east-1",
|
|
1707
|
+
customDomain: true
|
|
1708
|
+
};
|
|
1709
|
+
function getEnvConfig() {
|
|
1710
|
+
return IS_STAGING2 ? STAGING : PRODUCTION;
|
|
1112
1711
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
const stdout = await exec("launchctl", ["list"]);
|
|
1116
|
-
return stdout.includes(PLIST_LABEL);
|
|
1117
|
-
} catch {
|
|
1118
|
-
return false;
|
|
1119
|
-
}
|
|
1712
|
+
function getConfigDir2() {
|
|
1713
|
+
return join13(homedir4(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
|
|
1120
1714
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
return join9(homedir7(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
|
|
1715
|
+
function getPackageName() {
|
|
1716
|
+
return true ? "repowise" : "repowise";
|
|
1124
1717
|
}
|
|
1125
|
-
function buildUnit() {
|
|
1126
|
-
const cmd = resolveListenerCommand2();
|
|
1127
|
-
const execStart = [process.execPath, cmd.script, ...cmd.args].join(" ");
|
|
1128
|
-
const logs = logDir();
|
|
1129
|
-
return `[Unit]
|
|
1130
|
-
Description=RepoWise Listener
|
|
1131
|
-
After=network-online.target
|
|
1132
|
-
Wants=network-online.target
|
|
1133
1718
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
ExecStart=${execStart}
|
|
1137
|
-
Restart=on-failure
|
|
1138
|
-
RestartSec=10
|
|
1139
|
-
StandardOutput=append:${join9(logs, "listener-stdout.log")}
|
|
1140
|
-
StandardError=append:${join9(logs, "listener-stderr.log")}
|
|
1719
|
+
// src/lib/welcome.ts
|
|
1720
|
+
import chalk from "chalk";
|
|
1141
1721
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
await mkdir8(join9(homedir7(), ".config", "systemd", "user"), { recursive: true });
|
|
1148
|
-
await writeFile8(unitPath(), buildUnit());
|
|
1149
|
-
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
1150
|
-
await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
|
|
1151
|
-
await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
|
|
1152
|
-
}
|
|
1153
|
-
async function linuxUninstall() {
|
|
1154
|
-
try {
|
|
1155
|
-
await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
|
|
1156
|
-
} catch {
|
|
1157
|
-
}
|
|
1158
|
-
try {
|
|
1159
|
-
await exec("systemctl", ["--user", "disable", SYSTEMD_SERVICE]);
|
|
1160
|
-
} catch {
|
|
1161
|
-
}
|
|
1162
|
-
try {
|
|
1163
|
-
await unlink3(unitPath());
|
|
1164
|
-
} catch {
|
|
1165
|
-
}
|
|
1722
|
+
// src/lib/config.ts
|
|
1723
|
+
import { readFile as readFile6, writeFile as writeFile8, mkdir as mkdir8, rename as rename3, unlink as unlink6 } from "fs/promises";
|
|
1724
|
+
import { join as join14 } from "path";
|
|
1725
|
+
import lockfile3 from "proper-lockfile";
|
|
1726
|
+
async function getConfig() {
|
|
1166
1727
|
try {
|
|
1167
|
-
await
|
|
1728
|
+
const data = await readFile6(join14(getConfigDir2(), "config.json"), "utf-8");
|
|
1729
|
+
return JSON.parse(data);
|
|
1168
1730
|
} catch {
|
|
1731
|
+
return {};
|
|
1169
1732
|
}
|
|
1170
1733
|
}
|
|
1171
|
-
async function
|
|
1734
|
+
async function saveConfig(config2) {
|
|
1735
|
+
const dir = getConfigDir2();
|
|
1736
|
+
const path = join14(dir, "config.json");
|
|
1737
|
+
await mkdir8(dir, { recursive: true });
|
|
1738
|
+
const tmpPath = path + ".tmp";
|
|
1172
1739
|
try {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
} catch {
|
|
1176
|
-
|
|
1740
|
+
await writeFile8(tmpPath, JSON.stringify(config2, null, 2));
|
|
1741
|
+
await rename3(tmpPath, path);
|
|
1742
|
+
} catch (err) {
|
|
1743
|
+
try {
|
|
1744
|
+
await unlink6(tmpPath);
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
throw err;
|
|
1177
1748
|
}
|
|
1178
1749
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
const taskCmd = [process.execPath, cmd.script, ...cmd.args].map((a) => `"${a}"`).join(" ");
|
|
1184
|
-
await exec("schtasks", [
|
|
1185
|
-
"/create",
|
|
1186
|
-
"/tn",
|
|
1187
|
-
TASK_NAME,
|
|
1188
|
-
"/tr",
|
|
1189
|
-
taskCmd,
|
|
1190
|
-
"/sc",
|
|
1191
|
-
"onlogon",
|
|
1192
|
-
"/ru",
|
|
1193
|
-
process.env.USERNAME ?? "",
|
|
1194
|
-
"/f"
|
|
1195
|
-
]);
|
|
1196
|
-
await exec("schtasks", ["/run", "/tn", TASK_NAME]);
|
|
1197
|
-
}
|
|
1198
|
-
async function win32Uninstall() {
|
|
1199
|
-
try {
|
|
1200
|
-
await exec("schtasks", ["/end", "/tn", TASK_NAME]);
|
|
1201
|
-
} catch {
|
|
1202
|
-
}
|
|
1750
|
+
async function mergeAndSaveConfig(updates) {
|
|
1751
|
+
const dir = getConfigDir2();
|
|
1752
|
+
const path = join14(dir, "config.json");
|
|
1753
|
+
await mkdir8(dir, { recursive: true });
|
|
1203
1754
|
try {
|
|
1204
|
-
await
|
|
1755
|
+
await writeFile8(path, "", { flag: "a" });
|
|
1205
1756
|
} catch {
|
|
1206
1757
|
}
|
|
1207
|
-
|
|
1208
|
-
async function win32IsInstalled() {
|
|
1758
|
+
let release = null;
|
|
1209
1759
|
try {
|
|
1210
|
-
await
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1760
|
+
release = await lockfile3.lock(path, { stale: 1e4, retries: 3, realpath: false });
|
|
1761
|
+
let raw = {};
|
|
1762
|
+
try {
|
|
1763
|
+
raw = JSON.parse(await readFile6(path, "utf-8"));
|
|
1764
|
+
} catch {
|
|
1765
|
+
}
|
|
1766
|
+
const merged = { ...raw, ...updates };
|
|
1767
|
+
if (updates.repos) {
|
|
1768
|
+
const existingRepos = Array.isArray(raw["repos"]) ? raw["repos"] : [];
|
|
1769
|
+
const updatedRepoIds = new Set(updates.repos.map((r) => r.repoId));
|
|
1770
|
+
merged.repos = [
|
|
1771
|
+
...existingRepos.filter((r) => !updatedRepoIds.has(r.repoId)),
|
|
1772
|
+
...updates.repos
|
|
1773
|
+
];
|
|
1774
|
+
}
|
|
1775
|
+
await saveConfig(merged);
|
|
1776
|
+
} finally {
|
|
1777
|
+
if (release) {
|
|
1778
|
+
try {
|
|
1779
|
+
await release();
|
|
1780
|
+
} catch {
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1229
1783
|
}
|
|
1230
1784
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
case "linux":
|
|
1237
|
-
await linuxUninstall();
|
|
1238
|
-
break;
|
|
1239
|
-
case "win32":
|
|
1240
|
-
await win32Uninstall();
|
|
1241
|
-
break;
|
|
1242
|
-
default:
|
|
1243
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
1244
|
-
}
|
|
1785
|
+
|
|
1786
|
+
// src/lib/welcome.ts
|
|
1787
|
+
var W = 41;
|
|
1788
|
+
function row(styled, visible) {
|
|
1789
|
+
return chalk.cyan(" \u2502") + styled + " ".repeat(W - visible) + chalk.cyan("\u2502");
|
|
1245
1790
|
}
|
|
1246
|
-
async function
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1791
|
+
async function showWelcome(currentVersion) {
|
|
1792
|
+
try {
|
|
1793
|
+
const config2 = await getConfig();
|
|
1794
|
+
if (config2.lastSeenVersion === currentVersion) {
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const isUpgrade = !!config2.lastSeenVersion;
|
|
1798
|
+
const tag = isUpgrade ? "updated" : "installed";
|
|
1799
|
+
const titleText = `RepoWise v${currentVersion}`;
|
|
1800
|
+
const titleStyled = " " + chalk.bold(titleText) + chalk.green(` \u2713 ${tag}`);
|
|
1801
|
+
const titleVisible = 3 + titleText.length + 3 + tag.length;
|
|
1802
|
+
const border = "\u2500".repeat(W);
|
|
1803
|
+
console.log("");
|
|
1804
|
+
console.log(chalk.cyan(` \u256D${border}\u256E`));
|
|
1805
|
+
console.log(row("", 0));
|
|
1806
|
+
console.log(row(titleStyled, titleVisible));
|
|
1807
|
+
console.log(row("", 0));
|
|
1808
|
+
console.log(row(" " + chalk.dim("Get started:"), 15));
|
|
1809
|
+
console.log(row(" $ " + chalk.bold("repowise create"), 22));
|
|
1810
|
+
console.log(row("", 0));
|
|
1811
|
+
console.log(row(" " + chalk.dim("Thank you for using RepoWise!"), 32));
|
|
1812
|
+
console.log(row(" " + chalk.dim("https://repowise.ai"), 22));
|
|
1813
|
+
console.log(row("", 0));
|
|
1814
|
+
console.log(chalk.cyan(` \u2570${border}\u256F`));
|
|
1815
|
+
console.log("");
|
|
1816
|
+
await mergeAndSaveConfig({ lastSeenVersion: currentVersion });
|
|
1817
|
+
} catch {
|
|
1256
1818
|
}
|
|
1257
1819
|
}
|
|
1258
1820
|
|
|
1259
1821
|
// src/commands/create.ts
|
|
1822
|
+
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1823
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
1260
1824
|
import chalk5 from "chalk";
|
|
1261
1825
|
import ora from "ora";
|
|
1262
1826
|
|
|
1263
1827
|
// src/lib/auth.ts
|
|
1264
1828
|
import { createHash, randomBytes } from "crypto";
|
|
1265
|
-
import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as
|
|
1829
|
+
import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as unlink7 } from "fs/promises";
|
|
1266
1830
|
import http from "http";
|
|
1267
|
-
import {
|
|
1268
|
-
import { join as join10 } from "path";
|
|
1269
|
-
var CONFIG_DIR5 = join10(homedir8(), ".repowise");
|
|
1270
|
-
var CREDENTIALS_PATH2 = join10(CONFIG_DIR5, "credentials.json");
|
|
1831
|
+
import { join as join15 } from "path";
|
|
1271
1832
|
var CLI_CALLBACK_PORT = 19876;
|
|
1272
1833
|
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
1273
1834
|
function getCognitoConfigForStorage() {
|
|
@@ -1433,7 +1994,8 @@ async function refreshTokens2(refreshToken) {
|
|
|
1433
1994
|
}
|
|
1434
1995
|
async function getStoredCredentials2() {
|
|
1435
1996
|
try {
|
|
1436
|
-
const
|
|
1997
|
+
const credPath = join15(getConfigDir2(), "credentials.json");
|
|
1998
|
+
const data = await readFile7(credPath, "utf-8");
|
|
1437
1999
|
return JSON.parse(data);
|
|
1438
2000
|
} catch (err) {
|
|
1439
2001
|
if (err.code === "ENOENT" || err instanceof SyntaxError) {
|
|
@@ -1443,13 +2005,15 @@ async function getStoredCredentials2() {
|
|
|
1443
2005
|
}
|
|
1444
2006
|
}
|
|
1445
2007
|
async function storeCredentials2(credentials) {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
await
|
|
2008
|
+
const dir = getConfigDir2();
|
|
2009
|
+
const credPath = join15(dir, "credentials.json");
|
|
2010
|
+
await mkdir9(dir, { recursive: true, mode: 448 });
|
|
2011
|
+
await writeFile9(credPath, JSON.stringify(credentials, null, 2));
|
|
2012
|
+
await chmod4(credPath, 384);
|
|
1449
2013
|
}
|
|
1450
2014
|
async function clearCredentials() {
|
|
1451
2015
|
try {
|
|
1452
|
-
await
|
|
2016
|
+
await unlink7(join15(getConfigDir2(), "credentials.json"));
|
|
1453
2017
|
} catch (err) {
|
|
1454
2018
|
if (err.code !== "ENOENT") throw err;
|
|
1455
2019
|
}
|
|
@@ -1641,7 +2205,7 @@ async function selectAiTools() {
|
|
|
1641
2205
|
|
|
1642
2206
|
// src/lib/ai-tools.ts
|
|
1643
2207
|
import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir10, readdir } from "fs/promises";
|
|
1644
|
-
import { join as
|
|
2208
|
+
import { join as join16, dirname as dirname5 } from "path";
|
|
1645
2209
|
var AI_TOOL_CONFIG = {
|
|
1646
2210
|
cursor: {
|
|
1647
2211
|
label: "Cursor",
|
|
@@ -1770,8 +2334,8 @@ function generateReference(tool, repoName, contextFolder, contextFiles) {
|
|
|
1770
2334
|
}
|
|
1771
2335
|
async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
|
|
1772
2336
|
const config2 = AI_TOOL_CONFIG[tool];
|
|
1773
|
-
const fullPath =
|
|
1774
|
-
const dir =
|
|
2337
|
+
const fullPath = join16(repoRoot, config2.filePath);
|
|
2338
|
+
const dir = dirname5(fullPath);
|
|
1775
2339
|
if (dir !== repoRoot) {
|
|
1776
2340
|
await mkdir10(dir, { recursive: true });
|
|
1777
2341
|
}
|
|
@@ -1799,14 +2363,14 @@ async function updateToolConfig(repoRoot, tool, repoName, contextFolder, context
|
|
|
1799
2363
|
return { created };
|
|
1800
2364
|
}
|
|
1801
2365
|
async function scanLocalContextFiles(repoRoot, contextFolder) {
|
|
1802
|
-
const folderPath =
|
|
2366
|
+
const folderPath = join16(repoRoot, contextFolder);
|
|
1803
2367
|
try {
|
|
1804
2368
|
const entries = await readdir(folderPath, { withFileTypes: true, recursive: true });
|
|
1805
2369
|
const results = [];
|
|
1806
2370
|
for (const entry of entries) {
|
|
1807
2371
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
1808
2372
|
const parentDir = entry.parentPath ?? folderPath;
|
|
1809
|
-
const fullPath =
|
|
2373
|
+
const fullPath = join16(parentDir, entry.name);
|
|
1810
2374
|
const relFromContext = fullPath.slice(folderPath.length + 1);
|
|
1811
2375
|
results.push({
|
|
1812
2376
|
fileName: relFromContext,
|
|
@@ -1822,9 +2386,9 @@ async function scanLocalContextFiles(repoRoot, contextFolder) {
|
|
|
1822
2386
|
|
|
1823
2387
|
// src/lib/gitignore.ts
|
|
1824
2388
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
1825
|
-
import { join as
|
|
2389
|
+
import { join as join17 } from "path";
|
|
1826
2390
|
function ensureGitignore(repoRoot, entry) {
|
|
1827
|
-
const gitignorePath =
|
|
2391
|
+
const gitignorePath = join17(repoRoot, ".gitignore");
|
|
1828
2392
|
if (existsSync(gitignorePath)) {
|
|
1829
2393
|
const content = readFileSync(gitignorePath, "utf-8");
|
|
1830
2394
|
const lines = content.split("\n").map((l) => l.trim());
|
|
@@ -1838,6 +2402,24 @@ function ensureGitignore(repoRoot, entry) {
|
|
|
1838
2402
|
}
|
|
1839
2403
|
}
|
|
1840
2404
|
|
|
2405
|
+
// src/lib/git.ts
|
|
2406
|
+
import { execSync } from "child_process";
|
|
2407
|
+
function detectRepoRoot() {
|
|
2408
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
2409
|
+
}
|
|
2410
|
+
function detectRepoName(repoRoot) {
|
|
2411
|
+
try {
|
|
2412
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
2413
|
+
encoding: "utf-8",
|
|
2414
|
+
cwd: repoRoot
|
|
2415
|
+
}).trim();
|
|
2416
|
+
const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
|
|
2417
|
+
if (match?.[1]) return match[1];
|
|
2418
|
+
} catch {
|
|
2419
|
+
}
|
|
2420
|
+
return repoRoot.split("/").pop() ?? "unknown";
|
|
2421
|
+
}
|
|
2422
|
+
|
|
1841
2423
|
// src/lib/interview-handler.ts
|
|
1842
2424
|
import chalk3 from "chalk";
|
|
1843
2425
|
import { input } from "@inquirer/prompts";
|
|
@@ -2081,7 +2663,9 @@ var ProgressRenderer = class {
|
|
|
2081
2663
|
spinner.stop();
|
|
2082
2664
|
console.log(chalk4.cyan.bold(" \u2500\u2500 Context Generation \u2500\u2500"));
|
|
2083
2665
|
console.log(
|
|
2084
|
-
chalk4.cyan(
|
|
2666
|
+
chalk4.cyan(
|
|
2667
|
+
" \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
|
|
2668
|
+
)
|
|
2085
2669
|
);
|
|
2086
2670
|
console.log("");
|
|
2087
2671
|
spinner.start();
|
|
@@ -2247,21 +2831,6 @@ var ProgressRenderer = class {
|
|
|
2247
2831
|
};
|
|
2248
2832
|
|
|
2249
2833
|
// src/commands/create.ts
|
|
2250
|
-
function detectRepoRoot() {
|
|
2251
|
-
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
2252
|
-
}
|
|
2253
|
-
function detectRepoName(repoRoot) {
|
|
2254
|
-
try {
|
|
2255
|
-
const remoteUrl = execSync("git remote get-url origin", {
|
|
2256
|
-
encoding: "utf-8",
|
|
2257
|
-
cwd: repoRoot
|
|
2258
|
-
}).trim();
|
|
2259
|
-
const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
|
|
2260
|
-
if (match?.[1]) return match[1];
|
|
2261
|
-
} catch {
|
|
2262
|
-
}
|
|
2263
|
-
return repoRoot.split("/").pop() ?? "unknown";
|
|
2264
|
-
}
|
|
2265
2834
|
function formatElapsed(ms) {
|
|
2266
2835
|
const totalSeconds = Math.round(ms / 1e3);
|
|
2267
2836
|
const minutes = Math.floor(totalSeconds / 60);
|
|
@@ -2354,12 +2923,18 @@ async function create() {
|
|
|
2354
2923
|
console.log(
|
|
2355
2924
|
chalk5.cyan(
|
|
2356
2925
|
`
|
|
2357
|
-
|
|
2358
|
-
|
|
2926
|
+
If you're a team member, run: ${chalk5.bold("repowise member")}
|
|
2927
|
+
This will download context and configure your AI tools.
|
|
2928
|
+
`
|
|
2929
|
+
)
|
|
2930
|
+
);
|
|
2931
|
+
console.log(
|
|
2932
|
+
chalk5.dim(
|
|
2933
|
+
` To trigger a new full scan, visit: https://app.repowise.ai/repos/${repoId}
|
|
2934
|
+
To sync recent changes, use: repowise sync
|
|
2359
2935
|
`
|
|
2360
2936
|
)
|
|
2361
2937
|
);
|
|
2362
|
-
console.log(chalk5.dim(" To sync recent changes, use: repowise sync\n"));
|
|
2363
2938
|
process.exitCode = 1;
|
|
2364
2939
|
return;
|
|
2365
2940
|
}
|
|
@@ -2487,7 +3062,7 @@ async function create() {
|
|
|
2487
3062
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
2488
3063
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
2489
3064
|
if (files.length > 0) {
|
|
2490
|
-
const contextDir =
|
|
3065
|
+
const contextDir = join18(repoRoot, DEFAULT_CONTEXT_FOLDER);
|
|
2491
3066
|
mkdirSync(contextDir, { recursive: true });
|
|
2492
3067
|
let downloadedCount = 0;
|
|
2493
3068
|
let failedCount = 0;
|
|
@@ -2501,8 +3076,8 @@ async function create() {
|
|
|
2501
3076
|
const response = await fetch(presignedUrl);
|
|
2502
3077
|
if (response.ok) {
|
|
2503
3078
|
const content = await response.text();
|
|
2504
|
-
const filePath =
|
|
2505
|
-
mkdirSync(
|
|
3079
|
+
const filePath = join18(contextDir, file.fileName);
|
|
3080
|
+
mkdirSync(dirname6(filePath), { recursive: true });
|
|
2506
3081
|
writeFileSync2(filePath, content, "utf-8");
|
|
2507
3082
|
downloadedCount++;
|
|
2508
3083
|
} else {
|
|
@@ -2563,33 +3138,27 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
2563
3138
|
spinner.succeed("AI tools configured");
|
|
2564
3139
|
console.log(chalk5.dim(results.join("\n")));
|
|
2565
3140
|
}
|
|
2566
|
-
const
|
|
2567
|
-
const existingRepos = existingConfig.repos ?? [];
|
|
2568
|
-
const updatedRepos = existingRepos.filter((r) => r.repoId !== repoId);
|
|
3141
|
+
const updatedRepos = [];
|
|
2569
3142
|
if (repoRoot) {
|
|
2570
3143
|
const repoEntry = {
|
|
2571
3144
|
repoId,
|
|
2572
|
-
localPath: repoRoot
|
|
3145
|
+
localPath: repoRoot,
|
|
3146
|
+
apiUrl: getEnvConfig().apiUrl
|
|
2573
3147
|
};
|
|
2574
|
-
if (isStagingMode()) {
|
|
2575
|
-
repoEntry.apiUrl = getEnvConfig().apiUrl;
|
|
2576
|
-
}
|
|
2577
3148
|
if (repoPlatform) repoEntry.platform = repoPlatform;
|
|
2578
3149
|
if (repoExternalId) repoEntry.externalId = repoExternalId;
|
|
2579
3150
|
updatedRepos.push(repoEntry);
|
|
2580
3151
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
contextFolder,
|
|
2585
|
-
repos: updatedRepos
|
|
2586
|
-
});
|
|
3152
|
+
const configUpdate = { aiTools: tools, contextFolder };
|
|
3153
|
+
if (updatedRepos.length > 0) configUpdate.repos = updatedRepos;
|
|
3154
|
+
await mergeAndSaveConfig(configUpdate);
|
|
2587
3155
|
let listenerRunning = false;
|
|
2588
3156
|
try {
|
|
2589
|
-
await
|
|
2590
|
-
await startBackground();
|
|
3157
|
+
await ensureListenerRunning();
|
|
2591
3158
|
listenerRunning = true;
|
|
2592
3159
|
} catch {
|
|
3160
|
+
}
|
|
3161
|
+
if (!listenerRunning) {
|
|
2593
3162
|
console.log(
|
|
2594
3163
|
chalk5.yellow(
|
|
2595
3164
|
"Warning: Could not start listener automatically. Run the following to enable it:"
|
|
@@ -2626,11 +3195,255 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
2626
3195
|
}
|
|
2627
3196
|
}
|
|
2628
3197
|
|
|
2629
|
-
// src/commands/
|
|
3198
|
+
// src/commands/member.ts
|
|
3199
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3200
|
+
import { dirname as dirname7, join as join19 } from "path";
|
|
2630
3201
|
import chalk6 from "chalk";
|
|
2631
3202
|
import ora2 from "ora";
|
|
3203
|
+
var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
|
|
3204
|
+
function formatElapsed2(ms) {
|
|
3205
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
3206
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3207
|
+
const seconds = totalSeconds % 60;
|
|
3208
|
+
if (minutes === 0) return `${seconds}s`;
|
|
3209
|
+
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
3210
|
+
}
|
|
3211
|
+
async function member() {
|
|
3212
|
+
const startTime = Date.now();
|
|
3213
|
+
const spinner = ora2("Checking authentication...").start();
|
|
3214
|
+
try {
|
|
3215
|
+
let credentials = await getValidCredentials2();
|
|
3216
|
+
if (!credentials) {
|
|
3217
|
+
spinner.info(chalk6.yellow("Not logged in. Opening browser to authenticate..."));
|
|
3218
|
+
credentials = await performLogin();
|
|
3219
|
+
const { email } = decodeIdToken(credentials.idToken);
|
|
3220
|
+
spinner.succeed(chalk6.green(`Authenticated as ${chalk6.bold(email)}`));
|
|
3221
|
+
} else {
|
|
3222
|
+
spinner.succeed("Authenticated");
|
|
3223
|
+
}
|
|
3224
|
+
let repoId;
|
|
3225
|
+
let repoName;
|
|
3226
|
+
let repoRoot;
|
|
3227
|
+
let repoPlatform;
|
|
3228
|
+
let repoExternalId;
|
|
3229
|
+
spinner.start("Checking for pending repository...");
|
|
3230
|
+
try {
|
|
3231
|
+
const pending = await apiRequest("/v1/onboarding/pending");
|
|
3232
|
+
if (pending?.repoId) {
|
|
3233
|
+
repoId = pending.repoId;
|
|
3234
|
+
repoName = pending.repoName;
|
|
3235
|
+
spinner.succeed(`Found pending repository: ${chalk6.bold(repoName)}`);
|
|
3236
|
+
apiRequest("/v1/onboarding/pending", { method: "DELETE" }).catch(() => {
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
} catch {
|
|
3240
|
+
}
|
|
3241
|
+
if (!repoId) {
|
|
3242
|
+
spinner.text = "Detecting repository...";
|
|
3243
|
+
try {
|
|
3244
|
+
repoRoot = detectRepoRoot();
|
|
3245
|
+
repoName = detectRepoName(repoRoot);
|
|
3246
|
+
spinner.succeed(`Repository: ${chalk6.bold(repoName)}`);
|
|
3247
|
+
} catch {
|
|
3248
|
+
spinner.fail(
|
|
3249
|
+
chalk6.red("Not in a git repository. Run this command from your project directory.")
|
|
3250
|
+
);
|
|
3251
|
+
process.exitCode = 1;
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
} else {
|
|
3255
|
+
try {
|
|
3256
|
+
repoRoot = detectRepoRoot();
|
|
3257
|
+
const localRepoName = detectRepoName(repoRoot);
|
|
3258
|
+
if (repoName && localRepoName !== repoName && !repoName.endsWith(`/${localRepoName}`)) {
|
|
3259
|
+
spinner.warn(
|
|
3260
|
+
chalk6.yellow(
|
|
3261
|
+
`Pending repo is ${chalk6.bold(repoName)} but you're in ${chalk6.bold(localRepoName)}.
|
|
3262
|
+
Make sure you're in the right project directory, or the context files will be saved here.`
|
|
3263
|
+
)
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3266
|
+
} catch {
|
|
3267
|
+
spinner.fail(
|
|
3268
|
+
chalk6.red(
|
|
3269
|
+
"Please navigate to your project directory first, then run `repowise member` again."
|
|
3270
|
+
)
|
|
3271
|
+
);
|
|
3272
|
+
process.exitCode = 1;
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
if (!repoId || !repoPlatform) {
|
|
3277
|
+
spinner.start("Looking up repository...");
|
|
3278
|
+
try {
|
|
3279
|
+
const repos = await apiRequest("/v1/repos");
|
|
3280
|
+
const match = repos.find(
|
|
3281
|
+
(r) => r.repoId === repoId || r.name === repoName || r.fullName.endsWith(`/${repoName}`)
|
|
3282
|
+
);
|
|
3283
|
+
if (match) {
|
|
3284
|
+
repoId = match.repoId;
|
|
3285
|
+
repoPlatform = match.platform;
|
|
3286
|
+
repoExternalId = match.externalId;
|
|
3287
|
+
if (!repoName) repoName = match.fullName;
|
|
3288
|
+
spinner.succeed(`Repository: ${chalk6.bold(match.fullName)}`);
|
|
3289
|
+
}
|
|
3290
|
+
} catch {
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
if (!repoId) {
|
|
3294
|
+
spinner.fail(
|
|
3295
|
+
chalk6.red(
|
|
3296
|
+
"This repository is not connected to your team. Ask your team admin to add it on the dashboard."
|
|
3297
|
+
)
|
|
3298
|
+
);
|
|
3299
|
+
process.exitCode = 1;
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
spinner.start("Checking for context files...");
|
|
3303
|
+
let files = [];
|
|
3304
|
+
try {
|
|
3305
|
+
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
3306
|
+
files = listResult.data?.files ?? listResult.files ?? [];
|
|
3307
|
+
} catch {
|
|
3308
|
+
spinner.fail(chalk6.red("Failed to check context files on server."));
|
|
3309
|
+
process.exitCode = 1;
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
3312
|
+
if (files.length === 0) {
|
|
3313
|
+
spinner.fail(
|
|
3314
|
+
chalk6.red(
|
|
3315
|
+
"No context files found for this repository. Your team admin needs to run the initial scan first."
|
|
3316
|
+
)
|
|
3317
|
+
);
|
|
3318
|
+
process.exitCode = 1;
|
|
3319
|
+
return;
|
|
3320
|
+
}
|
|
3321
|
+
spinner.succeed(`Found ${chalk6.bold(files.length)} context files on server`);
|
|
3322
|
+
const { tools } = await selectAiTools();
|
|
3323
|
+
spinner.start("Downloading context files...");
|
|
3324
|
+
const contextDir = join19(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3325
|
+
mkdirSync2(contextDir, { recursive: true });
|
|
3326
|
+
let downloadedCount = 0;
|
|
3327
|
+
let failedCount = 0;
|
|
3328
|
+
for (const file of files) {
|
|
3329
|
+
if (file.fileName.includes("..")) {
|
|
3330
|
+
failedCount++;
|
|
3331
|
+
continue;
|
|
3332
|
+
}
|
|
3333
|
+
try {
|
|
3334
|
+
const urlResult = await apiRequest(`/v1/repos/${repoId}/context/files/${file.fileName}`);
|
|
3335
|
+
const presignedUrl = urlResult.data?.url ?? urlResult.url;
|
|
3336
|
+
const response = await fetch(presignedUrl);
|
|
3337
|
+
if (response.ok) {
|
|
3338
|
+
const content = await response.text();
|
|
3339
|
+
const filePath = join19(contextDir, file.fileName);
|
|
3340
|
+
mkdirSync2(dirname7(filePath), { recursive: true });
|
|
3341
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
3342
|
+
downloadedCount++;
|
|
3343
|
+
} else {
|
|
3344
|
+
failedCount++;
|
|
3345
|
+
}
|
|
3346
|
+
} catch {
|
|
3347
|
+
failedCount++;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
if (failedCount > 0) {
|
|
3351
|
+
spinner.warn(
|
|
3352
|
+
`Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER2}/ (${failedCount} failed)`
|
|
3353
|
+
);
|
|
3354
|
+
} else {
|
|
3355
|
+
spinner.succeed(`Downloaded ${downloadedCount} files to ./${DEFAULT_CONTEXT_FOLDER2}/`);
|
|
3356
|
+
}
|
|
3357
|
+
try {
|
|
3358
|
+
ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3359
|
+
} catch {
|
|
3360
|
+
}
|
|
3361
|
+
if (tools.length > 0) {
|
|
3362
|
+
spinner.start("Configuring AI tools...");
|
|
3363
|
+
const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3364
|
+
const configured = [];
|
|
3365
|
+
for (const tool of tools) {
|
|
3366
|
+
const { created } = await updateToolConfig(
|
|
3367
|
+
repoRoot,
|
|
3368
|
+
tool,
|
|
3369
|
+
repoName,
|
|
3370
|
+
DEFAULT_CONTEXT_FOLDER2,
|
|
3371
|
+
contextFiles
|
|
3372
|
+
);
|
|
3373
|
+
const config2 = AI_TOOL_CONFIG[tool];
|
|
3374
|
+
configured.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
|
|
3375
|
+
}
|
|
3376
|
+
spinner.succeed("AI tools configured");
|
|
3377
|
+
for (const msg of configured) {
|
|
3378
|
+
console.log(chalk6.dim(` ${msg}`));
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
spinner.start("Saving configuration...");
|
|
3382
|
+
const repoEntry = {
|
|
3383
|
+
repoId,
|
|
3384
|
+
localPath: repoRoot,
|
|
3385
|
+
apiUrl: getEnvConfig().apiUrl
|
|
3386
|
+
};
|
|
3387
|
+
if (repoPlatform) repoEntry.platform = repoPlatform;
|
|
3388
|
+
if (repoExternalId) repoEntry.externalId = repoExternalId;
|
|
3389
|
+
await mergeAndSaveConfig({
|
|
3390
|
+
aiTools: tools,
|
|
3391
|
+
contextFolder: DEFAULT_CONTEXT_FOLDER2,
|
|
3392
|
+
repos: [repoEntry]
|
|
3393
|
+
});
|
|
3394
|
+
spinner.succeed("Configuration saved");
|
|
3395
|
+
let listenerRunning = false;
|
|
3396
|
+
try {
|
|
3397
|
+
await ensureListenerRunning();
|
|
3398
|
+
listenerRunning = true;
|
|
3399
|
+
} catch {
|
|
3400
|
+
}
|
|
3401
|
+
try {
|
|
3402
|
+
await apiRequest("/v1/onboarding/complete", {
|
|
3403
|
+
method: "POST",
|
|
3404
|
+
body: JSON.stringify({ repoId })
|
|
3405
|
+
});
|
|
3406
|
+
} catch {
|
|
3407
|
+
}
|
|
3408
|
+
const elapsed = formatElapsed2(Date.now() - startTime);
|
|
3409
|
+
console.log("");
|
|
3410
|
+
console.log(chalk6.green.bold("All done! Context downloaded and configured."));
|
|
3411
|
+
console.log("");
|
|
3412
|
+
console.log(`Your AI tools now have access to project context for ${chalk6.bold(repoName)}.`);
|
|
3413
|
+
console.log(
|
|
3414
|
+
`Downloaded ${chalk6.bold(downloadedCount)} context files to ./${DEFAULT_CONTEXT_FOLDER2}/`
|
|
3415
|
+
);
|
|
3416
|
+
console.log("");
|
|
3417
|
+
if (listenerRunning) {
|
|
3418
|
+
console.log(
|
|
3419
|
+
chalk6.dim(
|
|
3420
|
+
"The RepoWise listener is running in the background \u2014\nyour context will stay in sync automatically."
|
|
3421
|
+
)
|
|
3422
|
+
);
|
|
3423
|
+
} else {
|
|
3424
|
+
console.log(
|
|
3425
|
+
chalk6.yellow(
|
|
3426
|
+
"Could not start listener automatically. Run `repowise listen --install` to enable auto-sync."
|
|
3427
|
+
)
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
console.log("");
|
|
3431
|
+
console.log(
|
|
3432
|
+
chalk6.dim(`Head back to the dashboard and click "Complete Onboarding" to finish setup.`)
|
|
3433
|
+
);
|
|
3434
|
+
console.log(chalk6.dim(`Total time: ${elapsed}`));
|
|
3435
|
+
} catch (err) {
|
|
3436
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
3437
|
+
spinner.fail(chalk6.red(`Setup failed: ${message}`));
|
|
3438
|
+
process.exitCode = 1;
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// src/commands/login.ts
|
|
3443
|
+
import chalk7 from "chalk";
|
|
3444
|
+
import ora3 from "ora";
|
|
2632
3445
|
async function login(options = {}) {
|
|
2633
|
-
const spinner =
|
|
3446
|
+
const spinner = ora3("Preparing login...").start();
|
|
2634
3447
|
try {
|
|
2635
3448
|
const codeVerifier = generateCodeVerifier();
|
|
2636
3449
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
@@ -2642,7 +3455,7 @@ async function login(options = {}) {
|
|
|
2642
3455
|
console.log(`
|
|
2643
3456
|
Open this URL in your browser to authenticate:
|
|
2644
3457
|
`);
|
|
2645
|
-
console.log(
|
|
3458
|
+
console.log(chalk7.cyan(authorizeUrl));
|
|
2646
3459
|
console.log(`
|
|
2647
3460
|
Waiting for authentication...`);
|
|
2648
3461
|
} else {
|
|
@@ -2656,7 +3469,7 @@ Waiting for authentication...`);
|
|
|
2656
3469
|
console.log(`
|
|
2657
3470
|
Could not open browser automatically. Open this URL:
|
|
2658
3471
|
`);
|
|
2659
|
-
console.log(
|
|
3472
|
+
console.log(chalk7.cyan(authorizeUrl));
|
|
2660
3473
|
console.log(`
|
|
2661
3474
|
Waiting for authentication...`);
|
|
2662
3475
|
}
|
|
@@ -2670,60 +3483,50 @@ Waiting for authentication...`);
|
|
|
2670
3483
|
credentials.cognito = getCognitoConfigForStorage();
|
|
2671
3484
|
await storeCredentials2(credentials);
|
|
2672
3485
|
const { email } = decodeIdToken(credentials.idToken);
|
|
2673
|
-
spinner.succeed(
|
|
3486
|
+
spinner.succeed(chalk7.green(`Logged in as ${chalk7.bold(email)}`));
|
|
2674
3487
|
} catch (err) {
|
|
2675
3488
|
const message = err instanceof Error ? err.message : "Login failed";
|
|
2676
|
-
spinner.fail(
|
|
3489
|
+
spinner.fail(chalk7.red(message));
|
|
2677
3490
|
process.exitCode = 1;
|
|
2678
3491
|
}
|
|
2679
3492
|
}
|
|
2680
3493
|
|
|
2681
3494
|
// src/commands/logout.ts
|
|
2682
|
-
import
|
|
3495
|
+
import chalk8 from "chalk";
|
|
2683
3496
|
async function logout() {
|
|
2684
3497
|
const creds = await getStoredCredentials2();
|
|
2685
3498
|
if (!creds) {
|
|
2686
|
-
console.log(
|
|
3499
|
+
console.log(chalk8.yellow("Not logged in."));
|
|
2687
3500
|
return;
|
|
2688
3501
|
}
|
|
2689
3502
|
await clearCredentials();
|
|
2690
|
-
console.log(
|
|
3503
|
+
console.log(chalk8.green("Logged out successfully."));
|
|
2691
3504
|
}
|
|
2692
3505
|
|
|
2693
3506
|
// src/commands/status.ts
|
|
2694
3507
|
import { readFile as readFile9 } from "fs/promises";
|
|
2695
|
-
import {
|
|
2696
|
-
import { basename as basename2, join as join14 } from "path";
|
|
2697
|
-
var STATE_PATH2 = join14(homedir9(), ".repowise", "listener-state.json");
|
|
2698
|
-
var CONFIG_PATH3 = join14(homedir9(), ".repowise", "config.json");
|
|
3508
|
+
import { basename as basename2, join as join20 } from "path";
|
|
2699
3509
|
async function status() {
|
|
3510
|
+
const configDir = getConfigDir2();
|
|
3511
|
+
const STATE_PATH = join20(configDir, "listener-state.json");
|
|
3512
|
+
const CONFIG_PATH = join20(configDir, "config.json");
|
|
2700
3513
|
let state = null;
|
|
2701
3514
|
try {
|
|
2702
|
-
const data = await readFile9(
|
|
3515
|
+
const data = await readFile9(STATE_PATH, "utf-8");
|
|
2703
3516
|
state = JSON.parse(data);
|
|
2704
3517
|
} catch {
|
|
2705
3518
|
}
|
|
2706
|
-
|
|
2707
|
-
let pid = null;
|
|
2708
|
-
try {
|
|
2709
|
-
const processStatus = await getStatus();
|
|
2710
|
-
processRunning = processStatus.running;
|
|
2711
|
-
pid = processStatus.pid;
|
|
2712
|
-
} catch {
|
|
2713
|
-
}
|
|
2714
|
-
let serviceInstalled = false;
|
|
2715
|
-
try {
|
|
2716
|
-
serviceInstalled = await isInstalled();
|
|
2717
|
-
} catch {
|
|
2718
|
-
}
|
|
3519
|
+
const listenerStatus = await getListenerStatus();
|
|
2719
3520
|
console.log("RepoWise Status");
|
|
2720
3521
|
console.log("===============");
|
|
2721
|
-
if (
|
|
2722
|
-
console.log(
|
|
3522
|
+
if (listenerStatus.running) {
|
|
3523
|
+
console.log(
|
|
3524
|
+
listenerStatus.method === "service" ? "Listener: running (managed by system service)" : `Listener: running (PID: ${listenerStatus.pid})`
|
|
3525
|
+
);
|
|
2723
3526
|
} else {
|
|
2724
3527
|
console.log("Listener: stopped");
|
|
2725
3528
|
}
|
|
2726
|
-
if (serviceInstalled) {
|
|
3529
|
+
if (listenerStatus.serviceInstalled) {
|
|
2727
3530
|
console.log("Auto-start: enabled");
|
|
2728
3531
|
} else {
|
|
2729
3532
|
console.log("Auto-start: disabled");
|
|
@@ -2735,7 +3538,7 @@ async function status() {
|
|
|
2735
3538
|
}
|
|
2736
3539
|
const repoNames = /* @__PURE__ */ new Map();
|
|
2737
3540
|
try {
|
|
2738
|
-
const configData = await readFile9(
|
|
3541
|
+
const configData = await readFile9(CONFIG_PATH, "utf-8");
|
|
2739
3542
|
const config2 = JSON.parse(configData);
|
|
2740
3543
|
for (const repo of config2.repos ?? []) {
|
|
2741
3544
|
repoNames.set(repo.repoId, basename2(repo.localPath));
|
|
@@ -2753,30 +3556,14 @@ async function status() {
|
|
|
2753
3556
|
}
|
|
2754
3557
|
|
|
2755
3558
|
// src/commands/sync.ts
|
|
2756
|
-
import {
|
|
2757
|
-
import {
|
|
2758
|
-
import
|
|
2759
|
-
import
|
|
2760
|
-
import ora3 from "ora";
|
|
3559
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3560
|
+
import { dirname as dirname8, join as join21 } from "path";
|
|
3561
|
+
import chalk9 from "chalk";
|
|
3562
|
+
import ora4 from "ora";
|
|
2761
3563
|
var POLL_INTERVAL_MS2 = 3e3;
|
|
2762
3564
|
var MAX_POLL_ATTEMPTS2 = 7200;
|
|
2763
|
-
var
|
|
2764
|
-
function
|
|
2765
|
-
return execSync2("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
2766
|
-
}
|
|
2767
|
-
function detectRepoName2(repoRoot) {
|
|
2768
|
-
try {
|
|
2769
|
-
const remoteUrl = execSync2("git remote get-url origin", {
|
|
2770
|
-
encoding: "utf-8",
|
|
2771
|
-
cwd: repoRoot
|
|
2772
|
-
}).trim();
|
|
2773
|
-
const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
|
|
2774
|
-
if (match?.[1]) return match[1];
|
|
2775
|
-
} catch {
|
|
2776
|
-
}
|
|
2777
|
-
return repoRoot.split("/").pop() ?? "unknown";
|
|
2778
|
-
}
|
|
2779
|
-
function formatElapsed2(ms) {
|
|
3565
|
+
var DEFAULT_CONTEXT_FOLDER3 = "repowise-context";
|
|
3566
|
+
function formatElapsed3(ms) {
|
|
2780
3567
|
const totalSeconds = Math.round(ms / 1e3);
|
|
2781
3568
|
const minutes = Math.floor(totalSeconds / 60);
|
|
2782
3569
|
const seconds = totalSeconds % 60;
|
|
@@ -2785,14 +3572,14 @@ function formatElapsed2(ms) {
|
|
|
2785
3572
|
}
|
|
2786
3573
|
async function sync() {
|
|
2787
3574
|
const startTime = Date.now();
|
|
2788
|
-
const spinner =
|
|
3575
|
+
const spinner = ora4("Checking authentication...").start();
|
|
2789
3576
|
try {
|
|
2790
3577
|
let credentials = await getValidCredentials2();
|
|
2791
3578
|
if (!credentials) {
|
|
2792
|
-
spinner.info(
|
|
3579
|
+
spinner.info(chalk9.yellow("Not logged in. Opening browser to authenticate..."));
|
|
2793
3580
|
credentials = await performLogin();
|
|
2794
3581
|
const { email } = decodeIdToken(credentials.idToken);
|
|
2795
|
-
spinner.succeed(
|
|
3582
|
+
spinner.succeed(chalk9.green(`Authenticated as ${chalk9.bold(email)}`));
|
|
2796
3583
|
} else {
|
|
2797
3584
|
spinner.succeed("Authenticated");
|
|
2798
3585
|
}
|
|
@@ -2800,12 +3587,12 @@ async function sync() {
|
|
|
2800
3587
|
let repoName;
|
|
2801
3588
|
spinner.start("Detecting repository...");
|
|
2802
3589
|
try {
|
|
2803
|
-
repoRoot =
|
|
2804
|
-
repoName =
|
|
2805
|
-
spinner.succeed(`Repository: ${
|
|
3590
|
+
repoRoot = detectRepoRoot();
|
|
3591
|
+
repoName = detectRepoName(repoRoot);
|
|
3592
|
+
spinner.succeed(`Repository: ${chalk9.bold(repoName)}`);
|
|
2806
3593
|
} catch {
|
|
2807
3594
|
spinner.fail(
|
|
2808
|
-
|
|
3595
|
+
chalk9.red("Not in a git repository. Run this command from your repo directory.")
|
|
2809
3596
|
);
|
|
2810
3597
|
process.exitCode = 1;
|
|
2811
3598
|
return;
|
|
@@ -2826,7 +3613,7 @@ async function sync() {
|
|
|
2826
3613
|
}
|
|
2827
3614
|
if (!repoId) {
|
|
2828
3615
|
spinner.fail(
|
|
2829
|
-
|
|
3616
|
+
chalk9.red(
|
|
2830
3617
|
"Could not find this repository in your RepoWise account. Run `repowise create` first."
|
|
2831
3618
|
)
|
|
2832
3619
|
);
|
|
@@ -2863,7 +3650,7 @@ async function sync() {
|
|
|
2863
3650
|
syncId = retryResult.syncId;
|
|
2864
3651
|
} else {
|
|
2865
3652
|
syncId = active.syncId;
|
|
2866
|
-
spinner.info(
|
|
3653
|
+
spinner.info(chalk9.cyan("Resuming existing sync..."));
|
|
2867
3654
|
spinner.start();
|
|
2868
3655
|
}
|
|
2869
3656
|
}
|
|
@@ -2871,7 +3658,7 @@ async function sync() {
|
|
|
2871
3658
|
const progressRenderer = new ProgressRenderer();
|
|
2872
3659
|
while (true) {
|
|
2873
3660
|
if (++pollAttempts > MAX_POLL_ATTEMPTS2) {
|
|
2874
|
-
spinner.fail(
|
|
3661
|
+
spinner.fail(chalk9.red("Sync timed out. Check dashboard for status."));
|
|
2875
3662
|
process.exitCode = 1;
|
|
2876
3663
|
return;
|
|
2877
3664
|
}
|
|
@@ -2895,15 +3682,15 @@ async function sync() {
|
|
|
2895
3682
|
const generatedFiles = syncResult.filesGenerated ?? [];
|
|
2896
3683
|
const fileCount = generatedFiles.length;
|
|
2897
3684
|
if (fileCount > 0) {
|
|
2898
|
-
spinner.succeed(
|
|
3685
|
+
spinner.succeed(chalk9.green(`Incremental update complete \u2014 ${fileCount} files updated`));
|
|
2899
3686
|
} else {
|
|
2900
|
-
spinner.succeed(
|
|
3687
|
+
spinner.succeed(chalk9.green("Incremental sync complete \u2014 no files needed updating"));
|
|
2901
3688
|
}
|
|
2902
3689
|
break;
|
|
2903
3690
|
}
|
|
2904
3691
|
if (syncResult.status === "failed") {
|
|
2905
3692
|
progressRenderer.finalize();
|
|
2906
|
-
spinner.fail(
|
|
3693
|
+
spinner.fail(chalk9.red(`Sync failed: ${syncResult.error ?? "Unknown error"}`));
|
|
2907
3694
|
process.exitCode = 1;
|
|
2908
3695
|
return;
|
|
2909
3696
|
}
|
|
@@ -2913,8 +3700,8 @@ async function sync() {
|
|
|
2913
3700
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
2914
3701
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
2915
3702
|
if (files.length > 0) {
|
|
2916
|
-
const contextDir =
|
|
2917
|
-
|
|
3703
|
+
const contextDir = join21(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
3704
|
+
mkdirSync3(contextDir, { recursive: true });
|
|
2918
3705
|
let downloadedCount = 0;
|
|
2919
3706
|
let failedCount = 0;
|
|
2920
3707
|
for (const file of files) {
|
|
@@ -2927,9 +3714,9 @@ async function sync() {
|
|
|
2927
3714
|
const response = await fetch(presignedUrl);
|
|
2928
3715
|
if (response.ok) {
|
|
2929
3716
|
const content = await response.text();
|
|
2930
|
-
const filePath =
|
|
2931
|
-
|
|
2932
|
-
|
|
3717
|
+
const filePath = join21(contextDir, file.fileName);
|
|
3718
|
+
mkdirSync3(dirname8(filePath), { recursive: true });
|
|
3719
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
2933
3720
|
downloadedCount++;
|
|
2934
3721
|
} else {
|
|
2935
3722
|
failedCount++;
|
|
@@ -2937,13 +3724,13 @@ async function sync() {
|
|
|
2937
3724
|
}
|
|
2938
3725
|
if (failedCount > 0) {
|
|
2939
3726
|
spinner.warn(
|
|
2940
|
-
`Downloaded ${downloadedCount}/${files.length} files to ./${
|
|
3727
|
+
`Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER3}/ (${failedCount} failed)`
|
|
2941
3728
|
);
|
|
2942
3729
|
} else {
|
|
2943
|
-
spinner.succeed(`Context files downloaded to ./${
|
|
3730
|
+
spinner.succeed(`Context files downloaded to ./${DEFAULT_CONTEXT_FOLDER3}/`);
|
|
2944
3731
|
}
|
|
2945
3732
|
try {
|
|
2946
|
-
ensureGitignore(repoRoot,
|
|
3733
|
+
ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
2947
3734
|
} catch {
|
|
2948
3735
|
}
|
|
2949
3736
|
} else {
|
|
@@ -2951,7 +3738,7 @@ async function sync() {
|
|
|
2951
3738
|
}
|
|
2952
3739
|
} catch (err) {
|
|
2953
3740
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2954
|
-
spinner.warn(
|
|
3741
|
+
spinner.warn(chalk9.yellow(`Could not download context files: ${msg}
|
|
2955
3742
|
Retry when online.`));
|
|
2956
3743
|
}
|
|
2957
3744
|
if (repoRoot && repoId) {
|
|
@@ -2959,29 +3746,24 @@ Retry when online.`));
|
|
|
2959
3746
|
const existingConfig = await getConfig();
|
|
2960
3747
|
const existingRepos = existingConfig.repos ?? [];
|
|
2961
3748
|
if (!existingRepos.some((r) => r.repoId === repoId)) {
|
|
2962
|
-
const repoEntry = { repoId, localPath: repoRoot };
|
|
2963
|
-
if (isStagingMode()) {
|
|
2964
|
-
repoEntry.apiUrl = getEnvConfig().apiUrl;
|
|
2965
|
-
}
|
|
3749
|
+
const repoEntry = { repoId, localPath: repoRoot, apiUrl: getEnvConfig().apiUrl };
|
|
2966
3750
|
if (repoPlatform) repoEntry.platform = repoPlatform;
|
|
2967
3751
|
if (repoExternalId) repoEntry.externalId = repoExternalId;
|
|
2968
|
-
|
|
2969
|
-
await saveConfig({ ...existingConfig, repos: existingRepos });
|
|
3752
|
+
await mergeAndSaveConfig({ repos: [repoEntry] });
|
|
2970
3753
|
}
|
|
2971
3754
|
} catch {
|
|
2972
3755
|
}
|
|
2973
3756
|
try {
|
|
2974
|
-
await
|
|
2975
|
-
await startBackground();
|
|
3757
|
+
await ensureListenerRunning();
|
|
2976
3758
|
} catch {
|
|
2977
3759
|
}
|
|
2978
3760
|
}
|
|
2979
|
-
const elapsed =
|
|
2980
|
-
console.log(
|
|
3761
|
+
const elapsed = formatElapsed3(Date.now() - startTime);
|
|
3762
|
+
console.log(chalk9.dim(`
|
|
2981
3763
|
Total time: ${elapsed}`));
|
|
2982
3764
|
} catch (err) {
|
|
2983
3765
|
const message = err instanceof Error ? err.message : "Sync failed";
|
|
2984
|
-
spinner.fail(
|
|
3766
|
+
spinner.fail(chalk9.red(message));
|
|
2985
3767
|
process.exitCode = 1;
|
|
2986
3768
|
}
|
|
2987
3769
|
}
|
|
@@ -3029,12 +3811,15 @@ async function listen(options) {
|
|
|
3029
3811
|
// src/commands/start.ts
|
|
3030
3812
|
async function start() {
|
|
3031
3813
|
try {
|
|
3032
|
-
|
|
3814
|
+
const status2 = await getListenerStatus();
|
|
3815
|
+
if (status2.running) {
|
|
3033
3816
|
console.log("Listener is already running.");
|
|
3034
3817
|
return;
|
|
3035
3818
|
}
|
|
3036
|
-
const
|
|
3037
|
-
console.log(
|
|
3819
|
+
const result = await ensureListenerRunning();
|
|
3820
|
+
console.log(
|
|
3821
|
+
result.method === "service" ? "Listener service installed and started." : `Listener started (PID: ${result.pid}).`
|
|
3822
|
+
);
|
|
3038
3823
|
} catch (err) {
|
|
3039
3824
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
3040
3825
|
console.error(`Failed to start listener: ${message}`);
|
|
@@ -3045,11 +3830,12 @@ async function start() {
|
|
|
3045
3830
|
// src/commands/stop.ts
|
|
3046
3831
|
async function stop2() {
|
|
3047
3832
|
try {
|
|
3048
|
-
|
|
3833
|
+
const status2 = await getListenerStatus();
|
|
3834
|
+
if (!status2.running) {
|
|
3049
3835
|
console.log("Listener is not running.");
|
|
3050
3836
|
return;
|
|
3051
3837
|
}
|
|
3052
|
-
await
|
|
3838
|
+
await stopListener();
|
|
3053
3839
|
console.log("Listener stopped.");
|
|
3054
3840
|
} catch (err) {
|
|
3055
3841
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -3059,18 +3845,18 @@ async function stop2() {
|
|
|
3059
3845
|
}
|
|
3060
3846
|
|
|
3061
3847
|
// src/commands/config.ts
|
|
3062
|
-
import
|
|
3063
|
-
import
|
|
3848
|
+
import chalk10 from "chalk";
|
|
3849
|
+
import ora5 from "ora";
|
|
3064
3850
|
import { select } from "@inquirer/prompts";
|
|
3065
3851
|
async function config() {
|
|
3066
|
-
const spinner =
|
|
3852
|
+
const spinner = ora5("Checking authentication...").start();
|
|
3067
3853
|
try {
|
|
3068
3854
|
let credentials = await getValidCredentials2();
|
|
3069
3855
|
if (!credentials) {
|
|
3070
|
-
spinner.info(
|
|
3856
|
+
spinner.info(chalk10.yellow("Not logged in. Opening browser to authenticate..."));
|
|
3071
3857
|
credentials = await performLogin();
|
|
3072
3858
|
const { email } = decodeIdToken(credentials.idToken);
|
|
3073
|
-
spinner.succeed(
|
|
3859
|
+
spinner.succeed(chalk10.green(`Authenticated as ${chalk10.bold(email)}`));
|
|
3074
3860
|
} else {
|
|
3075
3861
|
spinner.succeed("Authenticated");
|
|
3076
3862
|
}
|
|
@@ -3078,7 +3864,7 @@ async function config() {
|
|
|
3078
3864
|
const repos = await apiRequest("/v1/repos");
|
|
3079
3865
|
spinner.stop();
|
|
3080
3866
|
if (repos.length === 0) {
|
|
3081
|
-
console.log(
|
|
3867
|
+
console.log(chalk10.yellow("No repositories connected. Run `repowise create` first."));
|
|
3082
3868
|
return;
|
|
3083
3869
|
}
|
|
3084
3870
|
const repoId = await select({
|
|
@@ -3092,10 +3878,10 @@ async function config() {
|
|
|
3092
3878
|
const currentDelivery = repo.deliveryMode ?? "direct";
|
|
3093
3879
|
const currentBranch = repo.monitoredBranch ?? repo.defaultBranch;
|
|
3094
3880
|
console.log("");
|
|
3095
|
-
console.log(
|
|
3096
|
-
console.log(
|
|
3097
|
-
console.log(
|
|
3098
|
-
console.log(
|
|
3881
|
+
console.log(chalk10.bold(`Settings for ${repo.fullName}`));
|
|
3882
|
+
console.log(chalk10.dim(` Context storage: RepoWise servers`));
|
|
3883
|
+
console.log(chalk10.dim(` Delivery mode: ${currentDelivery}`));
|
|
3884
|
+
console.log(chalk10.dim(` Monitored branch: ${currentBranch}`));
|
|
3099
3885
|
console.log("");
|
|
3100
3886
|
const setting = await select({
|
|
3101
3887
|
message: "What would you like to change?",
|
|
@@ -3115,7 +3901,7 @@ async function config() {
|
|
|
3115
3901
|
default: currentDelivery
|
|
3116
3902
|
});
|
|
3117
3903
|
if (newMode === currentDelivery) {
|
|
3118
|
-
console.log(
|
|
3904
|
+
console.log(chalk10.dim("No change."));
|
|
3119
3905
|
return;
|
|
3120
3906
|
}
|
|
3121
3907
|
patch.deliveryMode = newMode;
|
|
@@ -3126,7 +3912,7 @@ async function config() {
|
|
|
3126
3912
|
default: currentBranch
|
|
3127
3913
|
});
|
|
3128
3914
|
if (newBranch === currentBranch) {
|
|
3129
|
-
console.log(
|
|
3915
|
+
console.log(chalk10.dim("No change."));
|
|
3130
3916
|
return;
|
|
3131
3917
|
}
|
|
3132
3918
|
patch.monitoredBranch = newBranch;
|
|
@@ -3136,29 +3922,29 @@ async function config() {
|
|
|
3136
3922
|
method: "PATCH",
|
|
3137
3923
|
body: JSON.stringify(patch)
|
|
3138
3924
|
});
|
|
3139
|
-
spinner.succeed(
|
|
3925
|
+
spinner.succeed(chalk10.green("Setting updated"));
|
|
3140
3926
|
} catch (err) {
|
|
3141
3927
|
spinner.stop();
|
|
3142
3928
|
const message = err instanceof Error ? err.message : "Config failed";
|
|
3143
|
-
console.error(
|
|
3929
|
+
console.error(chalk10.red(message));
|
|
3144
3930
|
process.exitCode = 1;
|
|
3145
3931
|
}
|
|
3146
3932
|
}
|
|
3147
3933
|
|
|
3148
3934
|
// bin/repowise.ts
|
|
3149
3935
|
var __filename = fileURLToPath3(import.meta.url);
|
|
3150
|
-
var __dirname =
|
|
3151
|
-
var pkg = JSON.parse(readFileSync2(
|
|
3936
|
+
var __dirname = dirname9(__filename);
|
|
3937
|
+
var pkg = JSON.parse(readFileSync2(join22(__dirname, "..", "..", "package.json"), "utf-8"));
|
|
3152
3938
|
var program = new Command();
|
|
3153
|
-
program.name(
|
|
3154
|
-
if (program.opts()["staging"]) {
|
|
3155
|
-
setStagingMode(true);
|
|
3156
|
-
}
|
|
3939
|
+
program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
|
|
3157
3940
|
await showWelcome(pkg.version);
|
|
3158
3941
|
});
|
|
3159
3942
|
program.command("create").description("Create context for a repository").action(async () => {
|
|
3160
3943
|
await create();
|
|
3161
3944
|
});
|
|
3945
|
+
program.command("member").description("Download context and configure AI tools (for team members)").action(async () => {
|
|
3946
|
+
await member();
|
|
3947
|
+
});
|
|
3162
3948
|
program.command("login").description("Authenticate with RepoWise").option("--no-browser", "Print login URL instead of opening browser").action(async (options) => {
|
|
3163
3949
|
await login(options);
|
|
3164
3950
|
});
|
|
@@ -3190,7 +3976,7 @@ if (process.argv[2] === "__listener") {
|
|
|
3190
3976
|
process.on("unhandledRejection", (reason) => {
|
|
3191
3977
|
console.error("Listener unhandled rejection:", reason);
|
|
3192
3978
|
});
|
|
3193
|
-
startListener(
|
|
3979
|
+
startListener().catch((err) => {
|
|
3194
3980
|
console.error("Listener fatal error:", err);
|
|
3195
3981
|
process.exitCode = 1;
|
|
3196
3982
|
});
|